diff --git a/examples/master/repository/tdr.py b/examples/master/repository/tdr.py new file mode 100644 index 000000000..cca9647e4 --- /dev/null +++ b/examples/master/repository/tdr.py @@ -0,0 +1,75 @@ +from artiq import * + + +class PulseNotReceivedError(Exception): + pass + + +class TDR(EnvExperiment): + """Time domain reflectometer. + + From ttl2 an impedance matched pulse is send onto a coax + cable with an open end. pmt0 (very short stub, high impedance) also + listens on the transmission line near ttl2. + + When the forward propagating pulse passes pmt0, the voltage is half of the + logic voltage and does not register as a rising edge. Once the + rising edge is reflected at an open end (same sign) and passes by pmt0 on + its way back to ttl2, it is detected. Analogously, hysteresis leads to + detection of the falling edge once the reflection reaches pmt0 after + one round trip time. + + This works marginally and is just a proof of principle: it relies on + hysteresis at FPGA inputs around half voltage and good impedance steps, + as well as reasonably low loss cable. It does not work well for longer + cables (>100 ns RTT). The default drive strength of 12 mA and 3.3 V would + be ~300 Ω but it seems 40 Ω series impedance at the output matches + the hysteresis of the input. + + This is also equivalent to a loopback tester or a delay measurement. + """ + def build(self): + self.attr_device("core") + self.attr_device("pmt0") + self.attr_device("ttl2") + + def run(self): + n = 1000 # repetitions + latency = 50e-9 # calibrated latency without a transmission line + pulse = 1e-6 # pulse length, larger than rtt + try: + self.many(n, seconds_to_mu(pulse, self.core)) + except PulseNotReceivedError: + print("to few edges: cable too long or wiring bad") + else: + print(self.t) + t_rise = mu_to_seconds(self.t[0], self.core)/n - latency + t_fall = mu_to_seconds(self.t[1], self.core)/n - latency - pulse + print("round trip times:") + print("rising: {:5g} ns, falling {:5g} ns".format( + t_rise/1e-9, t_fall/1e-9)) + + def rep(self, t): + self.t = t + + @kernel + def many(self, n, p): + t = [0 for i in range(2)] + self.core.break_realtime() + for i in range(n): + self.one(t, p) + self.rep(t) + + @kernel + def one(self, t, p): + with parallel: + self.pmt0.gate_both_mu(2*p) + with sequential: + t0 = now_mu() + self.ttl2.pulse_mu(p) + for i in range(len(t)): + ti = self.pmt0.timestamp_mu() + if ti <= 0: + raise PulseNotReceivedError + t[i] += ti - t0 + self.pmt0.count()