From d1eee7c0ea4d8e19404dbfa5dacd1ee4272b28b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 16 Nov 2018 13:17:43 +0000 Subject: [PATCH 1/6] ad9910: ensure sync is driven when required MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit close #1194 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 ae5842808..5d171ac55 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -101,6 +101,8 @@ class AD9910: self.pll_vco = pll_vco assert 0 <= pll_cp <= 7 self.pll_cp = pll_cp + if sync_delay_seed >= 0 and not self.cpld.sync_div: + raise ValueError("parent cpld does not drive SYNC") self.sync_delay_seed = sync_delay_seed self.io_update_delay = io_update_delay self.phase_mode = PHASE_MODE_CONTINUOUS @@ -430,6 +432,8 @@ class AD9910: Defaults to 15 (half range). :return: Tuple of optimal delay and window size. """ + if not self.cpld.sync_div: + raise ValueError("parent cpld does not drive SYNC") search_span = 31 # FIXME https://github.com/sinara-hw/Urukul/issues/16 # should both be 2-4 once kasli sync_in jitter is identified From 3ad68f65c51348c834bf094058edd41c3d030ea4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 16 Nov 2018 14:56:26 +0000 Subject: [PATCH 2/6] urukul: make get_att_mu() not alter state 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 | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/artiq/coredevice/urukul.py b/artiq/coredevice/urukul.py index 8bb514c09..52221a018 100644 --- a/artiq/coredevice/urukul.py +++ b/artiq/coredevice/urukul.py @@ -309,15 +309,17 @@ class CPLD: def get_att_mu(self): """Return the digital step attenuator settings in machine units. - This method will also (as a side effect) write the attenuator - settings of all four channels. - :return: 32 bit attenuator settings """ - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 32, + self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 32, SPIT_ATT_RD, CS_ATT) - self.bus.write(self.att_reg) - return self.bus.read() + self.bus.write(0) # shift in zeros, shift out current value + self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32, + SPIT_ATT_WR, CS_ATT) + delay(10*us) + self.att_reg = self.bus.read() + self.bus.write(self.att_reg) # shift in current value again and latch + return self.att_reg @kernel def set_sync_div(self, div): From 69e699c7bdd16e3df186f8d0cca3806dfb45cf39 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 17 Nov 2018 21:45:45 +0800 Subject: [PATCH 3/6] ttl: compensate for SED latency in input gating Closes #1137 --- artiq/coredevice/ttl.py | 18 ++++++--- artiq/test/coredevice/test_rtio.py | 63 ++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 5 deletions(-) diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index f7bf48bb9..3a56ea74f 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -106,11 +106,19 @@ class TTLInOut: :param channel: channel number """ - kernel_invariants = {"core", "channel"} + kernel_invariants = {"core", "channel", "gate_latency_mu"} - def __init__(self, dmgr, channel, core_device="core"): + def __init__(self, dmgr, channel, gate_latency_mu=None, + core_device="core"): self.core = dmgr.get(core_device) self.channel = channel + # With TTLs inputs, the gate control is connected to a high-latency + # path through SED. When looking at the RTIO counter to determine if + # the gate has closed, we need to take this latency into account. + # See: https://github.com/m-labs/artiq/issues/1137 + if gate_latency_mu is None: + gate_latency_mu = 13*self.core.ref_multiplier + self.gate_latency_mu = gate_latency_mu @kernel def set_oe(self, oe): @@ -323,7 +331,7 @@ class TTLInOut: ttl_input.count(ttl_input.gate_rising(100 * us)) """ count = 0 - while rtio_input_timestamp(up_to_timestamp_mu, self.channel) >= 0: + while rtio_input_timestamp(up_to_timestamp_mu + self.gate_latency_mu, self.channel) >= 0: count += 1 return count @@ -346,7 +354,7 @@ class TTLInOut: :return: The timestamp (in machine units) of the first event received; -1 on timeout. """ - return rtio_input_timestamp(up_to_timestamp_mu, self.channel) + return rtio_input_timestamp(up_to_timestamp_mu + self.gate_latency_mu, self.channel) # Input API: sampling @kernel @@ -414,7 +422,7 @@ class TTLInOut: rtio_output(now_mu(), self.channel, 2, 0) success = True try: - while rtio_input_timestamp(now_mu(), self.channel) != -1: + while rtio_input_timestamp(now_mu() + self.gate_latency_mu, self.channel) != -1: success = False except RTIOOverflow: success = False diff --git a/artiq/test/coredevice/test_rtio.py b/artiq/test/coredevice/test_rtio.py index 12f167c1b..b9c7539de 100644 --- a/artiq/test/coredevice/test_rtio.py +++ b/artiq/test/coredevice/test_rtio.py @@ -206,6 +206,66 @@ class LoopbackCount(EnvExperiment): self.set_dataset("count", self.loop_in.count(now_mu())) +class IncorrectPulseTiming(Exception): + pass + + +class LoopbackGateTiming(EnvExperiment): + def build(self): + self.setattr_device("core") + self.setattr_device("loop_in") + self.setattr_device("loop_out") + + @kernel + def run(self): + # Make sure there are no leftover events. + self.core.reset() + self.loop_in.input() + self.loop_out.output() + delay_mu(500) + self.loop_out.off() + delay_mu(5000) + + # Determine loop delay. + with parallel: + self.loop_in.gate_rising_mu(10000) + with sequential: + delay_mu(5000) + out_mu = now_mu() + self.loop_out.pulse_mu(1000) + in_mu = self.loop_in.timestamp_mu(now_mu()) + if in_mu < 0: + raise PulseNotReceived("Cannot determine loop delay") + loop_delay_mu = in_mu - out_mu + + # With the exact delay known, make sure tight gate timings work. + # In the most common configuration, 24 mu == 24 ns == 3 coarse periods, + # which should be plenty of slack. + delay_mu(10000) + + gate_start_mu = now_mu() + self.loop_in.gate_both_mu(24) + gate_end_mu = now_mu() + + # gateware latency offset between gate and input + lat_offset = 12*8 + out_mu = gate_start_mu - loop_delay_mu + lat_offset + at_mu(out_mu) + self.loop_out.pulse_mu(24) + + in_mu = self.loop_in.timestamp_mu(gate_end_mu) + if in_mu < 0: + raise PulseNotReceived() + if not (gate_start_mu <= (in_mu - lat_offset) <= gate_end_mu): + raise IncorrectPulseTiming("Input event should occur during gate") + if not (-2 < (in_mu - out_mu - loop_delay_mu) < 2): + raise IncorrectPulseTiming("Loop delay should not change") + + in_mu = self.loop_in.timestamp_mu(gate_end_mu) + if in_mu > 0: + raise IncorrectPulseTiming("Only one pulse should be received") + + class IncorrectLevel(Exception): pass @@ -430,6 +490,9 @@ class CoredeviceTest(ExperimentCase): count = self.dataset_mgr.get("count") self.assertEqual(count, npulses) + def test_loopback_gate_timing(self): + self.execute(LoopbackGateTiming) + def test_level(self): self.execute(Level) From 78d4b3a7da3c7c14283c6ba37b18df251b1d9e89 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 17 Nov 2018 21:47:32 +0800 Subject: [PATCH 4/6] gateware/targets: expose variant lists This allows writing scripts that build all variants. --- artiq/gateware/targets/kasli.py | 14 ++++++++------ artiq/gateware/targets/kc705.py | 13 ++++++------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index e232e2296..cee924fdf 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -1123,24 +1123,26 @@ class VLBAISatellite(_SatelliteBase): self.add_rtio(self.rtio_channels) +VARIANTS = {cls.__name__.lower(): cls for cls in [ + Opticlock, SUServo, PTB, PTB2, HUB, LUH, + SYSU, MITLL, MITLL2, USTC, Tsinghua, Tsinghua2, WIPM, NUDT, + VLBAIMaster, VLBAISatellite, Tester, Master, Satellite]} + + def main(): parser = argparse.ArgumentParser( description="ARTIQ device binary builder for Kasli systems") builder_args(parser) soc_kasli_args(parser) parser.set_defaults(output_dir="artiq_kasli") - variants = {cls.__name__.lower(): cls for cls in [ - Opticlock, SUServo, PTB, PTB2, HUB, LUH, - SYSU, MITLL, MITLL2, USTC, Tsinghua, Tsinghua2, WIPM, NUDT, - VLBAIMaster, VLBAISatellite, Tester, Master, Satellite]} parser.add_argument("-V", "--variant", default="opticlock", help="variant: {} (default: %(default)s)".format( - "/".join(sorted(variants.keys())))) + "/".join(sorted(VARIANTS.keys())))) args = parser.parse_args() variant = args.variant.lower() try: - cls = variants[variant] + cls = VARIANTS[variant] except KeyError: raise SystemExit("Invalid variant (-V/--variant)") diff --git a/artiq/gateware/targets/kc705.py b/artiq/gateware/targets/kc705.py index eb32aedb2..2b9975ec8 100755 --- a/artiq/gateware/targets/kc705.py +++ b/artiq/gateware/targets/kc705.py @@ -400,6 +400,9 @@ class SMA_SPI(_StandaloneBase): self.csr_devices.append("rtio_analyzer") +VARIANTS = {cls.__name__.lower(): cls for cls in [NIST_CLOCK, NIST_QC2, SMA_SPI]} + + def main(): parser = argparse.ArgumentParser( description="KC705 gateware and firmware builder") @@ -413,13 +416,9 @@ def main(): args = parser.parse_args() variant = args.variant.lower() - if variant == "nist_clock": - cls = NIST_CLOCK - elif variant == "nist_qc2": - cls = NIST_QC2 - elif variant == "sma_spi": - cls = SMA_SPI - else: + try: + cls = VARIANTS[variant] + except KeyError: raise SystemExit("Invalid variant (-V/--variant)") soc = cls(**soc_kc705_argdict(args)) From a3e0b1c5b4e325d7040a55608603aab9a670e534 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 17 Nov 2018 22:00:49 +0800 Subject: [PATCH 5/6] ad9914,spi2: add warnings about driver state and DMA. Closes #1113 --- artiq/coredevice/ad9914.py | 9 +++++++++ artiq/coredevice/spi2.py | 6 ++++++ 2 files changed, 15 insertions(+) diff --git a/artiq/coredevice/ad9914.py b/artiq/coredevice/ad9914.py index bd2ec4db0..d37efec8b 100644 --- a/artiq/coredevice/ad9914.py +++ b/artiq/coredevice/ad9914.py @@ -175,6 +175,10 @@ class AD9914: accumulator is set to the value it would have if the DDS had been running at the specified frequency since the start of the experiment. + + .. 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 @@ -190,6 +194,11 @@ class AD9914: The "frequency update" pulse is sent to the DDS with a fixed latency with respect to the current position of the time cursor. + When switching from other phase modes to the continuous phase mode, + there is no jump in the DDS phase. This is however not true when + using the continuous phase mode after playing back a DMA sequence + that contained the other phase modes. + :param ftw: frequency to generate. :param pow: adds an offset to the phase. :param phase_mode: if specified, overrides the default phase mode set diff --git a/artiq/coredevice/spi2.py b/artiq/coredevice/spi2.py index 205251084..70210841e 100644 --- a/artiq/coredevice/spi2.py +++ b/artiq/coredevice/spi2.py @@ -186,6 +186,12 @@ class SPIMaster: This method is portable and can also be called from e.g. :meth:`__init__`. + .. warning:: If this method is called while recording a DMA + sequence, the playback of the sequence will not update the + driver state. + When required, update the driver state manually (by calling + this method) after playing back a DMA sequence. + :param div: SPI clock divider (see: :meth:`set_config_mu`) :param length: SPI transfer length (see: :meth:`set_config_mu`) """ From b5cdb1c1e0bff04f5918d77c0ac9b343984d38c8 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sun, 18 Nov 2018 22:32:17 +0800 Subject: [PATCH 6/6] try to work around conda problem