urukul: rework EEPROM synchronization. Closes #1372

This commit is contained in:
Sebastien Bourdeauducq 2019-11-05 18:56:10 +08:00
parent bc3b55b1a8
commit 5279bc275a
2 changed files with 64 additions and 49 deletions

View File

@ -1,5 +1,4 @@
from numpy import int32, int64 from numpy import int32, int64
import functools
from artiq.language.core import ( from artiq.language.core import (
kernel, delay, portable, delay_mu, now_mu, at_mu) kernel, delay, portable, delay_mu, now_mu, at_mu)
@ -62,6 +61,43 @@ RAM_MODE_CONT_BIDIR_RAMP = 3
RAM_MODE_CONT_RAMPUP = 4 RAM_MODE_CONT_RAMPUP = 4
class SyncDataUser:
def __init__(self, core, sync_delay_seed, io_update_delay):
self.core = core
self.sync_delay_seed = sync_delay_seed
self.io_update_delay = io_update_delay
@kernel
def init(self):
pass
class SyncDataEeprom:
def __init__(self, dmgr, core, eeprom_str):
self.core = core
eeprom_device, eeprom_offset = eeprom_str.split(":")
self.eeprom_device = dmgr.get(eeprom_device)
self.eeprom_offset = int(eeprom_offset)
self.sync_delay_seed = 0
self.io_update_delay = 0
@kernel
def init(self):
word = self.eeprom_device.read_i32(self.eeprom_offset) >> 16
sync_delay_seed = word >> 8
if sync_delay_seed >= 0:
io_update_delay = word & 0xff
else:
io_update_delay = 0
if io_update_delay == 0xff: # unprogrammed EEPROM
io_update_delay = 0
# With Numpy, type(int32(-1) >> 1) == int64
self.sync_delay_seed = int32(sync_delay_seed)
self.io_update_delay = int32(io_update_delay)
class AD9910: class AD9910:
""" """
AD9910 DDS channel on Urukul. AD9910 DDS channel on Urukul.
@ -88,15 +124,17 @@ class AD9910:
and set this to the delay tap number returned (default: -1 to signal no and set this to the delay tap number returned (default: -1 to signal no
synchronization and no tuning during :meth:`init`). synchronization and no tuning during :meth:`init`).
Can be a string of the form "eeprom_device:byte_offset" to read the value Can be a string of the form "eeprom_device:byte_offset" to read the value
from a I2C EEPROM. from a I2C EEPROM; in which case, `io_update_delay` must be set to the
same string value.
:param io_update_delay: IO_UPDATE pulse alignment delay. :param io_update_delay: IO_UPDATE pulse alignment delay.
To align IO_UPDATE to SYNC_CLK, run :meth:`tune_io_update_delay` and To align IO_UPDATE to SYNC_CLK, run :meth:`tune_io_update_delay` and
set this to the delay tap number returned. set this to the delay tap number returned.
Can be a string of the form "eeprom_device:byte_offset" to read the value Can be a string of the form "eeprom_device:byte_offset" to read the value
from a I2C EEPROM. from a I2C EEPROM; in which case, `sync_delay_seed` must be set to the
same string value.
""" """
kernel_invariants = {"chip_select", "cpld", "core", "bus", kernel_invariants = {"chip_select", "cpld", "core", "bus",
"ftw_per_hz", "io_update_delay", "sysclk_per_mu"} "ftw_per_hz", "sysclk_per_mu"}
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None, def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1, pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1,
@ -128,41 +166,14 @@ class AD9910:
assert sysclk <= 1e9 assert sysclk <= 1e9
self.ftw_per_hz = (1 << 32)/sysclk self.ftw_per_hz = (1 << 32)/sysclk
self.sysclk_per_mu = int(round(sysclk*self.core.ref_period)) self.sysclk_per_mu = int(round(sysclk*self.core.ref_period))
self.sysclk = sysclk
@functools.lru_cache(maxsize=2) if isinstance(sync_delay_seed, str) or isinstance(io_update_delay, str):
def get_eeprom_sync_data(eeprom_str): if sync_delay_seed != io_update_delay:
device, offset = eeprom_str.split(":") raise ValueError("When using EEPROM, sync_delay_seed must be equal to io_update_delay")
device = dmgr.get(device) self.sync_data = SyncDataEeprom(dmgr, self.core, sync_delay_seed)
offset = int(offset)
word = device.read_i32(offset) >> 16
sync_delay_seed = word >> 8
if sync_delay_seed >= 0:
io_update_delay = word & 0xff
else: else:
io_update_delay = 0 self.sync_data = SyncDataUser(self.core, sync_delay_seed, io_update_delay)
if io_update_delay == 0xff: # unprogrammed EEPROM
io_update_delay = 0
# With Numpy, type(int32(-1) >> 1) == int64
return device, offset, int32(sync_delay_seed), int32(io_update_delay)
if isinstance(sync_delay_seed, str):
self.sync_delay_seed_eeprom, self.sync_delay_seed_offset, sync_delay_seed, _ = \
get_eeprom_sync_data(sync_delay_seed)
else:
self.sync_delay_seed_eeprom, self.sync_delay_seed_offset = None, None
if isinstance(io_update_delay, str):
self.io_update_delay_eeprom, self.io_update_delay_offset, _, io_update_delay = \
get_eeprom_sync_data(io_update_delay)
else:
self.io_update_delay_eeprom, self.io_update_delay_offset = None, None
if sync_delay_seed >= 0 and not self.cpld.sync_div:
raise ValueError("parent cpld does not drive SYNC")
self.sync_delay_seed = sync_delay_seed
if self.sync_delay_seed >= 0:
assert self.sysclk_per_mu == sysclk*self.core.ref_period
self.io_update_delay = io_update_delay
self.phase_mode = PHASE_MODE_CONTINUOUS self.phase_mode = PHASE_MODE_CONTINUOUS
@ -369,6 +380,14 @@ class AD9910:
:param blind: Do not read back DDS identity and do not wait for lock. :param blind: Do not read back DDS identity and do not wait for lock.
""" """
self.sync_data.init()
if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div:
raise ValueError("parent cpld does not drive SYNC")
if self.sync_data.sync_delay_seed >= 0:
if self.sysclk_per_mu != self.sysclk*self.core.ref_period:
raise ValueError("incorrect clock ratio for synchronization")
delay(50*ms) # slack
# Set SPI mode # Set SPI mode
self.set_cfr1() self.set_cfr1()
self.cpld.io_update.pulse(1*us) self.cpld.io_update.pulse(1*us)
@ -406,8 +425,8 @@ class AD9910:
if i >= 100 - 1: if i >= 100 - 1:
raise ValueError("PLL lock timeout") raise ValueError("PLL lock timeout")
delay(10*us) # slack delay(10*us) # slack
if self.sync_delay_seed >= 0: if self.sync_data.sync_delay_seed >= 0:
self.tune_sync_delay(self.sync_delay_seed) self.tune_sync_delay(self.sync_data.sync_delay_seed)
delay(1*ms) delay(1*ms)
@kernel @kernel
@ -468,7 +487,7 @@ class AD9910:
pow_ += dt*ftw*self.sysclk_per_mu >> 16 pow_ += dt*ftw*self.sysclk_per_mu >> 16
self.write64(_AD9910_REG_PROFILE0 + profile, self.write64(_AD9910_REG_PROFILE0 + profile,
(asf << 16) | (pow_ & 0xffff), ftw) (asf << 16) | (pow_ & 0xffff), ftw)
delay_mu(int64(self.io_update_delay)) delay_mu(int64(self.sync_data.io_update_delay))
self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYN_CCLK self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYN_CCLK
at_mu(now_mu() & ~7) # clear fine TSC again at_mu(now_mu() & ~7) # clear fine TSC again
if phase_mode != PHASE_MODE_CONTINUOUS: if phase_mode != PHASE_MODE_CONTINUOUS:

