Skip to content

Commit

Permalink
SUServo: pipelined coherent phase tracking mode.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
pmldrmota committed Jun 16, 2020
1 parent 13668a5 commit 2421931
Show file tree
Hide file tree
Showing 12 changed files with 414 additions and 50 deletions.
42 changes: 39 additions & 3 deletions artiq/coredevice/suservo.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ class SUServo:
and a photodetector connected to Sampler.
Additionally SU Servo supports multiple preconfigured profiles per channel
and features like automatic integrator hold.
and features like automatic integrator hold and coherent phase tracking.
Notes:
Expand Down Expand Up @@ -183,6 +183,25 @@ def set_config(self, enable):
"""
self.write(self.config_addr, enable)

@kernel
def reset_dds_phases(self):
for i in range(len(self.cplds)):
cpld = self.cplds[i]
dds = self.ddses[i]

prev_cpld_cfg = cpld.cfg_reg
cpld.cfg_write(prev_cpld_cfg | (0xf << urukul.CFG_MASK_NU))

# Clear phase accumulator. Next IO_UPDATE (first servo write) will remove
# clear flag again.
dds.set_cfr1(phase_autoclear=1)
delay(10 * us)
cpld.io_update.pulse(10 * us)
delay(10 * us)
dds.set_cfr1(phase_autoclear=0)

cpld.cfg_write(prev_cpld_cfg)

@kernel
def get_status(self):
"""Get current SU Servo status.
Expand Down Expand Up @@ -292,7 +311,7 @@ def __init__(self, dmgr, channel, servo_device):
self.dds = self.servo.ddses[self.servo_channel // 4]

@kernel
def set(self, en_out, en_iir=0, profile=0):
def set(self, en_out, en_iir=0, profile=0, en_pt=0):
"""Operate channel.
This method does not advance the timeline. Output RF switch setting
Expand All @@ -306,9 +325,26 @@ def set(self, en_out, en_iir=0, profile=0):
:param en_out: RF switch enable
:param en_iir: IIR updates enable
:param profile: Active profile (0-31)
:param en_pt: Coherent phase tracking enable
* en_pt=1: "coherent phase mode"
* en_pt=0: "continuous phase mode"
(see :func:`artiq.coredevice.ad9910.AD9910.set_phase_mode` for a
definition of the phase modes)
"""
rtio_output(self.channel << 8,
en_out | (en_iir << 1) | (profile << 2))
en_out | (en_iir << 1) | (en_pt << 2) | (profile << 3))

@kernel
def set_reference_time(self):
"""Set reference time for "coherent phase mode" (see :meth:`set`).
This method does not advance the timeline.
With en_pt=1 (see :meth:`set`), the tracked DDS output phase of
this channel will refer to the current timeline position.
"""
fine_ts = now_mu() & ((1 << FINE_TS_WIDTH) - 1)
rtio_output(self.channel << 8 | 1, self.dds.sysclk_per_mu * fine_ts)

@kernel
def set_dds_mu(self, profile, ftw, offs, pow_=0):
Expand Down
7 changes: 3 additions & 4 deletions artiq/examples/kasli_suservo/repository/suservo.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ def build(self):
self.setattr_device("core")
self.setattr_device("led0")
self.setattr_device("suservo0")
for i in range(8):
self.setattr_device("suservo0_ch{}".format(i))
self.setattr_device("suservo0_ch0")

def run(self):
self.init()
Expand Down Expand Up @@ -63,8 +62,8 @@ def init(self):
offset=-.5, # 5 V with above PGIA settings
frequency=71*MHz,
phase=0.)
# enable RF, IIR updates and profile 0
self.suservo0_ch0.set(en_out=1, en_iir=1, profile=0)
# enable RF, IIR updates, phase tracking and profile 0
self.suservo0_ch0.set(en_out=1, en_iir=1, en_pt=1, profile=0)
# enable global servo iterations
self.suservo0.set_config(enable=1)

