Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Urukul: Kasli-Urukul synchronization using SYNC IN driven by Kasli #1143

Closed
6 tasks done
jordens opened this issue Aug 30, 2018 · 3 comments · Fixed by #1190
Closed
6 tasks done

Urukul: Kasli-Urukul synchronization using SYNC IN driven by Kasli #1143

jordens opened this issue Aug 30, 2018 · 3 comments · Fixed by #1190

Comments

@jordens
Copy link
Member

jordens commented Aug 30, 2018

Status quo:

  • Phase locking of Urukul channels determined by reference frequency. Channels on boards with a common reference have stable phase relations.
  • Absolute phase between any two channels is not deterministic across restarts (resets of the DDS or interruptions of the reference frequency)
  • Relative phase updates work fine. Adding a phase offset to a channel does exactly that.
  • Update events may be registered and executed at different times between channels (0 or 4ns)

Synchronization steps and components:

  • The SYNC mechanism leads to synchronized DDS internal clocks (f_DDS/4 = 250 MHz): Setting the same frequency or phase leads to exactly the same output (provided the IO_UPDATE window is met, see below).
  • Having Kasli drive the SYNC tree also leads to alignment of the DDS internal clocks to the RTIO clock: If no absolute or coherent updates are used this leads to deterministic phase between DDS and the RTIO clock (RF switches, TTL pulses, photon time tags)
  • Aligning IO_UPDATE leads to synchronized registration of update events. This makes any update (absolute phase, frequency jump) deterministically synchronous among channels and guarantees deterministic absolute phase/frequency settings between channels and determinism between DDS and RF switch/TTL outputs, photon time tags.
  • All this only refers to the AD9910 variant. Synchronization of the AD9912 is unexplored.

Implementation roadmap:

  • 1. SYNC_IN pulse generator in Kasli, f_RTIO/f_SYNC integer ratios, align SYNC generator to user defined timestamp, AD9910 register interface to synchronization registers, Urukul SYNC_SEL setting, AD9910 sync RTIO/kernel API, user defined (device_db) SYNC_IN input delays

  • 2. Automatic SYNC_IN delay tuning from user defined (device_db) starting value (using SMP_ERR, and delay scans)

  • 3. automatic IO UPDATE delay tuning (either using IO_UPDATE_RET or indirectly with the DRG/IO_UPDATE/"Read effective FTW") and delay application (either to "Sync state preset value" or IO_UPDATE ODELAY)

  • 4. Phase modes (absolute, relative, coherent) as defined by the existing AD9914 implementation (optional)

  • 6. Development of unit tests and set up of continuous integration system (optional)

  • 7. Maintainance of continuous integration system (optional)

  • the is some proof of principle code of a sync generator (part of 1) in https://github.com/cjbe/artiq/ for driving SYNC from Kasli

@dnadlinger
Copy link
Collaborator

We have code for this in production use (minus automatic phase tracking); I'll try and push this somewhere once I have a chance to catch my breath. Anyone interested in this, please feel free to contact me in the meantime.

@jordens
Copy link
Member Author

jordens commented Oct 5, 2018

Funded by multiple groups at Uni Hannover and PTB.

@jordens jordens self-assigned this Oct 24, 2018
jordens added a commit that referenced this issue Oct 26, 2018
jordens added a commit that referenced this issue Oct 26, 2018
* expose multi device sync functionality
* sync delay configuration interface
* auto-tuning of sync delay from device_db seed

for #1143
jordens added a commit that referenced this issue Oct 26, 2018
jordens added a commit that referenced this issue Oct 31, 2018
jordens added a commit that referenced this issue Nov 2, 2018
* simplified and cross-referenced the explanation of the different
  phase modes.
* semantically and functionally merged absolute and tracking/coherent
  phase modes.
* simplified numerics to calculate phase correction
* added warning about possible inconsistency with DMA and default
  phase mode
