ttl: compensate for SED latency in input gating

Closes #1137
This commit is contained in:
Sebastien Bourdeauducq 2018-11-17 21:45:45 +08:00
parent 3ad68f65c5
commit 69e699c7bd
2 changed files with 76 additions and 5 deletions

View File

@ -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

View File

@ -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)