From 69e699c7bdd16e3df186f8d0cca3806dfb45cf39 Mon Sep 17 00:00:00 2001 From: Sebastien Bourdeauducq Date: Sat, 17 Nov 2018 21:45:45 +0800 Subject: [PATCH] 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)