Skip to content

Commit fc64ad8

Browse files
committed
SUServo: Convert io_update to SERDES and connect sync_in to ClockGen.
This is prerequisite for phase coherent operation of SUServo. The I/O update alignment delays can be configured by writing to servo memory. The optimal io_update alignment delay and sync_in delay can be found using the existing AD9910 (overloaded) methods, relying on half-duplex CPLD readback capability.
1 parent bd849c4 commit fc64ad8

File tree

8 files changed

+258
-34
lines changed

8 files changed

+258
-34
lines changed

artiq/coredevice/ad9910.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,8 @@ def clear_smp_err(self):
985985

986986
@kernel
987987
def tune_sync_delay(self,
988-
search_seed: TInt32 = 15) -> TTuple([TInt32, TInt32]):
988+
search_seed: TInt32 = 15,
989+
cpld_channel_idx: TInt32 = -1) -> TTuple([TInt32, TInt32]):
989990
"""Find a stable SYNC_IN delay.
990991
991992
This method first locates a valid SYNC_IN delay at zero validation
@@ -1001,6 +1002,9 @@ def tune_sync_delay(self,
10011002
Defaults to 15 (half range).
10021003
:return: Tuple of optimal delay and window size.
10031004
"""
1005+
if cpld_channel_idx == -1:
1006+
cpld_channel_idx = self.chip_select - 4
1007+
assert 0 <= cpld_channel_idx < 4, "Invalid channel index"
10041008
if not self.cpld.sync_div:
10051009
raise ValueError("parent cpld does not drive SYNC")
10061010
search_span = 31
@@ -1023,7 +1027,7 @@ def tune_sync_delay(self,
10231027
delay(100 * us)
10241028
err = urukul_sta_smp_err(self.cpld.sta_read())
10251029
delay(100 * us) # slack
1026-
if not (err >> (self.chip_select - 4)) & 1:
1030+
if not (err >> cpld_channel_idx) & 1:
10271031
next_seed = in_delay
10281032
break
10291033
if next_seed >= 0: # valid delay found, scan next window

artiq/coredevice/suservo.py

+86
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
T_CYCLE = (2*(8 + 64) + 2)*8*ns # Must match gateware Servo.t_cycle.
1212
COEFF_SHIFT = 11 # Must match gateware IIRWidths.shift
1313
PROFILE_WIDTH = 5 # Must match gateware IIRWidths.profile
14+
FINE_TS_WIDTH = 3 # Must match gateware IIRWidths.ioup_dly
1415

1516

1617
@portable
@@ -86,6 +87,7 @@ def __init__(self, dmgr, channel, pgia_device,
8687
self.num_channels = 4 * len(dds_devices)
8788
channel_width = ceil(log2(self.num_channels))
8889
coeff_depth = PROFILE_WIDTH + channel_width + 3
90+
self.io_dly_addr = 1 << (coeff_depth - 2)
8991
self.state_sel = 2 << (coeff_depth - 2)
9092
self.config_addr = 3 << (coeff_depth - 2)
9193
self.coeff_sel = 1 << coeff_depth
@@ -119,8 +121,20 @@ def init(self):
119121
prev_cpld_cfg = cpld.cfg_reg
120122
cpld.cfg_write(prev_cpld_cfg | (0xf << urukul.CFG_MASK_NU))
121123
dds.init(blind=True)
124+
125+
if dds.sync_data.sync_delay_seed != -1:
126+
for channel_idx in range(4):
127+
mask_nu_this = 1 << (urukul.CFG_MASK_NU + channel_idx)
128+
cpld.cfg_write(prev_cpld_cfg | mask_nu_this)
129+
delay(8 * us)
130+
dds.tune_sync_delay(dds.sync_data.sync_delay_seed,
131+
cpld_channel_idx=channel_idx)
132+
delay(50 * us)
122133
cpld.cfg_write(prev_cpld_cfg)
123134

135+
self.set_io_update_delays(
136+
[dds.sync_data.io_update_delay for dds in self.ddses])
137+
124138
@kernel
125139
def write(self, addr, value):
126140
"""Write to servo memory.
@@ -245,6 +259,18 @@ def get_adc(self, channel):
245259
gain = (self.gains >> (channel*2)) & 0b11
246260
return adc_mu_to_volts(val, gain)
247261

262+
@kernel
263+
def set_io_update_delays(self, dlys):
264+
"""Set IO_UPDATE pulse alignment delays.
265+
266+
:param dlys: List of delays for each Urukul
267+
"""
268+
bits = 0
269+
mask_fine_ts = (1 << FINE_TS_WIDTH) - 1
270+
for i in range(len(dlys)):
271+
bits |= (dlys[i] & mask_fine_ts) << (FINE_TS_WIDTH * i)
272+
self.write(self.io_dly_addr, bits)
273+
248274

249275
class Channel:
250276
"""Sampler-Urukul Servo channel
@@ -601,6 +627,16 @@ class CPLD(urukul.CPLD):
601627
back as the read-back buffer on the CPLD is 8 bits wide.
602628
"""
603629

630+
def __init__(self, dmgr, spi_device, io_update_device=None,
631+
**kwargs):
632+
# Separate IO_UPDATE TTL output device used by SUServo core,
633+
# if active, else by artiq.coredevice.suservo.AD9910
634+
# :meth:`measure_io_update_alignment`.
635+
# The urukul.CPLD driver utilises the CPLD CFG register
636+
# option instead for pulsing IO_UPDATE of masked DDSs.
637+
self.io_update_ttl = dmgr.get(io_update_device)
638+
urukul.CPLD.__init__(self, dmgr, spi_device, **kwargs)
639+
604640
@kernel
605641
def enable_readback(self):
606642
"""
@@ -746,3 +782,53 @@ def read64(self, addr):
746782
def read_ram(self, data):
747783
# 3-wire SPI transactions consisting of multiple transfers are not supported.
748784
raise NotImplementedError
785+
786+
@kernel
787+
def measure_io_update_alignment(self, delay_start, delay_stop):
788+
"""Use the digital ramp generator to locate the alignment between
789+
IO_UPDATE and SYNC_CLK.
790+
791+
Refer to `artiq.coredevice.ad9910` :meth:`measure_io_update_alignment`.
792+
In order that this method can operate the io_update_ttl also used by the SUServo
793+
core, deactivate the servo before (see :meth:`set_config`).
794+
"""
795+
# set up DRG
796+
self.set_cfr1(drg_load_lrr=1, drg_autoclear=1)
797+
# DRG -> FTW, DRG enable
798+
self.write32(ad9910._AD9910_REG_CFR2, 0x01090000)
799+
# no limits
800+
self.write64(ad9910._AD9910_REG_RAMP_LIMIT, -1, 0)
801+
# DRCTL=0, dt=1 t_SYNC_CLK
802+
self.write32(ad9910._AD9910_REG_RAMP_RATE, 0x00010000)
803+
# dFTW = 1, (work around negative slope)
804+
self.write64(ad9910._AD9910_REG_RAMP_STEP, -1, 0)
805+
# un-mask DDS
806+
cfg_masked = self.cpld.cfg_reg
807+
self.cpld.cfg_write(cfg_masked & ~(0xf << urukul.CFG_MASK_NU))
808+
delay(70 * us) # slack
809+
# delay io_update after RTIO edge
810+
t = now_mu() + 8 & ~7
811+
at_mu(t + delay_start)
812+
# assumes a maximum t_SYNC_CLK period
813+
self.cpld.io_update_ttl.pulse(self.core.mu_to_seconds(16 - delay_start)) # realign
814+
# re-mask DDS
815+
self.cpld.cfg_write(cfg_masked)
816+
delay(10 * us) # slack
817+
# disable DRG autoclear and LRR on io_update
818+
self.set_cfr1()
819+
delay(10 * us) # slack
820+
# stop DRG
821+
self.write64(ad9910._AD9910_REG_RAMP_STEP, 0, 0)
822+
delay(10 * us) # slack
823+
# un-mask DDS
824+
self.cpld.cfg_write(cfg_masked & ~(0xf << urukul.CFG_MASK_NU))
825+
at_mu(t + 0x20000 + delay_stop)
826+
self.cpld.io_update_ttl.pulse(self.core.mu_to_seconds(16 - delay_stop)) # realign
827+
# re-mask DDS
828+
self.cpld.cfg_write(cfg_masked)
829+
ftw = self.read32(ad9910._AD9910_REG_FTW) # read out effective FTW
830+
delay(100*us) # slack
831+
# disable DRG
832+
self.write32(ad9910._AD9910_REG_CFR2, 0x01010000)
833+
self.cpld.io_update.pulse(16 * ns)
834+
return ftw & 1

artiq/examples/kasli_suservo/device_db.py

+47-16
Original file line numberDiff line numberDiff line change
@@ -142,61 +142,74 @@
142142
"arguments": {"channel": 15},
143143
},
144144

145+
"ttl_urukul0_io_update": {
146+
"type": "local",
147+
"module": "artiq.coredevice.ttl",
148+
"class": "TTLOut",
149+
"arguments": {"channel": 16}
150+
},
151+
"ttl_urukul1_io_update": {
152+
"type": "local",
153+
"module": "artiq.coredevice.ttl",
154+
"class": "TTLOut",
155+
"arguments": {"channel": 17}
156+
},
157+
145158
"suservo0_ch0": {
146159
"type": "local",
147160
"module": "artiq.coredevice.suservo",
148161
"class": "Channel",
149-
"arguments": {"channel": 16, "servo_device": "suservo0"}
162+
"arguments": {"channel": 18, "servo_device": "suservo0"}
150163
},
151164
"suservo0_ch1": {
152165
"type": "local",
153166
"module": "artiq.coredevice.suservo",
154167
"class": "Channel",
155-
"arguments": {"channel": 17, "servo_device": "suservo0"}
168+
"arguments": {"channel": 19, "servo_device": "suservo0"}
156169
},
157170
"suservo0_ch2": {
158171
"type": "local",
159172
"module": "artiq.coredevice.suservo",
160173
"class": "Channel",
161-
"arguments": {"channel": 18, "servo_device": "suservo0"}
174+
"arguments": {"channel": 20, "servo_device": "suservo0"}
162175
},
163176
"suservo0_ch3": {
164177
"type": "local",
165178
"module": "artiq.coredevice.suservo",
166179
"class": "Channel",
167-
"arguments": {"channel": 19, "servo_device": "suservo0"}
180+
"arguments": {"channel": 21, "servo_device": "suservo0"}
168181
},
169182
"suservo0_ch4": {
170183
"type": "local",
171184
"module": "artiq.coredevice.suservo",
172185
"class": "Channel",
173-
"arguments": {"channel": 20, "servo_device": "suservo0"}
186+
"arguments": {"channel": 22, "servo_device": "suservo0"}
174187
},
175188
"suservo0_ch5": {
176189
"type": "local",
177190
"module": "artiq.coredevice.suservo",
178191
"class": "Channel",
179-
"arguments": {"channel": 21, "servo_device": "suservo0"}
192+
"arguments": {"channel": 23, "servo_device": "suservo0"}
180193
},
181194
"suservo0_ch6": {
182195
"type": "local",
183196
"module": "artiq.coredevice.suservo",
184197
"class": "Channel",
185-
"arguments": {"channel": 22, "servo_device": "suservo0"}
198+
"arguments": {"channel": 24, "servo_device": "suservo0"}
186199
},
187200
"suservo0_ch7": {
188201
"type": "local",
189202
"module": "artiq.coredevice.suservo",
190203
"class": "Channel",
191-
"arguments": {"channel": 23, "servo_device": "suservo0"}
204+
"arguments": {"channel": 25, "servo_device": "suservo0"}
192205
},
193206

194207
"suservo0": {
195208
"type": "local",
196209
"module": "artiq.coredevice.suservo",
197210
"class": "SUServo",
198211
"arguments": {
199-
"channel": 24,
212+
"channel": 26,
200213
"pgia_device": "spi_sampler0_pgia",
201214
"cpld_devices": ["urukul0_cpld", "urukul1_cpld"],
202215
"dds_devices": ["urukul0_dds", "urukul1_dds"],
@@ -207,60 +220,78 @@
207220
"type": "local",
208221
"module": "artiq.coredevice.spi2",
209222
"class": "SPIMaster",
210-
"arguments": {"channel": 25}
223+
"arguments": {"channel": 27}
211224
},
212225

213226
"spi_urukul0": {
214227
"type": "local",
215228
"module": "artiq.coredevice.spi2",
216229
"class": "SPIMaster",
217-
"arguments": {"channel": 26}
230+
"arguments": {"channel": 28}
218231
},
219232
"urukul0_cpld": {
220233
"type": "local",
221-
"module": "artiq.coredevice.urukul",
234+
"module": "artiq.coredevice.suservo",
222235
"class": "CPLD",
223236
"arguments": {
224237
"spi_device": "spi_urukul0",
238+
"io_update_device": "ttl_urukul0_io_update",
239+
"sync_device": "clkgen_dds_sync_in",
225240
"refclk": 100e6,
226241
"clk_sel": 0
227242
}
228243
},
229244
"urukul0_dds": {
230245
"type": "local",
231-
"module": "artiq.coredevice.ad9910",
246+
"module": "artiq.coredevice.suservo",
232247
"class": "AD9910",
233248
"arguments": {
234249
"pll_n": 40,
235250
"chip_select": 3,
236251
"cpld_device": "urukul0_cpld",
252+
"io_update_delay": 0,
253+
"sync_delay_seed": -1,
237254
}
238255
},
239256

240257
"spi_urukul1": {
241258
"type": "local",
242259
"module": "artiq.coredevice.spi2",
243260
"class": "SPIMaster",
244-
"arguments": {"channel": 27}
261+
"arguments": {"channel": 29}
245262
},
246263
"urukul1_cpld": {
247264
"type": "local",
248-
"module": "artiq.coredevice.urukul",
265+
"module": "artiq.coredevice.suservo",
249266
"class": "CPLD",
250267
"arguments": {
251268
"spi_device": "spi_urukul1",
269+
"io_update_device": "ttl_urukul1_io_update",
270+
"sync_device": "clkgen_dds_sync_in",
252271
"refclk": 100e6,
253272
"clk_sel": 0
254273
}
255274
},
256275
"urukul1_dds": {
257276
"type": "local",
258-
"module": "artiq.coredevice.ad9910",
277+
"module": "artiq.coredevice.suservo",
259278
"class": "AD9910",
260279
"arguments": {
261280
"pll_n": 40,
262281
"chip_select": 3,
263282
"cpld_device": "urukul1_cpld",
283+
"io_update_delay": 0,
284+
"sync_delay_seed": -1,
285+
}
286+
},
287+
288+
"clkgen_dds_sync_in": {
289+
"type": "local",
290+
"module": "artiq.coredevice.ttl",
291+
"class": "TTLClockGen",
292+
"arguments": {
293+
"channel": 30,
294+
"acc_width": 4
264295
}
265296
},
266297

artiq/frontend/artiq_ddb_template.py

+25
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,16 @@ def process_suservo(self, rtio_offset, peripheral):
424424
sampler_name = self.get_name("sampler")
425425
urukul_names = [self.get_name("urukul") for _ in range(2)]
426426
channel = count(0)
427+
for urukul_name in urukul_names:
428+
self.gen("""
429+
device_db["ttl_{urukul_name}_io_update"] = {{
430+
"type": "local",
431+
"module": "artiq.coredevice.ttl",
432+
"class": "TTLOut",
433+
"arguments": {{"channel": 0x{ttl_channel:06x}}}
434+
}}""",
435+
urukul_name=urukul_name,
436+
ttl_channel=rtio_offset+next(channel))
427437
for i in range(8):
428438
self.gen("""
429439
device_db["{suservo_name}_ch{suservo_chn}"] = {{
@@ -476,6 +486,8 @@ def process_suservo(self, rtio_offset, peripheral):
476486
"class": "CPLD",
477487
"arguments": {{
478488
"spi_device": "spi_{urukul_name}",
489+
"io_update_device": "ttl_{urukul_name}_io_update",
490+
"sync_device": "clkgen_{suservo_name}_dds_sync_in",
479491
"refclk": {refclk},
480492
"clk_sel": {clk_sel}
481493
}}
@@ -490,12 +502,25 @@ def process_suservo(self, rtio_offset, peripheral):
490502
"cpld_device": "{urukul_name}_cpld"{pll_vco}
491503
}}
492504
}}""",
505+
suservo_name=suservo_name,
493506
urukul_name=urukul_name,
494507
urukul_channel=rtio_offset+next(channel),
495508
refclk=peripheral.get("refclk", self.master_description["rtio_frequency"]),
496509
clk_sel=peripheral["clk_sel"],
497510
pll_vco=",\n \"pll_vco\": {}".format(pll_vco) if pll_vco is not None else "",
498511
pll_n=peripheral["pll_n"])
512+
self.gen("""
513+
device_db["clkgen_{suservo_name}_dds_sync_in"] = {{
514+
"type": "local",
515+
"module": "artiq.coredevice.ttl",
516+
"class": "TTLClockGen",
517+
"arguments": {{
518+
"channel": 0x{clkgen_channel:06x},
519+
"acc_width": 4
520+
}}
521+
}}""",
522+
suservo_name=suservo_name,
523+
clkgen_channel=rtio_offset+next(channel))
499524
return next(channel)
500525

501526
def process_zotino(self, rtio_offset, peripheral):

0 commit comments

Comments
 (0)