* restricted __all__ imports
* moved continuous/relative phase offset tracking from an instance
  variable to a "handle" returned by set()/set_mu() in order to avoid
  state inconsistency with DMA (#1113 #1115)

for #1143
jordens added a commit that referenced this issue Nov 5, 2018
jordens added a commit that referenced this issue Nov 5, 2018
for #1143, also add missing LUH device db
@jordens jordens mentioned this issue Nov 5, 2018
12 tasks
jordens added a commit that referenced this issue Nov 5, 2018
for #1143

Signed-off-by: Robert Jördens <rj@quartiq.de>
jordens added a commit that referenced this issue Nov 5, 2018
* expose multi device sync functionality
* sync delay configuration interface
* auto-tuning of sync delay from device_db seed

for #1143

Signed-off-by: Robert Jördens <rj@quartiq.de>
jordens added a commit that referenced this issue Nov 5, 2018
for #1143

Signed-off-by: Robert Jördens <rj@quartiq.de>
jordens added a commit that referenced this issue Nov 5, 2018
for #1143

Signed-off-by: Robert Jördens <rj@quartiq.de>
jordens added a commit that referenced this issue Nov 5, 2018
* simplified and cross-referenced the explanation of the different
  phase modes.
* semantically and functionally merged absolute and tracking/coherent
  phase modes.
* simplified numerics to calculate phase correction
* added warning about possible inconsistency with DMA and default
  phase mode
* restricted __all__ imports
* moved continuous/relative phase offset tracking from an instance
  variable to a "handle" returned by set()/set_mu() in order to avoid
  state inconsistency with DMA (#1113 #1115)

for #1143

Signed-off-by: Robert Jördens <rj@quartiq.de>
jordens added a commit that referenced this issue Nov 5, 2018
for #1143

Signed-off-by: Robert Jördens <rj@quartiq.de>
jordens added a commit that referenced this issue Nov 5, 2018
for #1143

Signed-off-by: Robert Jördens <rj@quartiq.de>
jordens added a commit that referenced this issue Nov 5, 2018
for #1143, also add missing LUH device db

Signed-off-by: Robert Jördens <rj@quartiq.de>
jordens added a commit that referenced this issue Nov 5, 2018
* urukul-sync: (29 commits)
  urukul: flake8 [nfc]
  ad9910: flake8 [nfc]
  urukul/ad9910 test: remove unused import
  test_urukul: relax speed
  urukul,ad9910: print speed metrics
  kasli: add PTB2 (external clock and SYNC)
  kasli: add sync to LUH, HUB, Opticlock
  kasli_tester: urukul0 mmcx clock defunct
  test_ad9910: relax ifc mode read
  tests: add Urukul-AD9910 HITL unittests including SYNC
  ad9910: add init bit explanation
  test: add Urukul CPLD HITL tests
  ad9910: fiducial timestamp for tracking phase mode
  ad9910: add phase modes
  ad9910: fix pll timeout loop
  tester: add urukul sync
  ptb: back out urukul-sync
  ad9910: add IO_UPDATE alignment and tuning
  urukul: set up sync_in generator
  ad9910: add io_update alignment measurement
  ...

close #1143
jordens added a commit that referenced this issue Nov 9, 2018
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>
@jordens
Copy link
Member Author

jordens commented Nov 13, 2018

Some example code to show the functionality:

from artiq.language import *

from artiq.coredevice.ad9910 import PHASE_MODE_TRACKING


class UrukulSync(EnvExperiment):
    def build(self):
        self.setattr_device("core")
        self.d0 = self.get_device("urukul0_ch0")
        self.d1 = self.get_device("urukul0_ch1")
        self.d2 = self.get_device("urukul0_ch2")
        self.d3 = self.get_device("urukul0_ch3")
        self.t = self.get_device("ttl4")

    @kernel
    def run(self):
        self.core.break_realtime()
        self.d0.cpld.init()
        self.d0.init()
        self.d1.init()
        self.d2.init()
        self.d3.init()

        # This calibration needs to be done only once to find good values.
        # The rest is happening at each future init() of the DDS.
        if self.d0.sync_delay_seed == -1:
            delay(100*us)
            d0, w0 = self.d0.tune_sync_delay()
            t0 = self.d0.tune_io_update_delay()
            d1, w1 = self.d1.tune_sync_delay()
            t1 = self.d0.tune_io_update_delay()
            d2, w2 = self.d2.tune_sync_delay()
            t2 = self.d0.tune_io_update_delay()
            d3, w3 = self.d3.tune_sync_delay()
            t3 = self.d0.tune_io_update_delay()
            # Add the values found to each of the four channels in your
            # device_db.py so that e.g. for urukul0_ch0 it looks like:
            #    "urukul0_ch0": {
            #       ...
            #        "class": "AD9910",
            #        "arguments": {
            #            "pll_n": 32,
            #            "chip_select": 4,
            #           "sync_delay_seed": D,
            #           "io_update_delay": T,
            #           "cpld_device": "urukul0_cpld",
            #           ...
            #       }
            # where T is the io_update_delay of the channel and
            # D is the sync_delay_seed of the channel below:
            print("sync_delay_seed", [d0, d1, d2, d3])
            print("io_update_delay", [t0, t1, t2, t3])
            # As long as the values don't differ too much between the channels,
            # using the mean for them is also fine.
            # This one is for information purposes only:
            # print("validation delays", [w0, w1, w2, w3])
            #
            # then run this script again
            return

        self.d0.set_phase_mode(PHASE_MODE_TRACKING)
        self.d1.set_phase_mode(PHASE_MODE_TRACKING)
        self.d2.set_phase_mode(PHASE_MODE_TRACKING)
        self.d3.set_phase_mode(PHASE_MODE_TRACKING)

        self.d0.set_att(10*dB)
        self.d1.set_att(10*dB)
        self.d2.set_att(10*dB)
        self.d3.set_att(10*dB)

        t = now_mu()
        self.d0.set(80*MHz, phase=0., ref_time=t)
        self.d1.set(80*MHz, phase=.5, ref_time=t)
        self.d2.set(80*MHz, phase=.25, ref_time=t)
        self.d3.set(80*MHz, phase=.75, ref_time=t)

        self.t.on()
        self.d0.sw.on()
        self.d1.sw.on()
        self.d2.sw.on()
        self.d3.sw.on()

Or:

    @kernel
    def multi(self):
        """more complicated phase gymnastics"""
        self.core.break_realtime()

        self.d0.cpld.init()
        self.d0.init()
        self.d1.init()

        delay(1*ms)

        self.d0.set_att(10.)
        self.d1.set_att(10.)

        dt = 1*us
        f = 1*MHz

        t0 = now_mu()
        self.d0.set(f, phase=0., ref_time=t0, phase_mode=1)
        self.d1.set(f, phase=0., ref_time=t0, phase_mode=1)
        delay(dt)
        self.d0.sw.on()
        self.d1.sw.on()
        delay(1.18*us)

        self.d0.sw.off()
        self.d1.sw.off()
        delay(dt)
        self.d0.sw.on()
        self.d1.sw.on()

        p0 = self.d1.set(f, phase=.5, ref_time=t0, phase_mode=1)
        delay(dt)

        self.d1.set(.6*f, phase=p0)
        delay(dt)

        self.d1.set(f, ref_time=t0, phase_mode=1)
        delay(dt)

        self.d0.sw.off()
        self.d1.sw.off()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants