Merge branch 'urukul-sync'

* 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
This commit is contained in:
Robert Jördens 2018-11-05 19:54:30 +01:00
commit 31f68ddf6c
12 changed files with 1331 additions and 100 deletions

View File

@ -1,13 +1,27 @@
from numpy import int32, int64
from artiq.language.core import kernel, delay, portable
from artiq.language.units import us, ns, ms
from artiq.language.core import (
kernel, delay, portable, delay_mu, now_mu, at_mu)
from artiq.language.units import us, ms
from artiq.coredevice import spi2 as spi
from artiq.coredevice import urukul
# Work around ARTIQ-Python import machinery
urukul_sta_pll_lock = urukul.urukul_sta_pll_lock
urukul_sta_smp_err = urukul.urukul_sta_smp_err
__all__ = [
"AD9910",
"PHASE_MODE_CONTINUOUS", "PHASE_MODE_ABSOLUTE", "PHASE_MODE_TRACKING"
]
_PHASE_MODE_DEFAULT = -1
PHASE_MODE_CONTINUOUS = 0
PHASE_MODE_ABSOLUTE = 1
PHASE_MODE_TRACKING = 2
_AD9910_REG_CFR1 = 0x00
_AD9910_REG_CFR2 = 0x01
_AD9910_REG_CFR3 = 0x02
@ -49,12 +63,21 @@ class AD9910:
Urukul CPLD instance).
:param pll_cp: DDS PLL charge pump setting.
:param pll_vco: DDS PLL VCO range selection.
:param sync_delay_seed: SYNC_IN delay tuning starting value.
To stabilize the SYNC_IN delay tuning, run :meth:`tune_sync_delay` once
and set this to the delay tap number returned (default: -1 to signal no
synchronization and no tuning during :meth:`init`).
:param io_update_delay: IO_UPDATE pulse alignment delay.
To align IO_UPDATE to SYNC_CLK, run :meth:`tune_io_update_delay` and
set this to the delay tap number returned.
"""
kernel_invariants = {"chip_select", "cpld", "core", "bus",
"ftw_per_hz", "pll_n", "pll_cp", "pll_vco"}
"ftw_per_hz", "pll_n", "io_update_delay",
"sysclk_per_mu"}
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
pll_n=40, pll_cp=7, pll_vco=5):
pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1,
io_update_delay=0):
self.cpld = dmgr.get(cpld_device)
self.core = self.cpld.core
self.bus = self.cpld.bus
@ -68,14 +91,64 @@ class AD9910:
assert self.cpld.refclk/4 <= 60e6
sysclk = self.cpld.refclk*pll_n/4 # Urukul clock fanout divider
assert sysclk <= 1e9
self.ftw_per_hz = 1./sysclk*(int64(1) << 32)
self.ftw_per_hz = (1 << 32)/sysclk
self.sysclk_per_mu = int(round(sysclk*self.core.ref_period))
assert self.sysclk_per_mu == sysclk*self.core.ref_period
assert 0 <= pll_vco <= 5
vco_min, vco_max = [(370, 510), (420, 590), (500, 700),
(600, 880), (700, 950), (820, 1150)][pll_vco]
(600, 880), (700, 950), (820, 1150)][pll_vco]
assert vco_min <= sysclk/1e6 <= vco_max
self.pll_vco = pll_vco
assert 0 <= pll_cp <= 7
self.pll_cp = pll_cp
self.sync_delay_seed = sync_delay_seed
self.io_update_delay = io_update_delay
self.phase_mode = PHASE_MODE_CONTINUOUS
@kernel
def set_phase_mode(self, phase_mode):
"""Set the default phase mode.
for future calls to :meth:`set` and
:meth:`set_mu`. Supported phase modes are:
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged
when changing frequency or phase. The DDS phase is the sum of the
phase accumulator and the phase offset. The only discontinuous
changes in the DDS output phase come from changes to the phase
offset. This mode is also knows as "relative phase mode".
:math:`\phi(t) = q(t^\prime) + p + (t - t^\prime) f`
* :const:`PHASE_MODE_ABSOLUTE`: the phase accumulator is reset when
changing frequency or phase. Thus, the phase of the DDS at the
time of the change is equal to the specified phase offset.
:math:`\phi(t) = p + (t - t^\prime) f`
* :const:`PHASE_MODE_TRACKING`: when changing frequency or phase,
the phase accumulator is cleared and the phase offset is offset
by the value the phase accumulator would have if the DDS had been
running at the specified frequency since a given fiducial
time stamp. This is functionally equivalent to
:const:`PHASE_MODE_ABSOLUTE`. The only difference is the fiducial
time stamp. This mode is also known as "coherent phase mode".
The default fiducial time stamp is 0.
:math:`\phi(t) = p + (t - T) f`
Where:
* :math:`\phi(t)`: the DDS output phase
* :math:`q(t) = \phi(t) - p`: DDS internal phase accumulator
* :math:`p`: phase offset
* :math:`f`: frequency
* :math:`t^\prime`: time stamp of setting :math:`p`, :math:`f`
* :math:`T`: fiducial time stamp
* :math:`t`: running time
.. warning:: This setting may become inconsistent when used as part of
a DMA recording. When using DMA, it is recommended to specify the
phase mode explicitly when calling :meth:`set` or :meth:`set_mu`.
"""
self.phase_mode = phase_mode
@kernel
def write32(self, addr, data):
@ -85,10 +158,10 @@ class AD9910:
:param data: Data to be written
"""
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select)
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write(addr << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
urukul.SPIT_DDS_WR, self.chip_select)
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write(data)
@kernel
@ -98,11 +171,11 @@ class AD9910:
:param addr: Register address
"""
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select)
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write((addr | 0x80) << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END
| spi.SPI_INPUT, 32,
urukul.SPIT_DDS_RD, self.chip_select)
self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
32, urukul.SPIT_DDS_RD, self.chip_select)
self.bus.write(0)
return self.bus.read()
@ -115,13 +188,13 @@ class AD9910:
:param data_low: Low (LSB) 32 data bits
"""
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select)
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write(addr << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG, 32,
urukul.SPIT_DDS_WR, self.chip_select)
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write(data_high)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
urukul.SPIT_DDS_WR, self.chip_select)
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write(data_low)
@kernel
@ -136,7 +209,7 @@ class AD9910:
"""
# Set SPI mode
self.write32(_AD9910_REG_CFR1, 0x00000002)
self.cpld.io_update.pulse(2*us)
self.cpld.io_update.pulse(1*us)
delay(1*ms)
if not blind:
# Use the AUX DAC setting to identify and confirm presence
@ -145,60 +218,121 @@ class AD9910:
raise ValueError("Urukul AD9910 AUX_DAC mismatch")
delay(50*us) # slack
# Configure PLL settings and bring up PLL
self.write32(_AD9910_REG_CFR2, 0x01400020)
self.cpld.io_update.pulse(2*us)
# enable amplitude scale from profiles
# read effective FTW
# sync timing validation disable (enabled later)
self.write32(_AD9910_REG_CFR2, 0x01010020)
self.cpld.io_update.pulse(1*us)
cfr3 = (0x0807c100 | (self.pll_vco << 24) |
(self.pll_cp << 19) | (self.pll_n << 1))
self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset
self.cpld.io_update.pulse(100*us)
self.cpld.io_update.pulse(1*us)
self.write32(_AD9910_REG_CFR3, cfr3)
self.cpld.io_update.pulse(100*us)
self.cpld.io_update.pulse(1*us)
if blind:
delay(100*ms)
return
# Wait for PLL lock, up to 100 ms
for i in range(100):
sta = self.cpld.sta_read()
lock = urukul_sta_pll_lock(sta)
delay(1*ms)
if lock & (1 << self.chip_select - 4):
return
raise ValueError("PLL lock timeout")
else:
# Wait for PLL lock, up to 100 ms
for i in range(100):
sta = self.cpld.sta_read()
lock = urukul_sta_pll_lock(sta)
delay(1*ms)
if lock & (1 << self.chip_select - 4):
break
if i >= 100 - 1:
raise ValueError("PLL lock timeout")
if self.sync_delay_seed >= 0:
self.tune_sync_delay(self.sync_delay_seed)
delay(1*ms)
@kernel
def set_mu(self, ftw, pow=0, asf=0x3fff):
def power_down(self, bits=0b1111):
"""Power down DDS.
:param bits: power down bits, see datasheet
"""
self.write32(_AD9910_REG_CFR1, 0x00000002 | (bits << 4))
self.cpld.io_update.pulse(1*us)
@kernel
def set_mu(self, ftw, pow=0, asf=0x3fff, phase_mode=_PHASE_MODE_DEFAULT,
ref_time=-1):
"""Set profile 0 data in machine units.
This uses machine units (FTW, POW, ASF). The frequency tuning word
width is 32, the phase offset word width is 16, and the amplitude
scale factor width is 12.
After the SPI transfer, the shared IO update pin is pulsed to
activate the data.
.. seealso: :meth:`set_phase_mode` for a definition of the different
phase modes.
:param ftw: Frequency tuning word: 32 bit.
:param pow: Phase tuning word: 16 bit unsigned.
:param asf: Amplitude scale factor: 14 bit unsigned.
:param phase_mode: If specified, overrides the default phase mode set
by :meth:`set_phase_mode` for this call.
:param ref_time: Fiducial time used to compute absolute or tracking
phase updates. In machine units as obtained by `now_mu()`.
:return: Resulting phase offset word after application of phase
tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in
subsequent calls, use this value as the "current" phase.
"""
if phase_mode == _PHASE_MODE_DEFAULT:
phase_mode = self.phase_mode
# Align to coarse RTIO which aligns SYNC_CLK
at_mu(now_mu() & ~0xf)
if phase_mode != PHASE_MODE_CONTINUOUS:
# Auto-clear phase accumulator on IO_UPDATE.
# This is active already for the next IO_UPDATE
self.write32(_AD9910_REG_CFR1, 0x00002002)
if phase_mode == PHASE_MODE_TRACKING and ref_time < 0:
# set default fiducial time stamp
ref_time = 0
if ref_time >= 0:
# 32 LSB are sufficient.
# Also no need to use IO_UPDATE time as this
# is equivalent to an output pipeline latency.
dt = int32(now_mu()) - int32(ref_time)
pow += dt*ftw*self.sysclk_per_mu >> 16
self.write64(_AD9910_REG_PR0, (asf << 16) | pow, ftw)
self.cpld.io_update.pulse(10*ns)
delay_mu(int64(self.io_update_delay))
self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYSCLK
at_mu(now_mu() & ~0xf)
if phase_mode != PHASE_MODE_CONTINUOUS:
self.write32(_AD9910_REG_CFR1, 0x00000002)
# future IO_UPDATE will activate
return pow
@portable(flags={"fast-math"})
def frequency_to_ftw(self, frequency):
"""Returns the frequency tuning word corresponding to the given
"""Return the frequency tuning word corresponding to the given
frequency.
"""
return int32(round(self.ftw_per_hz*frequency))
@portable(flags={"fast-math"})
def turns_to_pow(self, turns):
"""Returns the phase offset word corresponding to the given phase
"""Return the phase offset word corresponding to the given phase
in turns."""
return int32(round(turns*0x10000))
@portable(flags={"fast-math"})
def amplitude_to_asf(self, amplitude):
"""Returns amplitude scale factor corresponding to given amplitude."""
"""Return amplitude scale factor corresponding to given amplitude."""
return int32(round(amplitude*0x3ffe))
@portable(flags={"fast-math"})
def pow_to_turns(self, pow):
"""Return the phase in turns corresponding to a given phase offset
word."""
return pow/0x10000
@kernel
def set(self, frequency, phase=0.0, amplitude=1.0):
def set(self, frequency, phase=0.0, amplitude=1.0,
phase_mode=_PHASE_MODE_DEFAULT, ref_time=-1):
"""Set profile 0 data in SI units.
.. seealso:: :meth:`set_mu`
@ -206,10 +340,13 @@ class AD9910:
:param ftw: Frequency in Hz
:param pow: Phase tuning word in turns
:param asf: Amplitude in units of full scale
:param phase_mode: Phase mode constant
:param ref_time: Fiducial time stamp in machine units
:return: Resulting phase offset in turns
"""
self.set_mu(self.frequency_to_ftw(frequency),
self.turns_to_pow(phase),
self.amplitude_to_asf(amplitude))
return self.pow_to_turns(self.set_mu(
self.frequency_to_ftw(frequency), self.turns_to_pow(phase),
self.amplitude_to_asf(amplitude), phase_mode, ref_time))
@kernel
def set_att_mu(self, att):
@ -240,3 +377,148 @@ class AD9910:
:param state: CPLD CFG RF switch bit
"""
self.cpld.cfg_sw(self.chip_select - 4, state)
@kernel
def set_sync(self, in_delay, window):
"""Set the relevant parameters in the multi device synchronization
register. See the AD9910 datasheet for details. The SYNC clock
generator preset value is set to zero, and the SYNC_OUT generator is
disabled.
:param in_delay: SYNC_IN delay tap (0-31) in steps of ~75ps
:param window: Symmetric SYNC_IN validation window (0-15) in
steps of ~75ps for both hold and setup margin.
"""
self.write32(_AD9910_REG_MSYNC,
(window << 28) | # SYNC S/H validation delay
(1 << 27) | # SYNC receiver enable
(0 << 26) | # SYNC generator disable
(0 << 25) | # SYNC generator SYS rising edge
(0 << 18) | # SYNC preset
(0 << 11) | # SYNC output delay
(in_delay << 3)) # SYNC receiver delay
@kernel
def clear_smp_err(self):
"""Clear the SMP_ERR flag and enables SMP_ERR validity monitoring.
Violations of the SYNC_IN sample and hold margins will result in
SMP_ERR being asserted. This then also activates the red LED on
the respective Urukul channel.
Also modifies CFR2.
"""
self.write32(_AD9910_REG_CFR2, 0x01010020) # clear SMP_ERR
self.cpld.io_update.pulse(1*us)
self.write32(_AD9910_REG_CFR2, 0x01010000) # enable SMP_ERR
self.cpld.io_update.pulse(1*us)
@kernel
def tune_sync_delay(self, sync_delay_seed):
"""Find a stable SYNC_IN delay.
This method first locates the smallest SYNC_IN validity window at
minimum window size and then increases the window a bit to provide some
slack and stability.
It starts scanning delays around `sync_delay_seed` (see the
device database arguments and :class:`AD9910`) at maximum validation
window size and decreases the window size until a valid delay is found.
:param sync_delay_seed: Start value for valid SYNC_IN delay search.
:return: Tuple of optimal delay and window size.
"""
dt = 14 # 1/(f_SYSCLK*75ps) taps per SYSCLK period
max_delay = dt # 14*75ps > 1ns
max_window = dt//4 + 1 # 2*75ps*4 = 600ps high > 1ns/2
min_window = max(0, max_window - 2) # 2*75ps hold, 2*75ps setup
for window in range(max_window - min_window + 1):
window = max_window - window
for in_delay in range(max_delay):
# alternate search direction around seed_delay
if in_delay & 1:
in_delay = -in_delay
in_delay = sync_delay_seed + (in_delay >> 1)
if in_delay < 0:
in_delay = 0
elif in_delay > 31:
in_delay = 31
self.set_sync(in_delay, window)
self.clear_smp_err()
# integrate SMP_ERR statistics for a few hundred cycles
delay(10*us)
err = urukul_sta_smp_err(self.cpld.sta_read())
err = (err >> (self.chip_select - 4)) & 1
delay(40*us) # slack
if not err:
window -= min_window # add margin
self.set_sync(in_delay, window)
self.clear_smp_err()
delay(40*us) # slack
return in_delay, window
raise ValueError("no valid window/delay")
@kernel
def measure_io_update_alignment(self, io_up_delay):
"""Use the digital ramp generator to locate the alignment between
IO_UPDATE and SYNC_CLK.
The ramp generator is set up to a linear frequency ramp
(dFTW/t_SYNC_CLK=1) and started at a RTIO time stamp.
After scanning the alignment, an IO_UPDATE delay midway between two
edges should be chosen.
:return: odd/even SYNC_CLK cycle indicator
"""
# set up DRG
# DRG ACC autoclear and LRR on io update
self.write32(_AD9910_REG_CFR1, 0x0000c002)
# DRG -> FTW, DRG enable
self.write32(_AD9910_REG_CFR2, 0x01090000)
# no limits
self.write64(_AD9910_REG_DRAMPL, -1, 0)
# DRCTL=0, dt=1 t_SYNC_CLK
self.write32(_AD9910_REG_DRAMPR, 0x00010000)
# dFTW = 1, (work around negative slope)
self.write64(_AD9910_REG_DRAMPS, -1, 0)
at_mu(now_mu() + 0x10 & ~0xf) # align to RTIO/2
self.cpld.io_update.pulse_mu(8)
# disable DRG autoclear and LRR on io_update
self.write32(_AD9910_REG_CFR1, 0x00000002)
# stop DRG
self.write64(_AD9910_REG_DRAMPS, 0, 0)
at_mu((now_mu() + 0x10 & ~0xf) + io_up_delay) # delay
self.cpld.io_update.pulse_mu(32 - io_up_delay) # realign
ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW
delay(100*us) # slack
# disable DRG
self.write32(_AD9910_REG_CFR2, 0x01010000)
self.cpld.io_update.pulse_mu(8)
return ftw & 1
@kernel
def tune_io_update_delay(self):
"""Find a stable IO_UPDATE delay alignment.
Scan through increasing IO_UPDATE delays until a delay is found that
lets IO_UPDATE be registered in the next SYNC_CLK cycle. Return a
IO_UPDATE delay that is midway between two such SYNC_CLK transitions.
This method assumes that the IO_UPDATE TTLOut device has one machine
unit resolution (SERDES) and that the ratio between fine RTIO frequency
(RTIO time machine units) and SYNC_CLK is 4.
:return: Stable IO_UPDATE delay to be passed to the constructor
:class:`AD9910` via the device database.
"""
period = 4 # f_RTIO/f_SYNC = 4
max_delay = 8 # mu, 1 ns
d0 = self.io_update_delay
t0 = int32(self.measure_io_update_alignment(d0))
for i in range(max_delay - 1):
t = self.measure_io_update_alignment(
(d0 + i + 1) & (max_delay - 1))
if t != t0:
return (d0 + i + period//2) & (period - 1)
raise ValueError("no IO_UPDATE-SYNC_CLK alignment edge found")

View File

@ -430,14 +430,15 @@ class TTLClockGen:
The time cursor is not modified by any function in this class.
:param channel: channel number
:param acc_width: accumulator width in bits
"""
kernel_invariants = {"core", "channel", "acc_width"}
def __init__(self, dmgr, channel, core_device="core"):
def __init__(self, dmgr, channel, acc_width=24, core_device="core"):
self.core = dmgr.get(core_device)
self.channel = channel
self.acc_width = numpy.int64(24)
self.acc_width = numpy.int64(acc_width)
@portable
def frequency_to_ftw(self, frequency):

View File

@ -1,4 +1,4 @@
from artiq.language.core import kernel, delay, portable
from artiq.language.core import kernel, delay, portable, at_mu, now_mu
from artiq.language.units import us, ms
from numpy import int32
@ -61,7 +61,7 @@ def urukul_cfg(rf_sw, led, profile, io_update, mask_nu,
(io_update << CFG_IO_UPDATE) |
(mask_nu << CFG_MASK_NU) |
((clk_sel & 0x01) << CFG_CLK_SEL0) |
((clk_sel & 0x02) << (CFG_CLK_SEL1-1)) |
((clk_sel & 0x02) << (CFG_CLK_SEL1 - 1)) |
(sync_sel << CFG_SYNC_SEL) |
(rst << CFG_RST) |
(io_rst << CFG_IO_RST))
@ -109,12 +109,22 @@ class _RegIOUpdate:
self.cpld.cfg_write(cfg)
class _DummySync:
def __init__(self, cpld):
self.cpld = cpld
@kernel
def set_mu(self, ftw):
pass
class CPLD:
"""Urukul CPLD SPI router and configuration interface.
:param spi_device: SPI bus device name
:param io_update_device: IO update RTIO TTLOut channel name
:param dds_reset_device: DDS reset RTIO TTLOut channel name
:param sync_device: AD9910 SYNC_IN RTIO TTLClockGen channel name
:param refclk: Reference clock (SMA, MMCX or on-board 100 MHz oscillator)
frequency in Hz
:param clk_sel: Reference clock selection. For hardware revision >= 1.3
@ -122,20 +132,25 @@ class CPLD:
internal MMCX. For hardware revision <= v1.2 valid options are: 0 -
either XO or MMCX dependent on component population; 1 SMA. Unsupported
clocking options are silently ignored.
:param sync_sel: SYNC clock selection. 0 corresponds to SYNC clock over EEM
from FPGA. 1 corresponds to SYNC clock from DDS0.
:param sync_sel: SYNC_IN selection. 0 corresponds to SYNC_IN over EEM
from FPGA. 1 corresponds to SYNC_IN from DDS0.
:param rf_sw: Initial CPLD RF switch register setting (default: 0x0).
Knowledge of this state is not transferred between experiments.
:param att: Initial attenuator setting shift register (default:
0x00000000). See also: :meth:`set_all_att_mu`. Knowledge of this state
is not transferred between experiments.
:param sync_div: SYNC_IN generator divider. The ratio between the coarse
RTIO frequency and the SYNC_IN generator frequency (default: 2 if
`sync_device` was specified).
:param core_device: Core device name
"""
kernel_invariants = {"refclk", "bus", "core", "io_update"}
def __init__(self, dmgr, spi_device, io_update_device=None,
dds_reset_device=None, sync_sel=0, clk_sel=0, rf_sw=0,
refclk=125e6, att=0x00000000, core_device="core"):
dds_reset_device=None, sync_device=None,
sync_sel=0, clk_sel=0, rf_sw=0,
refclk=125e6, att=0x00000000, sync_div=None,
core_device="core"):
self.core = dmgr.get(core_device)
self.refclk = refclk
@ -147,11 +162,20 @@ class CPLD:
self.io_update = _RegIOUpdate(self)
if dds_reset_device is not None:
self.dds_reset = dmgr.get(dds_reset_device)
if sync_device is not None:
self.sync = dmgr.get(sync_device)
if sync_div is None:
sync_div = 2
else:
self.sync = _DummySync(self)
assert sync_div is None
sync_div = 0
self.cfg_reg = urukul_cfg(rf_sw=rf_sw, led=0, profile=0,
io_update=0, mask_nu=0, clk_sel=clk_sel,
sync_sel=sync_sel, rst=0, io_rst=0)
self.att_reg = att
self.sync_div = sync_div
@kernel
def cfg_write(self, cfg):
@ -207,6 +231,9 @@ class CPLD:
raise ValueError("Urukul proto_rev mismatch")
delay(100*us) # reset, slack
self.cfg_write(cfg)
if self.sync_div:
at_mu(now_mu() & ~0xf) # align to RTIO/2
self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16
delay(1*ms) # DDS wake up
@kernel
@ -289,3 +316,20 @@ class CPLD:
SPIT_ATT_RD, CS_ATT)
self.bus.write(self.att_reg)
return self.bus.read()
@kernel
def set_sync_div(self, div):
"""Set the SYNC_IN AD9910 pulse generator frequency
and align it to the current RTIO timestamp.
The SYNC_IN signal is derived from the coarse RTIO clock
and the divider must be a power of two two.
Configure ``sync_sel == 0``.
:param div: SYNC_IN frequency divider. Must be a power of two.
Minimum division ratio is 2. Maximum division ratio is 16.
"""
ftw_max = 1 << 4
ftw = ftw_max//div
assert ftw*div == ftw_max
self.sync.set_mu(ftw)

View File

@ -86,13 +86,19 @@ for j in range(3):
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 27 + 2*j}
"arguments": {"channel": 27 + 3*j}
},
"ttl_urukul{}_sync".format(j): {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLClockGen",
"arguments": {"channel": 28 + 3*j, "acc_width": 4}
},
"ttl_urukul{}_io_update".format(j): {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 28 + 2*j}
"arguments": {"channel": 29 + 3*j}
},
"urukul{}_cpld".format(j): {
"type": "local",
@ -100,6 +106,7 @@ for j in range(3):
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul{}".format(j),
"sync_device": "ttl_urukul{}_sync".format(j),
"io_update_device": "ttl_urukul{}_io_update".format(j),
"refclk": 100e6,
"clk_sel": 0
@ -126,13 +133,13 @@ device_db.update({
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 33}
"arguments": {"channel": 36}
},
"led1": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 34}
"arguments": {"channel": 37}
}
})
@ -142,19 +149,19 @@ device_db.update({
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 35}
"arguments": {"channel": 38}
},
"ttl_zotino0_ldac": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 36}
"arguments": {"channel": 39}
},
"ttl_zotino0_clr": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 37}
"arguments": {"channel": 40}
},
"zotino0": {
"type": "local",

View File

@ -0,0 +1,183 @@
core_addr = "staging.ber.quartiq.de"
device_db = {
"core": {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {"host": core_addr, "ref_period": 1e-9}
},
"core_log": {
"type": "controller",
"host": "::1",
"port": 1068,
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",
"class": "CoreCache"
},
"core_dma": {
"type": "local",
"module": "artiq.coredevice.dma",
"class": "CoreDMA"
},
"i2c_switch0": {
"type": "local",
"module": "artiq.coredevice.i2c",
"class": "PCA9548",
"arguments": {"address": 0xe0}
},
"i2c_switch1": {
"type": "local",
"module": "artiq.coredevice.i2c",
"class": "PCA9548",
"arguments": {"address": 0xe2}
},
}
device_db.update({
"ttl" + str(i): {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLInOut" if i < 4 else "TTLOut",
"arguments": {"channel": i},
} for i in range(24)
})
device_db.update({
"spi_sampler0_adc": {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 24}
},
"spi_sampler0_pgia": {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 25}
},
"spi_sampler0_cnv": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 26},
},
"sampler0": {
"type": "local",
"module": "artiq.coredevice.sampler",
"class": "Sampler",
"arguments": {
"spi_adc_device": "spi_sampler0_adc",
"spi_pgia_device": "spi_sampler0_pgia",
"cnv_device": "spi_sampler0_cnv"
}
}
})
for j in range(2):
device_db.update({
"spi_urukul{}".format(j): {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 27 + 3*j}
},
"ttl_urukul{}_sync".format(j): {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLClockGen",
"arguments": {"channel": 28 + 3*j, "acc_width": 4}
},
"ttl_urukul{}_io_update".format(j): {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 29 + 3*j}
},
"urukul{}_cpld".format(j): {
"type": "local",
"module": "artiq.coredevice.urukul",
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul{}".format(j),
"sync_device": "ttl_urukul{}_sync".format(j),
"io_update_device": "ttl_urukul{}_io_update".format(j),
"refclk": 100e6,
"clk_sel": 0
}
}
})
device_db.update({
"urukul{}_ch{}".format(j, i): {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 40,
"chip_select": 4 + i,
"cpld_device": "urukul{}_cpld".format(j)
}
} for i in range(4)
})
device_db.update({
"grabber0": {
"type": "local",
"module": "artiq.coredevice.grabber",
"class": "grabber",
"arguments": {"channel_base": 33}
}
})
device_db.update({
"led0": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 35}
},
"led1": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 36}
}
})
device_db.update({
"spi_zotino0": {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 37}
},
"ttl_zotino0_ldac": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 38}
},
"ttl_zotino0_clr": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 39}
},
"zotino0": {
"type": "local",
"module": "artiq.coredevice.zotino",
"class": "Zotino",
"arguments": {
"spi_device": "spi_zotino0",
"ldac_device": "ttl_zotino0_ldac",
"clr_device": "ttl_zotino0_clr"
}
}
})

View File

@ -198,7 +198,7 @@ device_db = {
"class": "TTLOut",
"arguments": {"channel": 25}
},
"novogorny0" : {
"novogorny0": {
"type": "local",
"module": "artiq.coredevice.novogorny",
"class": "Novogorny",
@ -313,23 +313,94 @@ device_db = {
"arguments": {"channel": 33}
},
"spi_urukul1": {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 34}
},
"ttl_urukul1_sync": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLClockGen",
"arguments": {"channel": 35, "acc_width": 4}
},
"ttl_urukul1_io_update": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 36}
},
"urukul1_cpld": {
"type": "local",
"module": "artiq.coredevice.urukul",
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul1",
"sync_device": "ttl_urukul1_sync",
"io_update_device": "ttl_urukul1_io_update",
"refclk": 100e6,
"clk_sel": 1
}
},
"urukul1_ch0": {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 40,
"chip_select": 4,
"cpld_device": "urukul1_cpld"
}
},
"urukul1_ch1": {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 40,
"chip_select": 5,
"cpld_device": "urukul1_cpld"
}
},
"urukul1_ch2": {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 40,
"chip_select": 6,
"cpld_device": "urukul1_cpld"
}
},
"urukul1_ch3": {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 40,
"chip_select": 7,
"cpld_device": "urukul1_cpld"
}
},
"spi_zotino0": {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 36}
"arguments": {"channel": 37}
},
"ttl_zotino0_ldac": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 37}
"arguments": {"channel": 38}
},
"ttl_zotino0_clr": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 38}
"arguments": {"channel": 39}
},
"zotino0": {
"type": "local",

View File

@ -0,0 +1,240 @@
core_addr = "staging.ber.quartiq.de"
device_db = {
"core": {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {"host": core_addr, "ref_period": 1e-9}
},
"core_log": {
"type": "controller",
"host": "::1",
"port": 1068,
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",
"class": "CoreCache"
},
"core_dma": {
"type": "local",
"module": "artiq.coredevice.dma",
"class": "CoreDMA"
},
"i2c_switch0": {
"type": "local",
"module": "artiq.coredevice.i2c",
"class": "PCA9548",
"arguments": {"address": 0xe0}
},
"i2c_switch1": {
"type": "local",
"module": "artiq.coredevice.i2c",
"class": "PCA9548",
"arguments": {"address": 0xe2}
},
}
device_db.update({
"ttl" + str(i): {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLInOut" if i < 4 else "TTLOut",
"arguments": {"channel": i},
} for i in range(24)
})
device_db.update({
"spi_sampler0_adc": {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 24}
},
"spi_sampler0_pgia": {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 25}
},
"spi_sampler0_cnv": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 26},
},
"sampler0": {
"type": "local",
"module": "artiq.coredevice.sampler",
"class": "Sampler",
"arguments": {
"spi_adc_device": "spi_sampler0_adc",
"spi_pgia_device": "spi_sampler0_pgia",
"cnv_device": "spi_sampler0_cnv"
}
}
})
device_db.update({
"spi_urukul0": {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 27}
},
"ttl_urukul0_io_update": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 28}
},
"ttl_urukul0_sw0": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 29}
},
"ttl_urukul0_sw1": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 30}
},
"ttl_urukul0_sw2": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 31}
},
"ttl_urukul0_sw3": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 32}
},
"urukul0_cpld": {
"type": "local",
"module": "artiq.coredevice.urukul",
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul0",
"io_update_device": "ttl_urukul0_io_update",
"refclk": 100e6,
"clk_sel": 0
}
}
})
device_db.update({
"urukul0_ch" + str(i): {
"type": "local",
"module": "artiq.coredevice.ad9912",
"class": "AD9912",
"arguments": {
"pll_n": 10,
"chip_select": 4 + i,
"cpld_device": "urukul0_cpld",
"sw_device": "ttl_urukul0_sw" + str(i)
}
} for i in range(4)
})
device_db.update({
"spi_urukul1": {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 33}
},
"ttl_urukul1_sync": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLClockGen",
"arguments": {"channel": 34, "acc_width": 4}
},
"ttl_urukul1_io_update": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 35}
},
"urukul1_cpld": {
"type": "local",
"module": "artiq.coredevice.urukul",
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul1",
"sync_device": "ttl_urukul1_sync",
"io_update_device": "ttl_urukul1_io_update",
"refclk": 125e6,
"clk_sel": 0
}
}
})
device_db.update({
"urukul1_ch" + str(i): {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 32,
"chip_select": 4 + i,
"cpld_device": "urukul1_cpld"
}
} for i in range(4)
})
device_db.update({
"led0": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 36}
},
"led1": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 37}
}
})
device_db.update({
"spi_zotino0": {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 38}
},
"ttl_zotino0_ldac": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 39}
},
"ttl_zotino0_clr": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 40}
},
"zotino0": {
"type": "local",
"module": "artiq.coredevice.zotino",
"class": "Zotino",
"arguments": {
"spi_device": "spi_zotino0",
"ldac_device": "ttl_zotino0_ldac",
"clr_device": "ttl_zotino0_clr"
}
}
})

View File

@ -57,36 +57,42 @@ device_db.update(
"class": "SPIMaster",
"arguments": {"channel": 8}
},
ttl_urukul0_io_update={
ttl_urukul0_sync={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 9}
"class": "TTLClockGen",
"arguments": {"channel": 9, "acc_width": 4}
},
ttl_urukul0_sw0={
ttl_urukul0_io_update={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 10}
},
ttl_urukul0_sw1={
ttl_urukul0_sw0={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 11}
},
ttl_urukul0_sw2={
ttl_urukul0_sw1={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 12}
},
ttl_urukul0_sw3={
ttl_urukul0_sw2={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 13}
},
ttl_urukul0_sw3={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 14}
},
urukul0_cpld={
"type": "local",
"module": "artiq.coredevice.urukul",
@ -94,6 +100,7 @@ device_db.update(
"arguments": {
"spi_device": "spi_urukul0",
"io_update_device": "ttl_urukul0_io_update",
"sync_device": "ttl_urukul0_sync",
"refclk": 125e6,
"clk_sel": 0
}
@ -119,19 +126,19 @@ device_db["spi_sampler0_adc"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 14}
"arguments": {"channel": 15}
}
device_db["spi_sampler0_pgia"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 15}
"arguments": {"channel": 16}
}
device_db["spi_sampler0_cnv"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 16},
"arguments": {"channel": 17},
}
device_db["sampler0"] = {
"type": "local",
@ -150,19 +157,19 @@ device_db["spi_zotino0"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 17}
"arguments": {"channel": 18}
}
device_db["ttl_zotino0_ldac"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 18}
"arguments": {"channel": 19}
}
device_db["ttl_zotino0_clr"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 19}
"arguments": {"channel": 20}
}
device_db["zotino0"] = {
"type": "local",
@ -181,7 +188,7 @@ device_db["grabber0"] = {
"type": "local",
"module": "artiq.coredevice.grabber",
"class": "Grabber",
"arguments": {"channel_base": 20}
"arguments": {"channel_base": 21}
}
@ -191,13 +198,19 @@ device_db.update(
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 22}
"arguments": {"channel": 23}
},
ttl_urukul1_sync={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLClockGen",
"arguments": {"channel": 24, "acc_width": 4}
},
ttl_urukul1_io_update={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 23}
"arguments": {"channel": 25}
},
urukul1_cpld={
"type": "local",
@ -206,6 +219,7 @@ device_db.update(
"arguments": {
"spi_device": "spi_urukul1",
"io_update_device": "ttl_urukul1_io_update",
"sync_device": "ttl_urukul1_sync",
"refclk": 100e6,
"clk_sel": 1
}
@ -231,7 +245,7 @@ for i in range(8):
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 24 + i},
"arguments": {"channel": 26 + i},
}
@ -241,7 +255,7 @@ for i in range(8):
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 32 + i},
"arguments": {"channel": 34 + i},
}
@ -250,19 +264,19 @@ device_db["spi_sampler1_adc"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 40}
"arguments": {"channel": 42}
}
device_db["spi_sampler1_pgia"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 41}
"arguments": {"channel": 43}
}
device_db["spi_sampler1_cnv"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 42},
"arguments": {"channel": 44},
}
device_db["sampler1"] = {
"type": "local",
@ -281,19 +295,19 @@ device_db["spi_zotino1"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 43}
"arguments": {"channel": 45}
}
device_db["ttl_zotino1_ldac"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 44}
"arguments": {"channel": 46}
}
device_db["ttl_zotino1_clr"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 45}
"arguments": {"channel": 47}
}
device_db["zotino1"] = {
"type": "local",
@ -312,13 +326,13 @@ device_db.update(
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 46}
"arguments": {"channel": 48}
},
led1={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 47}
"arguments": {"channel": 49}
},
)
@ -328,4 +342,10 @@ device_db.update(
loop_out="ttl4",
loop_in="ttl0",
# Urukul CPLD with sync and io_update, IFC MODE 0b1000
urukul_cpld="urukul0_cpld",
# Urukul AD9910 with switch TTL, internal 125 MHz MMCX connection
# FIXME: MMCX not connected
# urukul_ad9910="urukul0_ch0",
)

View File

@ -75,7 +75,7 @@ class Urukul(_EEM):
),
]
ttls = [(6, eem, "io_update"),
(7, eem, "dds_reset")]
(7, eem, "dds_reset_sync_in")]
if eem_aux is not None:
ttls += [(0, eem_aux, "sync_clk"),
(1, eem_aux, "sync_in"),
@ -113,7 +113,7 @@ class Urukul(_EEM):
),
]
ttls = [(6, eem0, "io_update"),
(7, eem0, "dds_reset"),
(7, eem0, "dds_reset_sync_in"),
(4, eem1, "sw0"),
(5, eem1, "sw1"),
(6, eem1, "sw2"),
@ -148,7 +148,8 @@ class Urukul(_EEM):
return ios
@classmethod
def add_std(cls, target, eem, eem_aux, ttl_out_cls, iostandard="LVDS_25"):
def add_std(cls, target, eem, eem_aux, ttl_out_cls, sync_gen_cls=None,
iostandard="LVDS_25"):
cls.add_extension(target, eem, eem_aux, iostandard=iostandard)
phy = spi2.SPIMaster(target.platform.request("urukul{}_spi_p".format(eem)),
@ -156,8 +157,13 @@ class Urukul(_EEM):
target.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4))
pads = target.platform.request("urukul{}_dds_reset".format(eem))
target.specials += DifferentialOutput(0, pads.p, pads.n)
pads = target.platform.request("urukul{}_dds_reset_sync_in".format(eem))
pad = Signal(reset=0)
target.specials += DifferentialOutput(pad, pads.p, pads.n)
if sync_gen_cls is not None: # AD9910 variant and SYNC_IN from EEM
phy = sync_gen_cls(pad, ftw_width=4)
target.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy))
pads = target.platform.request("urukul{}_io_update".format(eem))
phy = ttl_out_cls(pads.p, pads.n)
@ -170,7 +176,6 @@ class Urukul(_EEM):
target.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy))
class Sampler(_EEM):
@staticmethod
def io(eem, eem_aux, iostandard="LVDS_25"):
@ -522,7 +527,7 @@ class SUServo(_EEM):
target.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4))
pads = target.platform.request("{}_dds_reset".format(eem_urukul0))
pads = target.platform.request("{}_dds_reset_sync_in".format(eem_urukul0))
target.specials += DifferentialOutput(0, pads.p, pads.n)
for i, signal in enumerate("sw0 sw1 sw2 sw3".split()):

View File

@ -175,7 +175,8 @@ class Opticlock(_StandaloneBase):
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X,
ttl_simple.ClockGen)
eem.Zotino.add_std(self, 7, ttl_serdes_7series.Output_8X)
self.config["HAS_RTIO_LOG"] = None
@ -574,6 +575,47 @@ class PTB(_StandaloneBase):
self.add_rtio(self.rtio_channels)
class PTB2(_StandaloneBase):
"""PTB Kasli variant with Urukul1 SYNC and external reference clock"""
def __init__(self, hw_rev=None, **kwargs):
if hw_rev is None:
hw_rev = "v1.1"
_StandaloneBase.__init__(self, hw_rev=hw_rev, **kwargs)
self.config["SI5324_AS_SYNTHESIZER"] = None
self.config["SI5324_EXT_REF"] = None
self.config["RTIO_FREQUENCY"] = "125.0"
if hw_rev == "v1.0":
# EEM clock fan-out from Si5324, not MMCX
self.comb += self.platform.request("clk_sel").eq(1)
self.rtio_channels = []
eem.DIO.add_std(self, 0,
ttl_serdes_7series.InOut_8X, ttl_serdes_7series.Output_8X)
eem.DIO.add_std(self, 1,
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
eem.DIO.add_std(self, 2,
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
eem.Sampler.add_std(self, 3, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 5, 4, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X,
ttl_simple.ClockGen)
for i in (1, 2):
sfp_ctl = self.platform.request("sfp_ctl", i)
phy = ttl_simple.Output(sfp_ctl.led)
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
eem.Zotino.add_std(self, 7, ttl_serdes_7series.Output_8X)
self.config["HAS_RTIO_LOG"] = None
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
self.rtio_channels.append(rtio.LogChannel())
self.add_rtio(self.rtio_channels)
class HUB(_StandaloneBase):
"""HUB Kasli variant
@ -600,9 +642,12 @@ class HUB(_StandaloneBase):
eem.DIO.add_std(self, 2,
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
eem.Sampler.add_std(self, 3, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 4, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 5, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 4, None, ttl_serdes_7series.Output_8X,
ttl_simple.ClockGen)
eem.Urukul.add_std(self, 5, None, ttl_serdes_7series.Output_8X,
ttl_simple.ClockGen)
eem.Urukul.add_std(self, 6, None, ttl_serdes_7series.Output_8X,
ttl_simple.ClockGen)
for i in (1, 2):
sfp_ctl = self.platform.request("sfp_ctl", i)
@ -645,8 +690,10 @@ class LUH(_StandaloneBase):
eem.DIO.add_std(self, 2,
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
eem.Sampler.add_std(self, 3, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 4, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 5, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 4, None, ttl_serdes_7series.Output_8X,
ttl_simple.ClockGen)
eem.Urukul.add_std(self, 5, None, ttl_serdes_7series.Output_8X,
ttl_simple.ClockGen)
eem.Grabber.add_std(self, 6)
for i in (1, 2):
@ -688,11 +735,13 @@ class Tester(_StandaloneBase):
self.grabber_csr_group = []
eem.DIO.add_std(self, 5,
ttl_serdes_7series.InOut_8X, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 0, 1, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 0, 1, ttl_serdes_7series.Output_8X,
ttl_simple.ClockGen)
eem.Sampler.add_std(self, 3, 2, ttl_serdes_7series.Output_8X)
eem.Zotino.add_std(self, 4, ttl_serdes_7series.Output_8X)
eem.Grabber.add_std(self, 6)
eem.Urukul.add_std(self, 7, None, ttl_serdes_7series.Output_8X)
eem.Urukul.add_std(self, 7, None, ttl_serdes_7series.Output_8X,
ttl_simple.ClockGen)
eem.DIO.add_std(self, 8,
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
eem.DIO.add_std(self, 9,
@ -1079,8 +1128,8 @@ def main():
soc_kasli_args(parser)
parser.set_defaults(output_dir="artiq_kasli")
variants = {cls.__name__.lower(): cls for cls in [
Opticlock, SUServo, SYSU, MITLL, MITLL2, USTC,
Tsinghua, Tsinghua2, WIPM, NUDT, PTB, HUB, LUH,
Opticlock, SUServo, PTB, PTB2, HUB, LUH,
SYSU, MITLL, MITLL2, USTC, Tsinghua, Tsinghua2, WIPM, NUDT,
VLBAIMaster, VLBAISatellite, Tester, Master, Satellite]}
parser.add_argument("-V", "--variant", default="opticlock",
help="variant: {} (default: %(default)s)".format(

View File

@ -0,0 +1,180 @@
from artiq.experiment import *
from artiq.test.hardware_testbench import ExperimentCase
from artiq.coredevice.ad9910 import _AD9910_REG_FTW
from artiq.coredevice.urukul import (
urukul_sta_smp_err, CFG_CLK_SEL0, CFG_CLK_SEL1)
class AD9910Exp(EnvExperiment):
def build(self, runner):
self.setattr_device("core")
self.dev = self.get_device("urukul_ad9910")
self.runner = runner
def run(self):
getattr(self, self.runner)()
@kernel
def instantiate(self):
pass
@kernel
def init(self):
self.core.break_realtime()
self.dev.cpld.init()
self.dev.init()
@kernel
def init_fail(self):
self.core.break_realtime()
self.dev.cpld.init()
cfg = self.dev.cpld.cfg_reg
cfg &= ~(1 << CFG_CLK_SEL1)
cfg |= 1 << CFG_CLK_SEL0
self.dev.cpld.cfg_write(cfg)
# clk_sel=1, external SMA, should fail PLL lock
self.dev.init()
@kernel
def set_get(self):
self.core.break_realtime()
self.dev.cpld.init()
self.dev.init()
self.dev.set_att(20*dB)
f = 81.2345*MHz
self.dev.set(frequency=f, phase=.33, amplitude=.89)
self.set_dataset("ftw_set", self.dev.frequency_to_ftw(f))
self.set_dataset("ftw_get", self.dev.read32(_AD9910_REG_FTW))
@kernel
def set_speed(self):
self.core.break_realtime()
self.dev.cpld.init()
self.dev.init()
f = 81.2345*MHz
n = 10
t0 = self.core.get_rtio_counter_mu()
for i in range(n):
self.dev.set(frequency=f, phase=.33, amplitude=.89)
self.set_dataset("dt", self.core.mu_to_seconds(
self.core.get_rtio_counter_mu() - t0)/n)
@kernel
def set_speed_mu(self):
self.core.break_realtime()
self.dev.cpld.init()
self.dev.init()
n = 10
t0 = self.core.get_rtio_counter_mu()
for i in range(n):
self.dev.set_mu(0x12345678, 0x1234, 0x4321)
self.set_dataset("dt", self.core.mu_to_seconds(
self.core.get_rtio_counter_mu() - t0)/n)
@kernel
def sync_window(self):
self.core.break_realtime()
self.dev.cpld.init()
self.dev.init()
dly, win = self.dev.tune_sync_delay(self.dev.sync_delay_seed)
err = [0] * 32
self.sync_scan(err, win=win + 1) # tighten window by 2*75ps
self.set_dataset("dly", dly)
self.set_dataset("win", win)
self.set_dataset("err", err)
@kernel
def sync_scan(self, err, win):
for in_delay in range(len(err)):
self.dev.set_sync(in_delay=in_delay, window=win)
self.dev.clear_smp_err()
delay(10*us) # integrate SMP_ERR statistics
e = urukul_sta_smp_err(self.dev.cpld.sta_read())
err[in_delay] = (e >> (self.dev.chip_select - 4)) & 1
delay(50*us) # slack
@kernel
def io_update_delay(self):
self.core.break_realtime()
self.dev.cpld.init()
self.dev.init()
bins = [0]*8
self.scan_io_delay(bins)
self.set_dataset("bins", bins)
self.set_dataset("dly", self.dev.io_update_delay)
@kernel
def scan_io_delay(self, bins):
delay(100*us)
n = 100
for i in range(n):
for phase in range(len(bins)):
bins[phase] += self.dev.measure_io_update_alignment(phase)
delay(10*ms)
@kernel
def sw_readback(self):
self.core.break_realtime()
self.dev.cpld.init()
self.dev.init()
self.dev.cfg_sw(0)
self.dev.sw.on()
sw_on = (self.dev.cpld.sta_read() >> (self.dev.chip_select - 4)) & 1
delay(10*us)
self.dev.sw.off()
sw_off = (self.dev.cpld.sta_read() >> (self.dev.chip_select - 4)) & 1
self.set_dataset("sw", (sw_on, sw_off))
class AD9910Test(ExperimentCase):
def test_instantiate(self):
self.execute(AD9910Exp, "instantiate")
def test_init(self):
self.execute(AD9910Exp, "init")
def test_init_fail(self):
with self.assertRaises(ValueError):
self.execute(AD9910Exp, "init_fail")
def test_set_get(self):
self.execute(AD9910Exp, "set_get")
ftw_get = self.dataset_mgr.get("ftw_get")
ftw_set = self.dataset_mgr.get("ftw_set")
self.assertEqual(ftw_get, ftw_set)
def test_set_speed(self):
self.execute(AD9910Exp, "set_speed")
dt = self.dataset_mgr.get("dt")
print(dt)
self.assertLess(dt, 70*us)
def test_set_speed_mu(self):
self.execute(AD9910Exp, "set_speed_mu")
dt = self.dataset_mgr.get("dt")
print(dt)
self.assertLess(dt, 10*us)
def test_sync_window(self):
self.execute(AD9910Exp, "sync_window")
err = self.dataset_mgr.get("err")
dly = self.dataset_mgr.get("dly")
win = self.dataset_mgr.get("win")
print(dly, win, err)
# make sure one tap margin on either side of optimal delay
for i in -1, 0, 1:
self.assertEqual(err[i + dly], 0)
def test_io_update_delay(self):
self.execute(AD9910Exp, "io_update_delay")
dly = self.dataset_mgr.get("dly")
bins = self.dataset_mgr.get("bins")
print(dly, bins)
n = max(bins)
# test for 4-periodicity (SYNC_CLK) and maximal contrast
for i in range(len(bins)):
self.assertEqual(abs(bins[i] - bins[(i + 4) % 8]), n)
def test_sw_readback(self):
self.execute(AD9910Exp, "sw_readback")
self.assertEqual(self.dataset_mgr.get("sw"), (1, 0))

View File

@ -0,0 +1,149 @@
from artiq.experiment import *
from artiq.test.hardware_testbench import ExperimentCase
from artiq.coredevice import urukul
class UrukulExp(EnvExperiment):
def build(self, runner):
self.setattr_device("core")
self.dev = self.get_device("urukul_cpld")
self.runner = runner
def run(self):
getattr(self, self.runner)()
@kernel
def instantiate(self):
pass
@kernel
def init(self):
self.core.break_realtime()
self.dev.init()
@kernel
def cfg_write(self):
self.core.break_realtime()
self.dev.init()
self.dev.cfg_write(self.dev.cfg_reg)
@kernel
def sta_read(self):
self.core.break_realtime()
self.dev.init()
sta = self.dev.sta_read()
self.set_dataset("sta", sta)
@kernel
def switches(self):
self.core.break_realtime()
self.dev.init()
self.dev.io_rst()
self.dev.cfg_sw(0, 0)
self.dev.cfg_sw(0, 1)
self.dev.cfg_sw(3, 1)
self.dev.cfg_switches(0b1010)
@kernel
def switch_speed(self):
self.core.break_realtime()
self.dev.init()
n = 10
t0 = self.core.get_rtio_counter_mu()
for i in range(n):
self.dev.cfg_sw(3, i & 1)
self.set_dataset("dt", self.core.mu_to_seconds(
self.core.get_rtio_counter_mu() - t0)/n)
@kernel
def switches_readback(self):
self.core.reset() # clear switch TTLs
self.dev.init()
sw_set = 0b1010
self.dev.cfg_switches(sw_set)
sta_get = self.dev.sta_read()
self.set_dataset("sw_set", sw_set)
self.set_dataset("sta_get", sta_get)
@kernel
def att(self):
self.core.break_realtime()
self.dev.init()
att_set = 0x12345678
self.dev.set_all_att_mu(att_set)
att_get = self.dev.get_att_mu()
self.set_dataset("att_set", att_set)
self.set_dataset("att_get", att_get)
@kernel
def att_speed(self):
self.core.break_realtime()
self.dev.init()
n = 10
t0 = self.core.get_rtio_counter_mu()
for i in range(n):
self.dev.set_att(3, 30*dB)
self.set_dataset("dt", self.core.mu_to_seconds(
self.core.get_rtio_counter_mu() - t0)/n)
@kernel
def io_update(self):
self.core.break_realtime()
self.dev.init()
self.dev.io_update.pulse_mu(8)
@kernel
def sync(self):
self.core.break_realtime()
self.dev.init()
self.dev.set_sync_div(2)
class UrukulTest(ExperimentCase):
def test_instantiate(self):
self.execute(UrukulExp, "instantiate")
def test_init(self):
self.execute(UrukulExp, "init")
def test_cfg_write(self):
self.execute(UrukulExp, "cfg_write")
def test_sta_read(self):
self.execute(UrukulExp, "sta_read")
sta = self.dataset_mgr.get("sta")
print(hex(sta))
# self.assertEqual(urukul.urukul_sta_ifc_mode(sta), 0b0001)
def test_switches(self):
self.execute(UrukulExp, "switches")
def test_switch_speed(self):
self.execute(UrukulExp, "switch_speed")
dt = self.dataset_mgr.get("dt")
print(dt)
self.assertLess(dt, 5*us)
def test_switches_readback(self):
self.execute(UrukulExp, "switches_readback")
sw_get = urukul.urukul_sta_rf_sw(self.dataset_mgr.get("sta_get"))
sw_set = self.dataset_mgr.get("sw_set")
self.assertEqual(sw_get, sw_set)
def test_att(self):
self.execute(UrukulExp, "att")
att_set = self.dataset_mgr.get("att_set")
att_get = self.dataset_mgr.get("att_get")
self.assertEqual(att_set, att_get)
def test_att_speed(self):
self.execute(UrukulExp, "att_speed")
dt = self.dataset_mgr.get("dt")
print(dt)
self.assertLess(dt, 5*us)
def test_io_update(self):
self.execute(UrukulExp, "io_update")
def test_sync(self):
self.execute(UrukulExp, "sync")