mirror of https://github.com/m-labs/artiq.git
ad9910: rewire io_delay tuning
This now reliably locates the SYNC_CLK-IO_UPDATE edge by doing two scans at different delays between start and stop IO_UPDATE. It also works well when one delay is very close to the edge. And it correctly identifies which (start or stop) pulse hit or crossed the SYNC_CLK edge. for #1143 Signed-off-by: Robert Jördens <rj@quartiq.de>
This commit is contained in:
parent
fe3d6661eb
commit
14b6b63916
|
@ -466,17 +466,18 @@ class AD9910:
|
||||||
raise ValueError("no valid window/delay")
|
raise ValueError("no valid window/delay")
|
||||||
|
|
||||||
@kernel
|
@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
|
"""Use the digital ramp generator to locate the alignment between
|
||||||
IO_UPDATE and SYNC_CLK.
|
IO_UPDATE and SYNC_CLK.
|
||||||
|
|
||||||
The ramp generator is set up to a linear frequency ramp
|
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
|
:param delay_start: Start IO_UPDATE delay in machine units.
|
||||||
edges should be chosen.
|
:param delay_stop: Stop IO_UPDATE delay in machine units.
|
||||||
|
:return: Odd/even SYNC_CLK cycle indicator.
|
||||||
:return: odd/even SYNC_CLK cycle indicator
|
|
||||||
"""
|
"""
|
||||||
# set up DRG
|
# set up DRG
|
||||||
# DRG ACC autoclear and LRR on io update
|
# DRG ACC autoclear and LRR on io update
|
||||||
|
@ -489,14 +490,16 @@ class AD9910:
|
||||||
self.write32(_AD9910_REG_DRAMPR, 0x00010000)
|
self.write32(_AD9910_REG_DRAMPR, 0x00010000)
|
||||||
# dFTW = 1, (work around negative slope)
|
# dFTW = 1, (work around negative slope)
|
||||||
self.write64(_AD9910_REG_DRAMPS, -1, 0)
|
self.write64(_AD9910_REG_DRAMPS, -1, 0)
|
||||||
at_mu(now_mu() + 0x10 & ~0xf) # align to RTIO/2
|
# delay io_update after RTIO/2 edge
|
||||||
self.cpld.io_update.pulse_mu(8)
|
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
|
# disable DRG autoclear and LRR on io_update
|
||||||
self.write32(_AD9910_REG_CFR1, 0x00000002)
|
self.write32(_AD9910_REG_CFR1, 0x00000002)
|
||||||
# stop DRG
|
# stop DRG
|
||||||
self.write64(_AD9910_REG_DRAMPS, 0, 0)
|
self.write64(_AD9910_REG_DRAMPS, 0, 0)
|
||||||
at_mu((now_mu() + 0x10 & ~0xf) + io_up_delay) # delay
|
at_mu(t + 0x1000 + delay_stop)
|
||||||
self.cpld.io_update.pulse_mu(32 - io_up_delay) # realign
|
self.cpld.io_update.pulse_mu(32 - delay_stop) # realign
|
||||||
ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW
|
ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW
|
||||||
delay(100*us) # slack
|
delay(100*us) # slack
|
||||||
# disable DRG
|
# disable DRG
|
||||||
|
@ -510,22 +513,36 @@ class AD9910:
|
||||||
|
|
||||||
Scan through increasing IO_UPDATE delays until a delay is found that
|
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
|
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
|
This method assumes that the IO_UPDATE TTLOut device has one machine
|
||||||
unit resolution (SERDES) and that the ratio between fine RTIO frequency
|
unit resolution (SERDES).
|
||||||
(RTIO time machine units) and SYNC_CLK is 4.
|
|
||||||
|
|
||||||
:return: Stable IO_UPDATE delay to be passed to the constructor
|
:return: Stable IO_UPDATE delay to be passed to the constructor
|
||||||
:class:`AD9910` via the device database.
|
:class:`AD9910` via the device database.
|
||||||
"""
|
"""
|
||||||
period = 4 # f_RTIO/f_SYNC = 4
|
period = self.sysclk_per_mu * 4 # SYNC_CLK period
|
||||||
max_delay = 8 # mu, 1 ns
|
repeat = 100
|
||||||
d0 = self.io_update_delay
|
for i in range(period):
|
||||||
t0 = int32(self.measure_io_update_alignment(d0))
|
t = 0
|
||||||
for i in range(max_delay - 1):
|
# check whether the sync edge is strictly between i, i+2
|
||||||
t = self.measure_io_update_alignment(
|
for j in range(repeat):
|
||||||
(d0 + i + 1) & (max_delay - 1))
|
t += self.measure_io_update_alignment(i, i + 2)
|
||||||
if t != t0:
|
if t != 0: # no certain edge
|
||||||
return (d0 + i + period//2) & (period - 1)
|
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")
|
raise ValueError("no IO_UPDATE-SYNC_CLK alignment edge found")
|
||||||
|
|
|
@ -104,18 +104,21 @@ class AD9910Exp(EnvExperiment):
|
||||||
self.core.break_realtime()
|
self.core.break_realtime()
|
||||||
self.dev.cpld.init()
|
self.dev.cpld.init()
|
||||||
self.dev.init()
|
self.dev.init()
|
||||||
bins = [0]*8
|
bins1 = [0]*4
|
||||||
self.scan_io_delay(bins)
|
bins2 = [0]*4
|
||||||
self.set_dataset("bins", bins)
|
self.scan_io_delay(bins1, bins2)
|
||||||
self.set_dataset("dly", self.dev.io_update_delay)
|
self.set_dataset("bins1", bins1)
|
||||||
|
self.set_dataset("bins2", bins2)
|
||||||
|
self.set_dataset("dly", self.dev.tune_io_update_delay())
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def scan_io_delay(self, bins):
|
def scan_io_delay(self, bins1, bins2):
|
||||||
delay(100*us)
|
delay(100*us)
|
||||||
n = 100
|
n = 100
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
for phase in range(len(bins)):
|
for j in range(len(bins1)):
|
||||||
bins[phase] += self.dev.measure_io_update_alignment(phase)
|
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)
|
delay(10*ms)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
|
@ -174,12 +177,14 @@ class AD9910Test(ExperimentCase):
|
||||||
def test_io_update_delay(self):
|
def test_io_update_delay(self):
|
||||||
self.execute(AD9910Exp, "io_update_delay")
|
self.execute(AD9910Exp, "io_update_delay")
|
||||||
dly = self.dataset_mgr.get("dly")
|
dly = self.dataset_mgr.get("dly")
|
||||||
bins = self.dataset_mgr.get("bins")
|
bins1 = self.dataset_mgr.get("bins1")
|
||||||
print(dly, bins)
|
bins2 = self.dataset_mgr.get("bins2")
|
||||||
n = max(bins)
|
print(dly, bins1, bins2)
|
||||||
# test for 4-periodicity (SYNC_CLK) and maximal contrast
|
n = max(bins2)
|
||||||
for i in range(len(bins)):
|
# no edge at optimal delay
|
||||||
self.assertEqual(abs(bins[i] - bins[(i + 4) % 8]), n)
|
self.assertEqual(bins2[(dly + 1) & 3], 0)
|
||||||
|
# edge at expected position
|
||||||
|
self.assertEqual(bins2[(dly + 3) & 3], n)
|
||||||
|
|
||||||
def test_sw_readback(self):
|
def test_sw_readback(self):
|
||||||
self.execute(AD9910Exp, "sw_readback")
|
self.execute(AD9910Exp, "sw_readback")
|
||||||
|
|
Loading…
Reference in New Issue