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 diff --git a/artiq/coredevice/ad9914.py b/artiq/coredevice/ad9914.py index aceb79f17..c2a46c086 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 50487ba34..d6024b607 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`) """ diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index 59002f12d..4c281904a 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -107,12 +107,22 @@ class TTLInOut: :param channel: channel number """ - kernel_invariants = {"core", "channel", + kernel_invariants = {"core", "channel", "gate_latency_mu", "target_o", "target_oe", "target_sens", "target_sample"} - 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 + self.target_o = (channel << 8) + 0 self.target_oe = (channel << 8) + 1 self.target_sens = (channel << 8) + 2 @@ -329,7 +339,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 @@ -352,7 +362,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 @@ -420,7 +430,7 @@ class TTLInOut: rtio_output(self.target_sens, 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/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): diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index 72d100a83..2d7f872d0 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -1186,24 +1186,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 24a11cf96..d2e2ca0c3 100755 --- a/artiq/gateware/targets/kc705.py +++ b/artiq/gateware/targets/kc705.py @@ -402,6 +402,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") @@ -415,13 +418,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)) diff --git a/artiq/test/coredevice/test_rtio.py b/artiq/test/coredevice/test_rtio.py index ebc28a19f..9dd2e6197 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)