From f755a4682a8a262b9dd6ff7ead659ec4897a36a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 25 Oct 2018 10:13:15 +0000 Subject: [PATCH 01/29] device_db_ptb: fix zotino clr MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/examples/kasli_basic/device_db_ptb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/examples/kasli_basic/device_db_ptb.py b/artiq/examples/kasli_basic/device_db_ptb.py index cc04d1090..5aa1c6e6a 100644 --- a/artiq/examples/kasli_basic/device_db_ptb.py +++ b/artiq/examples/kasli_basic/device_db_ptb.py @@ -218,7 +218,7 @@ device_db.update({ "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 39} + "arguments": {"channel": 40} }, "zotino0": { "type": "local", From f62c1ff0bb1129c2383e9062cd3445ad31c3a8ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 25 Oct 2018 09:52:00 +0000 Subject: [PATCH 02/29] TTLClockGen: expose acc_width MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/coredevice/ttl.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index 09732fccc..4069c62aa 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -371,16 +371,17 @@ class TTLClockGen: The time cursor is not modified by any function in this class. :param channel: channel number + :param acc_width: accumulator width in bits """ kernel_invariants = {"core", "channel", "acc_width"} - def __init__(self, dmgr, channel, core_device="core"): + def __init__(self, dmgr, channel, acc_width=24, core_device="core"): self.core = dmgr.get(core_device) self.channel = channel # in RTIO cycles self.previous_timestamp = numpy.int64(0) - self.acc_width = numpy.int64(24) + self.acc_width = numpy.int64(acc_width) @portable def frequency_to_ftw(self, frequency): From 0433e8f4fe27303f4efbcfa35cdcbb189e0ba146 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 25 Oct 2018 09:51:21 +0000 Subject: [PATCH 03/29] urukul: add sync_in generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit for #1143 Signed-off-by: Robert Jördens --- artiq/coredevice/urukul.py | 27 ++++++++++++-- artiq/examples/kasli_basic/device_db_ptb.py | 41 ++++++++++++++------- artiq/gateware/eem.py | 11 ++++-- artiq/gateware/targets/kasli.py | 6 ++- 4 files changed, 63 insertions(+), 22 deletions(-) diff --git a/artiq/coredevice/urukul.py b/artiq/coredevice/urukul.py index fe9c1a68c..459f10f58 100644 --- a/artiq/coredevice/urukul.py +++ b/artiq/coredevice/urukul.py @@ -115,6 +115,7 @@ class CPLD: :param spi_device: SPI bus device name :param io_update_device: IO update RTIO TTLOut channel name :param dds_reset_device: DDS reset RTIO TTLOut channel name + :param sync_device: AD9910 SYNC_IN RTIO TTLClockGen channel name :param refclk: Reference clock (SMA, MMCX or on-board 100 MHz oscillator) frequency in Hz :param clk_sel: Reference clock selection. For hardware revision >= 1.3 @@ -122,8 +123,8 @@ class CPLD: internal MMCX. For hardware revision <= v1.2 valid options are: 0 - either XO or MMCX dependent on component population; 1 SMA. Unsupported clocking options are silently ignored. - :param sync_sel: SYNC clock selection. 0 corresponds to SYNC clock over EEM - from FPGA. 1 corresponds to SYNC clock from DDS0. + :param sync_sel: SYNC_IN selection. 0 corresponds to SYNC_IN over EEM + from FPGA. 1 corresponds to SYNC_IN from DDS0. :param rf_sw: Initial CPLD RF switch register setting (default: 0x0). Knowledge of this state is not transferred between experiments. :param att: Initial attenuator setting shift register (default: @@ -134,7 +135,8 @@ class CPLD: kernel_invariants = {"refclk", "bus", "core", "io_update"} def __init__(self, dmgr, spi_device, io_update_device=None, - dds_reset_device=None, sync_sel=0, clk_sel=0, rf_sw=0, + dds_reset_device=None, sync_device=None, + sync_sel=0, clk_sel=0, rf_sw=0, refclk=125e6, att=0x00000000, core_device="core"): self.core = dmgr.get(core_device) @@ -147,6 +149,8 @@ class CPLD: self.io_update = _RegIOUpdate(self) if dds_reset_device is not None: self.dds_reset = dmgr.get(dds_reset_device) + if sync_device is not None: + self.sync = dmgr.get(sync_device) self.cfg_reg = urukul_cfg(rf_sw=rf_sw, led=0, profile=0, io_update=0, mask_nu=0, clk_sel=clk_sel, @@ -289,3 +293,20 @@ class CPLD: SPIT_ATT_RD, CS_ATT) self.bus.write(self.att_reg) return self.bus.read() + + @kernel + def set_sync_div(self, div): + """Set the SYNC_IN AD9910 pulse generator frequency + and align it to the current RTIO timestamp. + + The SYNC_IN signal is derived from the coarse RTIO clock + and the divider must be a power of two two. + Configure ``sync_sel == 0``. + + :param div: SYNC_IN frequency divider. Must be a power of two. + Minimum division ratio is 2. Maximum division ratio is 16. + """ + ftw_max = 1 << 4 + ftw = ftw_max//div + assert ftw*div == ftw_max + self.sync.set_mu(ftw) diff --git a/artiq/examples/kasli_basic/device_db_ptb.py b/artiq/examples/kasli_basic/device_db_ptb.py index 5aa1c6e6a..a3c64bba7 100644 --- a/artiq/examples/kasli_basic/device_db_ptb.py +++ b/artiq/examples/kasli_basic/device_db_ptb.py @@ -87,36 +87,42 @@ device_db.update({ "class": "SPIMaster", "arguments": {"channel": 27} }, - "ttl_urukul0_io_update": { + "ttl_urukul0_sync": { "type": "local", "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 28} + "class": "TTLClockGen", + "arguments": {"channel": 28, "acc_width": 4} }, - "ttl_urukul0_sw0": { + "ttl_urukul0_io_update": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", "arguments": {"channel": 29} }, - "ttl_urukul0_sw1": { + "ttl_urukul0_sw0": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", "arguments": {"channel": 30} }, - "ttl_urukul0_sw2": { + "ttl_urukul0_sw1": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", "arguments": {"channel": 31} }, - "ttl_urukul0_sw3": { + "ttl_urukul0_sw2": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", "arguments": {"channel": 32} }, + "ttl_urukul0_sw3": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 33} + }, "urukul0_cpld": { "type": "local", "module": "artiq.coredevice.urukul", @@ -124,6 +130,7 @@ device_db.update({ "arguments": { "spi_device": "spi_urukul0", "io_update_device": "ttl_urukul0_io_update", + "sync_device": "ttl_urukul0_sync", "refclk": 100e6, "clk_sel": 0 } @@ -150,13 +157,19 @@ device_db.update({ "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 33} + "arguments": {"channel": 34} + }, + "ttl_urukul1_sync": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLClockGen", + "arguments": {"channel": 35, "acc_width": 4} }, "ttl_urukul1_io_update": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 34} + "arguments": {"channel": 36} }, "urukul1_cpld": { "type": "local", @@ -190,13 +203,13 @@ device_db.update({ "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 35} + "arguments": {"channel": 37} }, "led1": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 36} + "arguments": {"channel": 38} } }) @@ -206,19 +219,19 @@ device_db.update({ "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 37} + "arguments": {"channel": 39} }, "ttl_zotino0_ldac": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 38} + "arguments": {"channel": 40} }, "ttl_zotino0_clr": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 40} + "arguments": {"channel": 41} }, "zotino0": { "type": "local", diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 34680a714..3c556a4ce 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -148,7 +148,8 @@ class Urukul(_EEM): return ios @classmethod - def add_std(cls, target, eem, eem_aux, ttl_out_cls, iostandard="LVDS_25"): + def add_std(cls, target, eem, eem_aux, ttl_out_cls, sync_gen_cls=None, + iostandard="LVDS_25"): cls.add_extension(target, eem, eem_aux, iostandard=iostandard) phy = spi2.SPIMaster(target.platform.request("urukul{}_spi_p".format(eem)), @@ -157,7 +158,12 @@ class Urukul(_EEM): target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) pads = target.platform.request("urukul{}_dds_reset".format(eem)) - target.specials += DifferentialOutput(0, pads.p, pads.n) + pad = Signal(reset=0) + target.specials += DifferentialOutput(pad, pads.p, pads.n) + if sync_gen_cls is not None: # AD9910 variant and SYNC_IN from EEM + phy = sync_gen_cls(pad, ftw_width=4) + target.submodules += phy + 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) @@ -170,7 +176,6 @@ 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="LVDS_25"): diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index 79c93dfcc..b061ef648 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -516,8 +516,10 @@ class PTB(_StandaloneBase): eem.DIO.add_std(self, 2, ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X) eem.Sampler.add_std(self, 3, None, ttl_serdes_7series.Output_8X) - eem.Urukul.add_std(self, 5, 4, ttl_serdes_7series.Output_8X) - eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X) + eem.Urukul.add_std(self, 5, 4, ttl_serdes_7series.Output_8X, + ttl_simple.ClockGen) + eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X, + ttl_simple.ClockGen) for i in (1, 2): sfp_ctl = self.platform.request("sfp_ctl", i) From 35384448769a92503cd52a528716d3e50959491e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 26 Oct 2018 08:02:28 +0000 Subject: [PATCH 04/29] urukul: add sync_in to eem0-7 name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/gateware/eem.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 3c556a4ce..96050b748 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -75,7 +75,7 @@ class Urukul(_EEM): ), ] ttls = [(6, eem, "io_update"), - (7, eem, "dds_reset")] + (7, eem, "dds_reset_sync_in")] if eem_aux is not None: ttls += [(0, eem_aux, "sync_clk"), (1, eem_aux, "sync_in"), @@ -113,7 +113,7 @@ class Urukul(_EEM): ), ] ttls = [(6, eem0, "io_update"), - (7, eem0, "dds_reset"), + (7, eem0, "dds_reset_sync_in"), (4, eem1, "sw0"), (5, eem1, "sw1"), (6, eem1, "sw2"), @@ -157,7 +157,7 @@ class Urukul(_EEM): target.submodules += phy target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) - pads = target.platform.request("urukul{}_dds_reset".format(eem)) + pads = target.platform.request("urukul{}_dds_reset_sync_in".format(eem)) pad = Signal(reset=0) target.specials += DifferentialOutput(pad, pads.p, pads.n) if sync_gen_cls is not None: # AD9910 variant and SYNC_IN from EEM @@ -527,7 +527,7 @@ class SUServo(_EEM): target.submodules += phy target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) - pads = target.platform.request("{}_dds_reset".format(eem_urukul0)) + pads = target.platform.request("{}_dds_reset_sync_in".format(eem_urukul0)) target.specials += DifferentialOutput(0, pads.p, pads.n) for i, signal in enumerate("sw0 sw1 sw2 sw3".split()): From 0b3b07a7da67ca532b77b92e0e81ebdf1b64de33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 25 Oct 2018 16:02:54 +0000 Subject: [PATCH 05/29] ad9910: add power down method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index c7e1b49a1..442b40210 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -165,6 +165,16 @@ class AD9910: return raise ValueError("PLL lock timeout") + @kernel + def power_down(self, bits=0b1111): + """Power down DDS. + + :param bits: power down bits, see datasheet + """ + self.write32(_AD9910_REG_CFR1, 0x00000002 | (bits << 4)) + delay(1*us) + self.cpld.io_update.pulse(1*us) + @kernel def set_mu(self, ftw, pow=0, asf=0x3fff): """Set profile 0 data in machine units. From 8dbf5f87fd789ba32c7a655c828bab6cffcded73 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 25 Oct 2018 16:02:13 +0000 Subject: [PATCH 06/29] ad9910: simplify io_update pulsing on init, set_mu MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index 442b40210..0617bc952 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -136,7 +136,7 @@ class AD9910: """ # Set SPI mode self.write32(_AD9910_REG_CFR1, 0x00000002) - self.cpld.io_update.pulse(2*us) + self.cpld.io_update.pulse(1*us) delay(1*ms) if not blind: # Use the AUX DAC setting to identify and confirm presence @@ -146,13 +146,13 @@ class AD9910: delay(50*us) # slack # Configure PLL settings and bring up PLL self.write32(_AD9910_REG_CFR2, 0x01400020) - self.cpld.io_update.pulse(2*us) + self.cpld.io_update.pulse(1*us) cfr3 = (0x0807c100 | (self.pll_vco << 24) | (self.pll_cp << 19) | (self.pll_n << 1)) self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset - self.cpld.io_update.pulse(100*us) + self.cpld.io_update.pulse(1*us) self.write32(_AD9910_REG_CFR3, cfr3) - self.cpld.io_update.pulse(100*us) + self.cpld.io_update.pulse(1*us) if blind: delay(100*ms) return @@ -172,7 +172,6 @@ class AD9910: :param bits: power down bits, see datasheet """ self.write32(_AD9910_REG_CFR1, 0x00000002 | (bits << 4)) - delay(1*us) self.cpld.io_update.pulse(1*us) @kernel @@ -187,7 +186,7 @@ class AD9910: :param asf: Amplitude scale factor: 14 bit unsigned. """ self.write64(_AD9910_REG_PR0, (asf << 16) | pow, ftw) - self.cpld.io_update.pulse(10*ns) + self.cpld.io_update.pulse_mu(8) @portable(flags={"fast-math"}) def frequency_to_ftw(self, frequency): From 65e2ebf9600f28da0b3e3c5d7422ccf2b59134bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 26 Oct 2018 09:00:07 +0000 Subject: [PATCH 07/29] ad9910: add sync delay control, auto tuning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * expose multi device sync functionality * sync delay configuration interface * auto-tuning of sync delay from device_db seed for #1143 Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 49 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index 0617bc952..0ac288d07 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -6,6 +6,7 @@ from artiq.language.units import us, ns, ms from artiq.coredevice import spi2 as spi from artiq.coredevice import urukul urukul_sta_pll_lock = urukul.urukul_sta_pll_lock +urukul_sta_smp_err = urukul.urukul_sta_smp_err _AD9910_REG_CFR1 = 0x00 @@ -49,12 +50,15 @@ class AD9910: Urukul CPLD instance). :param pll_cp: DDS PLL charge pump setting. :param pll_vco: DDS PLL VCO range selection. + :param sync_delay_seed: SYNC_IN delay tuning starting value. + To stabilize the SYNC_IN delay tuning, run :meth:`tune_sync_delay` once and + set this to the delay tap number returned by :meth:`tune_sync_delay`. """ kernel_invariants = {"chip_select", "cpld", "core", "bus", - "ftw_per_hz", "pll_n", "pll_cp", "pll_vco"} + "ftw_per_hz", "pll_n", "pll_cp", "pll_vco", "sync_delay_seed"} def __init__(self, dmgr, chip_select, cpld_device, sw_device=None, - pll_n=40, pll_cp=7, pll_vco=5): + pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=8): self.cpld = dmgr.get(cpld_device) self.core = self.cpld.core self.bus = self.cpld.bus @@ -76,6 +80,7 @@ class AD9910: self.pll_vco = pll_vco assert 0 <= pll_cp <= 7 self.pll_cp = pll_cp + self.sync_delay_seed = sync_delay_seed @kernel def write32(self, addr, data): @@ -249,3 +254,43 @@ class AD9910: :param state: CPLD CFG RF switch bit """ self.cpld.cfg_sw(self.chip_select - 4, state) + + @kernel + def set_sync(self, in_delay, window, preset=0): + self.write32(_AD9910_REG_MSYNC, + (window << 28) | # SYNC S/H validation delay + (1 << 27) | # SYNC receiver enable + (0 << 26) | # SYNC generator disable + (0 << 25) | # SYNC generator SYS rising edge + (preset << 18) | # SYNC preset + (0 << 11) | # SYNC output delay + (in_delay << 3)) # SYNC receiver delay + self.write32(_AD9910_REG_CFR2, 0x01400020) # clear SMP_ERR + self.cpld.io_update.pulse(1*us) + self.write32(_AD9910_REG_CFR2, 0x01400000) # enable SMP_ERR + self.cpld.io_update.pulse(1*us) + + @kernel + def tune_sync_delay(self): + dt = 14 # 1/(f_SYSCLK*75ps) taps per SYSCLK period + max_delay = dt # 14*75ps > 1ns + max_window = dt//4 + 1 # 2*75ps*4 = 600ps high > 1ns/2 + min_window = dt//8 + 1 # 2*75ps hold, 2*75ps setup < 1ns/4 + for window in range(max_window - min_window + 1): + window = max_window - window + for in_delay in range(max_delay): + # alternate search direction around seed_delay + if in_delay & 1: + in_delay = -in_delay + in_delay = (self.sync_delay_seed + (in_delay >> 1)) & 0x1f + self.set_sync(in_delay, window) + # integrate SMP_ERR statistics for a few hundred cycles + delay(10*us) + err = urukul_sta_smp_err(self.cpld.sta_read()) + err = (err >> (self.chip_select - 4)) & 1 + delay(40*us) # slack + if not err: + window -= min_window # add margin + self.set_sync(in_delay, window) + return window, in_delay + raise ValueError("no valid window/delay") From 8a47a6b2fb2c32fb7a9f585068f371205fe99a61 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 26 Oct 2018 10:25:23 +0000 Subject: [PATCH 08/29] ad9910: disable sync_clk output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index 0ac288d07..b2a05f813 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -150,7 +150,7 @@ class AD9910: raise ValueError("Urukul AD9910 AUX_DAC mismatch") delay(50*us) # slack # Configure PLL settings and bring up PLL - self.write32(_AD9910_REG_CFR2, 0x01400020) + self.write32(_AD9910_REG_CFR2, 0x01000020) self.cpld.io_update.pulse(1*us) cfr3 = (0x0807c100 | (self.pll_vco << 24) | (self.pll_cp << 19) | (self.pll_n << 1)) @@ -265,9 +265,9 @@ class AD9910: (preset << 18) | # SYNC preset (0 << 11) | # SYNC output delay (in_delay << 3)) # SYNC receiver delay - self.write32(_AD9910_REG_CFR2, 0x01400020) # clear SMP_ERR + self.write32(_AD9910_REG_CFR2, 0x01000020) # clear SMP_ERR self.cpld.io_update.pulse(1*us) - self.write32(_AD9910_REG_CFR2, 0x01400000) # enable SMP_ERR + self.write32(_AD9910_REG_CFR2, 0x01000000) # enable SMP_ERR self.cpld.io_update.pulse(1*us) @kernel From 7b92282012b980ea59618aa148ecb1a1ce1b2184 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 26 Oct 2018 15:42:42 +0000 Subject: [PATCH 09/29] ad9910: add docs for sync tuning, refactor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 52 +++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index b2a05f813..a8c6ede9c 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -150,7 +150,7 @@ class AD9910: raise ValueError("Urukul AD9910 AUX_DAC mismatch") delay(50*us) # slack # Configure PLL settings and bring up PLL - self.write32(_AD9910_REG_CFR2, 0x01000020) + self.write32(_AD9910_REG_CFR2, 0x01010020) self.cpld.io_update.pulse(1*us) cfr3 = (0x0807c100 | (self.pll_vco << 24) | (self.pll_cp << 19) | (self.pll_n << 1)) @@ -256,22 +256,53 @@ class AD9910: self.cpld.cfg_sw(self.chip_select - 4, state) @kernel - def set_sync(self, in_delay, window, preset=0): + def set_sync(self, in_delay, window): + """Set the relevant parameters in the multi device synchronization + register. See the AD9910 datasheet for details. The SYNC clock + generator preset value is set to zero, and the SYNC_OUT generator is + disabled. + + :param in_delay: SYNC_IN delay tap (0-31) in steps of ~75ps + :param window: Symmetric SYNC_IN validation window (0-15) in + steps of ~75ps for both hold and setup margin. + """ self.write32(_AD9910_REG_MSYNC, (window << 28) | # SYNC S/H validation delay (1 << 27) | # SYNC receiver enable (0 << 26) | # SYNC generator disable (0 << 25) | # SYNC generator SYS rising edge - (preset << 18) | # SYNC preset + (0 << 18) | # SYNC preset (0 << 11) | # SYNC output delay (in_delay << 3)) # SYNC receiver delay - self.write32(_AD9910_REG_CFR2, 0x01000020) # clear SMP_ERR + + @kernel + def clear_smp_err(self): + """Clears the SMP_ERR flag and enables SMP_ERR validity monitoring. + Violations of the SYNC_IN sample and hold margins will result in + SMP_ERR being asserted. This then also activates the red LED on + the respective Urukul channel. + + Also modifies CFR2. + """ + self.write32(_AD9910_REG_CFR2, 0x01010020) # clear SMP_ERR self.cpld.io_update.pulse(1*us) - self.write32(_AD9910_REG_CFR2, 0x01000000) # enable SMP_ERR + self.write32(_AD9910_REG_CFR2, 0x01010000) # enable SMP_ERR self.cpld.io_update.pulse(1*us) @kernel def tune_sync_delay(self): + """Find a stable SYNC_IN delay. + + This method first locates the smallest SYNC_IN validity window at + minimum window size and then increases the window a bit to provide some + slack and stability. + + It starts scanning delays around :attr:`sync_delay_seed` (see the + device database arguments and :meth:`__init__`) at maximum validation window + size and decreases the window size until a valid delay is found. + + :return: Tuple of optimal delay and window size. + """ dt = 14 # 1/(f_SYSCLK*75ps) taps per SYSCLK period max_delay = dt # 14*75ps > 1ns max_window = dt//4 + 1 # 2*75ps*4 = 600ps high > 1ns/2 @@ -282,8 +313,13 @@ class AD9910: # alternate search direction around seed_delay if in_delay & 1: in_delay = -in_delay - in_delay = (self.sync_delay_seed + (in_delay >> 1)) & 0x1f + in_delay = self.sync_delay_seed + (in_delay >> 1) + if in_delay < 0: + in_delay = 0 + elif in_delay > 31: + in_delay = 31 self.set_sync(in_delay, window) + self.clear_smp_err() # integrate SMP_ERR statistics for a few hundred cycles delay(10*us) err = urukul_sta_smp_err(self.cpld.sta_read()) @@ -292,5 +328,7 @@ class AD9910: if not err: window -= min_window # add margin self.set_sync(in_delay, window) - return window, in_delay + self.clear_smp_err() + delay(40*us) # slack + return in_delay, window raise ValueError("no valid window/delay") From 4bbd833cfea4a1a9608c2857d14d9213c7adb0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 26 Oct 2018 15:54:05 +0000 Subject: [PATCH 10/29] ad9910: add io_update alignment measurement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit for #1143 Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 43 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index a8c6ede9c..bad7294cc 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -298,8 +298,8 @@ class AD9910: slack and stability. It starts scanning delays around :attr:`sync_delay_seed` (see the - device database arguments and :meth:`__init__`) at maximum validation window - size and decreases the window size until a valid delay is found. + device database arguments and :class:`AD9910`) at maximum validation + window size and decreases the window size until a valid delay is found. :return: Tuple of optimal delay and window size. """ @@ -332,3 +332,42 @@ class AD9910: delay(40*us) # slack return in_delay, window raise ValueError("no valid window/delay") + + @kernel + def measure_io_update_alignment(self, io_up_delay): + """Use the digital ramp generator to locate the alignment between + IO_UPDATE and SYNC_CLK. + + The ramp generator is set up to a linear frequency ramp + (dFTW/t_SYNC_CLK=1) and started at a RTIO timestamp. + + After scanning the alignment, an IO_UPDATE delay midway between two + edges should be chosen. + + :return: odd/even SYNC_CLK cycle indicator + """ + # set up DRG + # DRG ACC autoclear and LRR on io update + self.write32(_AD9910_REG_CFR1, 0x0000c002) + # DRG -> FTW, DRG enable + self.write32(_AD9910_REG_CFR2, 0x01090000) + # no limits + self.write64(_AD9910_REG_DRAMPL, -1, 0) + # DRCTL=0, dt=1 t_SYNC_CLK + self.write32(_AD9910_REG_DRAMPR, 0x00010000) + # dFTW = 1, (work around negative slope) + self.write64(_AD9910_REG_DRAMPS, -1, 0) + at_mu(now_mu() + 0x10 & ~0xf) # align to RTIO/2 + self.cpld.io_update.pulse_mu(8) + # disable DRG autoclear and LRR on io_update + self.write32(_AD9910_REG_CFR1, 0x00000002) + # stop DRG + self.write64(_AD9910_REG_DRAMPS, 0, 0) + at_mu((now_mu() + 0x10 & ~0xf) + io_up_delay) # delay + self.cpld.io_update.pulse_mu(32 - io_up_delay) # realign + ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW + delay(100*us) # slack + # disable DRG + self.write32(_AD9910_REG_CFR2, 0x01010000) + self.cpld.io_update.pulse_mu(8) + return ftw & 1 From 1066430fa8f9390369bcaa3937defee0efd1fd8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 31 Oct 2018 14:47:00 +0000 Subject: [PATCH 11/29] urukul: set up sync_in generator MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/coredevice/urukul.py | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/artiq/coredevice/urukul.py b/artiq/coredevice/urukul.py index 459f10f58..3c64c8ddb 100644 --- a/artiq/coredevice/urukul.py +++ b/artiq/coredevice/urukul.py @@ -109,6 +109,15 @@ class _RegIOUpdate: self.cpld.cfg_write(cfg) +class _DummySync: + def __init__(self, cpld): + self.cpld = cpld + + @kernel + def set_mu(self, ftw): + pass + + class CPLD: """Urukul CPLD SPI router and configuration interface. @@ -130,6 +139,9 @@ class CPLD: :param att: Initial attenuator setting shift register (default: 0x00000000). See also: :meth:`set_all_att_mu`. Knowledge of this state is not transferred between experiments. + :param sync_div: SYNC_IN generator divider. The ratio between the coarse + RTIO frequency and the SYNC_IN generator frequency (default: 2 if + :param:`sync_device` was specified). :param core_device: Core device name """ kernel_invariants = {"refclk", "bus", "core", "io_update"} @@ -137,7 +149,8 @@ class CPLD: def __init__(self, dmgr, spi_device, io_update_device=None, dds_reset_device=None, sync_device=None, sync_sel=0, clk_sel=0, rf_sw=0, - refclk=125e6, att=0x00000000, core_device="core"): + refclk=125e6, att=0x00000000, sync_div=None, + core_device="core"): self.core = dmgr.get(core_device) self.refclk = refclk @@ -151,11 +164,18 @@ class CPLD: self.dds_reset = dmgr.get(dds_reset_device) if sync_device is not None: self.sync = dmgr.get(sync_device) + if sync_div is None: + sync_div = 2 + else: + self.sync = _DummySync(self) + assert sync_div is None + sync_div = 0 self.cfg_reg = urukul_cfg(rf_sw=rf_sw, led=0, profile=0, io_update=0, mask_nu=0, clk_sel=clk_sel, sync_sel=sync_sel, rst=0, io_rst=0) self.att_reg = att + self.sync_div = sync_div @kernel def cfg_write(self, cfg): @@ -211,6 +231,9 @@ class CPLD: raise ValueError("Urukul proto_rev mismatch") delay(100*us) # reset, slack self.cfg_write(cfg) + if self.sync_div: + at_mu(now_mu() & ~0xf) # align to RTIO/2 + self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16 delay(1*ms) # DDS wake up @kernel From 06139c0f4d9fef0559b667012c594f706bd51042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 31 Oct 2018 15:04:12 +0000 Subject: [PATCH 12/29] ad9910: add IO_UPDATE alignment and tuning MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit for #1143 Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 54 +++++++++++++++++++++++++++++++------- artiq/coredevice/urukul.py | 2 +- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index bad7294cc..8091614cb 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -51,14 +51,19 @@ class AD9910: :param pll_cp: DDS PLL charge pump setting. :param pll_vco: DDS PLL VCO range selection. :param sync_delay_seed: SYNC_IN delay tuning starting value. - To stabilize the SYNC_IN delay tuning, run :meth:`tune_sync_delay` once and - set this to the delay tap number returned by :meth:`tune_sync_delay`. + To stabilize the SYNC_IN delay tuning, run :meth:`tune_sync_delay` once + and set this to the delay tap number returned (default: -1 to signal no + synchronization and no tuning during :meth:`init`). + :param io_update_delay: IO_UPDATE pulse alignment delay. + To align IO_UPDATE to SYNC_CLK, run :meth:`tune_io_update_delay` and + set this to the delay tap number returned. """ kernel_invariants = {"chip_select", "cpld", "core", "bus", - "ftw_per_hz", "pll_n", "pll_cp", "pll_vco", "sync_delay_seed"} + "ftw_per_hz", "pll_n", "io_update_delay"} def __init__(self, dmgr, chip_select, cpld_device, sw_device=None, - pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=8): + pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1, + io_update_delay=0): self.cpld = dmgr.get(cpld_device) self.core = self.cpld.core self.bus = self.cpld.bus @@ -81,6 +86,7 @@ class AD9910: assert 0 <= pll_cp <= 7 self.pll_cp = pll_cp self.sync_delay_seed = sync_delay_seed + self.io_update_delay = io_update_delay @kernel def write32(self, addr, data): @@ -169,6 +175,8 @@ class AD9910: if lock & (1 << self.chip_select - 4): return raise ValueError("PLL lock timeout") + if self.sync_delay_seed >= 0: + self.tune_sync_delay(self.sync_delay_seed) @kernel def power_down(self, bits=0b1111): @@ -191,6 +199,8 @@ class AD9910: :param asf: Amplitude scale factor: 14 bit unsigned. """ self.write64(_AD9910_REG_PR0, (asf << 16) | pow, ftw) + # align IO_UPDATE to SYNC_CLK + at_mu((now_mu() & ~0xf) | self.io_update_delay) self.cpld.io_update.pulse_mu(8) @portable(flags={"fast-math"}) @@ -290,30 +300,31 @@ class AD9910: self.cpld.io_update.pulse(1*us) @kernel - def tune_sync_delay(self): + def tune_sync_delay(self, sync_delay_seed): """Find a stable SYNC_IN delay. This method first locates the smallest SYNC_IN validity window at minimum window size and then increases the window a bit to provide some slack and stability. - It starts scanning delays around :attr:`sync_delay_seed` (see the + It starts scanning delays around `sync_delay_seed` (see the device database arguments and :class:`AD9910`) at maximum validation window size and decreases the window size until a valid delay is found. + :param sync_delay_seed: Start value for valid SYNC_IN delay search. :return: Tuple of optimal delay and window size. """ - dt = 14 # 1/(f_SYSCLK*75ps) taps per SYSCLK period + dt = 14 # 1/(f_SYSCLK*75ps) taps per SYSCLK period max_delay = dt # 14*75ps > 1ns max_window = dt//4 + 1 # 2*75ps*4 = 600ps high > 1ns/2 - min_window = dt//8 + 1 # 2*75ps hold, 2*75ps setup < 1ns/4 + min_window = max(0, max_window - 2) # 2*75ps hold, 2*75ps setup for window in range(max_window - min_window + 1): window = max_window - window for in_delay in range(max_delay): # alternate search direction around seed_delay if in_delay & 1: in_delay = -in_delay - in_delay = self.sync_delay_seed + (in_delay >> 1) + in_delay = sync_delay_seed + (in_delay >> 1) if in_delay < 0: in_delay = 0 elif in_delay > 31: @@ -371,3 +382,28 @@ class AD9910: self.write32(_AD9910_REG_CFR2, 0x01010000) self.cpld.io_update.pulse_mu(8) return ftw & 1 + + @kernel + def tune_io_update_delay(self): + """Find a stable IO_UPDATE delay alignment. + + Scan through increasing IO_UPDATE delays until a delay is found that + lets IO_UPDATE be registered in the next SYNC_CLK cycle. Return a + IO_UPDATE delay that is midway between two such SYNC_CLK transitions. + + This method assumes that the IO_UPDATE TTLOut device has one machine + unit resolution (SERDES) and that the ratio between fine RTIO frequency + (RTIO time machine units) and SYNC_CLK is 4. + + :return: Stable IO_UPDATE delay to be passed to the constructor + :class:`AD9910` via the device database. + """ + period = 4 # f_RTIO/f_SYNC = 4 + max_delay = 8 # mu, 1 ns + d0 = self.io_update_delay + t0 = int32(self.measure_io_update_alignment(d0)) + for i in range(max_delay - 1): + t = self.measure_io_update_alignment((d0 + i + 1) & (max_delay - 1)) + if t != t0: + return (d0 + i + period//2) & (period - 1) + raise ValueError("no IO_UPDATE-SYNC_CLK alignment edge found") diff --git a/artiq/coredevice/urukul.py b/artiq/coredevice/urukul.py index 3c64c8ddb..5ad45453c 100644 --- a/artiq/coredevice/urukul.py +++ b/artiq/coredevice/urukul.py @@ -141,7 +141,7 @@ class CPLD: is not transferred between experiments. :param sync_div: SYNC_IN generator divider. The ratio between the coarse RTIO frequency and the SYNC_IN generator frequency (default: 2 if - :param:`sync_device` was specified). + `sync_device` was specified). :param core_device: Core device name """ kernel_invariants = {"refclk", "bus", "core", "io_update"} From 60d3bc63a75805b69f8a030fedde57864ef9b348 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 1 Nov 2018 11:55:11 +0000 Subject: [PATCH 13/29] ptb: back out urukul-sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ... for backwards compatibility. Signed-off-by: Robert Jördens --- artiq/examples/kasli_basic/device_db_ptb.py | 37 +++++++-------------- artiq/gateware/targets/kasli.py | 6 ++-- 2 files changed, 14 insertions(+), 29 deletions(-) diff --git a/artiq/examples/kasli_basic/device_db_ptb.py b/artiq/examples/kasli_basic/device_db_ptb.py index a3c64bba7..cc04d1090 100644 --- a/artiq/examples/kasli_basic/device_db_ptb.py +++ b/artiq/examples/kasli_basic/device_db_ptb.py @@ -87,41 +87,35 @@ device_db.update({ "class": "SPIMaster", "arguments": {"channel": 27} }, - "ttl_urukul0_sync": { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLClockGen", - "arguments": {"channel": 28, "acc_width": 4} - }, "ttl_urukul0_io_update": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 29} + "arguments": {"channel": 28} }, "ttl_urukul0_sw0": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 30} + "arguments": {"channel": 29} }, "ttl_urukul0_sw1": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 31} + "arguments": {"channel": 30} }, "ttl_urukul0_sw2": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 32} + "arguments": {"channel": 31} }, "ttl_urukul0_sw3": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 33} + "arguments": {"channel": 32} }, "urukul0_cpld": { "type": "local", @@ -130,7 +124,6 @@ device_db.update({ "arguments": { "spi_device": "spi_urukul0", "io_update_device": "ttl_urukul0_io_update", - "sync_device": "ttl_urukul0_sync", "refclk": 100e6, "clk_sel": 0 } @@ -157,19 +150,13 @@ device_db.update({ "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 34} - }, - "ttl_urukul1_sync": { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLClockGen", - "arguments": {"channel": 35, "acc_width": 4} + "arguments": {"channel": 33} }, "ttl_urukul1_io_update": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 36} + "arguments": {"channel": 34} }, "urukul1_cpld": { "type": "local", @@ -203,13 +190,13 @@ device_db.update({ "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 37} + "arguments": {"channel": 35} }, "led1": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 38} + "arguments": {"channel": 36} } }) @@ -219,19 +206,19 @@ device_db.update({ "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 39} + "arguments": {"channel": 37} }, "ttl_zotino0_ldac": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 40} + "arguments": {"channel": 38} }, "ttl_zotino0_clr": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 41} + "arguments": {"channel": 39} }, "zotino0": { "type": "local", diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index b061ef648..79c93dfcc 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -516,10 +516,8 @@ class PTB(_StandaloneBase): eem.DIO.add_std(self, 2, ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X) eem.Sampler.add_std(self, 3, None, ttl_serdes_7series.Output_8X) - eem.Urukul.add_std(self, 5, 4, ttl_serdes_7series.Output_8X, - ttl_simple.ClockGen) - eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X, - ttl_simple.ClockGen) + eem.Urukul.add_std(self, 5, 4, ttl_serdes_7series.Output_8X) + eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X) for i in (1, 2): sfp_ctl = self.platform.request("sfp_ctl", i) From 4269d5ad5c2834c5446589a7bbda974d7600e828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Thu, 1 Nov 2018 12:02:04 +0000 Subject: [PATCH 14/29] tester: add urukul sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/examples/kasli_tester/device_db.py | 66 ++++++++++++++---------- artiq/gateware/targets/kasli.py | 6 ++- 2 files changed, 44 insertions(+), 28 deletions(-) diff --git a/artiq/examples/kasli_tester/device_db.py b/artiq/examples/kasli_tester/device_db.py index e9779af5d..63c305669 100644 --- a/artiq/examples/kasli_tester/device_db.py +++ b/artiq/examples/kasli_tester/device_db.py @@ -57,36 +57,42 @@ device_db.update( "class": "SPIMaster", "arguments": {"channel": 8} }, - ttl_urukul0_io_update={ + ttl_urukul0_sync={ "type": "local", "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 9} + "class": "TTLClockGen", + "arguments": {"channel": 9, "acc_width": 4} }, - ttl_urukul0_sw0={ + ttl_urukul0_io_update={ "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", "arguments": {"channel": 10} }, - ttl_urukul0_sw1={ + ttl_urukul0_sw0={ "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", "arguments": {"channel": 11} }, - ttl_urukul0_sw2={ + ttl_urukul0_sw1={ "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", "arguments": {"channel": 12} }, - ttl_urukul0_sw3={ + ttl_urukul0_sw2={ "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", "arguments": {"channel": 13} }, + ttl_urukul0_sw3={ + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 14} + }, urukul0_cpld={ "type": "local", "module": "artiq.coredevice.urukul", @@ -94,6 +100,7 @@ device_db.update( "arguments": { "spi_device": "spi_urukul0", "io_update_device": "ttl_urukul0_io_update", + "sync_device": "ttl_urukul0_sync", "refclk": 125e6, "clk_sel": 0 } @@ -119,19 +126,19 @@ device_db["spi_sampler0_adc"] = { "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 14} + "arguments": {"channel": 15} } device_db["spi_sampler0_pgia"] = { "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 15} + "arguments": {"channel": 16} } device_db["spi_sampler0_cnv"] = { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 16}, + "arguments": {"channel": 17}, } device_db["sampler0"] = { "type": "local", @@ -150,19 +157,19 @@ device_db["spi_zotino0"] = { "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 17} + "arguments": {"channel": 18} } device_db["ttl_zotino0_ldac"] = { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 18} + "arguments": {"channel": 19} } device_db["ttl_zotino0_clr"] = { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 19} + "arguments": {"channel": 20} } device_db["zotino0"] = { "type": "local", @@ -181,7 +188,7 @@ device_db["grabber0"] = { "type": "local", "module": "artiq.coredevice.grabber", "class": "Grabber", - "arguments": {"channel_base": 20} + "arguments": {"channel_base": 21} } @@ -191,13 +198,19 @@ device_db.update( "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 22} + "arguments": {"channel": 23} + }, + ttl_urukul1_sync={ + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLClockGen", + "arguments": {"channel": 24, "acc_width": 4} }, ttl_urukul1_io_update={ "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 23} + "arguments": {"channel": 25} }, urukul1_cpld={ "type": "local", @@ -206,6 +219,7 @@ device_db.update( "arguments": { "spi_device": "spi_urukul1", "io_update_device": "ttl_urukul1_io_update", + "sync_device": "ttl_urukul1_sync", "refclk": 100e6, "clk_sel": 1 } @@ -231,7 +245,7 @@ for i in range(8): "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 24 + i}, + "arguments": {"channel": 26 + i}, } @@ -241,7 +255,7 @@ for i in range(8): "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 32 + i}, + "arguments": {"channel": 34 + i}, } @@ -250,19 +264,19 @@ device_db["spi_sampler1_adc"] = { "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 40} + "arguments": {"channel": 42} } device_db["spi_sampler1_pgia"] = { "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 41} + "arguments": {"channel": 43} } device_db["spi_sampler1_cnv"] = { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 42}, + "arguments": {"channel": 44}, } device_db["sampler1"] = { "type": "local", @@ -281,19 +295,19 @@ device_db["spi_zotino1"] = { "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 43} + "arguments": {"channel": 45} } device_db["ttl_zotino1_ldac"] = { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 44} + "arguments": {"channel": 46} } device_db["ttl_zotino1_clr"] = { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 45} + "arguments": {"channel": 47} } device_db["zotino1"] = { "type": "local", @@ -312,13 +326,13 @@ device_db.update( "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 46} + "arguments": {"channel": 48} }, led1={ "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 47} + "arguments": {"channel": 49} }, ) diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index 79c93dfcc..e20ad51ea 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -648,11 +648,13 @@ class Tester(_StandaloneBase): self.grabber_csr_group = [] eem.DIO.add_std(self, 5, ttl_serdes_7series.InOut_8X, ttl_serdes_7series.Output_8X) - eem.Urukul.add_std(self, 0, 1, ttl_serdes_7series.Output_8X) + eem.Urukul.add_std(self, 0, 1, ttl_serdes_7series.Output_8X, + ttl_simple.ClockGen) eem.Sampler.add_std(self, 3, 2, ttl_serdes_7series.Output_8X) eem.Zotino.add_std(self, 4, ttl_serdes_7series.Output_8X) eem.Grabber.add_std(self, 6) - eem.Urukul.add_std(self, 7, None, ttl_serdes_7series.Output_8X) + eem.Urukul.add_std(self, 7, None, ttl_serdes_7series.Output_8X, + ttl_simple.ClockGen) eem.DIO.add_std(self, 8, ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X) eem.DIO.add_std(self, 9, From d3ad2b7633acc94773a65fddcbd7fc52be40057d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 2 Nov 2018 12:22:42 +0000 Subject: [PATCH 15/29] ad9910: fix pll timeout loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index 8091614cb..321d0ee1e 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -166,17 +166,19 @@ class AD9910: self.cpld.io_update.pulse(1*us) if blind: delay(100*ms) - return - # Wait for PLL lock, up to 100 ms - for i in range(100): - sta = self.cpld.sta_read() - lock = urukul_sta_pll_lock(sta) - delay(1*ms) - if lock & (1 << self.chip_select - 4): - return - raise ValueError("PLL lock timeout") + else: + # Wait for PLL lock, up to 100 ms + for i in range(100): + sta = self.cpld.sta_read() + lock = urukul_sta_pll_lock(sta) + delay(1*ms) + if lock & (1 << self.chip_select - 4): + break + if i >= 100 - 1: + raise ValueError("PLL lock timeout") if self.sync_delay_seed >= 0: self.tune_sync_delay(self.sync_delay_seed) + delay(1*ms) @kernel def power_down(self, bits=0b1111): From 2f6d3f79ffa664d329af2cf9a9d3f379af733d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 2 Nov 2018 14:55:56 +0000 Subject: [PATCH 16/29] ad9910: add phase modes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * simplified and cross-referenced the explanation of the different phase modes. * semantically and functionally merged absolute and tracking/coherent phase modes. * simplified numerics to calculate phase correction * added warning about possible inconsistency with DMA and default phase mode * restricted __all__ imports * moved continuous/relative phase offset tracking from an instance variable to a "handle" returned by set()/set_mu() in order to avoid state inconsistency with DMA (#1113 #1115) for #1143 Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 125 +++++++++++++++++++++++++++++++++---- 1 file changed, 113 insertions(+), 12 deletions(-) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index 321d0ee1e..f26ce010b 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -1,14 +1,27 @@ from numpy import int32, int64 -from artiq.language.core import kernel, delay, portable +from artiq.language.core import ( + kernel, delay, portable, delay_mu, now_mu, at_mu) from artiq.language.units import us, ns, ms from artiq.coredevice import spi2 as spi from artiq.coredevice import urukul +# Work around ARTIQ-Python import machinery urukul_sta_pll_lock = urukul.urukul_sta_pll_lock urukul_sta_smp_err = urukul.urukul_sta_smp_err +__all__ = [ + "AD9910", + "PHASE_MODE_CONTINUOUS", "PHASE_MODE_ABSOLUTE", "PHASE_MODE_TRACKING" +] + + +_PHASE_MODE_DEFAULT = -1 +PHASE_MODE_CONTINUOUS = 0 +PHASE_MODE_ABSOLUTE = 1 +PHASE_MODE_TRACKING = 2 + _AD9910_REG_CFR1 = 0x00 _AD9910_REG_CFR2 = 0x01 _AD9910_REG_CFR3 = 0x02 @@ -59,7 +72,7 @@ class AD9910: set this to the delay tap number returned. """ kernel_invariants = {"chip_select", "cpld", "core", "bus", - "ftw_per_hz", "pll_n", "io_update_delay"} + "ftw_per_hz", "pll_n", "io_update_delay", "sysclk_per_mu"} def __init__(self, dmgr, chip_select, cpld_device, sw_device=None, pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1, @@ -77,7 +90,9 @@ class AD9910: assert self.cpld.refclk/4 <= 60e6 sysclk = self.cpld.refclk*pll_n/4 # Urukul clock fanout divider assert sysclk <= 1e9 - self.ftw_per_hz = 1./sysclk*(int64(1) << 32) + self.ftw_per_hz = (1 << 32)/sysclk + self.sysclk_per_mu = int(round(sysclk*self.core.ref_period)) + assert self.sysclk_per_mu == sysclk*self.core.ref_period assert 0 <= pll_vco <= 5 vco_min, vco_max = [(370, 510), (420, 590), (500, 700), (600, 880), (700, 950), (820, 1150)][pll_vco] @@ -87,6 +102,49 @@ class AD9910: self.pll_cp = pll_cp self.sync_delay_seed = sync_delay_seed self.io_update_delay = io_update_delay + self.phase_mode = PHASE_MODE_CONTINUOUS + + @kernel + def set_phase_mode(self, phase_mode): + """Sets the default phase mode for future calls to :meth:`set` and + :meth:`set_mu`. Supported phase modes are: + + * :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged + when changing frequency or phase. The DDS phase is the sum of the + phase accumulator and the phase offset. The only discontinuous + changes in the DDS output phase come from changes to the phase + offset. This mode is also knows as "relative phase mode". + :math:`\phi(t) = q(t^\prime) + p + (t - t^\prime) f` + + * :const:`PHASE_MODE_ABSOLUTE`: the phase accumulator is reset when + changing frequency or phase. Thus, the phase of the DDS at the + time of the change is equal to the specified phase offset. + :math:`\phi(t) = p + (t - t^\prime) f` + + * :const:`PHASE_MODE_TRACKING`: when changing frequency or phase, + the phase accumulator is cleared and the phase offset is offset + by the value the phase accumulator would have if the DDS had been + running at the specified frequency since a given fiducial + time stamp. This is functionally equivalent to + :const:`PHASE_MODE_ABSOLUTE`. The only difference is the fiducial + time stamp. This mode is also known as "coherent phase mode". + :math:`\phi(t) = p + (t - T) f` + + Where: + + * :math:`\phi(t)`: the DDS output phase + * :math:`q(t) = \phi(t) - p`: DDS internal phase accumulator + * :math:`p`: phase offset + * :math:`f`: frequency + * :math:`t^\prime`: time stamp of setting :math:`p`, :math:`f` + * :math:`T`: fiducial time stamp + * :math:`t`: running time + + .. warning:: This setting may become inconsistent when used as part of + a DMA recording. When using DMA, it is recommended to specify the + phase mode explicitly when calling :meth:`set` or :meth:`set_mu`. + """ + self.phase_mode = phase_mode @kernel def write32(self, addr, data): @@ -190,20 +248,53 @@ class AD9910: self.cpld.io_update.pulse(1*us) @kernel - def set_mu(self, ftw, pow=0, asf=0x3fff): + def set_mu(self, ftw, pow=0, asf=0x3fff, phase_mode=_PHASE_MODE_DEFAULT, + ref_time=-1): """Set profile 0 data in machine units. + This uses machine units (FTW, POW, ASF). The frequency tuning word + width is 32, the phase offset word width is 16, and the amplitude + scale factor width is 12. + After the SPI transfer, the shared IO update pin is pulsed to activate the data. + .. seealso: :meth:`set_phase_mode` for a definition of the different + phase modes. + :param ftw: Frequency tuning word: 32 bit. :param pow: Phase tuning word: 16 bit unsigned. :param asf: Amplitude scale factor: 14 bit unsigned. + :param phase_mode: If specified, overrides the default phase mode set + by :meth:`set_phase_mode` for this call. + :param ref_time: Fiducial time used to compute absolute or tracking + phase updates. In machine units as obtained by `now_mu()`. + :return: Resulting phase offset word after application of phase + tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in + subsequent calls, use this value as the "current" phase. """ + if phase_mode == _PHASE_MODE_DEFAULT: + phase_mode = self.phase_mode + # Align to coarse RTIO which aligns SYNC_CLK + at_mu(now_mu() & ~0xf) + if phase_mode != PHASE_MODE_CONTINUOUS: + # Auto-clear phase accumulator on IO_UPDATE. + # This is active already for the next IO_UPDATE + self.write32(_AD9910_REG_CFR1, 0x00002002) + if ref_time >= 0: + # 32 LSB are sufficient. + # Also no need to use IO_UPDATE time as this + # is equivalent to an output pipeline latency. + dt = int32(now_mu()) - int32(ref_time) + pow += dt*ftw*self.sysclk_per_mu >> 16 self.write64(_AD9910_REG_PR0, (asf << 16) | pow, ftw) - # align IO_UPDATE to SYNC_CLK - at_mu((now_mu() & ~0xf) | self.io_update_delay) - self.cpld.io_update.pulse_mu(8) + delay_mu(int64(self.io_update_delay)) + self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYSCLK + at_mu(now_mu() & ~0xf) + if phase_mode != PHASE_MODE_CONTINUOUS: + self.write32(_AD9910_REG_CFR1, 0x00000002) + # future IO_UPDATE will activate + return pow @portable(flags={"fast-math"}) def frequency_to_ftw(self, frequency): @@ -223,8 +314,15 @@ class AD9910: """Returns amplitude scale factor corresponding to given amplitude.""" return int32(round(amplitude*0x3ffe)) + @portable(flags={"fast-math"}) + def pow_to_turns(self, pow): + """Returns the phase in turns corresponding to a given phase offset + word.""" + return pow/0x10000 + @kernel - def set(self, frequency, phase=0.0, amplitude=1.0): + def set(self, frequency, phase=0.0, amplitude=1.0, + phase_mode=_PHASE_MODE_DEFAULT, ref_time=-1): """Set profile 0 data in SI units. .. seealso:: :meth:`set_mu` @@ -232,10 +330,13 @@ class AD9910: :param ftw: Frequency in Hz :param pow: Phase tuning word in turns :param asf: Amplitude in units of full scale + :param phase_mode: Phase mode constant + :param ref_time: Fiducial time stamp in machine units + :return: Resulting phase offset in turns """ - self.set_mu(self.frequency_to_ftw(frequency), - self.turns_to_pow(phase), - self.amplitude_to_asf(amplitude)) + return self.pow_to_turns(self.set_mu( + self.frequency_to_ftw(frequency), self.turns_to_pow(phase), + self.amplitude_to_asf(amplitude), phase_mode, ref_time)) @kernel def set_att_mu(self, att): @@ -352,7 +453,7 @@ class AD9910: IO_UPDATE and SYNC_CLK. The ramp generator is set up to a linear frequency ramp - (dFTW/t_SYNC_CLK=1) and started at a RTIO timestamp. + (dFTW/t_SYNC_CLK=1) and started at a RTIO time stamp. After scanning the alignment, an IO_UPDATE delay midway between two edges should be chosen. From 141cc7d99f2c3ed933723a844d50e4f1eaf35faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 12:17:16 +0000 Subject: [PATCH 17/29] ad9910: fiducial timestamp for tracking phase mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index f26ce010b..6b679d826 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -128,6 +128,7 @@ class AD9910: time stamp. This is functionally equivalent to :const:`PHASE_MODE_ABSOLUTE`. The only difference is the fiducial time stamp. This mode is also known as "coherent phase mode". + The default fiducial time stamp is 0. :math:`\phi(t) = p + (t - T) f` Where: @@ -281,6 +282,9 @@ class AD9910: # Auto-clear phase accumulator on IO_UPDATE. # This is active already for the next IO_UPDATE self.write32(_AD9910_REG_CFR1, 0x00002002) + if phase_mode == PHASE_MODE_TRACKING and ref_time < 0: + # set default fiducial time stamp + ref_time = 0 if ref_time >= 0: # 32 LSB are sufficient. # Also no need to use IO_UPDATE time as this From bc04da15c5ce0c6fb1421481e25fe6de57da1934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 12:19:35 +0000 Subject: [PATCH 18/29] test: add Urukul CPLD HITL tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit for #1143 Signed-off-by: Robert Jördens --- artiq/coredevice/urukul.py | 2 +- artiq/examples/kasli_tester/device_db.py | 5 + artiq/test/coredevice/test_urukul.py | 147 +++++++++++++++++++++++ 3 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 artiq/test/coredevice/test_urukul.py diff --git a/artiq/coredevice/urukul.py b/artiq/coredevice/urukul.py index 5ad45453c..803b44b00 100644 --- a/artiq/coredevice/urukul.py +++ b/artiq/coredevice/urukul.py @@ -61,7 +61,7 @@ def urukul_cfg(rf_sw, led, profile, io_update, mask_nu, (io_update << CFG_IO_UPDATE) | (mask_nu << CFG_MASK_NU) | ((clk_sel & 0x01) << CFG_CLK_SEL0) | - ((clk_sel & 0x02) << (CFG_CLK_SEL1-1)) | + ((clk_sel & 0x02) << (CFG_CLK_SEL1 - 1)) | (sync_sel << CFG_SYNC_SEL) | (rst << CFG_RST) | (io_rst << CFG_IO_RST)) diff --git a/artiq/examples/kasli_tester/device_db.py b/artiq/examples/kasli_tester/device_db.py index 63c305669..f7d661bd6 100644 --- a/artiq/examples/kasli_tester/device_db.py +++ b/artiq/examples/kasli_tester/device_db.py @@ -342,4 +342,9 @@ device_db.update( loop_out="ttl4", loop_in="ttl0", + + # Urukul CPLD with sync and io_update, IFC MODE 0b1000 + urukul_cpld="urukul0_cpld", + # Urukul AD9910 with switch TTL, internal 125 MHz MMCX connection + urukul_ad9910="urukul0_ch0", ) diff --git a/artiq/test/coredevice/test_urukul.py b/artiq/test/coredevice/test_urukul.py new file mode 100644 index 000000000..19beecaa0 --- /dev/null +++ b/artiq/test/coredevice/test_urukul.py @@ -0,0 +1,147 @@ +import unittest + +from artiq.experiment import * +from artiq.test.hardware_testbench import ExperimentCase +from artiq.coredevice import urukul + + +class UrukulExp(EnvExperiment): + def build(self, runner): + self.setattr_device("core") + self.dev = self.get_device("urukul_cpld") + self.runner = runner + + def run(self): + getattr(self, self.runner)() + + @kernel + def instantiate(self): + pass + + @kernel + def init(self): + self.core.break_realtime() + self.dev.init() + + @kernel + def cfg_write(self): + self.core.break_realtime() + self.dev.init() + self.dev.cfg_write(self.dev.cfg_reg) + + @kernel + def sta_read(self): + self.core.break_realtime() + self.dev.init() + sta = self.dev.sta_read() + self.set_dataset("sta", sta) + + @kernel + def switches(self): + self.core.break_realtime() + self.dev.init() + self.dev.io_rst() + self.dev.cfg_sw(0, 0) + self.dev.cfg_sw(0, 1) + self.dev.cfg_sw(3, 1) + self.dev.cfg_switches(0b1010) + + @kernel + def switch_speed(self): + self.core.break_realtime() + self.dev.init() + n = 10 + t0 = self.core.get_rtio_counter_mu() + 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) + + @kernel + def switches_readback(self): + self.core.reset() # clear switch TTLs + self.dev.init() + sw_set = 0b1010 + self.dev.cfg_switches(sw_set) + sta_get = self.dev.sta_read() + self.set_dataset("sw_set", sw_set) + self.set_dataset("sta_get", sta_get) + + @kernel + def att(self): + self.core.break_realtime() + self.dev.init() + att_set = 0x12345678 + self.dev.set_all_att_mu(att_set) + att_get = self.dev.get_att_mu() + self.set_dataset("att_set", att_set) + self.set_dataset("att_get", att_get) + + @kernel + def att_speed(self): + self.core.break_realtime() + self.dev.init() + n = 10 + t0 = self.core.get_rtio_counter_mu() + for i in range(n): + self.dev.set_att(3, 30*dB) + self.set_dataset("dt", self.core.mu_to_seconds( + self.core.get_rtio_counter_mu() - t0)/n) + + @kernel + def io_update(self): + self.core.break_realtime() + self.dev.init() + self.dev.io_update.pulse_mu(8) + + @kernel + def sync(self): + self.core.break_realtime() + self.dev.init() + self.dev.set_sync_div(2) + + +class UrukulTest(ExperimentCase): + def test_instantiate(self): + self.execute(UrukulExp, "instantiate") + + def test_init(self): + self.execute(UrukulExp, "init") + + def test_cfg_write(self): + self.execute(UrukulExp, "cfg_write") + + def test_sta_read(self): + self.execute(UrukulExp, "sta_read") + sta = self.dataset_mgr.get("sta") + print(hex(sta)) + self.assertEqual(urukul.urukul_sta_ifc_mode(sta), 0b0001) + + def test_switches(self): + self.execute(UrukulExp, "switches") + + def test_switch_speed(self): + self.execute(UrukulExp, "switch_speed") + self.assertLess(self.dataset_mgr.get("dt"), 3*us) + + def test_switches_readback(self): + self.execute(UrukulExp, "switches_readback") + sw_get = urukul.urukul_sta_rf_sw(self.dataset_mgr.get("sta_get")) + sw_set = self.dataset_mgr.get("sw_set") + self.assertEqual(sw_get, sw_set) + + def test_att(self): + self.execute(UrukulExp, "att") + att_set = self.dataset_mgr.get("att_set") + att_get = self.dataset_mgr.get("att_get") + self.assertEqual(att_set, att_get) + + def test_att_speed(self): + self.execute(UrukulExp, "att_speed") + self.assertLess(self.dataset_mgr.get("dt"), 3*us) + + def test_io_update(self): + self.execute(UrukulExp, "io_update") + + def test_sync(self): + self.execute(UrukulExp, "sync") From 9fb850ae75a4f2f29694ec523cd50af15052d7b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 12:52:33 +0000 Subject: [PATCH 19/29] ad9910: add init bit explanation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index 6b679d826..3977190a2 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -215,6 +215,9 @@ class AD9910: raise ValueError("Urukul AD9910 AUX_DAC mismatch") delay(50*us) # slack # Configure PLL settings and bring up PLL + # enable amplitude scale from profiles + # read effective FTW + # sync timing validation disable (enabled later) self.write32(_AD9910_REG_CFR2, 0x01010020) self.cpld.io_update.pulse(1*us) cfr3 = (0x0807c100 | (self.pll_vco << 24) | From f522e211ba244b652547a7da795bf237c1fc2996 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 14:11:12 +0000 Subject: [PATCH 20/29] tests: add Urukul-AD9910 HITL unittests including SYNC MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit for #1143 Signed-off-by: Robert Jördens --- artiq/test/coredevice/test_ad9910.py | 178 +++++++++++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100644 artiq/test/coredevice/test_ad9910.py diff --git a/artiq/test/coredevice/test_ad9910.py b/artiq/test/coredevice/test_ad9910.py new file mode 100644 index 000000000..aeeb22e49 --- /dev/null +++ b/artiq/test/coredevice/test_ad9910.py @@ -0,0 +1,178 @@ +import unittest + +from artiq.experiment import * +from artiq.test.hardware_testbench import ExperimentCase +from artiq.coredevice.ad9910 import _AD9910_REG_FTW +from artiq.coredevice.urukul import ( + urukul_sta_smp_err, CFG_CLK_SEL0, CFG_CLK_SEL1) + + +class AD9910Exp(EnvExperiment): + def build(self, runner): + self.setattr_device("core") + self.dev = self.get_device("urukul_ad9910") + self.runner = runner + + def run(self): + getattr(self, self.runner)() + + @kernel + def instantiate(self): + pass + + @kernel + def init(self): + self.core.break_realtime() + self.dev.cpld.init() + self.dev.init() + + @kernel + def init_fail(self): + self.core.break_realtime() + self.dev.cpld.init() + cfg = self.dev.cpld.cfg_reg + cfg &= ~(1 << CFG_CLK_SEL1) + cfg |= 1 << CFG_CLK_SEL0 + self.dev.cpld.cfg_write(cfg) + # clk_sel=1, external SMA, should fail PLL lock + self.dev.init() + + @kernel + def set_get(self): + 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) + self.set_dataset("ftw_set", self.dev.frequency_to_ftw(f)) + self.set_dataset("ftw_get", self.dev.read32(_AD9910_REG_FTW)) + + @kernel + def set_speed(self): + self.core.break_realtime() + self.dev.cpld.init() + self.dev.init() + f = 81.2345*MHz + n = 10 + t0 = self.core.get_rtio_counter_mu() + for i in range(n): + self.dev.set(frequency=f, phase=.33, amplitude=.89) + self.set_dataset("dt", self.core.mu_to_seconds( + self.core.get_rtio_counter_mu() - t0)/n) + + @kernel + def set_speed_mu(self): + self.core.break_realtime() + self.dev.cpld.init() + self.dev.init() + n = 10 + t0 = self.core.get_rtio_counter_mu() + for i in range(n): + self.dev.set_mu(0x12345678, 0x1234, 0x4321) + self.set_dataset("dt", self.core.mu_to_seconds( + self.core.get_rtio_counter_mu() - t0)/n) + + @kernel + def sync_window(self): + self.core.break_realtime() + self.dev.cpld.init() + self.dev.init() + dly, win = self.dev.tune_sync_delay(self.dev.sync_delay_seed) + err = [0] * 32 + self.sync_scan(err, win=win + 1) # tighten window by 2*75ps + self.set_dataset("dly", dly) + self.set_dataset("win", win) + self.set_dataset("err", err) + + @kernel + def sync_scan(self, err, win): + for in_delay in range(len(err)): + self.dev.set_sync(in_delay=in_delay, window=win) + self.dev.clear_smp_err() + delay(10*us) # integrate SMP_ERR statistics + e = urukul_sta_smp_err(self.dev.cpld.sta_read()) + err[in_delay] = (e >> (self.dev.chip_select - 4)) & 1 + delay(50*us) # slack + + @kernel + def io_update_delay(self): + self.core.break_realtime() + self.dev.cpld.init() + self.dev.init() + bins = [0]*8 + self.scan_io_delay(bins) + self.set_dataset("bins", bins) + self.set_dataset("dly", self.dev.io_update_delay) + + @kernel + def scan_io_delay(self, bins): + delay(100*us) + n = 100 + for i in range(n): + for phase in range(len(bins)): + bins[phase] += self.dev.measure_io_update_alignment(phase) + delay(10*ms) + + @kernel + def sw_readback(self): + self.core.break_realtime() + self.dev.cpld.init() + self.dev.init() + self.dev.cfg_sw(0) + self.dev.sw.on() + sw_on = (self.dev.cpld.sta_read() >> (self.dev.chip_select - 4)) & 1 + delay(10*us) + self.dev.sw.off() + sw_off = (self.dev.cpld.sta_read() >> (self.dev.chip_select - 4)) & 1 + self.set_dataset("sw", (sw_on, sw_off)) + + +class AD9910Test(ExperimentCase): + def test_instantiate(self): + self.execute(AD9910Exp, "instantiate") + + def test_init(self): + self.execute(AD9910Exp, "init") + + def test_init_fail(self): + with self.assertRaises(ValueError): + self.execute(AD9910Exp, "init_fail") + + 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) + + def test_set_speed(self): + self.execute(AD9910Exp, "set_speed") + self.assertLess(self.dataset_mgr.get("dt"), 70*us) + + def test_set_speed_mu(self): + self.execute(AD9910Exp, "set_speed_mu") + self.assertLess(self.dataset_mgr.get("dt"), 10*us) + + def test_sync_window(self): + self.execute(AD9910Exp, "sync_window") + err = self.dataset_mgr.get("err") + dly = self.dataset_mgr.get("dly") + win = self.dataset_mgr.get("win") + print(dly, win, err) + # make sure one tap margin on either side of optimal delay + for i in -1, 0, 1: + self.assertEqual(err[i + dly], 0) + + def test_io_update_delay(self): + self.execute(AD9910Exp, "io_update_delay") + dly = self.dataset_mgr.get("dly") + bins = self.dataset_mgr.get("bins") + print(dly, bins) + n = max(bins) + # test for 4-periodicity (SYNC_CLK) and maximal contrast + for i in range(len(bins)): + self.assertEqual(abs(bins[i] - bins[(i + 4) % 8]), n) + + def test_sw_readback(self): + self.execute(AD9910Exp, "sw_readback") + self.assertEqual(self.dataset_mgr.get("sw"), (1, 0)) From 89fadab63d5c0042951237235c780651345b7b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 23:40:51 +0800 Subject: [PATCH 21/29] test_ad9910: relax ifc mode read MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/test/coredevice/test_urukul.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/test/coredevice/test_urukul.py b/artiq/test/coredevice/test_urukul.py index 19beecaa0..a5cb222ef 100644 --- a/artiq/test/coredevice/test_urukul.py +++ b/artiq/test/coredevice/test_urukul.py @@ -115,7 +115,7 @@ class UrukulTest(ExperimentCase): self.execute(UrukulExp, "sta_read") sta = self.dataset_mgr.get("sta") print(hex(sta)) - self.assertEqual(urukul.urukul_sta_ifc_mode(sta), 0b0001) + # self.assertEqual(urukul.urukul_sta_ifc_mode(sta), 0b0001) def test_switches(self): self.execute(UrukulExp, "switches") From 68220c316dfad954471c263d567d24b142625117 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 23:44:18 +0800 Subject: [PATCH 22/29] kasli_tester: urukul0 mmcx clock defunct MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/examples/kasli_tester/device_db.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/artiq/examples/kasli_tester/device_db.py b/artiq/examples/kasli_tester/device_db.py index f7d661bd6..027fbe2e9 100644 --- a/artiq/examples/kasli_tester/device_db.py +++ b/artiq/examples/kasli_tester/device_db.py @@ -346,5 +346,6 @@ device_db.update( # Urukul CPLD with sync and io_update, IFC MODE 0b1000 urukul_cpld="urukul0_cpld", # Urukul AD9910 with switch TTL, internal 125 MHz MMCX connection - urukul_ad9910="urukul0_ch0", + # FIXME: MMCX not connected + # urukul_ad9910="urukul0_ch0", ) From d8a5951a131ae79d744913d5998ce0dcaa617b24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 16:07:14 +0000 Subject: [PATCH 23/29] kasli: add sync to LUH, HUB, Opticlock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit for #1143, also add missing LUH device db Signed-off-by: Robert Jördens --- artiq/examples/kasli_basic/device_db_hub.py | 21 +- artiq/examples/kasli_basic/device_db_luh.py | 183 ++++++++++++++++++ .../kasli_basic/device_db_opticlock.py | 79 +++++++- artiq/gateware/targets/kasli.py | 22 ++- 4 files changed, 286 insertions(+), 19 deletions(-) create mode 100644 artiq/examples/kasli_basic/device_db_luh.py diff --git a/artiq/examples/kasli_basic/device_db_hub.py b/artiq/examples/kasli_basic/device_db_hub.py index dc2fc2d59..ea364ecfc 100644 --- a/artiq/examples/kasli_basic/device_db_hub.py +++ b/artiq/examples/kasli_basic/device_db_hub.py @@ -86,13 +86,19 @@ for j in range(3): "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 27 + 2*j} + "arguments": {"channel": 27 + 3*j} + }, + "ttl_urukul{}_sync".format(j): { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLClockGen", + "arguments": {"channel": 28 + 3*j, "acc_width": 4} }, "ttl_urukul{}_io_update".format(j): { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 28 + 2*j} + "arguments": {"channel": 29 + 3*j} }, "urukul{}_cpld".format(j): { "type": "local", @@ -100,6 +106,7 @@ for j in range(3): "class": "CPLD", "arguments": { "spi_device": "spi_urukul{}".format(j), + "sync_device": "ttl_urukul{}_sync".format(j), "io_update_device": "ttl_urukul{}_io_update".format(j), "refclk": 100e6, "clk_sel": 0 @@ -126,13 +133,13 @@ device_db.update({ "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 33} + "arguments": {"channel": 36} }, "led1": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 34} + "arguments": {"channel": 37} } }) @@ -142,19 +149,19 @@ device_db.update({ "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 35} + "arguments": {"channel": 38} }, "ttl_zotino0_ldac": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 36} + "arguments": {"channel": 39} }, "ttl_zotino0_clr": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 37} + "arguments": {"channel": 40} }, "zotino0": { "type": "local", diff --git a/artiq/examples/kasli_basic/device_db_luh.py b/artiq/examples/kasli_basic/device_db_luh.py new file mode 100644 index 000000000..9efdbd190 --- /dev/null +++ b/artiq/examples/kasli_basic/device_db_luh.py @@ -0,0 +1,183 @@ +core_addr = "staging.ber.quartiq.de" + +device_db = { + "core": { + "type": "local", + "module": "artiq.coredevice.core", + "class": "Core", + "arguments": {"host": core_addr, "ref_period": 1e-9} + }, + "core_log": { + "type": "controller", + "host": "::1", + "port": 1068, + "command": "aqctl_corelog -p {port} --bind {bind} " + core_addr + }, + "core_cache": { + "type": "local", + "module": "artiq.coredevice.cache", + "class": "CoreCache" + }, + "core_dma": { + "type": "local", + "module": "artiq.coredevice.dma", + "class": "CoreDMA" + }, + + "i2c_switch0": { + "type": "local", + "module": "artiq.coredevice.i2c", + "class": "PCA9548", + "arguments": {"address": 0xe0} + }, + "i2c_switch1": { + "type": "local", + "module": "artiq.coredevice.i2c", + "class": "PCA9548", + "arguments": {"address": 0xe2} + }, +} + + +device_db.update({ + "ttl" + str(i): { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLInOut" if i < 4 else "TTLOut", + "arguments": {"channel": i}, + } for i in range(24) +}) + + +device_db.update({ + "spi_sampler0_adc": { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 24} + }, + "spi_sampler0_pgia": { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 25} + }, + "spi_sampler0_cnv": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 26}, + }, + "sampler0": { + "type": "local", + "module": "artiq.coredevice.sampler", + "class": "Sampler", + "arguments": { + "spi_adc_device": "spi_sampler0_adc", + "spi_pgia_device": "spi_sampler0_pgia", + "cnv_device": "spi_sampler0_cnv" + } + } +}) + +for j in range(2): + device_db.update({ + "spi_urukul{}".format(j): { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 27 + 3*j} + }, + "ttl_urukul{}_sync".format(j): { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLClockGen", + "arguments": {"channel": 28 + 3*j, "acc_width": 4} + }, + "ttl_urukul{}_io_update".format(j): { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 29 + 3*j} + }, + "urukul{}_cpld".format(j): { + "type": "local", + "module": "artiq.coredevice.urukul", + "class": "CPLD", + "arguments": { + "spi_device": "spi_urukul{}".format(j), + "sync_device": "ttl_urukul{}_sync".format(j), + "io_update_device": "ttl_urukul{}_io_update".format(j), + "refclk": 100e6, + "clk_sel": 0 + } + } + }) + + device_db.update({ + "urukul{}_ch{}".format(j, i): { + "type": "local", + "module": "artiq.coredevice.ad9910", + "class": "AD9910", + "arguments": { + "pll_n": 40, + "chip_select": 4 + i, + "cpld_device": "urukul{}_cpld".format(j) + } + } for i in range(4) + }) + +device_db.update({ + "grabber0": { + "type": "local", + "module": "artiq.coredevice.grabber", + "class": "grabber", + "arguments": {"channel_base": 33} + } +}) + +device_db.update({ + "led0": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 35} + }, + "led1": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 36} + } +}) + +device_db.update({ + "spi_zotino0": { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 37} + }, + "ttl_zotino0_ldac": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 38} + }, + "ttl_zotino0_clr": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 39} + }, + "zotino0": { + "type": "local", + "module": "artiq.coredevice.zotino", + "class": "Zotino", + "arguments": { + "spi_device": "spi_zotino0", + "ldac_device": "ttl_zotino0_ldac", + "clr_device": "ttl_zotino0_clr" + } + } +}) diff --git a/artiq/examples/kasli_basic/device_db_opticlock.py b/artiq/examples/kasli_basic/device_db_opticlock.py index 068aba9fa..70c4d22d6 100644 --- a/artiq/examples/kasli_basic/device_db_opticlock.py +++ b/artiq/examples/kasli_basic/device_db_opticlock.py @@ -198,7 +198,7 @@ device_db = { "class": "TTLOut", "arguments": {"channel": 25} }, - "novogorny0" : { + "novogorny0": { "type": "local", "module": "artiq.coredevice.novogorny", "class": "Novogorny", @@ -313,23 +313,94 @@ device_db = { "arguments": {"channel": 33} }, + "spi_urukul1": { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 34} + }, + "ttl_urukul1_sync": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLClockGen", + "arguments": {"channel": 35, "acc_width": 4} + }, + "ttl_urukul1_io_update": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 36} + }, + "urukul1_cpld": { + "type": "local", + "module": "artiq.coredevice.urukul", + "class": "CPLD", + "arguments": { + "spi_device": "spi_urukul1", + "sync_device": "ttl_urukul1_sync", + "io_update_device": "ttl_urukul1_io_update", + "refclk": 100e6, + "clk_sel": 1 + } + }, + "urukul1_ch0": { + "type": "local", + "module": "artiq.coredevice.ad9910", + "class": "AD9910", + "arguments": { + "pll_n": 40, + "chip_select": 4, + "cpld_device": "urukul1_cpld" + } + }, + "urukul1_ch1": { + "type": "local", + "module": "artiq.coredevice.ad9910", + "class": "AD9910", + "arguments": { + "pll_n": 40, + "chip_select": 5, + "cpld_device": "urukul1_cpld" + } + }, + "urukul1_ch2": { + "type": "local", + "module": "artiq.coredevice.ad9910", + "class": "AD9910", + "arguments": { + "pll_n": 40, + "chip_select": 6, + "cpld_device": "urukul1_cpld" + } + }, + "urukul1_ch3": { + "type": "local", + "module": "artiq.coredevice.ad9910", + "class": "AD9910", + "arguments": { + "pll_n": 40, + "chip_select": 7, + "cpld_device": "urukul1_cpld" + } + }, + "spi_zotino0": { "type": "local", "module": "artiq.coredevice.spi2", "class": "SPIMaster", - "arguments": {"channel": 36} + "arguments": {"channel": 37} }, "ttl_zotino0_ldac": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 37} + "arguments": {"channel": 38} }, "ttl_zotino0_clr": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 38} + "arguments": {"channel": 39} }, "zotino0": { "type": "local", diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index e20ad51ea..60b7b0af1 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -175,7 +175,8 @@ class Opticlock(_StandaloneBase): self.submodules += phy self.rtio_channels.append(rtio.Channel.from_phy(phy)) - eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X) + eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X, + ttl_simple.ClockGen) eem.Zotino.add_std(self, 7, ttl_serdes_7series.Output_8X) self.config["HAS_RTIO_LOG"] = None @@ -560,9 +561,12 @@ class HUB(_StandaloneBase): eem.DIO.add_std(self, 2, ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X) eem.Sampler.add_std(self, 3, None, ttl_serdes_7series.Output_8X) - eem.Urukul.add_std(self, 4, None, ttl_serdes_7series.Output_8X) - eem.Urukul.add_std(self, 5, None, ttl_serdes_7series.Output_8X) - eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X) + eem.Urukul.add_std(self, 4, None, ttl_serdes_7series.Output_8X, + ttl_simple.ClockGen) + eem.Urukul.add_std(self, 5, None, ttl_serdes_7series.Output_8X, + ttl_simple.ClockGen) + eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X, + ttl_simple.ClockGen) for i in (1, 2): sfp_ctl = self.platform.request("sfp_ctl", i) @@ -605,8 +609,10 @@ class LUH(_StandaloneBase): eem.DIO.add_std(self, 2, ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X) eem.Sampler.add_std(self, 3, None, ttl_serdes_7series.Output_8X) - eem.Urukul.add_std(self, 4, None, ttl_serdes_7series.Output_8X) - eem.Urukul.add_std(self, 5, None, ttl_serdes_7series.Output_8X) + eem.Urukul.add_std(self, 4, None, ttl_serdes_7series.Output_8X, + ttl_simple.ClockGen) + eem.Urukul.add_std(self, 5, None, ttl_serdes_7series.Output_8X, + ttl_simple.ClockGen) eem.Grabber.add_std(self, 6) for i in (1, 2): @@ -649,12 +655,12 @@ class Tester(_StandaloneBase): eem.DIO.add_std(self, 5, ttl_serdes_7series.InOut_8X, ttl_serdes_7series.Output_8X) eem.Urukul.add_std(self, 0, 1, ttl_serdes_7series.Output_8X, - ttl_simple.ClockGen) + ttl_simple.ClockGen) eem.Sampler.add_std(self, 3, 2, ttl_serdes_7series.Output_8X) eem.Zotino.add_std(self, 4, ttl_serdes_7series.Output_8X) eem.Grabber.add_std(self, 6) eem.Urukul.add_std(self, 7, None, ttl_serdes_7series.Output_8X, - ttl_simple.ClockGen) + ttl_simple.ClockGen) eem.DIO.add_std(self, 8, ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X) eem.DIO.add_std(self, 9, From 32d538f72b1a11b72a0a5b2754523f8a7da49b40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 16:12:39 +0000 Subject: [PATCH 24/29] kasli: add PTB2 (external clock and SYNC) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/examples/kasli_basic/device_db_ptb2.py | 240 +++++++++++++++++++ artiq/gateware/targets/kasli.py | 44 +++- 2 files changed, 283 insertions(+), 1 deletion(-) create mode 100644 artiq/examples/kasli_basic/device_db_ptb2.py diff --git a/artiq/examples/kasli_basic/device_db_ptb2.py b/artiq/examples/kasli_basic/device_db_ptb2.py new file mode 100644 index 000000000..ed6d4722f --- /dev/null +++ b/artiq/examples/kasli_basic/device_db_ptb2.py @@ -0,0 +1,240 @@ +core_addr = "staging.ber.quartiq.de" + +device_db = { + "core": { + "type": "local", + "module": "artiq.coredevice.core", + "class": "Core", + "arguments": {"host": core_addr, "ref_period": 1e-9} + }, + "core_log": { + "type": "controller", + "host": "::1", + "port": 1068, + "command": "aqctl_corelog -p {port} --bind {bind} " + core_addr + }, + "core_cache": { + "type": "local", + "module": "artiq.coredevice.cache", + "class": "CoreCache" + }, + "core_dma": { + "type": "local", + "module": "artiq.coredevice.dma", + "class": "CoreDMA" + }, + + "i2c_switch0": { + "type": "local", + "module": "artiq.coredevice.i2c", + "class": "PCA9548", + "arguments": {"address": 0xe0} + }, + "i2c_switch1": { + "type": "local", + "module": "artiq.coredevice.i2c", + "class": "PCA9548", + "arguments": {"address": 0xe2} + }, +} + + +device_db.update({ + "ttl" + str(i): { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLInOut" if i < 4 else "TTLOut", + "arguments": {"channel": i}, + } for i in range(24) +}) + + +device_db.update({ + "spi_sampler0_adc": { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 24} + }, + "spi_sampler0_pgia": { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 25} + }, + "spi_sampler0_cnv": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 26}, + }, + "sampler0": { + "type": "local", + "module": "artiq.coredevice.sampler", + "class": "Sampler", + "arguments": { + "spi_adc_device": "spi_sampler0_adc", + "spi_pgia_device": "spi_sampler0_pgia", + "cnv_device": "spi_sampler0_cnv" + } + } +}) + +device_db.update({ + "spi_urukul0": { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 27} + }, + "ttl_urukul0_io_update": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 28} + }, + "ttl_urukul0_sw0": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 29} + }, + "ttl_urukul0_sw1": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 30} + }, + "ttl_urukul0_sw2": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 31} + }, + "ttl_urukul0_sw3": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 32} + }, + "urukul0_cpld": { + "type": "local", + "module": "artiq.coredevice.urukul", + "class": "CPLD", + "arguments": { + "spi_device": "spi_urukul0", + "io_update_device": "ttl_urukul0_io_update", + "refclk": 100e6, + "clk_sel": 0 + } + } +}) + +device_db.update({ + "urukul0_ch" + str(i): { + "type": "local", + "module": "artiq.coredevice.ad9912", + "class": "AD9912", + "arguments": { + "pll_n": 10, + "chip_select": 4 + i, + "cpld_device": "urukul0_cpld", + "sw_device": "ttl_urukul0_sw" + str(i) + } + } for i in range(4) +}) + + +device_db.update({ + "spi_urukul1": { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 33} + }, + "ttl_urukul1_sync": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLClockGen", + "arguments": {"channel": 34, "acc_width": 4} + }, + "ttl_urukul1_io_update": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 35} + }, + "urukul1_cpld": { + "type": "local", + "module": "artiq.coredevice.urukul", + "class": "CPLD", + "arguments": { + "spi_device": "spi_urukul1", + "sync_device": "ttl_urukul1_sync", + "io_update_device": "ttl_urukul1_io_update", + "refclk": 125e6, + "clk_sel": 0 + } + } +}) + +device_db.update({ + "urukul1_ch" + str(i): { + "type": "local", + "module": "artiq.coredevice.ad9910", + "class": "AD9910", + "arguments": { + "pll_n": 32, + "chip_select": 4 + i, + "cpld_device": "urukul1_cpld" + } + } for i in range(4) +}) + + +device_db.update({ + "led0": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 36} + }, + "led1": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 37} + } +}) + + +device_db.update({ + "spi_zotino0": { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 38} + }, + "ttl_zotino0_ldac": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 39} + }, + "ttl_zotino0_clr": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 40} + }, + "zotino0": { + "type": "local", + "module": "artiq.coredevice.zotino", + "class": "Zotino", + "arguments": { + "spi_device": "spi_zotino0", + "ldac_device": "ttl_zotino0_ldac", + "clr_device": "ttl_zotino0_clr" + } + } +}) diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index 60b7b0af1..61191e963 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -535,6 +535,47 @@ class PTB(_StandaloneBase): self.add_rtio(self.rtio_channels) +class PTB2(_StandaloneBase): + """PTB Kasli variant with Urukul1 SYNC and external reference clock""" + def __init__(self, hw_rev=None, **kwargs): + if hw_rev is None: + hw_rev = "v1.1" + _StandaloneBase.__init__(self, hw_rev=hw_rev, **kwargs) + + self.config["SI5324_AS_SYNTHESIZER"] = None + self.config["SI5324_EXT_REF"] = None + self.config["RTIO_FREQUENCY"] = "125.0" + if hw_rev == "v1.0": + # EEM clock fan-out from Si5324, not MMCX + self.comb += self.platform.request("clk_sel").eq(1) + + self.rtio_channels = [] + eem.DIO.add_std(self, 0, + ttl_serdes_7series.InOut_8X, ttl_serdes_7series.Output_8X) + eem.DIO.add_std(self, 1, + ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X) + eem.DIO.add_std(self, 2, + ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X) + eem.Sampler.add_std(self, 3, None, ttl_serdes_7series.Output_8X) + eem.Urukul.add_std(self, 5, 4, ttl_serdes_7series.Output_8X) + eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X, + ttl_simple.ClockGen) + + for i in (1, 2): + sfp_ctl = self.platform.request("sfp_ctl", i) + phy = ttl_simple.Output(sfp_ctl.led) + self.submodules += phy + self.rtio_channels.append(rtio.Channel.from_phy(phy)) + + eem.Zotino.add_std(self, 7, ttl_serdes_7series.Output_8X) + + self.config["HAS_RTIO_LOG"] = None + self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels) + self.rtio_channels.append(rtio.LogChannel()) + + self.add_rtio(self.rtio_channels) + + class HUB(_StandaloneBase): """HUB Kasli variant @@ -1047,7 +1088,8 @@ def main(): soc_kasli_args(parser) parser.set_defaults(output_dir="artiq_kasli") variants = {cls.__name__.lower(): cls for cls in [ - Opticlock, SUServo, SYSU, MITLL, MITLL2, USTC, Tsinghua, WIPM, NUDT, PTB, HUB, LUH, + Opticlock, SUServo, PTB, PTB2, HUB, LUH, + SYSU, MITLL, MITLL2, USTC, Tsinghua, WIPM, NUDT, VLBAIMaster, VLBAISatellite, Tester, Master, Satellite]} parser.add_argument("-V", "--variant", default="opticlock", help="variant: {} (default: %(default)s)".format( From 89fecfab505eef55a2014c042d728a7d7fcd1fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 16:16:07 +0000 Subject: [PATCH 25/29] urukul,ad9910: print speed metrics MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/test/coredevice/test_ad9910.py | 8 ++++++-- artiq/test/coredevice/test_urukul.py | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/artiq/test/coredevice/test_ad9910.py b/artiq/test/coredevice/test_ad9910.py index aeeb22e49..52d1c5be4 100644 --- a/artiq/test/coredevice/test_ad9910.py +++ b/artiq/test/coredevice/test_ad9910.py @@ -147,11 +147,15 @@ class AD9910Test(ExperimentCase): def test_set_speed(self): self.execute(AD9910Exp, "set_speed") - self.assertLess(self.dataset_mgr.get("dt"), 70*us) + dt = self.dataset_mgr.get("dt") + print(dt) + self.assertLess(dt, 70*us) def test_set_speed_mu(self): self.execute(AD9910Exp, "set_speed_mu") - self.assertLess(self.dataset_mgr.get("dt"), 10*us) + dt = self.dataset_mgr.get("dt") + print(dt) + self.assertLess(dt, 10*us) def test_sync_window(self): self.execute(AD9910Exp, "sync_window") diff --git a/artiq/test/coredevice/test_urukul.py b/artiq/test/coredevice/test_urukul.py index a5cb222ef..ef24e9a86 100644 --- a/artiq/test/coredevice/test_urukul.py +++ b/artiq/test/coredevice/test_urukul.py @@ -122,7 +122,9 @@ class UrukulTest(ExperimentCase): def test_switch_speed(self): self.execute(UrukulExp, "switch_speed") - self.assertLess(self.dataset_mgr.get("dt"), 3*us) + dt = self.dataset_mgr.get("dt") + print(dt) + self.assertLess(dt, 3*us) def test_switches_readback(self): self.execute(UrukulExp, "switches_readback") @@ -138,7 +140,9 @@ class UrukulTest(ExperimentCase): def test_att_speed(self): self.execute(UrukulExp, "att_speed") - self.assertLess(self.dataset_mgr.get("dt"), 3*us) + dt = self.dataset_mgr.get("dt") + print(dt) + self.assertLess(dt, 3*us) def test_io_update(self): self.execute(UrukulExp, "io_update") From 36c5a7cd04bf79ef2bc214fdc0173e76829c6e33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 18:54:37 +0100 Subject: [PATCH 26/29] test_urukul: relax speed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit works fine at < 3µs here but needs <5 µs on buildbot-kasli-tester Signed-off-by: Robert Jördens --- artiq/test/coredevice/test_urukul.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/test/coredevice/test_urukul.py b/artiq/test/coredevice/test_urukul.py index ef24e9a86..34b2fd128 100644 --- a/artiq/test/coredevice/test_urukul.py +++ b/artiq/test/coredevice/test_urukul.py @@ -124,7 +124,7 @@ class UrukulTest(ExperimentCase): self.execute(UrukulExp, "switch_speed") dt = self.dataset_mgr.get("dt") print(dt) - self.assertLess(dt, 3*us) + self.assertLess(dt, 5*us) def test_switches_readback(self): self.execute(UrukulExp, "switches_readback") @@ -142,7 +142,7 @@ class UrukulTest(ExperimentCase): self.execute(UrukulExp, "att_speed") dt = self.dataset_mgr.get("dt") print(dt) - self.assertLess(dt, 3*us) + self.assertLess(dt, 5*us) def test_io_update(self): self.execute(UrukulExp, "io_update") From 6d525e2f9ad1d17aadee23859bd2a8ba47899f37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 19:40:57 +0100 Subject: [PATCH 27/29] urukul/ad9910 test: remove unused import MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/test/coredevice/test_ad9910.py | 2 -- artiq/test/coredevice/test_urukul.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/artiq/test/coredevice/test_ad9910.py b/artiq/test/coredevice/test_ad9910.py index 52d1c5be4..407755df6 100644 --- a/artiq/test/coredevice/test_ad9910.py +++ b/artiq/test/coredevice/test_ad9910.py @@ -1,5 +1,3 @@ -import unittest - from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase from artiq.coredevice.ad9910 import _AD9910_REG_FTW diff --git a/artiq/test/coredevice/test_urukul.py b/artiq/test/coredevice/test_urukul.py index 34b2fd128..2141303c4 100644 --- a/artiq/test/coredevice/test_urukul.py +++ b/artiq/test/coredevice/test_urukul.py @@ -1,5 +1,3 @@ -import unittest - from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase from artiq.coredevice import urukul From 832690af9a49a485fb47bff366b15c9a13bafa27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 19:44:51 +0100 Subject: [PATCH 28/29] ad9910: flake8 [nfc] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/coredevice/ad9910.py | 47 +++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index 3977190a2..9bdfd37fb 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -2,7 +2,7 @@ from numpy import int32, int64 from artiq.language.core import ( kernel, delay, portable, delay_mu, now_mu, at_mu) -from artiq.language.units import us, ns, ms +from artiq.language.units import us, ms from artiq.coredevice import spi2 as spi from artiq.coredevice import urukul @@ -72,11 +72,12 @@ class AD9910: set this to the delay tap number returned. """ kernel_invariants = {"chip_select", "cpld", "core", "bus", - "ftw_per_hz", "pll_n", "io_update_delay", "sysclk_per_mu"} + "ftw_per_hz", "pll_n", "io_update_delay", + "sysclk_per_mu"} def __init__(self, dmgr, chip_select, cpld_device, sw_device=None, - pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1, - io_update_delay=0): + pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1, + io_update_delay=0): self.cpld = dmgr.get(cpld_device) self.core = self.cpld.core self.bus = self.cpld.bus @@ -95,7 +96,7 @@ class AD9910: assert self.sysclk_per_mu == sysclk*self.core.ref_period assert 0 <= pll_vco <= 5 vco_min, vco_max = [(370, 510), (420, 590), (500, 700), - (600, 880), (700, 950), (820, 1150)][pll_vco] + (600, 880), (700, 950), (820, 1150)][pll_vco] assert vco_min <= sysclk/1e6 <= vco_max self.pll_vco = pll_vco assert 0 <= pll_cp <= 7 @@ -106,7 +107,9 @@ class AD9910: @kernel def set_phase_mode(self, phase_mode): - """Sets the default phase mode for future calls to :meth:`set` and + """Set the default phase mode. + + for future calls to :meth:`set` and :meth:`set_mu`. Supported phase modes are: * :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged @@ -155,10 +158,10 @@ class AD9910: :param data: Data to be written """ self.bus.set_config_mu(urukul.SPI_CONFIG, 8, - urukul.SPIT_DDS_WR, self.chip_select) + urukul.SPIT_DDS_WR, self.chip_select) self.bus.write(addr << 24) self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, - urukul.SPIT_DDS_WR, self.chip_select) + urukul.SPIT_DDS_WR, self.chip_select) self.bus.write(data) @kernel @@ -168,11 +171,11 @@ class AD9910: :param addr: Register address """ self.bus.set_config_mu(urukul.SPI_CONFIG, 8, - urukul.SPIT_DDS_WR, self.chip_select) + urukul.SPIT_DDS_WR, self.chip_select) self.bus.write((addr | 0x80) << 24) - self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END - | spi.SPI_INPUT, 32, - urukul.SPIT_DDS_RD, self.chip_select) + self.bus.set_config_mu( + urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + 32, urukul.SPIT_DDS_RD, self.chip_select) self.bus.write(0) return self.bus.read() @@ -185,13 +188,13 @@ class AD9910: :param data_low: Low (LSB) 32 data bits """ self.bus.set_config_mu(urukul.SPI_CONFIG, 8, - urukul.SPIT_DDS_WR, self.chip_select) + urukul.SPIT_DDS_WR, self.chip_select) self.bus.write(addr << 24) self.bus.set_config_mu(urukul.SPI_CONFIG, 32, - urukul.SPIT_DDS_WR, self.chip_select) + urukul.SPIT_DDS_WR, self.chip_select) self.bus.write(data_high) self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, - urukul.SPIT_DDS_WR, self.chip_select) + urukul.SPIT_DDS_WR, self.chip_select) self.bus.write(data_low) @kernel @@ -305,25 +308,25 @@ class AD9910: @portable(flags={"fast-math"}) def frequency_to_ftw(self, frequency): - """Returns the frequency tuning word corresponding to the given + """Return the frequency tuning word corresponding to the given frequency. """ return int32(round(self.ftw_per_hz*frequency)) @portable(flags={"fast-math"}) def turns_to_pow(self, turns): - """Returns the phase offset word corresponding to the given phase + """Return the phase offset word corresponding to the given phase in turns.""" return int32(round(turns*0x10000)) @portable(flags={"fast-math"}) def amplitude_to_asf(self, amplitude): - """Returns amplitude scale factor corresponding to given amplitude.""" + """Return amplitude scale factor corresponding to given amplitude.""" return int32(round(amplitude*0x3ffe)) @portable(flags={"fast-math"}) def pow_to_turns(self, pow): - """Returns the phase in turns corresponding to a given phase offset + """Return the phase in turns corresponding to a given phase offset word.""" return pow/0x10000 @@ -397,7 +400,8 @@ class AD9910: @kernel def clear_smp_err(self): - """Clears the SMP_ERR flag and enables SMP_ERR validity monitoring. + """Clear the SMP_ERR flag and enables SMP_ERR validity monitoring. + Violations of the SYNC_IN sample and hold margins will result in SMP_ERR being asserted. This then also activates the red LED on the respective Urukul channel. @@ -513,7 +517,8 @@ class AD9910: d0 = self.io_update_delay t0 = int32(self.measure_io_update_alignment(d0)) for i in range(max_delay - 1): - t = self.measure_io_update_alignment((d0 + i + 1) & (max_delay - 1)) + t = self.measure_io_update_alignment( + (d0 + i + 1) & (max_delay - 1)) if t != t0: return (d0 + i + period//2) & (period - 1) raise ValueError("no IO_UPDATE-SYNC_CLK alignment edge found") From 6fb18270a210671dfaa369c5bb1f23ee1bd8d2ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Mon, 5 Nov 2018 19:45:24 +0100 Subject: [PATCH 29/29] urukul: flake8 [nfc] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Robert Jördens --- artiq/coredevice/urukul.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/coredevice/urukul.py b/artiq/coredevice/urukul.py index 803b44b00..9f96a92ed 100644 --- a/artiq/coredevice/urukul.py +++ b/artiq/coredevice/urukul.py @@ -1,4 +1,4 @@ -from artiq.language.core import kernel, delay, portable +from artiq.language.core import kernel, delay, portable, at_mu, now_mu from artiq.language.units import us, ms from numpy import int32