diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index d0538f5be..a37f48705 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -466,17 +466,18 @@ class AD9910: raise ValueError("no valid window/delay") @kernel - def measure_io_update_alignment(self, io_up_delay): + def measure_io_update_alignment(self, delay_start, delay_stop): """Use the digital ramp generator to locate the alignment between IO_UPDATE and SYNC_CLK. The ramp generator is set up to a linear frequency ramp - (dFTW/t_SYNC_CLK=1) and started at a RTIO time stamp. + (dFTW/t_SYNC_CLK=1) and started at a coarse RTIO time stamp plus + `delay_start` and stopped at a coarse RTIO time stamp plus + `delay_stop`. - After scanning the alignment, an IO_UPDATE delay midway between two - edges should be chosen. - - :return: odd/even SYNC_CLK cycle indicator + :param delay_start: Start IO_UPDATE delay in machine units. + :param delay_stop: Stop IO_UPDATE delay in machine units. + :return: Odd/even SYNC_CLK cycle indicator. """ # set up DRG # DRG ACC autoclear and LRR on io update @@ -489,14 +490,16 @@ class AD9910: self.write32(_AD9910_REG_DRAMPR, 0x00010000) # dFTW = 1, (work around negative slope) self.write64(_AD9910_REG_DRAMPS, -1, 0) - at_mu(now_mu() + 0x10 & ~0xf) # align to RTIO/2 - self.cpld.io_update.pulse_mu(8) + # delay io_update after RTIO/2 edge + t = now_mu() + 0x10 & ~0xf + at_mu(t + delay_start) + self.cpld.io_update.pulse_mu(32 - delay_start) # realign # disable DRG autoclear and LRR on io_update self.write32(_AD9910_REG_CFR1, 0x00000002) # stop DRG self.write64(_AD9910_REG_DRAMPS, 0, 0) - at_mu((now_mu() + 0x10 & ~0xf) + io_up_delay) # delay - self.cpld.io_update.pulse_mu(32 - io_up_delay) # realign + at_mu(t + 0x1000 + delay_stop) + self.cpld.io_update.pulse_mu(32 - delay_stop) # realign ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW delay(100*us) # slack # disable DRG @@ -510,22 +513,36 @@ class AD9910: Scan through increasing IO_UPDATE delays until a delay is found that lets IO_UPDATE be registered in the next SYNC_CLK cycle. Return a - IO_UPDATE delay that is midway between two such SYNC_CLK transitions. + IO_UPDATE delay that is as far away from that SYNC_CLK edge + as possible. This method assumes that the IO_UPDATE TTLOut device has one machine - unit resolution (SERDES) and that the ratio between fine RTIO frequency - (RTIO time machine units) and SYNC_CLK is 4. + unit resolution (SERDES). :return: Stable IO_UPDATE delay to be passed to the constructor :class:`AD9910` via the device database. """ - period = 4 # f_RTIO/f_SYNC = 4 - max_delay = 8 # mu, 1 ns - d0 = self.io_update_delay - t0 = int32(self.measure_io_update_alignment(d0)) - for i in range(max_delay - 1): - t = self.measure_io_update_alignment( - (d0 + i + 1) & (max_delay - 1)) - if t != t0: - return (d0 + i + period//2) & (period - 1) + period = self.sysclk_per_mu * 4 # SYNC_CLK period + repeat = 100 + for i in range(period): + t = 0 + # check whether the sync edge is strictly between i, i+2 + for j in range(repeat): + t += self.measure_io_update_alignment(i, i + 2) + if t != 0: # no certain edge + continue + # check left/right half: i,i+1 and i+1,i+2 + t1 = [0, 0] + for j in range(repeat): + t1[0] += self.measure_io_update_alignment(i, i + 1) + t1[1] += self.measure_io_update_alignment(i + 1, i + 2) + if ((t1[0] == 0 and t1[1] == repeat) or # edge left of i + 1 + (t1[0] == repeat and t1[1] == 0) or # edge right of i + 1 + (t1[0] != 0 and t1[1] != 0 and # edge very close to i + 1 + t1[0] != repeat and t1[1] != repeat)): + # the good delay is period//2 after the edge + return (i + 1 + period//2) & (period - 1) + else: # can't interpret result + raise ValueError( + "no clear IO_UPDATE-SYNC_CLK alignment edge found") raise ValueError("no IO_UPDATE-SYNC_CLK alignment edge found") diff --git a/artiq/test/coredevice/test_ad9910.py b/artiq/test/coredevice/test_ad9910.py index 4ce47fdee..93e77c0b4 100644 --- a/artiq/test/coredevice/test_ad9910.py +++ b/artiq/test/coredevice/test_ad9910.py @@ -104,18 +104,21 @@ class AD9910Exp(EnvExperiment): self.core.break_realtime() self.dev.cpld.init() self.dev.init() - bins = [0]*8 - self.scan_io_delay(bins) - self.set_dataset("bins", bins) - self.set_dataset("dly", self.dev.io_update_delay) + bins1 = [0]*4 + bins2 = [0]*4 + self.scan_io_delay(bins1, bins2) + self.set_dataset("bins1", bins1) + self.set_dataset("bins2", bins2) + self.set_dataset("dly", self.dev.tune_io_update_delay()) @kernel - def scan_io_delay(self, bins): + def scan_io_delay(self, bins1, bins2): delay(100*us) n = 100 for i in range(n): - for phase in range(len(bins)): - bins[phase] += self.dev.measure_io_update_alignment(phase) + for j in range(len(bins1)): + bins1[j] += self.dev.measure_io_update_alignment(j, j + 1) + bins2[j] += self.dev.measure_io_update_alignment(j, j + 2) delay(10*ms) @kernel @@ -174,12 +177,14 @@ class AD9910Test(ExperimentCase): def test_io_update_delay(self): self.execute(AD9910Exp, "io_update_delay") dly = self.dataset_mgr.get("dly") - bins = self.dataset_mgr.get("bins") - print(dly, bins) - n = max(bins) - # test for 4-periodicity (SYNC_CLK) and maximal contrast - for i in range(len(bins)): - self.assertEqual(abs(bins[i] - bins[(i + 4) % 8]), n) + bins1 = self.dataset_mgr.get("bins1") + bins2 = self.dataset_mgr.get("bins2") + print(dly, bins1, bins2) + n = max(bins2) + # no edge at optimal delay + self.assertEqual(bins2[(dly + 1) & 3], 0) + # edge at expected position + self.assertEqual(bins2[(dly + 3) & 3], n) def test_sw_readback(self): self.execute(AD9910Exp, "sw_readback")