Skip to content

Commit e643e47

Browse files
committed
SUServo: pipelined coherent phase tracking mode.
Extension of the per-channel control interface by a flag en_pt, serving to enable coherent DDS phase mode. This works by tracking the DDS-internal phase accumulator as well as the servo runtime to calculate the POW accordingly. The reference time for this calculation can be set for each output channel individually in real time.
1 parent fc64ad8 commit e643e47

File tree

11 files changed

+431
-84
lines changed

11 files changed

+431
-84
lines changed

artiq/coredevice/suservo.py

+24-4
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
from artiq.language.core import kernel, delay, delay_mu, portable
22
from artiq.language.units import us, ns
3+
from artiq.language import *
34
from artiq.coredevice.rtio import rtio_output, rtio_input_data
45
from artiq.coredevice import spi2 as spi
56
from artiq.coredevice import urukul, sampler, ad9910
67
from math import ceil, log2
8+
from numpy import int32, int64
79

810

911
COEFF_WIDTH = 18 # Must match gateware IIRWidths.coeff
@@ -40,7 +42,7 @@ class SUServo:
4042
and a photodetector connected to Sampler.
4143
4244
Additionally SU Servo supports multiple preconfigured profiles per channel
43-
and features like automatic integrator hold.
45+
and features like automatic integrator hold and coherent phase tracking.
4446
4547
Notes:
4648
@@ -64,7 +66,8 @@ class SUServo:
6466
"""
6567
kernel_invariants = {"channel", "core", "pgia", "cplds", "ddses",
6668
"ref_period_mu", "num_channels", "coeff_sel",
67-
"state_sel", "config_addr", "write_enable"}
69+
"state_sel", "io_dly_addr", "config_addr",
70+
"write_enable"}
6871

6972
def __init__(self, dmgr, channel, pgia_device,
7073
cpld_devices, dds_devices,
@@ -291,7 +294,7 @@ def __init__(self, dmgr, channel, servo_device):
291294
self.dds = self.servo.ddses[self.servo_channel // 4]
292295

293296
@kernel
294-
def set(self, en_out, en_iir=0, profile=0):
297+
def set(self, en_out, en_iir=0, profile=0, en_pt=0):
295298
"""Operate channel.
296299
297300
This method does not advance the timeline. Output RF switch setting
@@ -305,9 +308,26 @@ def set(self, en_out, en_iir=0, profile=0):
305308
:param en_out: RF switch enable
306309
:param en_iir: IIR updates enable
307310
:param profile: Active profile (0-31)
311+
:param en_pt: Coherent phase tracking enable
312+
* en_pt=1: "coherent phase mode"
313+
* en_pt=0: "continuous phase mode"
314+
(see :func:`artiq.coredevice.ad9910.AD9910.set_phase_mode` for a
315+
definition of the phase modes)
308316
"""
309317
rtio_output(self.channel << 8,
310-
en_out | (en_iir << 1) | (profile << 2))
318+
en_out | (en_iir << 1) | (en_pt << 2) | (profile << 3))
319+
320+
@kernel
321+
def set_reference_time(self):
322+
"""Set reference time for "coherent phase mode" (see :meth:`set`).
323+
324+
This method does not advance the timeline.
325+
With en_pt=1 (see :meth:`set`), the tracked DDS output phase of
326+
this channel will refer to the current timeline position.
327+
328+
"""
329+
fine_ts = now_mu() & ((1 << FINE_TS_WIDTH) - 1)
330+
rtio_output(self.channel << 8 | 1, self.dds.sysclk_per_mu * fine_ts)
311331

312332
@kernel
313333
def set_dds_mu(self, profile, ftw, offs, pow_=0):

artiq/gateware/eem.py

+7-4
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ def io(*eems, iostandard):
547547
def add_std(cls, target, eems_sampler, eems_urukul,
548548
t_rtt=4, clk=1, shift=11, profile=5,
549549
sync_gen_cls=ttl_simple.ClockGen,
550-
iostandard=default_iostandard):
550+
iostandard=default_iostandard, sysclk_per_clk=8):
551551
"""Add a 8-channel Sampler-Urukul Servo
552552
553553
:param t_rtt: upper estimate for clock round-trip propagation time from
@@ -563,6 +563,8 @@ def add_std(cls, target, eems_sampler, eems_urukul,
563563
(default: 11)
564564
:param profile: log2 of the number of profiles for each DDS channel
565565
(default: 5)
566+
:param sysclk_per_clk: DDS "sysclk" (4*refclk = 1GHz typ.) cycles per
567+
FPGA "sys" clock (125MHz typ.) cycles (default: 8)
566568
"""
567569
cls.add_extension(
568570
target, *(eems_sampler + sum(eems_urukul, [])),
@@ -583,15 +585,16 @@ def add_std(cls, target, eems_sampler, eems_urukul,
583585
t_conv=57 - 4, t_rtt=t_rtt + 4)
584586
iir_p = servo.IIRWidths(state=25, coeff=18, adc=16, asf=14, word=16,
585587
accu=48, shift=shift, profile=profile, dly=8)
586-
dds_p = servo.DDSParams(width=8 + 32 + 16 + 16,
587-
channels=4 * len(eem_urukul), clk=clk)
588+
dds_p = servo.DDSParams(width=8 + 32 + 16 + 16, sysclk_per_clk=sysclk_per_clk,
589+
channels=4*len(eem_urukul), clk=clk)
588590
su = servo.Servo(sampler_pads, urukul_pads, adc_p, iir_p, dds_p)
589591
su = ClockDomainsRenamer("rio_phy")(su)
590592
# explicitly name the servo submodule to enable the migen namer to derive
591593
# a name for the adc return clock domain
592594
setattr(target.submodules, "suservo_eem{}".format(eems_sampler[0]), su)
593595

594-
ctrls = [rtservo.RTServoCtrl(ctrl) for ctrl in su.iir.ctrl]
596+
ctrls = [rtservo.RTServoCtrl(ctrl, ctrl_reftime)
597+
for ctrl, ctrl_reftime in zip(su.iir.ctrl, su.iir.ctrl_reftime)]
595598
target.submodules += ctrls
596599
target.rtio_channels.extend(
597600
rtio.Channel.from_phy(ctrl) for ctrl in ctrls)

artiq/gateware/rtio/phy/servo.py

+17-11
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,32 @@
11
from migen import *
2-
32
from artiq.gateware.rtio import rtlink
43

54

65
class RTServoCtrl(Module):
76
"""Per channel RTIO control interface"""
8-
def __init__(self, ctrl):
7+
def __init__(self, ctrl, ctrl_reftime):
98
self.rtlink = rtlink.Interface(
10-
rtlink.OInterface(len(ctrl.profile) + 2))
9+
rtlink.OInterface(
10+
data_width=max(len(ctrl.profile) + 3,
11+
len(ctrl_reftime.sysclks_fine)),
12+
address_width=1)
13+
)
1114

1215
# # #
1316

17+
sel_ref = self.rtlink.o.address[0]
1418
self.comb += [
15-
ctrl.stb.eq(self.rtlink.o.stb),
16-
self.rtlink.o.busy.eq(0)
19+
ctrl.stb.eq(self.rtlink.o.stb & ~sel_ref),
20+
self.rtlink.o.busy.eq(0),
21+
ctrl_reftime.stb.eq(self.rtlink.o.stb & sel_ref),
1722
]
23+
ctrl_cases = {
24+
0: Cat(ctrl.en_out, ctrl.en_iir, ctrl.en_pt, ctrl.profile).eq(
25+
self.rtlink.o.data),
26+
1: ctrl_reftime.sysclks_fine.eq(self.rtlink.o.data),
27+
}
1828
self.sync.rio_phy += [
19-
If(self.rtlink.o.stb,
20-
Cat(ctrl.en_out, ctrl.en_iir, ctrl.profile).eq(
21-
self.rtlink.o.data)
22-
)
29+
If(self.rtlink.o.stb, Case(self.rtlink.o.address, ctrl_cases))
2330
]
2431

2532

@@ -110,9 +117,8 @@ def __init__(self, w, servo, io_update_phys):
110117
# # #
111118

112119
config = Signal(w.coeff, reset=0)
113-
status = Signal(8 + len(servo.iir.ctrl))
120+
status = Signal(len(self.rtlink.i.data))
114121
pad = Signal(6)
115-
assert len(status) <= len(self.rtlink.i.data)
116122
self.comb += [
117123
Cat(servo.start).eq(config),
118124
status.eq(Cat(servo.start, servo.done, pad,

artiq/gateware/suservo/dds_ser.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import logging
2+
from collections import namedtuple
23

34
from migen import *
45

56
from artiq.coredevice.urukul import DEFAULT_PROFILE
67

78
from . import spi
89

9-
1010
logger = logging.getLogger(__name__)
1111

12-
13-
DDSParams = spi.SPIParams
12+
DDSParams = namedtuple("DDSParams", spi.SPIParams._fields + (
13+
"sysclk_per_clk", # DDS_CLK per FPGA system clock
14+
))
1415

1516

1617
class DDS(spi.SPISimple):

0 commit comments

Comments
 (0)