Expand Down
17 changes: 14 additions & 3 deletions artiq/gateware/eem.py
Original file line number Diff line number Diff line change
Expand Up @@ -482,9 +482,17 @@ def io(*eems, iostandard="LVDS_25"):
def add_std(cls, target, eems_sampler, eems_urukul,
t_rtt=4, clk=1, shift=11, profile=5,
sync_gen_cls=ttl_simple.ClockGen,
iostandard="LVDS_25"):
iostandard="LVDS_25", sysclk_per_clk=8):
"""Add a 8-channel Sampler-Urukul Servo
Extends target.rtio_channels by 6 * len(eems_urukul) + 3 RTIO channels:
* len(eems_urukul) IO_UPDATE TTL outputs
* 4*len(eems_urukul) SUServo channel controls (en_out, en_iir, en_pt, profile)
* SUServo coefficient memory
* Sampler PGIA SPI interface
* len(eems_urukul) Urukul SPI interfaces
* Urukul SYNC clock generator (if not None)
:param t_rtt: upper estimate for clock round-trip propagation time from
``sck`` at the FPGA to ``clkout`` at the FPGA, measured in RTIO
coarse cycles (default: 4). This is the sum of the round-trip
Expand All @@ -498,6 +506,8 @@ def add_std(cls, target, eems_sampler, eems_urukul,
(default: 11)
:param profile: log2 of the number of profiles for each DDS channel
(default: 5)
:param sysclk_per_clk: DDS "sysclk" (4*refclk = 1GHz typ.) cycles per
FPGA "sys" clock (125MHz typ.) cycles (default: 8)
"""
cls.add_extension(
target, *(eems_sampler + sum(eems_urukul, [])),
Expand All @@ -518,15 +528,16 @@ def add_std(cls, target, eems_sampler, eems_urukul,
t_conv=57 - 4, t_rtt=t_rtt + 4)
iir_p = servo.IIRWidths(state=25, coeff=18, adc=16, asf=14, word=16,
accu=48, shift=shift, profile=profile, dly=8)
dds_p = servo.DDSParams(width=8 + 32 + 16 + 16,
dds_p = servo.DDSParams(width=8 + 32 + 16 + 16, sysclk_per_clk=sysclk_per_clk,
channels=4*len(eem_urukul), clk=clk)
su = servo.Servo(sampler_pads, urukul_pads, adc_p, iir_p, dds_p)
su = ClockDomainsRenamer("rio_phy")(su)
# explicitly name the servo submodule to enable the migen namer to derive
# a name for the adc return clock domain
setattr(target.submodules, "suservo_eem{}".format(eems_sampler[0]), su)

ctrls = [rtservo.RTServoCtrl(ctrl) for ctrl in su.iir.ctrl]
ctrls = [rtservo.RTServoCtrl(ctrl, ctrl_reftime)
for ctrl, ctrl_reftime in zip(su.iir.ctrl, su.iir.ctrl_reftime)]
target.submodules += ctrls
target.rtio_channels.extend(
rtio.Channel.from_phy(ctrl) for ctrl in ctrls)
Expand Down
24 changes: 16 additions & 8 deletions artiq/gateware/rtio/phy/servo.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,29 @@

class RTServoCtrl(Module):
"""Per channel RTIO control interface"""
def __init__(self, ctrl):
def __init__(self, ctrl, ctrl_reftime):
self.rtlink = rtlink.Interface(
rtlink.OInterface(len(ctrl.profile) + 2))
rtlink.OInterface(
data_width=max(len(ctrl.profile) + 3,
len(ctrl_reftime.sysclks_fine)),
address_width=1)
)

# # #

sel_ref = self.rtlink.o.address[0]
self.comb += [
ctrl.stb.eq(self.rtlink.o.stb),
self.rtlink.o.busy.eq(0)
ctrl.stb.eq(self.rtlink.o.stb & ~sel_ref),
self.rtlink.o.busy.eq(0),
ctrl_reftime.stb.eq(self.rtlink.o.stb & sel_ref),
]
ctrl_cases = {
0: Cat(ctrl.en_out, ctrl.en_iir, ctrl.en_pt, ctrl.profile).eq(
self.rtlink.o.data),
1: ctrl_reftime.sysclks_fine.eq(self.rtlink.o.data),
}
self.sync.rio_phy += [
If(self.rtlink.o.stb,
Cat(ctrl.en_out, ctrl.en_iir, ctrl.profile).eq(
self.rtlink.o.data)
)
If(self.rtlink.o.stb, Case(self.rtlink.o.address, ctrl_cases))
]


Expand Down
7 changes: 4 additions & 3 deletions artiq/gateware/suservo/dds_ser.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import logging
from collections import namedtuple

from migen import *

from . import spi


logger = logging.getLogger(__name__)


DDSParams = spi.SPIParams
DDSParams = namedtuple("DDSParams", spi.SPIParams._fields + (
"sysclk_per_clk", # DDS_CLK per FPGA system clock
))


class DDS(spi.SPISimple):
Expand Down
Loading

0 comments on commit 2421931

Please sign in to comment.