View File

@ -3,7 +3,7 @@ import os
import select import select
from artiq.experiment import * from artiq.experiment import *
from artiq.coredevice.ad9910 import AD9910 from artiq.coredevice.ad9910 import AD9910, SyncDataEeprom
if os.name == "nt": if os.name == "nt":
import msvcrt import msvcrt
@ -240,15 +240,11 @@ class KasliTester(EnvExperiment):
print("Calibrating inter-device synchronization...") print("Calibrating inter-device synchronization...")
for channel_name, channel_dev in self.urukuls: for channel_name, channel_dev in self.urukuls:
if (not isinstance(channel_dev, AD9910) or if (not isinstance(channel_dev, AD9910) or
(channel_dev.sync_delay_seed_eeprom is None and channel_dev.io_update_delay_eeprom is None)): not isinstance(channel_dev.sync_data, SyncDataEeprom)):
print("{}\tno synchronization".format(channel_name)) print("{}\tno EEPROM synchronization".format(channel_name))
elif channel_dev.sync_delay_seed_eeprom is not channel_dev.io_update_delay_eeprom:
print("{}\tunsupported EEPROM configuration".format(channel_name))
elif channel_dev.sync_delay_seed_offset != channel_dev.io_update_delay_offset:
print("{}\tunsupported EEPROM offsets".format(channel_name))
else: else:
eeprom = channel_dev.sync_delay_seed_eeprom eeprom = channel_dev.sync_data.eeprom_device
offset = channel_dev.sync_delay_seed_offset offset = channel_dev.sync_data.eeprom_offset
sync_delay_seed, io_update_delay = self.calibrate_urukul(channel_dev) sync_delay_seed, io_update_delay = self.calibrate_urukul(channel_dev)
print("{}\t{} {}".format(channel_name, sync_delay_seed, io_update_delay)) print("{}\t{} {}".format(channel_name, sync_delay_seed, io_update_delay))
eeprom_word = (sync_delay_seed << 24) | (io_update_delay << 16) eeprom_word = (sync_delay_seed << 24) | (io_update_delay << 16)