From b0f9fd9c4c573bfe584651513ca73b941ee6d8c1 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Wed, 15 Jun 2022 12:40:21 +0000 Subject: [PATCH 01/46] implement main driver functions --- artiq/coredevice/phaser.py | 58 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index c9bf0c479..6207715de 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -40,6 +40,16 @@ PHASER_ADDR_DUC1_P = 0x26 PHASER_ADDR_DAC1_DATA = 0x28 PHASER_ADDR_DAC1_TEST = 0x2c +# servo registers +PHASER_ADDR_SERVO_CFG0 = 0x30 +PHASER_ADDR_SERVO_CFG1 = 0x31 + +# 0x32 - 0x61 ab regs +PHASER_ADDR_SERVO_AB_BASE = 0x32 +# 0x62 - 0x71 offset regs +PHASER_ADDR_SERVO_OFFSET_BASE = 0x62 + + PHASER_SEL_DAC = 1 << 0 PHASER_SEL_TRF0 = 1 << 1 PHASER_SEL_TRF1 = 1 << 2 @@ -382,6 +392,14 @@ class Phaser: response = rtio_input_data(self.channel_base) return response >> self.miso_delay + @kernel + def write16(self, addr, data: TInt32): + """Write 16 bit to a sequence of FPGA registers.""" + for offset in range(2): + byte = data >> 8 + self.write8(addr + offset, byte) + data <<= 8 + @kernel def write32(self, addr, data: TInt32): """Write 32 bit to a sequence of FPGA registers.""" @@ -1039,6 +1057,46 @@ class PhaserChannel: data = data ^ ((1 << 12) | (1 << 13)) self.trf_write(data) + @kernel + def set_servo_enable(self, en=1): + """Set the servo enable to True or False. + + :param en: 1 to enable servo, 0 to disable + """ + addr = PHASER_ADDR_SERVO_CFG1 if self.index == 1 else PHASER_ADDR_SERVO_CFG0 + content = self.phaser.read8(addr) + delay(.1*ms) + content = (content | 1) & en + self.phaser.write8(addr, content) + + @kernel + def set_servo_profile(self, profile): + """Set the servo profile. + + :param profile: [0:3] profile index to select for channel + """ + if profile not in range(4): + raise ValueError("invalid profile index") + addr = PHASER_ADDR_SERVO_CFG1 if self.index == 1 else PHASER_ADDR_SERVO_CFG0 + content = self.phaser.read8(addr) + delay(.1*ms) + # shift one left and leave en bit + content = (profile << 1) | (content & 1) + self.phaser.write8(addr, content) + + @kernel + def load_servo_profile(self, profile, ab, offset): + """Set the servo enable to True or False. + """ + if profile not in range(4): + raise ValueError("invalid profile index") + # Should I check here if the profile I want to load is selected? What do I do if it is? + addr = PHASER_ADDR_SERVO_AB_BASE + (6 * profile) + (self.index * 24) + for coef in ab: + self.phaser.write16(addr, coef) + addr +=2 + addr = PHASER_ADDR_SERVO_OFFSET_BASE + (2 * profile) + (self.index * 8) + self.phaser.write16(addr, offset) class PhaserOscillator: """Phaser IQ channel oscillator (NCO/DDS). From 1bddadc6e24508ee191087e1b22e5826c2144091 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Wed, 15 Jun 2022 17:32:11 +0000 Subject: [PATCH 02/46] cleanup and comments --- artiq/coredevice/phaser.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 6207715de..14da24228 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1073,7 +1073,7 @@ class PhaserChannel: def set_servo_profile(self, profile): """Set the servo profile. - :param profile: [0:3] profile index to select for channel + :param profile: profile index to select for channel (0 to 3) """ if profile not in range(4): raise ValueError("invalid profile index") @@ -1086,11 +1086,17 @@ class PhaserChannel: @kernel def load_servo_profile(self, profile, ab, offset): - """Set the servo enable to True or False. + """Load a servo profile consiting of the three filter coefficients and an output offset. + + :param profile: profile to load (0 to 3) + :param ab: 3 entry coefficient vector (16 bit) + :param offset: output offset (16 bit) """ if profile not in range(4): raise ValueError("invalid profile index") - # Should I check here if the profile I want to load is selected? What do I do if it is? + if len(ab) != 3: + raise ValueError("invalid number of coefficients") + # Should I check here if the profile I want to load is selected? Aka read the register. What do I do if it is? addr = PHASER_ADDR_SERVO_AB_BASE + (6 * profile) + (self.index * 24) for coef in ab: self.phaser.write16(addr, coef) @@ -1098,6 +1104,7 @@ class PhaserChannel: addr = PHASER_ADDR_SERVO_OFFSET_BASE + (2 * profile) + (self.index * 8) self.phaser.write16(addr, offset) + class PhaserOscillator: """Phaser IQ channel oscillator (NCO/DDS). From ae3f1c1c7174cce85e91ba29b486b4520a4a2600 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Fri, 17 Jun 2022 11:47:45 +0000 Subject: [PATCH 03/46] adapt servo functions. Todo: docu --- artiq/coredevice/phaser.py | 35 +++++++++++++---------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 14da24228..0bb468e23 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1058,41 +1058,32 @@ class PhaserChannel: self.trf_write(data) @kernel - def set_servo_enable(self, en=1): - """Set the servo enable to True or False. - - :param en: 1 to enable servo, 0 to disable - """ - addr = PHASER_ADDR_SERVO_CFG1 if self.index == 1 else PHASER_ADDR_SERVO_CFG0 - content = self.phaser.read8(addr) - delay(.1*ms) - content = (content | 1) & en - self.phaser.write8(addr, content) - - @kernel - def set_servo_profile(self, profile): - """Set the servo profile. + def set_servo(self, bypass=1, hold=0, profile=0): + """Set the servo configuration. + :param bypass: 1 to enable bypass (default), 0 to engage servo + :param hold: 1 to hold the servo IIR filter output constant, 0 for normal operation :param profile: profile index to select for channel (0 to 3) """ - if profile not in range(4): + if (profile < 0) | (profile > 3): raise ValueError("invalid profile index") addr = PHASER_ADDR_SERVO_CFG1 if self.index == 1 else PHASER_ADDR_SERVO_CFG0 - content = self.phaser.read8(addr) - delay(.1*ms) - # shift one left and leave en bit - content = (profile << 1) | (content & 1) - self.phaser.write8(addr, content) + if bypass == 0: + data = 1 + if hold == 1: + data = data | (1 << 1) + data = data | (profile << 2) + self.phaser.write8(addr, data) @kernel - def load_servo_profile(self, profile, ab, offset): + def set_iir_mu(self, profile, ab, offset): """Load a servo profile consiting of the three filter coefficients and an output offset. :param profile: profile to load (0 to 3) :param ab: 3 entry coefficient vector (16 bit) :param offset: output offset (16 bit) """ - if profile not in range(4): + if (profile < 0) | (profile > 3): raise ValueError("invalid profile index") if len(ab) != 3: raise ValueError("invalid number of coefficients") From 2044dc3ae59400b3e717d4ab261e1aaf748fe178 Mon Sep 17 00:00:00 2001 From: Norman Krackow Date: Fri, 17 Jun 2022 14:39:37 +0200 Subject: [PATCH 04/46] Update artiq/coredevice/phaser.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Jördens --- artiq/coredevice/phaser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 0bb468e23..3f5e4262c 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1067,7 +1067,7 @@ class PhaserChannel: """ if (profile < 0) | (profile > 3): raise ValueError("invalid profile index") - addr = PHASER_ADDR_SERVO_CFG1 if self.index == 1 else PHASER_ADDR_SERVO_CFG0 + addr = PHASER_ADDR_SERVO_CFG0 + self.index if bypass == 0: data = 1 if hold == 1: From dc49372d57d0e1a3d6e025a230f53af49c7db422 Mon Sep 17 00:00:00 2001 From: Norman Krackow Date: Fri, 17 Jun 2022 14:40:07 +0200 Subject: [PATCH 05/46] Update artiq/coredevice/phaser.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Jördens --- artiq/coredevice/phaser.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 3f5e4262c..e696fa227 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1072,7 +1072,9 @@ class PhaserChannel: data = 1 if hold == 1: data = data | (1 << 1) - data = data | (profile << 2) + if bypass: + hold = 1 + data = (profile << 2) | (hold << 1) | (bypass << 0) self.phaser.write8(addr, data) @kernel From d09153411fd8fa38cb4d569e54cbd4a6eb8fd6e7 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Fri, 17 Jun 2022 13:03:21 +0000 Subject: [PATCH 06/46] adress some review comments --- artiq/coredevice/phaser.py | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index e696fa227..7d0f87315 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -395,10 +395,10 @@ class Phaser: @kernel def write16(self, addr, data: TInt32): """Write 16 bit to a sequence of FPGA registers.""" - for offset in range(2): - byte = data >> 8 - self.write8(addr + offset, byte) - data <<= 8 + byte = data >> 8 + self.write8(addr, byte) + byte = data & 0xFF + self.write8(addr + 1, byte) @kernel def write32(self, addr, data: TInt32): @@ -1065,35 +1065,34 @@ class PhaserChannel: :param hold: 1 to hold the servo IIR filter output constant, 0 for normal operation :param profile: profile index to select for channel (0 to 3) """ - if (profile < 0) | (profile > 3): + if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") addr = PHASER_ADDR_SERVO_CFG0 + self.index if bypass == 0: data = 1 if hold == 1: - data = data | (1 << 1) + data = data or (1 << 1) if bypass: hold = 1 - data = (profile << 2) | (hold << 1) | (bypass << 0) + data = (profile << 2) or (hold << 1) or (bypass << 0) self.phaser.write8(addr, data) @kernel - def set_iir_mu(self, profile, ab, offset): + def set_iir_mu(self, profile, b0, b1, a1, offset): """Load a servo profile consiting of the three filter coefficients and an output offset. :param profile: profile to load (0 to 3) :param ab: 3 entry coefficient vector (16 bit) :param offset: output offset (16 bit) """ - if (profile < 0) | (profile > 3): + if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") - if len(ab) != 3: - raise ValueError("invalid number of coefficients") - # Should I check here if the profile I want to load is selected? Aka read the register. What do I do if it is? + # 24 byte-sized ab registers per channel and 6 (2 bytes * 3 coefficients) registers per profile addr = PHASER_ADDR_SERVO_AB_BASE + (6 * profile) + (self.index * 24) - for coef in ab: + for coef in [b0, b1, a1]: self.phaser.write16(addr, coef) - addr +=2 + addr += 2 + # 8 offset registers per channel and 2 registers per offset addr = PHASER_ADDR_SERVO_OFFSET_BASE + (2 * profile) + (self.index * 8) self.phaser.write16(addr, offset) From 5df766e6da75c37fd29217e75b7c933e40f87cfc Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Tue, 21 Jun 2022 07:36:59 +0000 Subject: [PATCH 07/46] fix ors --- artiq/coredevice/phaser.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 7d0f87315..b620e653e 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1058,7 +1058,7 @@ class PhaserChannel: self.trf_write(data) @kernel - def set_servo(self, bypass=1, hold=0, profile=0): + def set_servo(self, profile=0, bypass=1, hold=0): """Set the servo configuration. :param bypass: 1 to enable bypass (default), 0 to engage servo @@ -1068,13 +1068,14 @@ class PhaserChannel: if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") addr = PHASER_ADDR_SERVO_CFG0 + self.index + data = 0 if bypass == 0: data = 1 if hold == 1: - data = data or (1 << 1) + data = data | (1 << 1) if bypass: hold = 1 - data = (profile << 2) or (hold << 1) or (bypass << 0) + data = (profile << 2) | (hold << 1) | (bypass << 0) self.phaser.write8(addr, data) @kernel From 751af3144e83707a2345e132ab47cf13565303bb Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Tue, 21 Jun 2022 07:43:28 +0000 Subject: [PATCH 08/46] fix old line that I forgot --- artiq/coredevice/phaser.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index b620e653e..c418f990d 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1073,9 +1073,7 @@ class PhaserChannel: data = 1 if hold == 1: data = data | (1 << 1) - if bypass: - hold = 1 - data = (profile << 2) | (hold << 1) | (bypass << 0) + data = data | (profile << 2) self.phaser.write8(addr, data) @kernel From 0388161754b0a2f3ed4e31017b9d3ad0fe493b2c Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Tue, 21 Jun 2022 07:49:29 +0000 Subject: [PATCH 09/46] disable servo in init --- artiq/coredevice/phaser.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index c418f990d..417bcf32f 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -320,6 +320,9 @@ class Phaser: delay(.1*ms) channel.set_att_mu(0x00) # minimum attenuation + # disable servo, set iir profile to 0 and disable iir hold + channel.set_servo(0, 1, 0) + # test oscillators and DUC for i in range(len(channel.oscillator)): oscillator = channel.oscillator[i] From 8bea821f9346d5d66a090ce7e30b76e152750d58 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Tue, 21 Jun 2022 08:43:55 +0000 Subject: [PATCH 10/46] just &1 to stay in field --- artiq/coredevice/phaser.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 417bcf32f..9d1d2a63c 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1071,12 +1071,7 @@ class PhaserChannel: if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") addr = PHASER_ADDR_SERVO_CFG0 + self.index - data = 0 - if bypass == 0: - data = 1 - if hold == 1: - data = data | (1 << 1) - data = data | (profile << 2) + data = (profile << 2) | ((hold & 1) << 1) | (~bypass & 1) self.phaser.write8(addr, data) @kernel From 57176fedb29ae64e72b562bd6dfa58df7dfbca47 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Tue, 21 Jun 2022 09:29:42 +0000 Subject: [PATCH 11/46] add servo docu --- artiq/coredevice/phaser.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 9d1d2a63c..6826bef7f 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -122,6 +122,26 @@ class Phaser: configured through a shared SPI bus that is accessed and controlled via FPGA registers. + Each phaser output channel features a servo to control the RF output amplitude + using feedback from an ADC. The servo consists of a first order IIR (infinite + impulse response) filter fed by the ADC and a multiplier that scales the I + and Q datastreams from the DUC by the IIR output. + + Each channels IIR features 4 profiles, each consisting of the [b0, b1, a1] filter + coefficients as well as an output offset. The coefficients and offset can be + set for each profile individually and the profiles each have their own filter + state. To avoid transient effects, care should be taken to not update the + coefficents in the currently selected profile. + + The IIR output can be put on hold for each channel. In hold mode, the filter + still ingests samples and updates its input x0 and x1 registers, but does not + update the y0, y1 output registers. The servo can also be bypassed. + + After power-up the servo is bypassed, in profile 0, with coefficients [0, 0, 0] + and hold is disabled. If older gateware without ther servo is loaded onto the + Phaser FPGA, the device simply behaves as if the servo is bypassed and none of + the servo functions have any effect. + .. note:: Various register settings of the DAC and the quadrature upconverters are available to be modified through the `dac`, `trf0`, `trf1` dictionaries. These can be set through the device database From b67a70392d8e60d87bb663a0b5f07c6acf712b20 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Tue, 21 Jun 2022 09:59:40 +0000 Subject: [PATCH 12/46] rename to coeff base and shorter write16 --- artiq/coredevice/phaser.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 6826bef7f..99f3f098c 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -45,7 +45,7 @@ PHASER_ADDR_SERVO_CFG0 = 0x30 PHASER_ADDR_SERVO_CFG1 = 0x31 # 0x32 - 0x61 ab regs -PHASER_ADDR_SERVO_AB_BASE = 0x32 +PHASER_ADDR_SERVO_COEFFICIENTS_BASE = 0x32 # 0x62 - 0x71 offset regs PHASER_ADDR_SERVO_OFFSET_BASE = 0x62 @@ -418,10 +418,8 @@ class Phaser: @kernel def write16(self, addr, data: TInt32): """Write 16 bit to a sequence of FPGA registers.""" - byte = data >> 8 - self.write8(addr, byte) - byte = data & 0xFF - self.write8(addr + 1, byte) + self.write8(addr, data >> 8) + self.write8(addr + 1, data) @kernel def write32(self, addr, data: TInt32): From ce4055db3beae3c0ec9b7629179ecdb9e14a429c Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Tue, 21 Jun 2022 10:11:49 +0000 Subject: [PATCH 13/46] force hold on bypass and use names in set_servo() in init --- artiq/coredevice/phaser.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 99f3f098c..e4c5197a9 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -138,7 +138,7 @@ class Phaser: update the y0, y1 output registers. The servo can also be bypassed. After power-up the servo is bypassed, in profile 0, with coefficients [0, 0, 0] - and hold is disabled. If older gateware without ther servo is loaded onto the + and hold is enabled. If older gateware without ther servo is loaded onto the Phaser FPGA, the device simply behaves as if the servo is bypassed and none of the servo functions have any effect. @@ -340,8 +340,7 @@ class Phaser: delay(.1*ms) channel.set_att_mu(0x00) # minimum attenuation - # disable servo, set iir profile to 0 and disable iir hold - channel.set_servo(0, 1, 0) + channel.set_servo(profile=0, bypass=1, hold=1) # test oscillators and DUC for i in range(len(channel.oscillator)): @@ -1082,14 +1081,16 @@ class PhaserChannel: def set_servo(self, profile=0, bypass=1, hold=0): """Set the servo configuration. - :param bypass: 1 to enable bypass (default), 0 to engage servo + :param bypass: 1 to enable bypass (default), 0 to engage servo. If bypassed, hold + is forced since the control loop is broken. :param hold: 1 to hold the servo IIR filter output constant, 0 for normal operation :param profile: profile index to select for channel (0 to 3) """ if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") addr = PHASER_ADDR_SERVO_CFG0 + self.index - data = (profile << 2) | ((hold & 1) << 1) | (~bypass & 1) + # enforce hold if the servo is bypassed + data = (profile << 2) | (((hold | bypass) & 1) << 1) | (~bypass & 1) self.phaser.write8(addr, data) @kernel From 43c94577ce5bf653cf58905929d611f2b32cab54 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Wed, 22 Jun 2022 15:35:49 +0000 Subject: [PATCH 14/46] impl set_iir. untested --- artiq/coredevice/phaser.py | 44 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index e4c5197a9..1e93db118 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -68,6 +68,10 @@ PHASER_DAC_SEL_TEST = 1 PHASER_HW_REV_VARIANT = 1 << 4 +SERVO_COEFF_WIDTH = 16 +SERVO_COEFF_SHIFT = 14 +SERVO_T_CYCLE = (32+12+192+24+4)*ns # Must match gateware ADC parameters + class Phaser: """Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver. @@ -127,7 +131,7 @@ class Phaser: impulse response) filter fed by the ADC and a multiplier that scales the I and Q datastreams from the DUC by the IIR output. - Each channels IIR features 4 profiles, each consisting of the [b0, b1, a1] filter + Each channel IIR features 4 profiles, each consisting of the [b0, b1, a1] filter coefficients as well as an output offset. The coefficients and offset can be set for each profile individually and the profiles each have their own filter state. To avoid transient effects, care should be taken to not update the @@ -1104,13 +1108,49 @@ class PhaserChannel: if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") # 24 byte-sized ab registers per channel and 6 (2 bytes * 3 coefficients) registers per profile - addr = PHASER_ADDR_SERVO_AB_BASE + (6 * profile) + (self.index * 24) + addr = PHASER_ADDR_SERVO_COEFFICIENTS_BASE + (6 * profile) + (self.index * 24) for coef in [b0, b1, a1]: self.phaser.write16(addr, coef) addr += 2 # 8 offset registers per channel and 2 registers per offset addr = PHASER_ADDR_SERVO_OFFSET_BASE + (2 * profile) + (self.index * 8) self.phaser.write16(addr, offset) + + @kernel + def set_iir(self, profile, kp, ki=0., g=0., x_offset=0., y_offset=0.): + + NORM = 1 << SERVO_COEFF_SHIFT + COEFF_MAX = 1 << SERVO_COEFF_WIDTH - 1 + + kp *= NORM + if ki == 0.: + # pure P + a1 = 0 + b1 = 0 + b0 = int(round(kp)) + else: + # I or PI + ki *= NORM*SERVO_T_CYCLE/2. + if g == 0.: + c = 1. + a1 = NORM + else: + c = 1./(1. + ki/(g*NORM)) + a1 = int(round((2.*c - 1.)*NORM)) + b0 = int(round(kp + ki*c)) + b1 = int(round(kp + (ki - 2.*kp)*c)) + if b1 == -b0: + raise ValueError("low integrator gain and/or gain limit") + + if (b0 >= COEFF_MAX or b0 < -COEFF_MAX or + b1 >= COEFF_MAX or b1 < -COEFF_MAX): + raise ValueError("high gains") + + forward_gain = b0 + b1 + effective_offset = y_offset + forward_gain * x_offset + + self.set_iir_mu(profile, b0, b1, a1, effective_offset) + class PhaserOscillator: From c0581178d60ffb9cd2882ed9ef2d085692335ddf Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Wed, 22 Jun 2022 16:20:59 +0000 Subject: [PATCH 15/46] impl offsets. to be tested --- artiq/coredevice/phaser.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 1e93db118..c23fe48e6 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -69,6 +69,7 @@ PHASER_DAC_SEL_TEST = 1 PHASER_HW_REV_VARIANT = 1 << 4 SERVO_COEFF_WIDTH = 16 +SERVO_DATA_WIDTH = 16 SERVO_COEFF_SHIFT = 14 SERVO_T_CYCLE = (32+12+192+24+4)*ns # Must match gateware ADC parameters @@ -1121,6 +1122,7 @@ class PhaserChannel: NORM = 1 << SERVO_COEFF_SHIFT COEFF_MAX = 1 << SERVO_COEFF_WIDTH - 1 + DATA_MAX = 1 << SERVO_DATA_WIDTH - 1 kp *= NORM if ki == 0.: @@ -1146,8 +1148,8 @@ class PhaserChannel: b1 >= COEFF_MAX or b1 < -COEFF_MAX): raise ValueError("high gains") - forward_gain = b0 + b1 - effective_offset = y_offset + forward_gain * x_offset + forward_gain = (b0 + b1) * (DATA_MAX - NORM) + effective_offset = int(round(DATA_MAX * y_offset + forward_gain * x_offset)) self.set_iir_mu(profile, b0, b1, a1, effective_offset) From 56c59e38f0f4a879be6b04748852e0ef97b5467a Mon Sep 17 00:00:00 2001 From: Norman Krackow Date: Thu, 23 Jun 2022 09:15:50 +0200 Subject: [PATCH 16/46] Update artiq/coredevice/phaser.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Robert Jördens --- artiq/coredevice/phaser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index c23fe48e6..ba7a25dd9 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1148,7 +1148,7 @@ class PhaserChannel: b1 >= COEFF_MAX or b1 < -COEFF_MAX): raise ValueError("high gains") - forward_gain = (b0 + b1) * (DATA_MAX - NORM) + forward_gain = (b0 + b1) * (1 << SERVO_DATA_WIDTH - 1 - SERVO_COEFF_SHIFT) effective_offset = int(round(DATA_MAX * y_offset + forward_gain * x_offset)) self.set_iir_mu(profile, b0, b1, a1, effective_offset) From 24b4ec46bd4b3fbad907d7dfaafbbf22226d1830 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Thu, 23 Jun 2022 08:48:28 +0000 Subject: [PATCH 17/46] more documentation --- artiq/coredevice/phaser.py | 67 ++++++++++++++++++++++++++++++++++---- 1 file changed, 61 insertions(+), 6 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index ba7a25dd9..2a10bf5b9 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -130,7 +130,8 @@ class Phaser: Each phaser output channel features a servo to control the RF output amplitude using feedback from an ADC. The servo consists of a first order IIR (infinite impulse response) filter fed by the ADC and a multiplier that scales the I - and Q datastreams from the DUC by the IIR output. + and Q datastreams from the DUC by the IIR output. The IIR state is updated at + the 3.788 MHz ADC sampling rate. Each channel IIR features 4 profiles, each consisting of the [b0, b1, a1] filter coefficients as well as an output offset. The coefficients and offset can be @@ -1088,8 +1089,8 @@ class PhaserChannel: :param bypass: 1 to enable bypass (default), 0 to engage servo. If bypassed, hold is forced since the control loop is broken. - :param hold: 1 to hold the servo IIR filter output constant, 0 for normal operation - :param profile: profile index to select for channel (0 to 3) + :param hold: 1 to hold the servo IIR filter output constant, 0 for normal operation. + :param profile: Profile index to select for channel. (0 to 3) """ if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") @@ -1102,9 +1103,32 @@ class PhaserChannel: def set_iir_mu(self, profile, b0, b1, a1, offset): """Load a servo profile consiting of the three filter coefficients and an output offset. - :param profile: profile to load (0 to 3) - :param ab: 3 entry coefficient vector (16 bit) - :param offset: output offset (16 bit) + Avoid setting the IIR parameters of the currently active profile. + + The recurrence relation is (all data signed and MSB aligned): + + .. math:: + a_0 y_n = a_1 y_{n - 1} + b_0 x_n + b_1 x_{n - 1} + o + + Where: + + * :math:`y_n` and :math:`y_{n-1}` are the current and previous + filter outputs, clipped to :math:`[0, 1[`. + * :math:`x_n` and :math:`x_{n-1}` are the current and previous + filter inputs in :math:`[-1, 1[`. + * :math:`o` is the offset + * :math:`a_0` is the normalization factor :math:`2^{14}` + * :math:`a_1` is the feedback gain + * :math:`b_0` and :math:`b_1` are the feedforward gains for the two + delays + + .. seealso:: :meth:`set_iir` + + :param profile: Profile to set (0 to 3) + :param b0: b0 filter coefficient (16 bit signed) + :param b1: b1 filter coefficient (16 bit signed) + :param a1: a1 filter coefficient (16 bit signed) + :param offset: Output offset (16 bit signed) """ if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") @@ -1119,7 +1143,38 @@ class PhaserChannel: @kernel def set_iir(self, profile, kp, ki=0., g=0., x_offset=0., y_offset=0.): + """Set servo profile IIR coefficients. + Avoid setting the IIR parameters of the currently active profile. + + Gains are given in units of output full per scale per input full scale. + + The transfer function is (up to time discretization and + coefficient quantization errors): + + .. math:: + H(s) = k_p + \\frac{k_i}{s + \\frac{k_i}{g}} + + Where: + * :math:`s = \\sigma + i\\omega` is the complex frequency + * :math:`k_p` is the proportional gain + * :math:`k_i` is the integrator gain + * :math:`g` is the integrator gain limit + + :param profile: Profile number (0-3) + :param kp: Proportional gain. This is usually negative (closed + loop, positive ADC voltage, positive setpoint). When 0, this + implements a pure I controller. + :param ki: Integrator gain (rad/s). Equivalent to the gain at 1 Hz. + When 0 (the default) this implements a pure P controller. + Same sign as ``kp``. + :param g: Integrator gain limit (1). When 0 (the default) the + integrator gain limit is infinite. Same sign as ``ki``. + :param x_offset: IIR input offset. Used as the negative + setpoint when stabilizing to a desired input setpoint. Will + be converted to an equivalent output offset and added to y_offset. + :param y_offset: IIR output offset. + """ NORM = 1 << SERVO_COEFF_SHIFT COEFF_MAX = 1 << SERVO_COEFF_WIDTH - 1 DATA_MAX = 1 << SERVO_DATA_WIDTH - 1 From ab097b8ef9f955b07fe23ec869dbc5e8a5585ee0 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Thu, 23 Jun 2022 09:37:37 +0000 Subject: [PATCH 18/46] add offset to coefficients as data --- artiq/coredevice/phaser.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 2a10bf5b9..d7fc15f51 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -44,10 +44,8 @@ PHASER_ADDR_DAC1_TEST = 0x2c PHASER_ADDR_SERVO_CFG0 = 0x30 PHASER_ADDR_SERVO_CFG1 = 0x31 -# 0x32 - 0x61 ab regs -PHASER_ADDR_SERVO_COEFFICIENTS_BASE = 0x32 -# 0x62 - 0x71 offset regs -PHASER_ADDR_SERVO_OFFSET_BASE = 0x62 +# 0x32 - 0x71 servo coefficients + offset data +PHASER_ADDR_SERVO_DATA_BASE = 0x32 PHASER_SEL_DAC = 1 << 0 @@ -1133,13 +1131,10 @@ class PhaserChannel: if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") # 24 byte-sized ab registers per channel and 6 (2 bytes * 3 coefficients) registers per profile - addr = PHASER_ADDR_SERVO_COEFFICIENTS_BASE + (6 * profile) + (self.index * 24) - for coef in [b0, b1, a1]: - self.phaser.write16(addr, coef) + addr = PHASER_ADDR_SERVO_DATA_BASE + (8 * profile) + (self.index * 32) + for data in [b0, b1, a1, offset]: + self.phaser.write16(addr, data) addr += 2 - # 8 offset registers per channel and 2 registers per offset - addr = PHASER_ADDR_SERVO_OFFSET_BASE + (2 * profile) + (self.index * 8) - self.phaser.write16(addr, offset) @kernel def set_iir(self, profile, kp, ki=0., g=0., x_offset=0., y_offset=0.): From 3f8a221c764d7d66e81cbce5d2f21c56615fac3e Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Thu, 23 Jun 2022 10:08:34 +0000 Subject: [PATCH 19/46] flip logic of enable bit to bypass bit and update some comments --- artiq/coredevice/phaser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index d7fc15f51..76e9fec7e 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1094,7 +1094,7 @@ class PhaserChannel: raise ValueError("invalid profile index") addr = PHASER_ADDR_SERVO_CFG0 + self.index # enforce hold if the servo is bypassed - data = (profile << 2) | (((hold | bypass) & 1) << 1) | (~bypass & 1) + data = (profile << 2) | (((hold | bypass) & 1) << 1) | (bypass & 1) self.phaser.write8(addr, data) @kernel @@ -1130,7 +1130,7 @@ class PhaserChannel: """ if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") - # 24 byte-sized ab registers per channel and 6 (2 bytes * 3 coefficients) registers per profile + # 32 byte-sized data registers per channel and 8 (2 bytes * (3 coefficients + 1 offset)) registers per profile addr = PHASER_ADDR_SERVO_DATA_BASE + (8 * profile) + (self.index * 32) for data in [b0, b1, a1, offset]: self.phaser.write16(addr, data) From 2e834cf406426bf844435b653f2dd39775c75c32 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Thu, 23 Jun 2022 10:20:38 +0000 Subject: [PATCH 20/46] unflip logic.. --- artiq/coredevice/phaser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index 76e9fec7e..bb3cfd6df 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1094,7 +1094,7 @@ class PhaserChannel: raise ValueError("invalid profile index") addr = PHASER_ADDR_SERVO_CFG0 + self.index # enforce hold if the servo is bypassed - data = (profile << 2) | (((hold | bypass) & 1) << 1) | (bypass & 1) + data = (profile << 2) | (((hold | bypass) & 1) << 1) | (~bypass & 1) self.phaser.write8(addr, data) @kernel From d8cfe22501e3e85654d20dab90bceb822de00c57 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Thu, 23 Jun 2022 15:18:55 +0000 Subject: [PATCH 21/46] add note about setpoint resolution --- artiq/coredevice/phaser.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index bb3cfd6df..c6e614a01 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1144,6 +1144,10 @@ class PhaserChannel: Gains are given in units of output full per scale per input full scale. + .. note:: Due to inherent constraints of the fixed point datatypes and IIR + filters, the setpoint resolution depends on the selected gains. This is + especially the case for low ``ki`` gains. + The transfer function is (up to time discretization and coefficient quantization errors): From 689a2ef8ba82d92e6c68e17465efa0ff031559e8 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Thu, 23 Jun 2022 15:23:00 +0000 Subject: [PATCH 22/46] refine note --- artiq/coredevice/phaser.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index c6e614a01..b69677500 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1145,8 +1145,8 @@ class PhaserChannel: Gains are given in units of output full per scale per input full scale. .. note:: Due to inherent constraints of the fixed point datatypes and IIR - filters, the setpoint resolution depends on the selected gains. This is - especially the case for low ``ki`` gains. + filters, the ``x_offset`` (setpoint) resolution depends on the selected gains. + Low ``ki`` gains will lead to a low ``x_offset`` resolution. The transfer function is (up to time discretization and coefficient quantization errors): From 953dd899fd9f06088a05cf9650492c14377169ce Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Thu, 23 Jun 2022 15:46:15 +0000 Subject: [PATCH 23/46] refine docu --- artiq/coredevice/phaser.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index b69677500..eeadfc73e 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -133,13 +133,15 @@ class Phaser: Each channel IIR features 4 profiles, each consisting of the [b0, b1, a1] filter coefficients as well as an output offset. The coefficients and offset can be - set for each profile individually and the profiles each have their own filter - state. To avoid transient effects, care should be taken to not update the - coefficents in the currently selected profile. + set for each profile individually and the profiles each have their own ``y0``, + ``y1`` output registers (the ``x0``, ``x1`` inputs are shared). To avoid + transient effects, care should be taken to not update the coefficents in the + currently selected profile. The IIR output can be put on hold for each channel. In hold mode, the filter - still ingests samples and updates its input x0 and x1 registers, but does not - update the y0, y1 output registers. The servo can also be bypassed. + still ingests samples and updates its input ``x0`` and ``x1`` registers, but + does not update the ``y0``, ``y1`` output registers. The servo can also be + bypassed. After power-up the servo is bypassed, in profile 0, with coefficients [0, 0, 0] and hold is enabled. If older gateware without ther servo is loaded onto the From 9c8ffa54b2c3ef7ccb5e0cdca5c3742a982a1d3a Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Wed, 6 Jul 2022 14:33:46 +0000 Subject: [PATCH 24/46] reverse to servo enable. hopefully adapted all comments etc. --- artiq/coredevice/phaser.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index eeadfc73e..9fad1d964 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -138,14 +138,16 @@ class Phaser: transient effects, care should be taken to not update the coefficents in the currently selected profile. + The servo can be en- or disabled for each channel. When disabled, the servo + output multiplier is simply bypassed and the datastream reaches the DAC unscaled. + The IIR output can be put on hold for each channel. In hold mode, the filter still ingests samples and updates its input ``x0`` and ``x1`` registers, but - does not update the ``y0``, ``y1`` output registers. The servo can also be - bypassed. + does not update the ``y0``, ``y1`` output registers. - After power-up the servo is bypassed, in profile 0, with coefficients [0, 0, 0] + After power-up the servo is disabled, in profile 0, with coefficients [0, 0, 0] and hold is enabled. If older gateware without ther servo is loaded onto the - Phaser FPGA, the device simply behaves as if the servo is bypassed and none of + Phaser FPGA, the device simply behaves as if the servo is disabled and none of the servo functions have any effect. .. note:: Various register settings of the DAC and the quadrature @@ -346,7 +348,7 @@ class Phaser: delay(.1*ms) channel.set_att_mu(0x00) # minimum attenuation - channel.set_servo(profile=0, bypass=1, hold=1) + channel.set_servo(profile=0, enable=0, hold=1) # test oscillators and DUC for i in range(len(channel.oscillator)): @@ -1084,19 +1086,19 @@ class PhaserChannel: self.trf_write(data) @kernel - def set_servo(self, profile=0, bypass=1, hold=0): + def set_servo(self, profile=0, enable=0, hold=0): """Set the servo configuration. - :param bypass: 1 to enable bypass (default), 0 to engage servo. If bypassed, hold - is forced since the control loop is broken. + :param enable: 1 to enable servo, 0 to disable servo (default). If disabled, + the servo is bypassed and hold is enforced since the control loop is broken. :param hold: 1 to hold the servo IIR filter output constant, 0 for normal operation. :param profile: Profile index to select for channel. (0 to 3) """ if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") addr = PHASER_ADDR_SERVO_CFG0 + self.index - # enforce hold if the servo is bypassed - data = (profile << 2) | (((hold | bypass) & 1) << 1) | (~bypass & 1) + # enforce hold if the servo is disabled + data = (profile << 2) | (((hold | ~enable) & 1) << 1) | (enable & 1) self.phaser.write8(addr, data) @kernel From 8be945d5c7b22280ac2b052cd549d946b091fa3d Mon Sep 17 00:00:00 2001 From: Spaqin Date: Thu, 7 Jul 2022 10:52:53 +0800 Subject: [PATCH 25/46] Urukul monitoring (#1142, #1921) --- RELEASE_NOTES.rst | 1 + artiq/dashboard/moninj.py | 81 ++++++++++++++++------ artiq/gateware/eem.py | 22 +++--- artiq/gateware/eem_7series.py | 2 +- artiq/gateware/rtio/phy/dds.py | 123 +++++++++++++++++++++++++++++++++ 5 files changed, 198 insertions(+), 31 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 19a97a206..213f77598 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -45,6 +45,7 @@ Highlights: switch is supported. * The "ip" config option can now be set to "use_dhcp" in order to use DHCP to obtain an IP address. DHCP will also be used if no "ip" config option is set. +* Urukul monitoring and frequency setting (through dashboard) is now supported. Breaking changes: diff --git a/artiq/dashboard/moninj.py b/artiq/dashboard/moninj.py index df5854259..ef514b4b1 100644 --- a/artiq/dashboard/moninj.py +++ b/artiq/dashboard/moninj.py @@ -8,6 +8,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui from sipyco.sync_struct import Subscriber from artiq.coredevice.comm_moninj import * +from artiq.coredevice.ad9910 import _AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7, _AD9910_REG_FTW +from artiq.coredevice.ad9912_reg import AD9912_POW1 from artiq.gui.tools import LayoutWidget from artiq.gui.flowlayout import FlowLayout @@ -179,14 +181,45 @@ class _SimpleDisplayWidget(QtWidgets.QFrame): raise NotImplementedError +class _DDSModel: + def __init__(self, dds_type, ref_clk, cpld=None, pll=1, clk_div=0): + self.cpld = cpld + self.cur_frequency = 0 + self.cur_reg = 0 + self.dds_type = dds_type + self.is_urukul = dds_type in ["AD9910", "AD9912"] + + if dds_type == "AD9914": + self.ftw_per_hz = 2**32 / ref_clk + else: + if dds_type == "AD9910": + max_freq = 1 << 32 + clk_mult = [4, 1, 2, 4] + elif dds_type == "AD9912": # AD9912 + max_freq = 1 << 48 + clk_mult = [1, 1, 2, 4] + else: + raise NotImplementedError + sysclk = ref_clk / clk_mult[clk_div] * pll + self.ftw_per_hz = 1 / sysclk * max_freq + + def monitor_update(self, probe, value): + if self.dds_type == "AD9912": + value = value << 16 + self.cur_frequency = self._ftw_to_freq(value) + + def _ftw_to_freq(self, ftw): + return ftw / self.ftw_per_hz + + class _DDSWidget(QtWidgets.QFrame): - def __init__(self, dm, title, bus_channel=0, channel=0, cpld=None): + def __init__(self, dm, title, bus_channel=0, channel=0, dds_model=None): self.dm = dm self.bus_channel = bus_channel self.channel = channel self.dds_name = title - self.cpld = cpld self.cur_frequency = 0 + self.dds_model = dds_model QtWidgets.QFrame.__init__(self) @@ -249,7 +282,7 @@ class _DDSWidget(QtWidgets.QFrame): set_grid.addWidget(set_btn, 0, 1, 1, 1) # for urukuls also allow switching off RF - if self.cpld: + if self.dds_model.is_urukul: off_btn = QtWidgets.QToolButton() off_btn.setText("Off") off_btn.setToolTip("Switch off the output") @@ -276,7 +309,7 @@ class _DDSWidget(QtWidgets.QFrame): set_btn.clicked.connect(self.set_clicked) apply.clicked.connect(self.apply_changes) - if self.cpld: + if self.dds_model.is_urukul: off_btn.clicked.connect(self.off_clicked) self.value_edit.returnPressed.connect(lambda: self.apply_changes(None)) self.value_edit.escapePressedConnect(self.cancel_changes) @@ -293,19 +326,20 @@ class _DDSWidget(QtWidgets.QFrame): self.value_edit.selectAll() def off_clicked(self, set): - self.dm.dds_channel_toggle(self.dds_name, self.cpld, sw=False) + self.dm.dds_channel_toggle(self.dds_name, self.dds_model, sw=False) def apply_changes(self, apply): self.data_stack.setCurrentIndex(0) self.button_stack.setCurrentIndex(0) frequency = float(self.value_edit.text())*1e6 - self.dm.dds_set_frequency(self.dds_name, self.cpld, frequency) + self.dm.dds_set_frequency(self.dds_name, self.dds_model, frequency) def cancel_changes(self, cancel): self.data_stack.setCurrentIndex(0) self.button_stack.setCurrentIndex(0) def refresh_display(self): + self.cur_frequency = self.dds_model.cur_frequency self.value_label.setText("{:.7f}" .format(self.cur_frequency/1e6)) self.value_edit.setText("{:.7f}" @@ -356,7 +390,8 @@ def setup_from_ddb(ddb): bus_channel = v["arguments"]["bus_channel"] channel = v["arguments"]["channel"] dds_sysclk = v["arguments"]["sysclk"] - widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel)) + model = _DDSModel(v["class"], dds_sysclk) + widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model)) description.add(widget) elif (v["module"] == "artiq.coredevice.ad9910" and v["class"] == "AD9910") or \ @@ -368,7 +403,11 @@ def setup_from_ddb(ddb): dds_cpld = v["arguments"]["cpld_device"] spi_dev = ddb[dds_cpld]["arguments"]["spi_device"] bus_channel = ddb[spi_dev]["arguments"]["channel"] - widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, dds_cpld)) + pll = v["arguments"]["pll_n"] + refclk = ddb[dds_cpld]["arguments"]["refclk"] + clk_div = v["arguments"].get("clk_div", 0) + model = _DDSModel( v["class"], refclk, dds_cpld, pll, clk_div) + widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model)) description.add(widget) elif ( (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx") or (v["module"] == "artiq.coredevice.zotino" and v["class"] == "Zotino")): @@ -385,7 +424,7 @@ def setup_from_ddb(ddb): mi_port = v.get("port_proxy", 1383) except KeyError: pass - return mi_addr, mi_port, dds_sysclk, description + return mi_addr, mi_port, description class _DeviceManager: @@ -415,15 +454,13 @@ class _DeviceManager: return ddb def notify(self, mod): - mi_addr, mi_port, dds_sysclk, description = setup_from_ddb(self.ddb) + mi_addr, mi_port, description = setup_from_ddb(self.ddb) if (mi_addr, mi_port) != (self.mi_addr, self.mi_port): self.mi_addr = mi_addr self.mi_port = mi_port self.reconnect_mi.set() - self.dds_sysclk = dds_sysclk - for to_remove in self.description - description: widget = self.widgets_by_uid[to_remove.uid] del self.widgets_by_uid[to_remove.uid] @@ -512,13 +549,13 @@ class _DeviceManager: scheduling["flush"]) logger.info("Submitted '%s', RID is %d", title, rid) - def dds_set_frequency(self, dds_channel, dds_cpld, freq): + def dds_set_frequency(self, dds_channel, dds_model, freq): # create kernel and fill it in and send-by-content - if dds_cpld: + if dds_model.is_urukul: # urukuls need CPLD init and switch to on # keep previous config if it was set already cpld_dev = """self.setattr_device("core_cache") - self.setattr_device("{}")""".format(dds_cpld) + self.setattr_device("{}")""".format(dds_model.cpld) cpld_init = """cfg = self.core_cache.get("_{cpld}_cfg") if len(cfg) > 0: self.{cpld}.cfg_reg = cfg[0] @@ -526,10 +563,10 @@ class _DeviceManager: self.{cpld}.init() self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg]) cfg = self.core_cache.get("_{cpld}_cfg") - """.format(cpld=dds_cpld) + """.format(cpld=dds_model.cpld) cfg_sw = """self.{}.cfg_sw(True) cfg[0] = self.{}.cfg_reg - """.format(dds_channel, dds_cpld) + """.format(dds_channel, dds_model.cpld) else: cpld_dev = "" cpld_init = "" @@ -560,7 +597,7 @@ class _DeviceManager: "SetDDS", "Set DDS {} {}MHz".format(dds_channel, freq/1e6))) - def dds_channel_toggle(self, dds_channel, dds_cpld, sw=True): + def dds_channel_toggle(self, dds_model, sw=True): # urukul only toggle_exp = textwrap.dedent(""" from artiq.experiment import * @@ -586,7 +623,7 @@ class _DeviceManager: self.{ch}.init() self.{ch}.cfg_sw({sw}) cfg[0] = self.{cpld}.cfg_reg - """.format(ch=dds_channel, cpld=dds_cpld, sw=sw)) + """.format(ch=dds_channel, cpld=dds_model.cpld, sw=sw)) asyncio.ensure_future( self._submit_by_content( toggle_exp, @@ -619,11 +656,11 @@ class _DeviceManager: elif probe == TTLProbe.oe.value: widget.cur_oe = bool(value) widget.refresh_display() - if (channel, probe) in self.dds_widgets: + elif (channel, probe) in self.dds_widgets: widget = self.dds_widgets[(channel, probe)] - widget.cur_frequency = value*self.dds_sysclk/2**32 + widget.dds_model.monitor_update(probe, value) widget.refresh_display() - if (channel, probe) in self.dac_widgets: + elif (channel, probe) in self.dac_widgets: widget = self.dac_widgets[(channel, probe)] widget.cur_value = value widget.refresh_display() diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 23a85eca8..467f3cae2 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -3,7 +3,7 @@ from migen.build.generic_platform import * from migen.genlib.io import DifferentialOutput from artiq.gateware import rtio -from artiq.gateware.rtio.phy import spi2, ad53xx_monitor, grabber +from artiq.gateware.rtio.phy import spi2, ad53xx_monitor, dds, grabber from artiq.gateware.suservo import servo, pads as servo_pads from artiq.gateware.rtio.phy import servo as rtservo, fastino, phaser @@ -222,13 +222,13 @@ class Urukul(_EEM): return ios @classmethod - def add_std(cls, target, eem, eem_aux, ttl_out_cls, sync_gen_cls=None, iostandard=default_iostandard): + def add_std(cls, target, eem, eem_aux, ttl_out_cls, dds_type, sync_gen_cls=None, iostandard=default_iostandard): cls.add_extension(target, eem, eem_aux, iostandard=iostandard) - phy = spi2.SPIMaster(target.platform.request("urukul{}_spi_p".format(eem)), + spi_phy = spi2.SPIMaster(target.platform.request("urukul{}_spi_p".format(eem)), target.platform.request("urukul{}_spi_n".format(eem))) - target.submodules += phy - target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) + target.submodules += spi_phy + target.rtio_channels.append(rtio.Channel.from_phy(spi_phy, ififo_depth=4)) pads = target.platform.request("urukul{}_dds_reset_sync_in".format(eem)) if sync_gen_cls is not None: # AD9910 variant and SYNC_IN from EEM @@ -237,9 +237,14 @@ class Urukul(_EEM): target.rtio_channels.append(rtio.Channel.from_phy(phy)) pads = target.platform.request("urukul{}_io_update".format(eem)) - phy = ttl_out_cls(pads.p, pads.n) - target.submodules += phy - target.rtio_channels.append(rtio.Channel.from_phy(phy)) + io_upd_phy = ttl_out_cls(pads.p, pads.n) + target.submodules += io_upd_phy + target.rtio_channels.append(rtio.Channel.from_phy(io_upd_phy)) + + dds_monitor = dds.UrukulMonitor(spi_phy, io_upd_phy, dds_type) + target.submodules += dds_monitor + spi_phy.probes.extend(dds_monitor.probes) + if eem_aux is not None: for signal in "sw0 sw1 sw2 sw3".split(): pads = target.platform.request("urukul{}_{}".format(eem, signal)) @@ -247,6 +252,7 @@ class Urukul(_EEM): target.submodules += phy target.rtio_channels.append(rtio.Channel.from_phy(phy)) + class Sampler(_EEM): @staticmethod def io(eem, eem_aux, iostandard): diff --git a/artiq/gateware/eem_7series.py b/artiq/gateware/eem_7series.py index 1f5f770c2..1983b1009 100644 --- a/artiq/gateware/eem_7series.py +++ b/artiq/gateware/eem_7series.py @@ -47,7 +47,7 @@ def peripheral_urukul(module, peripheral, **kwargs): else: sync_gen_cls = None eem.Urukul.add_std(module, port, port_aux, ttl_serdes_7series.Output_8X, - sync_gen_cls, **kwargs) + peripheral["dds"], sync_gen_cls, **kwargs) def peripheral_novogorny(module, peripheral, **kwargs): diff --git a/artiq/gateware/rtio/phy/dds.py b/artiq/gateware/rtio/phy/dds.py index c542937fa..ca7494c20 100644 --- a/artiq/gateware/rtio/phy/dds.py +++ b/artiq/gateware/rtio/phy/dds.py @@ -3,6 +3,11 @@ from migen import * from artiq.gateware import ad9_dds from artiq.gateware.rtio.phy.wishbone import RT2WB +from artiq.coredevice.spi2 import SPI_CONFIG_ADDR, SPI_DATA_ADDR, SPI_END +from artiq.coredevice.urukul import CS_DDS_CH0, CS_DDS_MULTI, CFG_IO_UPDATE, CS_CFG + +from artiq.coredevice.ad9912_reg import AD9912_POW1 +from artiq.coredevice.ad9910 import _AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7, _AD9910_REG_FTW class AD9914(Module): def __init__(self, pads, nchannels, onehot=False, **kwargs): @@ -54,3 +59,121 @@ class AD9914(Module): self.sync.rio_phy += If(current_address == 2**len(pads.a), [ If(selected(c), probe.eq(ftw)) for c, (probe, ftw) in enumerate(zip(self.probes, ftws))]) + + +class UrukulMonitor(Module): + def __init__(self, spi_phy, io_update_phy, dds, nchannels=4): + self.spi_phy = spi_phy + self.io_update_phy = io_update_phy + + self.probes = [Signal(32) for i in range(nchannels)] + + self.cs = Signal(8) + self.current_data = Signal.like(self.spi_phy.rtlink.o.data) + current_address = Signal.like(self.spi_phy.rtlink.o.address) + data_length = Signal(8) + flags = Signal(8) + + self.sync.rio += If(self.spi_phy.rtlink.o.stb, [ + current_address.eq(self.spi_phy.rtlink.o.address), + self.current_data.eq(self.spi_phy.rtlink.o.data), + If(self.spi_phy.rtlink.o.address == SPI_CONFIG_ADDR, [ + self.cs.eq(self.spi_phy.rtlink.o.data[24:]), + data_length.eq(self.spi_phy.rtlink.o.data[8:16] + 1), + flags.eq(self.spi_phy.rtlink.o.data[0:8]) + ]) + ]) + + for i in range(nchannels): + ch_sel = Signal() + self.comb += ch_sel.eq( + ((self.cs == CS_DDS_MULTI) | (self.cs == i + CS_DDS_CH0)) + & (current_address == SPI_DATA_ADDR) + ) + + if dds == "ad9912": + mon_cls = _AD9912Monitor + elif dds == "ad9910": + mon_cls = _AD9910Monitor + else: + raise NotImplementedError + + monitor = mon_cls(self.current_data, data_length, flags, ch_sel) + self.submodules += monitor + + self.sync.rio_phy += [ + If(ch_sel & self.is_io_update(), self.probes[i].eq(monitor.ftw)) + ] + + def is_io_update(self): + # shifted 8 bits left for 32-bit bus + reg_io_upd = (self.cs == CS_CFG) & self.current_data[8 + CFG_IO_UPDATE] + phy_io_upd = False + if self.io_update_phy: + phy_io_upd = self.io_update_phy.rtlink.o.stb & self.io_update_phy.rtlink.o.data + return phy_io_upd | reg_io_upd + + +class _AD9912Monitor(Module): + def __init__(self, current_data, data_length, flags, ch_sel): + self.ftw = Signal(32, reset_less=True) + + fsm = ClockDomainsRenamer("rio_phy")(FSM(reset_state="IDLE")) + self.submodules += fsm + + reg_addr = current_data[16:29] + reg_write = ~current_data[31] + + fsm.act("IDLE", + If(ch_sel & reg_write, + If((data_length == 16) & (reg_addr == AD9912_POW1), + NextState("READ") + ) + ) + ) + + fsm.act("READ", + If(ch_sel, + If(flags & SPI_END, + # lower 16 bits (16-32 from 48-bit transfer) + NextValue(self.ftw[:16], current_data[16:]), + NextState("IDLE") + ).Else( + NextValue(self.ftw[16:], current_data[:16]) + ) + ) + ) + + +class _AD9910Monitor(Module): + def __init__(self, current_data, data_length, flags, ch_sel): + self.ftw = Signal(32, reset_less=True) + + fsm = ClockDomainsRenamer("rio_phy")(FSM(reset_state="IDLE")) + self.submodules += fsm + + reg_addr = current_data[24:29] + reg_write = ~current_data[31] + + fsm.act("IDLE", + If(ch_sel & reg_write, + If((data_length == 8) & (_AD9910_REG_PROFILE7 >= reg_addr) & (reg_addr >= _AD9910_REG_PROFILE0), + NextState("READ") + ).Elif(reg_addr == _AD9910_REG_FTW, + If((data_length == 24) & (flags & SPI_END), + NextValue(self.ftw[:16], current_data[8:24]) + ).Elif(data_length == 8, + NextState("READ") + ) + ) + ) + ) + + fsm.act("READ", + If(ch_sel, + If(flags & SPI_END, + NextValue(self.ftw, current_data), + NextState("IDLE") + ) + ) + ) From c9fb7b410fee9937832482ff9d727fbfbc970a20 Mon Sep 17 00:00:00 2001 From: mwojcik Date: Thu, 7 Jul 2022 11:24:20 +0800 Subject: [PATCH 26/46] moninj: fix underflows for urukul freq set --- artiq/dashboard/moninj.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/artiq/dashboard/moninj.py b/artiq/dashboard/moninj.py index ef514b4b1..fabc0a526 100644 --- a/artiq/dashboard/moninj.py +++ b/artiq/dashboard/moninj.py @@ -560,6 +560,7 @@ class _DeviceManager: if len(cfg) > 0: self.{cpld}.cfg_reg = cfg[0] else: + delay(10*ms) self.{cpld}.init() self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg]) cfg = self.core_cache.get("_{cpld}_cfg") @@ -582,8 +583,8 @@ class _DeviceManager: @kernel def run(self): - self.core.break_realtime() - delay(2*ms) + self.core.reset() + delay(5*ms) {cpld_init} self.{dds_channel}.init() self.{dds_channel}.set({freq}) @@ -612,11 +613,12 @@ class _DeviceManager: @kernel def run(self): self.core.break_realtime() - delay(2*ms) + delay(5*ms) cfg = self.core_cache.get("_{cpld}_cfg") if len(cfg) > 0: self.{cpld}.cfg_reg = cfg[0] else: + delay(10*ms) self.{cpld}.init() self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg]) cfg = self.core_cache.get("_{cpld}_cfg") From 46f2842d38dfee43aa86a652c98eb9fc453ca6fc Mon Sep 17 00:00:00 2001 From: mwojcik Date: Thu, 7 Jul 2022 12:30:09 +0800 Subject: [PATCH 27/46] moninj: fix underflows by order of operation fix channel toggle --- artiq/dashboard/moninj.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/artiq/dashboard/moninj.py b/artiq/dashboard/moninj.py index fabc0a526..b893c778a 100644 --- a/artiq/dashboard/moninj.py +++ b/artiq/dashboard/moninj.py @@ -560,7 +560,7 @@ class _DeviceManager: if len(cfg) > 0: self.{cpld}.cfg_reg = cfg[0] else: - delay(10*ms) + delay(15*ms) self.{cpld}.init() self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg]) cfg = self.core_cache.get("_{cpld}_cfg") @@ -584,8 +584,8 @@ class _DeviceManager: @kernel def run(self): self.core.reset() - delay(5*ms) {cpld_init} + delay(5*ms) self.{dds_channel}.init() self.{dds_channel}.set({freq}) {cfg_sw} @@ -598,7 +598,7 @@ class _DeviceManager: "SetDDS", "Set DDS {} {}MHz".format(dds_channel, freq/1e6))) - def dds_channel_toggle(self, dds_model, sw=True): + def dds_channel_toggle(self, dds_channel, dds_model, sw=True): # urukul only toggle_exp = textwrap.dedent(""" from artiq.experiment import * @@ -612,16 +612,16 @@ class _DeviceManager: @kernel def run(self): - self.core.break_realtime() - delay(5*ms) + self.core.reset() cfg = self.core_cache.get("_{cpld}_cfg") if len(cfg) > 0: self.{cpld}.cfg_reg = cfg[0] else: - delay(10*ms) + delay(15*ms) self.{cpld}.init() self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg]) cfg = self.core_cache.get("_{cpld}_cfg") + delay(5*ms) self.{ch}.init() self.{ch}.cfg_sw({sw}) cfg[0] = self.{cpld}.cfg_reg From 7aa61048723d44a3da5d92ce085ff31819aa973f Mon Sep 17 00:00:00 2001 From: kk1050 <103404672+kk1050@users.noreply.github.com> Date: Thu, 7 Jul 2022 17:01:34 +0800 Subject: [PATCH 28/46] Add method to check if termination is requested (#811, #1932) Co-authored-by: kk105 --- artiq/frontend/artiq_master.py | 1 + artiq/master/scheduler.py | 10 ++++++++++ artiq/master/worker_impl.py | 6 ++++++ 3 files changed, 17 insertions(+) diff --git a/artiq/frontend/artiq_master.py b/artiq/frontend/artiq_master.py index 99c2fc9f1..da57e6b3d 100755 --- a/artiq/frontend/artiq_master.py +++ b/artiq/frontend/artiq_master.py @@ -128,6 +128,7 @@ def main(): "scheduler_request_termination": scheduler.request_termination, "scheduler_get_status": scheduler.get_status, "scheduler_check_pause": scheduler.check_pause, + "scheduler_check_termination": scheduler.check_termination, "ccb_issue": ccb_issue, }) experiment_db.scan_repository_async() diff --git a/artiq/master/scheduler.py b/artiq/master/scheduler.py index d6d44acef..eea832a17 100644 --- a/artiq/master/scheduler.py +++ b/artiq/master/scheduler.py @@ -490,3 +490,13 @@ class Scheduler: return False return r.priority_key() > run.priority_key() raise KeyError("RID not found") + + def check_termination(self, rid): + """Returns ``True`` if termination is requested.""" + for pipeline in self._pipelines.values(): + if rid in pipeline.pool.runs: + run = pipeline.pool.runs[rid] + if run.termination_requested: + return True + return False + \ No newline at end of file diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index 97589dc14..33d34ddf8 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -111,6 +111,12 @@ class Scheduler: rid = self.rid return self._check_pause(rid) + _check_termination = staticmethod(make_parent_action("scheduler_check_termination")) + def check_termination(self, rid=None) -> TBool: + if rid is None: + rid = self.rid + return self._check_termination(rid) + _submit = staticmethod(make_parent_action("scheduler_submit")) def submit(self, pipeline_name=None, expid=None, priority=None, due_date=None, flush=False): if pipeline_name is None: From c7394802bdc02a7e7c9b62163df10ebb70ddf57f Mon Sep 17 00:00:00 2001 From: Deepskyhunter <48083317+Deepskyhunter@users.noreply.github.com> Date: Thu, 7 Jul 2022 17:20:08 +0800 Subject: [PATCH 29/46] aqctl_moninj_proxy: clear listeners on disconnect --- artiq/frontend/aqctl_moninj_proxy.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/artiq/frontend/aqctl_moninj_proxy.py b/artiq/frontend/aqctl_moninj_proxy.py index 8ce6581a9..fcf9e259d 100755 --- a/artiq/frontend/aqctl_moninj_proxy.py +++ b/artiq/frontend/aqctl_moninj_proxy.py @@ -116,6 +116,9 @@ class MonitorMux: else: raise ValueError + def disconnect_cb(self): + self.listeners.clear() + class ProxyConnection: def __init__(self, monitor_mux, reader, writer): @@ -203,7 +206,9 @@ def main(): signal_handler.setup() try: monitor_mux = MonitorMux() - comm_moninj = CommMonInj(monitor_mux.monitor_cb, monitor_mux.injection_status_cb) + comm_moninj = CommMonInj(monitor_mux.monitor_cb, + monitor_mux.injection_status_cb, + monitor_mux.disconnect_cb) monitor_mux.comm_moninj = comm_moninj loop.run_until_complete(comm_moninj.connect(args.core_addr)) try: From 734b2a6747894e1ebc9d4ea5e1696c3840d9bef0 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 7 Jul 2022 18:03:17 +0800 Subject: [PATCH 30/46] flake: update rpi-1 host key --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index 1ab9d10af..d37d4cc62 100644 --- a/flake.nix +++ b/flake.nix @@ -467,7 +467,7 @@ mkdir $HOME/.ssh cp /opt/hydra_id_ed25519 $HOME/.ssh/id_ed25519 cp /opt/hydra_id_ed25519.pub $HOME/.ssh/id_ed25519.pub - echo "rpi-1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPOBQVcsvk6WgRj18v4m0zkFeKrcN9gA+r6sxQxNwFpv" > $HOME/.ssh/known_hosts + echo "rpi-1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIACtBFDVBYoAE4fpJCTANZSE0bcVpTR3uvfNvb80C4i5" > $HOME/.ssh/known_hosts chmod 600 $HOME/.ssh/id_ed25519 LOCKCTL=$(mktemp -d) mkfifo $LOCKCTL/lockctl From 57ac6ec00370d2c183f3e22666261532496d3852 Mon Sep 17 00:00:00 2001 From: SingularitySurfer Date: Thu, 7 Jul 2022 10:19:47 +0000 Subject: [PATCH 31/46] add release note --- RELEASE_NOTES.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 213f77598..0c1f0af74 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -21,6 +21,7 @@ Highlights: - Expose the DAC coarse mixer and ``sif_sync`` - Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs. - Add helpers to align Phaser updates to the RTIO timeline (``get_next_frame_mu()``) + - Implemented Phaser-servo. * Core device moninj is now proxied via the ``aqctl_moninj_proxy`` controller. * The configuration entry ``rtio_clock`` supports multiple clocking settings, deprecating the usage of compile-time options. From 4ea11f46099d8d8045cbcac1cf2f02ee9763bafc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 7 Jul 2022 16:03:35 +0200 Subject: [PATCH 32/46] RELEASE_NOTES: update servo note --- RELEASE_NOTES.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 0c1f0af74..6f8783795 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -21,7 +21,7 @@ Highlights: - Expose the DAC coarse mixer and ``sif_sync`` - Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs. - Add helpers to align Phaser updates to the RTIO timeline (``get_next_frame_mu()``) - - Implemented Phaser-servo. + - Implemented Phaser-servo. This requires recent gateware on Phaser. * Core device moninj is now proxied via the ``aqctl_moninj_proxy`` controller. * The configuration entry ``rtio_clock`` supports multiple clocking settings, deprecating the usage of compile-time options. From 9ba239b8b22110ed07fc2197b833d1cf8e1bb70f Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 8 Jul 2022 11:35:17 +0800 Subject: [PATCH 33/46] flake: add aarch64 openocd package --- flake.nix | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/flake.nix b/flake.nix index d37d4cc62..de3a43b4c 100644 --- a/flake.nix +++ b/flake.nix @@ -16,6 +16,7 @@ outputs = { self, nixpkgs, mozilla-overlay, sipyco, src-pythonparser, artiq-comtools, src-migen, src-misoc }: let pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; }; + pkgs-aarch64 = import nixpkgs { system = "aarch64-linux"; }; artiqVersionMajor = 7; artiqVersionMinor = self.sourceInfo.revCount or 0; @@ -305,7 +306,7 @@ dontFixup = true; }; - openocd-bscanspi = let + openocd-bscanspi-f = pkgs: let bscan_spi_bitstreams-pkg = pkgs.stdenv.mkDerivation { name = "bscan_spi_bitstreams"; src = pkgs.fetchFromGitHub { @@ -362,8 +363,9 @@ }; in rec { packages.x86_64-linux = { - inherit pythonparser qasync openocd-bscanspi artiq; + inherit pythonparser qasync artiq; inherit migen misoc asyncserial microscope vivadoEnv vivado; + openocd-bscanspi = openocd-bscanspi-f pkgs; artiq-board-kc705-nist_clock = makeArtiqBoardPackage { target = "kc705"; variant = "nist_clock"; @@ -443,6 +445,10 @@ ''; }; + packages.aarch64-linux = { + openocd-bscanspi = openocd-bscanspi-f pkgs-aarch64; + }; + hydraJobs = { inherit (packages.x86_64-linux) artiq artiq-board-kc705-nist_clock openocd-bscanspi; kc705-hitl = pkgs.stdenv.mkDerivation { From cb711e0ee39ac1231e2d1e51a9e652cd8db9fe18 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 8 Jul 2022 17:51:02 +0800 Subject: [PATCH 34/46] edit ARTIQ-7 release notes --- RELEASE_NOTES.rst | 59 +++++++++++++++++++++++++++++------------------ 1 file changed, 36 insertions(+), 23 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 213f77598..ce4d5a858 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -9,43 +9,52 @@ ARTIQ-7 Highlights: * New hardware support: - - Kasli-SoC, a new EEM carrier based on a Zynq SoC, enabling much faster kernel execution. + - Kasli-SoC, a new EEM carrier based on a Zynq SoC, enabling much faster kernel execution + (see: https://arxiv.org/abs/2111.15290). + - DRTIO support on Zynq-based devices (Kasli-SoC and ZC706). + - DRTIO support on KC705. - HVAMP_8CH 8 channel HV amplifier for Fastino / Zotinos - Almazny mezzanine board for Mirny -* TTL device output can be now configured to work as a clock generator. + - Phaser: improved documentation, exposed the DAC coarse mixer and ``sif_sync``, exposed upconverter calibration + and enabling/disabling of upconverter LO & RF outputs, added helpers to align Phaser updates to the + RTIO timeline (``get_next_frame_mu()`` + - Urukul: ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912. * Softcore targets now use the RISC-V architecture (VexRiscv) instead of OR1K (mor1kx). * Gateware FPU is supported on KC705 and Kasli 2.0. * Faster compilation for large arrays/lists. -* Phaser: - - Improved documentation - - Expose the DAC coarse mixer and ``sif_sync`` - - Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs. - - Add helpers to align Phaser updates to the RTIO timeline (``get_next_frame_mu()``) -* Core device moninj is now proxied via the ``aqctl_moninj_proxy`` controller. +* Faster exception handling. +* Several exception handling bugs fixed. +* Support for a simpler shared library system with faster calls into the runtime. This is only used by the NAC3 + compiler (nac3ld) and improves RTIO output performance (test_pulse_rate) by 9-10%. +* Moninj improvements: + - Urukul monitoring and frequency setting (through dashboard) is now supported. + - Core device moninj is now proxied via the ``aqctl_moninj_proxy`` controller. * The configuration entry ``rtio_clock`` supports multiple clocking settings, deprecating the usage of compile-time options. -* Packaging via Nix Flakes. -* Firmware and gateware can now be built on-demand on the M-Labs server using ``afws_client`` - (subscribers only). +* Added support for 100MHz RTIO clock in DRTIO. +* Previously detected RTIO async errors are reported to the host after each kernel terminates and a + warning is logged. The warning is additional to the one already printed in the core device log + immediately upon detection of the error. * Extended Kasli gateware JSON description with configuration for SPI over DIO. -* ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912. +* TTL outputs can be now configured to work as a clock generator from the JSON. * On Kasli, the number of FIFO lanes in the scalable events dispatcher (SED) can now be configured in - the JSON hardware description file. + the JSON. * ``artiq_ddb_template`` generates edge-counter keys that start with the key of the corresponding TTL device (e.g. ``ttl_0_counter`` for the edge counter on TTL device ``ttl_0``). * ``artiq_master`` now has an ``--experiment-subdir`` option to scan only a subdirectory of the repository when building the list of experiments. -* Added support for 100MHz RTIO clock in DRTIO. -* Previously detected RTIO async errors are reported to the host after each kernel terminates and a - warning is logged. The warning is additional to the one already printed in the core device log upon - detection of the error. +* Experiments can now be submitted by-content. +* The master can now optionally log all experiments submitted into a CSV file. * Removed worker DB warning for writing a dataset that is also in the archive. -* ``PCA9548`` I2C switch class renamed to ``I2CSwitch``, to accomodate support for PCA9547, and - possibly other switches in future. Readback has been removed, and now only one channel per - switch is supported. -* The "ip" config option can now be set to "use_dhcp" in order to use DHCP to obtain an IP address. - DHCP will also be used if no "ip" config option is set. -* Urukul monitoring and frequency setting (through dashboard) is now supported. +* Experiments can now call ``scheduler.check_termination()`` to test if the user + has requested graceful termination. +* ARTIQ command-line programs and controllers now exit cleanly on Ctrl-C. +* ``artiq_coremgmt reboot`` now reloads gateware as well, providing a more thorough and reliable + device reset (7-series FPGAs only). +* Firmware and gateware can now be built on-demand on the M-Labs server using ``afws_client`` + (subscribers only). Self-compilation remains possible. +* Easier-to-use packaging via Nix Flakes. +* Python 3.10 support (experimental). Breaking changes: @@ -60,6 +69,10 @@ Breaking changes: * Mirny: Added extra delays in ``ADF5356.sync()``. This avoids the need of an extra delay before calling `ADF5356.init()`. * The deprecated ``set_dataset(..., save=...)`` is no longer supported. +* The ``PCA9548`` I2C switch class was renamed to ``I2CSwitch``, to accomodate support for PCA9547, + and possibly other switches in future. Readback has been removed, and now only one channel per + switch is supported. + ARTIQ-6 ------- From 5c461443e47553ed1baddf685f2d8731ba0e8e11 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 8 Jul 2022 17:52:58 +0800 Subject: [PATCH 35/46] flake: update dependencies --- flake.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/flake.lock b/flake.lock index bb98ea5f2..7eaac721f 100644 --- a/flake.lock +++ b/flake.lock @@ -26,11 +26,11 @@ "mozilla-overlay": { "flake": false, "locked": { - "lastModified": 1650459918, - "narHash": "sha256-sroCK+QJTmoXtcRkwZyKOP9iAYOPID2Bwdxn4GkG16w=", + "lastModified": 1657214286, + "narHash": "sha256-rO/4oymKXU09wG2bcTt4uthPCp1XsBZjxuCJo3yVXNs=", "owner": "mozilla", "repo": "nixpkgs-mozilla", - "rev": "e1f7540fc0a8b989fb8cf701dc4fd7fc76bcf168", + "rev": "0508a66e28a5792fdfb126bbf4dec1029c2509e0", "type": "github" }, "original": { @@ -41,11 +41,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1655278232, - "narHash": "sha256-H6s7tnHYiDKFCcLADS4sl1sUq0dDJuRQXCieguk/6SA=", + "lastModified": 1657123678, + "narHash": "sha256-cowVkScfUPlbBXUp08MeVk/wgm9E1zp1uC+9no2hZYw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "8b538fcb329a7bc3d153962f17c509ee49166973", + "rev": "316b762afdb9e142a803f29c49a88b4a47db80ee", "type": "github" }, "original": { @@ -89,11 +89,11 @@ "src-migen": { "flake": false, "locked": { - "lastModified": 1650337393, - "narHash": "sha256-rm1SlFmF2ASz0vIy2nDEzGlyRw2oYNeJRr8Kh8Mg2Qc=", + "lastModified": 1656649178, + "narHash": "sha256-A91sZRrprEuPOtIUVxm6wX5djac9wnNZQ4+cU1nvJPc=", "owner": "m-labs", "repo": "migen", - "rev": "d4e3f34177c32f09904397179e6ed9c83175e528", + "rev": "0fb91737090fe45fd764ea3f71257a4c53c7a4ae", "type": "github" }, "original": { From db4bccda7ed7724f7641b169f1d1c9425a15e8a3 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 8 Jul 2022 18:49:40 +0800 Subject: [PATCH 36/46] flake: bump major version --- flake.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index de3a43b4c..641630975 100644 --- a/flake.nix +++ b/flake.nix @@ -18,7 +18,7 @@ pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; }; pkgs-aarch64 = import nixpkgs { system = "aarch64-linux"; }; - artiqVersionMajor = 7; + artiqVersionMajor = 8; artiqVersionMinor = self.sourceInfo.revCount or 0; artiqVersionId = self.sourceInfo.shortRev or "unknown"; artiqVersion = (builtins.toString artiqVersionMajor) + "." + (builtins.toString artiqVersionMinor) + "." + artiqVersionId + ".beta"; From 4b1715c80b93a4c2b70b1954b1f8b10628397ca7 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Thu, 21 Jul 2022 11:58:25 +0800 Subject: [PATCH 37/46] typo --- artiq/language/environment.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/language/environment.py b/artiq/language/environment.py index 969664d15..e61bf228b 100644 --- a/artiq/language/environment.py +++ b/artiq/language/environment.py @@ -485,7 +485,7 @@ def is_experiment(o): def is_public_experiment(o): - """Checks if a Pyhton object is a top-level, + """Checks if a Python object is a top-level, non underscore-prefixed, experiment class. """ return is_experiment(o) and not o.__name__.startswith("_") From 748e28be387dce70c091dda5deb53a14476221b6 Mon Sep 17 00:00:00 2001 From: kk1050 <103404672+kk1050@users.noreply.github.com> Date: Tue, 26 Jul 2022 09:49:48 +0800 Subject: [PATCH 38/46] artiq_flash: bail out if scan chain is wrong Due to OpenOCD limitations, there currently doesn't seem to be a better way of doing it. Upstream patch may be coming. --- artiq/frontend/artiq_flash.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/artiq/frontend/artiq_flash.py b/artiq/frontend/artiq_flash.py index 532d1da92..1462ec83e 100755 --- a/artiq/frontend/artiq_flash.py +++ b/artiq/frontend/artiq_flash.py @@ -118,7 +118,12 @@ class Programmer: "telnet_port disabled" ] + preinit_script self._loaded = defaultdict(lambda: None) - self._script = ["init"] + self._script = [ + "set error_msg \"Trying to use configured scan chain anyway\"", + "if {[string first $error_msg [capture \"init\"]] != -1} {", + "puts \"Found error and exiting\"", + "exit}" + ] def _transfer_script(self, script): if isinstance(self._client, LocalClient): From 2a7a72b27a093ae5287d5abcb2124eccdb5c1155 Mon Sep 17 00:00:00 2001 From: Deepskyhunter <48083317+Deepskyhunter@users.noreply.github.com> Date: Tue, 26 Jul 2022 10:42:03 +0800 Subject: [PATCH 39/46] language.environment: error out if unknown arguments are passed (#1927) Closes #1641 --- artiq/language/environment.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/artiq/language/environment.py b/artiq/language/environment.py index e61bf228b..204bf823f 100644 --- a/artiq/language/environment.py +++ b/artiq/language/environment.py @@ -209,18 +209,28 @@ class TraceArgumentManager: self.requested_args[key] = processor, group, tooltip return None + def check_unprocessed_arguments(self): + pass class ProcessArgumentManager: def __init__(self, unprocessed_arguments): self.unprocessed_arguments = unprocessed_arguments + self._processed_arguments = set() def get(self, key, processor, group, tooltip): if key in self.unprocessed_arguments: r = processor.process(self.unprocessed_arguments[key]) + self._processed_arguments.add(key) else: r = processor.default() return r + def check_unprocessed_arguments(self): + unprocessed = set(self.unprocessed_arguments.keys()) -\ + self._processed_arguments + if unprocessed: + raise AttributeError("Invalid argument(s): " + + ", ".join(unprocessed)) class HasEnvironment: """Provides methods to manage the environment of an experiment (arguments, @@ -242,6 +252,7 @@ class HasEnvironment: self.__in_build = True self.build(*args, **kwargs) self.__in_build = False + self.__argument_mgr.check_unprocessed_arguments() def register_child(self, child): self.children.append(child) From 0cdb06fdf53a375dc7f08c4a9b85685ab5d4321f Mon Sep 17 00:00:00 2001 From: Deepskyhunter <48083317+Deepskyhunter@users.noreply.github.com> Date: Thu, 28 Jul 2022 17:55:25 +0800 Subject: [PATCH 40/46] language/environment: support no argument manager unbreak tests --- artiq/language/environment.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/artiq/language/environment.py b/artiq/language/environment.py index 204bf823f..9ce7034cc 100644 --- a/artiq/language/environment.py +++ b/artiq/language/environment.py @@ -252,7 +252,8 @@ class HasEnvironment: self.__in_build = True self.build(*args, **kwargs) self.__in_build = False - self.__argument_mgr.check_unprocessed_arguments() + if self.__argument_mgr is not None: + self.__argument_mgr.check_unprocessed_arguments() def register_child(self, child): self.children.append(child) From a3ae82502cd22ab79ea595120b30f14af79aa49a Mon Sep 17 00:00:00 2001 From: Alex Wong Tat Hang <32214395+TopQuark12@users.noreply.github.com> Date: Mon, 1 Aug 2022 10:21:28 +0800 Subject: [PATCH 41/46] gtx_7series: fix IBUFGS_GTE2 buffer parameters Co-authored-by: topquark12 --- artiq/gateware/drtio/transceiver/gtx_7series.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/artiq/gateware/drtio/transceiver/gtx_7series.py b/artiq/gateware/drtio/transceiver/gtx_7series.py index 28b9536db..3d96971ca 100644 --- a/artiq/gateware/drtio/transceiver/gtx_7series.py +++ b/artiq/gateware/drtio/transceiver/gtx_7series.py @@ -295,7 +295,10 @@ class GTX(Module, TransceiverInterface): i_CEB=stable_clkin_n, i_I=clock_pads.p, i_IB=clock_pads.n, - o_O=refclk + o_O=refclk, + p_CLKCM_CFG="0b1", + p_CLKRCV_TRST="0b1", + p_CLKSWING_CFG="0b11" ) rtio_tx_clk = Signal() From f31279411ef7f198ee697290efe957544896ebdb Mon Sep 17 00:00:00 2001 From: Deepskyhunter <48083317+Deepskyhunter@users.noreply.github.com> Date: Tue, 2 Aug 2022 17:09:56 +0800 Subject: [PATCH 42/46] dashboard/moninj: make arguments a dict for DDS setters --- artiq/dashboard/moninj.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/dashboard/moninj.py b/artiq/dashboard/moninj.py index b893c778a..b43355314 100644 --- a/artiq/dashboard/moninj.py +++ b/artiq/dashboard/moninj.py @@ -534,7 +534,7 @@ class _DeviceManager: "log_level": logging.WARNING, "content": content, "class_name": class_name, - "arguments": [] + "arguments": {} } scheduling = { "pipeline_name": "main", From 185c91f52209ea35229afe3362b242ff2f49f0ff Mon Sep 17 00:00:00 2001 From: cc78078 <110895089+cc78078@users.noreply.github.com> Date: Thu, 11 Aug 2022 15:06:58 +0800 Subject: [PATCH 43/46] kasli: add Error LED to MasterBase and SatelliteBase --- artiq/gateware/targets/kasli.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index 71ee933a5..75fb0c3bf 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -282,6 +282,9 @@ class MasterBase(MiniSoC, AMPSoC): platform = self.platform if platform.hw_rev == "v2.0": + self.submodules.error_led = gpio.GPIOOut(Cat( + self.platform.request("error_led"))) + self.csr_devices.append("error_led") self.submodules += SMAClkinForward(platform) i2c = self.platform.request("i2c") @@ -462,6 +465,9 @@ class SatelliteBase(BaseSoC): disable_cdr_clk_ibuf = Signal(reset=1) disable_cdr_clk_ibuf.attr.add("no_retiming") if self.platform.hw_rev == "v2.0": + self.submodules.error_led = gpio.GPIOOut(Cat( + self.platform.request("error_led"))) + self.csr_devices.append("error_led") cdr_clk_clean = self.platform.request("cdr_clk_clean") else: cdr_clk_clean = self.platform.request("si5324_clkout") From 3535d0f1ae98829538b013714aa1a63c0621f9ce Mon Sep 17 00:00:00 2001 From: cc78078 Date: Fri, 12 Aug 2022 12:41:50 +0800 Subject: [PATCH 44/46] kasli: relocate the SatelliteBase Error LED code (#1955) --- artiq/gateware/targets/kasli.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index 75fb0c3bf..5938fc947 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -462,12 +462,14 @@ class SatelliteBase(BaseSoC): platform = self.platform - disable_cdr_clk_ibuf = Signal(reset=1) - disable_cdr_clk_ibuf.attr.add("no_retiming") if self.platform.hw_rev == "v2.0": self.submodules.error_led = gpio.GPIOOut(Cat( self.platform.request("error_led"))) self.csr_devices.append("error_led") + + disable_cdr_clk_ibuf = Signal(reset=1) + disable_cdr_clk_ibuf.attr.add("no_retiming") + if self.platform.hw_rev == "v2.0": cdr_clk_clean = self.platform.request("cdr_clk_clean") else: cdr_clk_clean = self.platform.request("si5324_clkout") From 27397625ba66ed0dba59517717f9fc0e0bafce20 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Fri, 12 Aug 2022 13:41:05 +0800 Subject: [PATCH 45/46] dashboard: improve moninj logging --- artiq/dashboard/moninj.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/dashboard/moninj.py b/artiq/dashboard/moninj.py index b43355314..0ad0955d7 100644 --- a/artiq/dashboard/moninj.py +++ b/artiq/dashboard/moninj.py @@ -695,11 +695,11 @@ class _DeviceManager: logger.info("cancelled connection to moninj") break except: - logger.error("failed to connect to moninj", exc_info=True) + logger.error("failed to connect to moninj. Is aqctl_moninj_proxy running?", exc_info=True) await asyncio.sleep(10.) self.reconnect_mi.set() else: - logger.info("ARTIQ dashboard connected to moninj proxy (%s)", + logger.info("ARTIQ dashboard connected to moninj (%s)", self.mi_addr) self.mi_connection = new_mi_connection for ttl_channel in self.ttl_widgets.keys(): From 3c72b8d646a93d476a9d28c57151eb33be33bda7 Mon Sep 17 00:00:00 2001 From: kk1050 <103404672+kk1050@users.noreply.github.com> Date: Tue, 16 Aug 2022 14:02:01 +0800 Subject: [PATCH 46/46] dashboard: use break_realtime instead of reset for Urukul set freq (#1940) --- artiq/dashboard/moninj.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/dashboard/moninj.py b/artiq/dashboard/moninj.py index 0ad0955d7..1a58ad90d 100644 --- a/artiq/dashboard/moninj.py +++ b/artiq/dashboard/moninj.py @@ -583,7 +583,7 @@ class _DeviceManager: @kernel def run(self): - self.core.reset() + self.core.break_realtime() {cpld_init} delay(5*ms) self.{dds_channel}.init() @@ -612,7 +612,7 @@ class _DeviceManager: @kernel def run(self): - self.core.reset() + self.core.break_realtime() cfg = self.core_cache.get("_{cpld}_cfg") if len(cfg) > 0: self.{cpld}.cfg_reg = cfg[0]