forked from M-Labs/artiq
Merge branch 'master' into nac3
This commit is contained in:
commit
0953a07582
@ -3,48 +3,66 @@
|
|||||||
Release notes
|
Release notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
Unreleased
|
||||||
|
----------
|
||||||
|
|
||||||
|
Highlights:
|
||||||
|
|
||||||
|
* Implemented Phaser-servo. This requires recent gateware on Phaser.
|
||||||
|
|
||||||
|
|
||||||
ARTIQ-7
|
ARTIQ-7
|
||||||
-------
|
-------
|
||||||
|
|
||||||
Highlights:
|
Highlights:
|
||||||
|
|
||||||
* New hardware support:
|
* New hardware support:
|
||||||
- Kasli-SoC, a new EEM carrier based on a Zynq SoC, enabling much faster kernel execution.
|
- Kasli-SoC, a new EEM carrier based on a Zynq SoC, enabling much faster kernel execution
|
||||||
|
(see: https://arxiv.org/abs/2111.15290).
|
||||||
|
- DRTIO support on Zynq-based devices (Kasli-SoC and ZC706).
|
||||||
|
- DRTIO support on KC705.
|
||||||
- HVAMP_8CH 8 channel HV amplifier for Fastino / Zotinos
|
- HVAMP_8CH 8 channel HV amplifier for Fastino / Zotinos
|
||||||
- Almazny mezzanine board for Mirny
|
- Almazny mezzanine board for Mirny
|
||||||
* TTL device output can be now configured to work as a clock generator.
|
- Phaser: improved documentation, exposed the DAC coarse mixer and ``sif_sync``, exposed upconverter calibration
|
||||||
|
and enabling/disabling of upconverter LO & RF outputs, added helpers to align Phaser updates to the
|
||||||
|
RTIO timeline (``get_next_frame_mu()``
|
||||||
|
- Urukul: ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912.
|
||||||
* Softcore targets now use the RISC-V architecture (VexRiscv) instead of OR1K (mor1kx).
|
* Softcore targets now use the RISC-V architecture (VexRiscv) instead of OR1K (mor1kx).
|
||||||
* Gateware FPU is supported on KC705 and Kasli 2.0.
|
* Gateware FPU is supported on KC705 and Kasli 2.0.
|
||||||
* Faster compilation for large arrays/lists.
|
* Faster compilation for large arrays/lists.
|
||||||
* Phaser:
|
* Faster exception handling.
|
||||||
- Improved documentation
|
* Several exception handling bugs fixed.
|
||||||
- Expose the DAC coarse mixer and ``sif_sync``
|
* Support for a simpler shared library system with faster calls into the runtime. This is only used by the NAC3
|
||||||
- Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs.
|
compiler (nac3ld) and improves RTIO output performance (test_pulse_rate) by 9-10%.
|
||||||
- Add helpers to align Phaser updates to the RTIO timeline (``get_next_frame_mu()``)
|
* Moninj improvements:
|
||||||
* Core device moninj is now proxied via the ``aqctl_moninj_proxy`` controller.
|
- Urukul monitoring and frequency setting (through dashboard) is now supported.
|
||||||
|
- Core device moninj is now proxied via the ``aqctl_moninj_proxy`` controller.
|
||||||
* The configuration entry ``rtio_clock`` supports multiple clocking settings, deprecating the usage
|
* The configuration entry ``rtio_clock`` supports multiple clocking settings, deprecating the usage
|
||||||
of compile-time options.
|
of compile-time options.
|
||||||
* Packaging via Nix Flakes.
|
* Added support for 100MHz RTIO clock in DRTIO.
|
||||||
* Firmware and gateware can now be built on-demand on the M-Labs server using ``afws_client``
|
* Previously detected RTIO async errors are reported to the host after each kernel terminates and a
|
||||||
(subscribers only).
|
warning is logged. The warning is additional to the one already printed in the core device log
|
||||||
|
immediately upon detection of the error.
|
||||||
* Extended Kasli gateware JSON description with configuration for SPI over DIO.
|
* Extended Kasli gateware JSON description with configuration for SPI over DIO.
|
||||||
* ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912.
|
* TTL outputs can be now configured to work as a clock generator from the JSON.
|
||||||
* On Kasli, the number of FIFO lanes in the scalable events dispatcher (SED) can now be configured in
|
* On Kasli, the number of FIFO lanes in the scalable events dispatcher (SED) can now be configured in
|
||||||
the JSON hardware description file.
|
the JSON.
|
||||||
* ``artiq_ddb_template`` generates edge-counter keys that start with the key of the corresponding
|
* ``artiq_ddb_template`` generates edge-counter keys that start with the key of the corresponding
|
||||||
TTL device (e.g. ``ttl_0_counter`` for the edge counter on TTL device ``ttl_0``).
|
TTL device (e.g. ``ttl_0_counter`` for the edge counter on TTL device ``ttl_0``).
|
||||||
* ``artiq_master`` now has an ``--experiment-subdir`` option to scan only a subdirectory of the
|
* ``artiq_master`` now has an ``--experiment-subdir`` option to scan only a subdirectory of the
|
||||||
repository when building the list of experiments.
|
repository when building the list of experiments.
|
||||||
* Added support for 100MHz RTIO clock in DRTIO.
|
* Experiments can now be submitted by-content.
|
||||||
* Previously detected RTIO async errors are reported to the host after each kernel terminates and a
|
* The master can now optionally log all experiments submitted into a CSV file.
|
||||||
warning is logged. The warning is additional to the one already printed in the core device log upon
|
|
||||||
detection of the error.
|
|
||||||
* Removed worker DB warning for writing a dataset that is also in the archive.
|
* Removed worker DB warning for writing a dataset that is also in the archive.
|
||||||
* ``PCA9548`` I2C switch class renamed to ``I2CSwitch``, to accomodate support for PCA9547, and
|
* Experiments can now call ``scheduler.check_termination()`` to test if the user
|
||||||
possibly other switches in future. Readback has been removed, and now only one channel per
|
has requested graceful termination.
|
||||||
switch is supported.
|
* ARTIQ command-line programs and controllers now exit cleanly on Ctrl-C.
|
||||||
* The "ip" config option can now be set to "use_dhcp" in order to use DHCP to obtain an IP address.
|
* ``artiq_coremgmt reboot`` now reloads gateware as well, providing a more thorough and reliable
|
||||||
DHCP will also be used if no "ip" config option is set.
|
device reset (7-series FPGAs only).
|
||||||
|
* Firmware and gateware can now be built on-demand on the M-Labs server using ``afws_client``
|
||||||
|
(subscribers only). Self-compilation remains possible.
|
||||||
|
* Easier-to-use packaging via Nix Flakes.
|
||||||
|
* Python 3.10 support (experimental).
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
|
||||||
@ -59,6 +77,10 @@ Breaking changes:
|
|||||||
* Mirny: Added extra delays in ``ADF5356.sync()``. This avoids the need of an extra delay before
|
* Mirny: Added extra delays in ``ADF5356.sync()``. This avoids the need of an extra delay before
|
||||||
calling `ADF5356.init()`.
|
calling `ADF5356.init()`.
|
||||||
* The deprecated ``set_dataset(..., save=...)`` is no longer supported.
|
* The deprecated ``set_dataset(..., save=...)`` is no longer supported.
|
||||||
|
* The ``PCA9548`` I2C switch class was renamed to ``I2CSwitch``, to accomodate support for PCA9547,
|
||||||
|
and possibly other switches in future. Readback has been removed, and now only one channel per
|
||||||
|
switch is supported.
|
||||||
|
|
||||||
|
|
||||||
ARTIQ-6
|
ARTIQ-6
|
||||||
-------
|
-------
|
||||||
|
@ -41,6 +41,14 @@ PHASER_ADDR_DUC1_P = 0x26
|
|||||||
PHASER_ADDR_DAC1_DATA = 0x28
|
PHASER_ADDR_DAC1_DATA = 0x28
|
||||||
PHASER_ADDR_DAC1_TEST = 0x2c
|
PHASER_ADDR_DAC1_TEST = 0x2c
|
||||||
|
|
||||||
|
# servo registers
|
||||||
|
PHASER_ADDR_SERVO_CFG0 = 0x30
|
||||||
|
PHASER_ADDR_SERVO_CFG1 = 0x31
|
||||||
|
|
||||||
|
# 0x32 - 0x71 servo coefficients + offset data
|
||||||
|
PHASER_ADDR_SERVO_DATA_BASE = 0x32
|
||||||
|
|
||||||
|
|
||||||
PHASER_SEL_DAC = 1 << 0
|
PHASER_SEL_DAC = 1 << 0
|
||||||
PHASER_SEL_TRF0 = 1 << 1
|
PHASER_SEL_TRF0 = 1 << 1
|
||||||
PHASER_SEL_TRF1 = 1 << 2
|
PHASER_SEL_TRF1 = 1 << 2
|
||||||
@ -59,6 +67,11 @@ PHASER_DAC_SEL_TEST = 1
|
|||||||
|
|
||||||
PHASER_HW_REV_VARIANT = 1 << 4
|
PHASER_HW_REV_VARIANT = 1 << 4
|
||||||
|
|
||||||
|
SERVO_COEFF_WIDTH = 16
|
||||||
|
SERVO_DATA_WIDTH = 16
|
||||||
|
SERVO_COEFF_SHIFT = 14
|
||||||
|
SERVO_T_CYCLE = (32+12+192+24+4)*ns # Must match gateware ADC parameters
|
||||||
|
|
||||||
|
|
||||||
@nac3
|
@nac3
|
||||||
class Phaser:
|
class Phaser:
|
||||||
@ -114,6 +127,31 @@ class Phaser:
|
|||||||
configured through a shared SPI bus that is accessed and controlled via
|
configured through a shared SPI bus that is accessed and controlled via
|
||||||
FPGA registers.
|
FPGA registers.
|
||||||
|
|
||||||
|
Each phaser output channel features a servo to control the RF output amplitude
|
||||||
|
using feedback from an ADC. The servo consists of a first order IIR (infinite
|
||||||
|
impulse response) filter fed by the ADC and a multiplier that scales the I
|
||||||
|
and Q datastreams from the DUC by the IIR output. The IIR state is updated at
|
||||||
|
the 3.788 MHz ADC sampling rate.
|
||||||
|
|
||||||
|
Each channel IIR features 4 profiles, each consisting of the [b0, b1, a1] filter
|
||||||
|
coefficients as well as an output offset. The coefficients and offset can be
|
||||||
|
set for each profile individually and the profiles each have their own ``y0``,
|
||||||
|
``y1`` output registers (the ``x0``, ``x1`` inputs are shared). To avoid
|
||||||
|
transient effects, care should be taken to not update the coefficents in the
|
||||||
|
currently selected profile.
|
||||||
|
|
||||||
|
The servo can be en- or disabled for each channel. When disabled, the servo
|
||||||
|
output multiplier is simply bypassed and the datastream reaches the DAC unscaled.
|
||||||
|
|
||||||
|
The IIR output can be put on hold for each channel. In hold mode, the filter
|
||||||
|
still ingests samples and updates its input ``x0`` and ``x1`` registers, but
|
||||||
|
does not update the ``y0``, ``y1`` output registers.
|
||||||
|
|
||||||
|
After power-up the servo is disabled, in profile 0, with coefficients [0, 0, 0]
|
||||||
|
and hold is enabled. If older gateware without ther servo is loaded onto the
|
||||||
|
Phaser FPGA, the device simply behaves as if the servo is disabled and none of
|
||||||
|
the servo functions have any effect.
|
||||||
|
|
||||||
.. note:: Various register settings of the DAC and the quadrature
|
.. note:: Various register settings of the DAC and the quadrature
|
||||||
upconverters are available to be modified through the `dac`, `trf0`,
|
upconverters are available to be modified through the `dac`, `trf0`,
|
||||||
`trf1` dictionaries. These can be set through the device database
|
`trf1` dictionaries. These can be set through the device database
|
||||||
@ -322,6 +360,8 @@ class Phaser:
|
|||||||
self.core.delay(.1*ms)
|
self.core.delay(.1*ms)
|
||||||
channel.set_att_mu(0x00) # minimum attenuation
|
channel.set_att_mu(0x00) # minimum attenuation
|
||||||
|
|
||||||
|
channel.set_servo(profile=0, enable=False, hold=True)
|
||||||
|
|
||||||
# test oscillators and DUC
|
# test oscillators and DUC
|
||||||
for i in range(len(channel.oscillator)):
|
for i in range(len(channel.oscillator)):
|
||||||
oscillator = channel.oscillator[i]
|
oscillator = channel.oscillator[i]
|
||||||
@ -394,6 +434,12 @@ class Phaser:
|
|||||||
response = rtio_input_data(self.channel_base)
|
response = rtio_input_data(self.channel_base)
|
||||||
return response >> self.miso_delay
|
return response >> self.miso_delay
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def write16(self, addr: int32, data: int32):
|
||||||
|
"""Write 16 bit to a sequence of FPGA registers."""
|
||||||
|
self.write8(addr, data >> 8)
|
||||||
|
self.write8(addr + 1, data)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write32(self, addr: int32, data: int32):
|
def write32(self, addr: int32, data: int32):
|
||||||
"""Write 32 bit to a sequence of FPGA registers."""
|
"""Write 32 bit to a sequence of FPGA registers."""
|
||||||
@ -1059,6 +1105,133 @@ class PhaserChannel:
|
|||||||
data = data ^ ((1 << 12) | (1 << 13))
|
data = data ^ ((1 << 12) | (1 << 13))
|
||||||
self.trf_write(data)
|
self.trf_write(data)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_servo(self, profile: int32 = 0, enable: bool = False, hold: bool = False):
|
||||||
|
"""Set the servo configuration.
|
||||||
|
|
||||||
|
:param enable: True to enable servo, False to disable servo (default). If disabled,
|
||||||
|
the servo is bypassed and hold is enforced since the control loop is broken.
|
||||||
|
:param hold: True to hold the servo IIR filter output constant, False for normal operation.
|
||||||
|
:param profile: Profile index to select for channel. (0 to 3)
|
||||||
|
"""
|
||||||
|
if (profile < 0) or (profile > 3):
|
||||||
|
raise ValueError("invalid profile index")
|
||||||
|
addr = PHASER_ADDR_SERVO_CFG0 + self.index
|
||||||
|
# enforce hold if the servo is disabled
|
||||||
|
data = (profile << 2) | ((int32(hold) | int32(not enable)) << 1) | int32(enable)
|
||||||
|
self.phaser.write8(addr, data)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_iir_mu(self, profile: int32, b0: int32, b1: int32, a1: int32, offset: int32):
|
||||||
|
"""Load a servo profile consiting of the three filter coefficients and an output offset.
|
||||||
|
|
||||||
|
Avoid setting the IIR parameters of the currently active profile.
|
||||||
|
|
||||||
|
The recurrence relation is (all data signed and MSB aligned):
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
a_0 y_n = a_1 y_{n - 1} + b_0 x_n + b_1 x_{n - 1} + o
|
||||||
|
|
||||||
|
Where:
|
||||||
|
|
||||||
|
* :math:`y_n` and :math:`y_{n-1}` are the current and previous
|
||||||
|
filter outputs, clipped to :math:`[0, 1[`.
|
||||||
|
* :math:`x_n` and :math:`x_{n-1}` are the current and previous
|
||||||
|
filter inputs in :math:`[-1, 1[`.
|
||||||
|
* :math:`o` is the offset
|
||||||
|
* :math:`a_0` is the normalization factor :math:`2^{14}`
|
||||||
|
* :math:`a_1` is the feedback gain
|
||||||
|
* :math:`b_0` and :math:`b_1` are the feedforward gains for the two
|
||||||
|
delays
|
||||||
|
|
||||||
|
.. seealso:: :meth:`set_iir`
|
||||||
|
|
||||||
|
:param profile: Profile to set (0 to 3)
|
||||||
|
:param b0: b0 filter coefficient (16 bit signed)
|
||||||
|
:param b1: b1 filter coefficient (16 bit signed)
|
||||||
|
:param a1: a1 filter coefficient (16 bit signed)
|
||||||
|
:param offset: Output offset (16 bit signed)
|
||||||
|
"""
|
||||||
|
if (profile < 0) or (profile > 3):
|
||||||
|
raise ValueError("invalid profile index")
|
||||||
|
# 32 byte-sized data registers per channel and 8 (2 bytes * (3 coefficients + 1 offset)) registers per profile
|
||||||
|
addr = PHASER_ADDR_SERVO_DATA_BASE + (8 * profile) + (self.index * 32)
|
||||||
|
for data in [b0, b1, a1, offset]:
|
||||||
|
self.phaser.write16(addr, data)
|
||||||
|
addr += 2
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_iir(self, profile: int32, kp: float, ki: float = 0., g: float = 0., x_offset: float = 0., y_offset: float = 0.):
|
||||||
|
"""Set servo profile IIR coefficients.
|
||||||
|
|
||||||
|
Avoid setting the IIR parameters of the currently active profile.
|
||||||
|
|
||||||
|
Gains are given in units of output full per scale per input full scale.
|
||||||
|
|
||||||
|
.. note:: Due to inherent constraints of the fixed point datatypes and IIR
|
||||||
|
filters, the ``x_offset`` (setpoint) resolution depends on the selected gains.
|
||||||
|
Low ``ki`` gains will lead to a low ``x_offset`` resolution.
|
||||||
|
|
||||||
|
The transfer function is (up to time discretization and
|
||||||
|
coefficient quantization errors):
|
||||||
|
|
||||||
|
.. math::
|
||||||
|
H(s) = k_p + \\frac{k_i}{s + \\frac{k_i}{g}}
|
||||||
|
|
||||||
|
Where:
|
||||||
|
* :math:`s = \\sigma + i\\omega` is the complex frequency
|
||||||
|
* :math:`k_p` is the proportional gain
|
||||||
|
* :math:`k_i` is the integrator gain
|
||||||
|
* :math:`g` is the integrator gain limit
|
||||||
|
|
||||||
|
:param profile: Profile number (0-3)
|
||||||
|
:param kp: Proportional gain. This is usually negative (closed
|
||||||
|
loop, positive ADC voltage, positive setpoint). When 0, this
|
||||||
|
implements a pure I controller.
|
||||||
|
:param ki: Integrator gain (rad/s). Equivalent to the gain at 1 Hz.
|
||||||
|
When 0 (the default) this implements a pure P controller.
|
||||||
|
Same sign as ``kp``.
|
||||||
|
:param g: Integrator gain limit (1). When 0 (the default) the
|
||||||
|
integrator gain limit is infinite. Same sign as ``ki``.
|
||||||
|
:param x_offset: IIR input offset. Used as the negative
|
||||||
|
setpoint when stabilizing to a desired input setpoint. Will
|
||||||
|
be converted to an equivalent output offset and added to y_offset.
|
||||||
|
:param y_offset: IIR output offset.
|
||||||
|
"""
|
||||||
|
NORM = 1 << SERVO_COEFF_SHIFT
|
||||||
|
COEFF_MAX = 1 << SERVO_COEFF_WIDTH - 1
|
||||||
|
DATA_MAX = 1 << SERVO_DATA_WIDTH - 1
|
||||||
|
|
||||||
|
kp *= float(NORM)
|
||||||
|
if ki == 0.:
|
||||||
|
# pure P
|
||||||
|
a1 = 0
|
||||||
|
b1 = 0
|
||||||
|
b0 = round(kp)
|
||||||
|
else:
|
||||||
|
# I or PI
|
||||||
|
ki *= float(NORM)*SERVO_T_CYCLE/2.
|
||||||
|
if g == 0.:
|
||||||
|
c = 1.
|
||||||
|
a1 = NORM
|
||||||
|
else:
|
||||||
|
c = 1./(1. + ki/(g*float(NORM)))
|
||||||
|
a1 = round((2.*c - 1.)*float(NORM))
|
||||||
|
b0 = round(kp + ki*c)
|
||||||
|
b1 = round(kp + (ki - 2.*kp)*c)
|
||||||
|
if b1 == -b0:
|
||||||
|
raise ValueError("low integrator gain and/or gain limit")
|
||||||
|
|
||||||
|
if (b0 >= COEFF_MAX or b0 < -COEFF_MAX or
|
||||||
|
b1 >= COEFF_MAX or b1 < -COEFF_MAX):
|
||||||
|
raise ValueError("high gains")
|
||||||
|
|
||||||
|
forward_gain = (b0 + b1) * (1 << SERVO_DATA_WIDTH - 1 - SERVO_COEFF_SHIFT)
|
||||||
|
effective_offset = round(float(DATA_MAX) * y_offset + float(forward_gain) * x_offset)
|
||||||
|
|
||||||
|
self.set_iir_mu(profile, b0, b1, a1, effective_offset)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@nac3
|
@nac3
|
||||||
class PhaserOscillator:
|
class PhaserOscillator:
|
||||||
|
@ -8,6 +8,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui
|
|||||||
from sipyco.sync_struct import Subscriber
|
from sipyco.sync_struct import Subscriber
|
||||||
|
|
||||||
from artiq.coredevice.comm_moninj import *
|
from artiq.coredevice.comm_moninj import *
|
||||||
|
from artiq.coredevice.ad9910 import _AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7, _AD9910_REG_FTW
|
||||||
|
from artiq.coredevice.ad9912_reg import AD9912_POW1
|
||||||
from artiq.gui.tools import LayoutWidget
|
from artiq.gui.tools import LayoutWidget
|
||||||
from artiq.gui.flowlayout import FlowLayout
|
from artiq.gui.flowlayout import FlowLayout
|
||||||
|
|
||||||
@ -179,14 +181,45 @@ class _SimpleDisplayWidget(QtWidgets.QFrame):
|
|||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
class _DDSModel:
|
||||||
|
def __init__(self, dds_type, ref_clk, cpld=None, pll=1, clk_div=0):
|
||||||
|
self.cpld = cpld
|
||||||
|
self.cur_frequency = 0
|
||||||
|
self.cur_reg = 0
|
||||||
|
self.dds_type = dds_type
|
||||||
|
self.is_urukul = dds_type in ["AD9910", "AD9912"]
|
||||||
|
|
||||||
|
if dds_type == "AD9914":
|
||||||
|
self.ftw_per_hz = 2**32 / ref_clk
|
||||||
|
else:
|
||||||
|
if dds_type == "AD9910":
|
||||||
|
max_freq = 1 << 32
|
||||||
|
clk_mult = [4, 1, 2, 4]
|
||||||
|
elif dds_type == "AD9912": # AD9912
|
||||||
|
max_freq = 1 << 48
|
||||||
|
clk_mult = [1, 1, 2, 4]
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
|
sysclk = ref_clk / clk_mult[clk_div] * pll
|
||||||
|
self.ftw_per_hz = 1 / sysclk * max_freq
|
||||||
|
|
||||||
|
def monitor_update(self, probe, value):
|
||||||
|
if self.dds_type == "AD9912":
|
||||||
|
value = value << 16
|
||||||
|
self.cur_frequency = self._ftw_to_freq(value)
|
||||||
|
|
||||||
|
def _ftw_to_freq(self, ftw):
|
||||||
|
return ftw / self.ftw_per_hz
|
||||||
|
|
||||||
|
|
||||||
class _DDSWidget(QtWidgets.QFrame):
|
class _DDSWidget(QtWidgets.QFrame):
|
||||||
def __init__(self, dm, title, bus_channel=0, channel=0, cpld=None):
|
def __init__(self, dm, title, bus_channel=0, channel=0, dds_model=None):
|
||||||
self.dm = dm
|
self.dm = dm
|
||||||
self.bus_channel = bus_channel
|
self.bus_channel = bus_channel
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
self.dds_name = title
|
self.dds_name = title
|
||||||
self.cpld = cpld
|
|
||||||
self.cur_frequency = 0
|
self.cur_frequency = 0
|
||||||
|
self.dds_model = dds_model
|
||||||
|
|
||||||
QtWidgets.QFrame.__init__(self)
|
QtWidgets.QFrame.__init__(self)
|
||||||
|
|
||||||
@ -249,7 +282,7 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||||||
set_grid.addWidget(set_btn, 0, 1, 1, 1)
|
set_grid.addWidget(set_btn, 0, 1, 1, 1)
|
||||||
|
|
||||||
# for urukuls also allow switching off RF
|
# for urukuls also allow switching off RF
|
||||||
if self.cpld:
|
if self.dds_model.is_urukul:
|
||||||
off_btn = QtWidgets.QToolButton()
|
off_btn = QtWidgets.QToolButton()
|
||||||
off_btn.setText("Off")
|
off_btn.setText("Off")
|
||||||
off_btn.setToolTip("Switch off the output")
|
off_btn.setToolTip("Switch off the output")
|
||||||
@ -276,7 +309,7 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||||||
|
|
||||||
set_btn.clicked.connect(self.set_clicked)
|
set_btn.clicked.connect(self.set_clicked)
|
||||||
apply.clicked.connect(self.apply_changes)
|
apply.clicked.connect(self.apply_changes)
|
||||||
if self.cpld:
|
if self.dds_model.is_urukul:
|
||||||
off_btn.clicked.connect(self.off_clicked)
|
off_btn.clicked.connect(self.off_clicked)
|
||||||
self.value_edit.returnPressed.connect(lambda: self.apply_changes(None))
|
self.value_edit.returnPressed.connect(lambda: self.apply_changes(None))
|
||||||
self.value_edit.escapePressedConnect(self.cancel_changes)
|
self.value_edit.escapePressedConnect(self.cancel_changes)
|
||||||
@ -293,19 +326,20 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||||||
self.value_edit.selectAll()
|
self.value_edit.selectAll()
|
||||||
|
|
||||||
def off_clicked(self, set):
|
def off_clicked(self, set):
|
||||||
self.dm.dds_channel_toggle(self.dds_name, self.cpld, sw=False)
|
self.dm.dds_channel_toggle(self.dds_name, self.dds_model, sw=False)
|
||||||
|
|
||||||
def apply_changes(self, apply):
|
def apply_changes(self, apply):
|
||||||
self.data_stack.setCurrentIndex(0)
|
self.data_stack.setCurrentIndex(0)
|
||||||
self.button_stack.setCurrentIndex(0)
|
self.button_stack.setCurrentIndex(0)
|
||||||
frequency = float(self.value_edit.text())*1e6
|
frequency = float(self.value_edit.text())*1e6
|
||||||
self.dm.dds_set_frequency(self.dds_name, self.cpld, frequency)
|
self.dm.dds_set_frequency(self.dds_name, self.dds_model, frequency)
|
||||||
|
|
||||||
def cancel_changes(self, cancel):
|
def cancel_changes(self, cancel):
|
||||||
self.data_stack.setCurrentIndex(0)
|
self.data_stack.setCurrentIndex(0)
|
||||||
self.button_stack.setCurrentIndex(0)
|
self.button_stack.setCurrentIndex(0)
|
||||||
|
|
||||||
def refresh_display(self):
|
def refresh_display(self):
|
||||||
|
self.cur_frequency = self.dds_model.cur_frequency
|
||||||
self.value_label.setText("<font size=\"4\">{:.7f}</font>"
|
self.value_label.setText("<font size=\"4\">{:.7f}</font>"
|
||||||
.format(self.cur_frequency/1e6))
|
.format(self.cur_frequency/1e6))
|
||||||
self.value_edit.setText("{:.7f}"
|
self.value_edit.setText("{:.7f}"
|
||||||
@ -356,7 +390,8 @@ def setup_from_ddb(ddb):
|
|||||||
bus_channel = v["arguments"]["bus_channel"]
|
bus_channel = v["arguments"]["bus_channel"]
|
||||||
channel = v["arguments"]["channel"]
|
channel = v["arguments"]["channel"]
|
||||||
dds_sysclk = v["arguments"]["sysclk"]
|
dds_sysclk = v["arguments"]["sysclk"]
|
||||||
widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel))
|
model = _DDSModel(v["class"], dds_sysclk)
|
||||||
|
widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model))
|
||||||
description.add(widget)
|
description.add(widget)
|
||||||
elif (v["module"] == "artiq.coredevice.ad9910"
|
elif (v["module"] == "artiq.coredevice.ad9910"
|
||||||
and v["class"] == "AD9910") or \
|
and v["class"] == "AD9910") or \
|
||||||
@ -368,7 +403,11 @@ def setup_from_ddb(ddb):
|
|||||||
dds_cpld = v["arguments"]["cpld_device"]
|
dds_cpld = v["arguments"]["cpld_device"]
|
||||||
spi_dev = ddb[dds_cpld]["arguments"]["spi_device"]
|
spi_dev = ddb[dds_cpld]["arguments"]["spi_device"]
|
||||||
bus_channel = ddb[spi_dev]["arguments"]["channel"]
|
bus_channel = ddb[spi_dev]["arguments"]["channel"]
|
||||||
widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, dds_cpld))
|
pll = v["arguments"]["pll_n"]
|
||||||
|
refclk = ddb[dds_cpld]["arguments"]["refclk"]
|
||||||
|
clk_div = v["arguments"].get("clk_div", 0)
|
||||||
|
model = _DDSModel( v["class"], refclk, dds_cpld, pll, clk_div)
|
||||||
|
widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model))
|
||||||
description.add(widget)
|
description.add(widget)
|
||||||
elif ( (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx")
|
elif ( (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx")
|
||||||
or (v["module"] == "artiq.coredevice.zotino" and v["class"] == "Zotino")):
|
or (v["module"] == "artiq.coredevice.zotino" and v["class"] == "Zotino")):
|
||||||
@ -385,7 +424,7 @@ def setup_from_ddb(ddb):
|
|||||||
mi_port = v.get("port_proxy", 1383)
|
mi_port = v.get("port_proxy", 1383)
|
||||||
except KeyError:
|
except KeyError:
|
||||||
pass
|
pass
|
||||||
return mi_addr, mi_port, dds_sysclk, description
|
return mi_addr, mi_port, description
|
||||||
|
|
||||||
|
|
||||||
class _DeviceManager:
|
class _DeviceManager:
|
||||||
@ -415,15 +454,13 @@ class _DeviceManager:
|
|||||||
return ddb
|
return ddb
|
||||||
|
|
||||||
def notify(self, mod):
|
def notify(self, mod):
|
||||||
mi_addr, mi_port, dds_sysclk, description = setup_from_ddb(self.ddb)
|
mi_addr, mi_port, description = setup_from_ddb(self.ddb)
|
||||||
|
|
||||||
if (mi_addr, mi_port) != (self.mi_addr, self.mi_port):
|
if (mi_addr, mi_port) != (self.mi_addr, self.mi_port):
|
||||||
self.mi_addr = mi_addr
|
self.mi_addr = mi_addr
|
||||||
self.mi_port = mi_port
|
self.mi_port = mi_port
|
||||||
self.reconnect_mi.set()
|
self.reconnect_mi.set()
|
||||||
|
|
||||||
self.dds_sysclk = dds_sysclk
|
|
||||||
|
|
||||||
for to_remove in self.description - description:
|
for to_remove in self.description - description:
|
||||||
widget = self.widgets_by_uid[to_remove.uid]
|
widget = self.widgets_by_uid[to_remove.uid]
|
||||||
del self.widgets_by_uid[to_remove.uid]
|
del self.widgets_by_uid[to_remove.uid]
|
||||||
@ -497,7 +534,7 @@ class _DeviceManager:
|
|||||||
"log_level": logging.WARNING,
|
"log_level": logging.WARNING,
|
||||||
"content": content,
|
"content": content,
|
||||||
"class_name": class_name,
|
"class_name": class_name,
|
||||||
"arguments": []
|
"arguments": {}
|
||||||
}
|
}
|
||||||
scheduling = {
|
scheduling = {
|
||||||
"pipeline_name": "main",
|
"pipeline_name": "main",
|
||||||
@ -512,24 +549,25 @@ class _DeviceManager:
|
|||||||
scheduling["flush"])
|
scheduling["flush"])
|
||||||
logger.info("Submitted '%s', RID is %d", title, rid)
|
logger.info("Submitted '%s', RID is %d", title, rid)
|
||||||
|
|
||||||
def dds_set_frequency(self, dds_channel, dds_cpld, freq):
|
def dds_set_frequency(self, dds_channel, dds_model, freq):
|
||||||
# create kernel and fill it in and send-by-content
|
# create kernel and fill it in and send-by-content
|
||||||
if dds_cpld:
|
if dds_model.is_urukul:
|
||||||
# urukuls need CPLD init and switch to on
|
# urukuls need CPLD init and switch to on
|
||||||
# keep previous config if it was set already
|
# keep previous config if it was set already
|
||||||
cpld_dev = """self.setattr_device("core_cache")
|
cpld_dev = """self.setattr_device("core_cache")
|
||||||
self.setattr_device("{}")""".format(dds_cpld)
|
self.setattr_device("{}")""".format(dds_model.cpld)
|
||||||
cpld_init = """cfg = self.core_cache.get("_{cpld}_cfg")
|
cpld_init = """cfg = self.core_cache.get("_{cpld}_cfg")
|
||||||
if len(cfg) > 0:
|
if len(cfg) > 0:
|
||||||
self.{cpld}.cfg_reg = cfg[0]
|
self.{cpld}.cfg_reg = cfg[0]
|
||||||
else:
|
else:
|
||||||
|
delay(15*ms)
|
||||||
self.{cpld}.init()
|
self.{cpld}.init()
|
||||||
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
|
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
|
||||||
cfg = self.core_cache.get("_{cpld}_cfg")
|
cfg = self.core_cache.get("_{cpld}_cfg")
|
||||||
""".format(cpld=dds_cpld)
|
""".format(cpld=dds_model.cpld)
|
||||||
cfg_sw = """self.{}.cfg_sw(True)
|
cfg_sw = """self.{}.cfg_sw(True)
|
||||||
cfg[0] = self.{}.cfg_reg
|
cfg[0] = self.{}.cfg_reg
|
||||||
""".format(dds_channel, dds_cpld)
|
""".format(dds_channel, dds_model.cpld)
|
||||||
else:
|
else:
|
||||||
cpld_dev = ""
|
cpld_dev = ""
|
||||||
cpld_init = ""
|
cpld_init = ""
|
||||||
@ -546,8 +584,8 @@ class _DeviceManager:
|
|||||||
@kernel
|
@kernel
|
||||||
def run(self):
|
def run(self):
|
||||||
self.core.break_realtime()
|
self.core.break_realtime()
|
||||||
delay(2*ms)
|
|
||||||
{cpld_init}
|
{cpld_init}
|
||||||
|
delay(5*ms)
|
||||||
self.{dds_channel}.init()
|
self.{dds_channel}.init()
|
||||||
self.{dds_channel}.set({freq})
|
self.{dds_channel}.set({freq})
|
||||||
{cfg_sw}
|
{cfg_sw}
|
||||||
@ -560,7 +598,7 @@ class _DeviceManager:
|
|||||||
"SetDDS",
|
"SetDDS",
|
||||||
"Set DDS {} {}MHz".format(dds_channel, freq/1e6)))
|
"Set DDS {} {}MHz".format(dds_channel, freq/1e6)))
|
||||||
|
|
||||||
def dds_channel_toggle(self, dds_channel, dds_cpld, sw=True):
|
def dds_channel_toggle(self, dds_channel, dds_model, sw=True):
|
||||||
# urukul only
|
# urukul only
|
||||||
toggle_exp = textwrap.dedent("""
|
toggle_exp = textwrap.dedent("""
|
||||||
from artiq.experiment import *
|
from artiq.experiment import *
|
||||||
@ -575,18 +613,19 @@ class _DeviceManager:
|
|||||||
@kernel
|
@kernel
|
||||||
def run(self):
|
def run(self):
|
||||||
self.core.break_realtime()
|
self.core.break_realtime()
|
||||||
delay(2*ms)
|
|
||||||
cfg = self.core_cache.get("_{cpld}_cfg")
|
cfg = self.core_cache.get("_{cpld}_cfg")
|
||||||
if len(cfg) > 0:
|
if len(cfg) > 0:
|
||||||
self.{cpld}.cfg_reg = cfg[0]
|
self.{cpld}.cfg_reg = cfg[0]
|
||||||
else:
|
else:
|
||||||
|
delay(15*ms)
|
||||||
self.{cpld}.init()
|
self.{cpld}.init()
|
||||||
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
|
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
|
||||||
cfg = self.core_cache.get("_{cpld}_cfg")
|
cfg = self.core_cache.get("_{cpld}_cfg")
|
||||||
|
delay(5*ms)
|
||||||
self.{ch}.init()
|
self.{ch}.init()
|
||||||
self.{ch}.cfg_sw({sw})
|
self.{ch}.cfg_sw({sw})
|
||||||
cfg[0] = self.{cpld}.cfg_reg
|
cfg[0] = self.{cpld}.cfg_reg
|
||||||
""".format(ch=dds_channel, cpld=dds_cpld, sw=sw))
|
""".format(ch=dds_channel, cpld=dds_model.cpld, sw=sw))
|
||||||
asyncio.ensure_future(
|
asyncio.ensure_future(
|
||||||
self._submit_by_content(
|
self._submit_by_content(
|
||||||
toggle_exp,
|
toggle_exp,
|
||||||
@ -619,11 +658,11 @@ class _DeviceManager:
|
|||||||
elif probe == TTLProbe.oe.value:
|
elif probe == TTLProbe.oe.value:
|
||||||
widget.cur_oe = bool(value)
|
widget.cur_oe = bool(value)
|
||||||
widget.refresh_display()
|
widget.refresh_display()
|
||||||
if (channel, probe) in self.dds_widgets:
|
elif (channel, probe) in self.dds_widgets:
|
||||||
widget = self.dds_widgets[(channel, probe)]
|
widget = self.dds_widgets[(channel, probe)]
|
||||||
widget.cur_frequency = value*self.dds_sysclk/2**32
|
widget.dds_model.monitor_update(probe, value)
|
||||||
widget.refresh_display()
|
widget.refresh_display()
|
||||||
if (channel, probe) in self.dac_widgets:
|
elif (channel, probe) in self.dac_widgets:
|
||||||
widget = self.dac_widgets[(channel, probe)]
|
widget = self.dac_widgets[(channel, probe)]
|
||||||
widget.cur_value = value
|
widget.cur_value = value
|
||||||
widget.refresh_display()
|
widget.refresh_display()
|
||||||
@ -656,11 +695,11 @@ class _DeviceManager:
|
|||||||
logger.info("cancelled connection to moninj")
|
logger.info("cancelled connection to moninj")
|
||||||
break
|
break
|
||||||
except:
|
except:
|
||||||
logger.error("failed to connect to moninj", exc_info=True)
|
logger.error("failed to connect to moninj. Is aqctl_moninj_proxy running?", exc_info=True)
|
||||||
await asyncio.sleep(10.)
|
await asyncio.sleep(10.)
|
||||||
self.reconnect_mi.set()
|
self.reconnect_mi.set()
|
||||||
else:
|
else:
|
||||||
logger.info("ARTIQ dashboard connected to moninj proxy (%s)",
|
logger.info("ARTIQ dashboard connected to moninj (%s)",
|
||||||
self.mi_addr)
|
self.mi_addr)
|
||||||
self.mi_connection = new_mi_connection
|
self.mi_connection = new_mi_connection
|
||||||
for ttl_channel in self.ttl_widgets.keys():
|
for ttl_channel in self.ttl_widgets.keys():
|
||||||
|
@ -116,6 +116,9 @@ class MonitorMux:
|
|||||||
else:
|
else:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
|
def disconnect_cb(self):
|
||||||
|
self.listeners.clear()
|
||||||
|
|
||||||
|
|
||||||
class ProxyConnection:
|
class ProxyConnection:
|
||||||
def __init__(self, monitor_mux, reader, writer):
|
def __init__(self, monitor_mux, reader, writer):
|
||||||
@ -203,7 +206,9 @@ def main():
|
|||||||
signal_handler.setup()
|
signal_handler.setup()
|
||||||
try:
|
try:
|
||||||
monitor_mux = MonitorMux()
|
monitor_mux = MonitorMux()
|
||||||
comm_moninj = CommMonInj(monitor_mux.monitor_cb, monitor_mux.injection_status_cb)
|
comm_moninj = CommMonInj(monitor_mux.monitor_cb,
|
||||||
|
monitor_mux.injection_status_cb,
|
||||||
|
monitor_mux.disconnect_cb)
|
||||||
monitor_mux.comm_moninj = comm_moninj
|
monitor_mux.comm_moninj = comm_moninj
|
||||||
loop.run_until_complete(comm_moninj.connect(args.core_addr))
|
loop.run_until_complete(comm_moninj.connect(args.core_addr))
|
||||||
try:
|
try:
|
||||||
|
@ -114,7 +114,12 @@ class Programmer:
|
|||||||
"telnet_port disabled"
|
"telnet_port disabled"
|
||||||
] + preinit_script
|
] + preinit_script
|
||||||
self._loaded = defaultdict(lambda: None)
|
self._loaded = defaultdict(lambda: None)
|
||||||
self._script = ["init"]
|
self._script = [
|
||||||
|
"set error_msg \"Trying to use configured scan chain anyway\"",
|
||||||
|
"if {[string first $error_msg [capture \"init\"]] != -1} {",
|
||||||
|
"puts \"Found error and exiting\"",
|
||||||
|
"exit}"
|
||||||
|
]
|
||||||
|
|
||||||
def _transfer_script(self, script):
|
def _transfer_script(self, script):
|
||||||
if isinstance(self._client, LocalClient):
|
if isinstance(self._client, LocalClient):
|
||||||
|
@ -128,6 +128,7 @@ def main():
|
|||||||
"scheduler_request_termination": scheduler.request_termination,
|
"scheduler_request_termination": scheduler.request_termination,
|
||||||
"scheduler_get_status": scheduler.get_status,
|
"scheduler_get_status": scheduler.get_status,
|
||||||
"scheduler_check_pause": scheduler.check_pause,
|
"scheduler_check_pause": scheduler.check_pause,
|
||||||
|
"scheduler_check_termination": scheduler.check_termination,
|
||||||
"ccb_issue": ccb_issue,
|
"ccb_issue": ccb_issue,
|
||||||
})
|
})
|
||||||
experiment_db.scan_repository_async()
|
experiment_db.scan_repository_async()
|
||||||
|
@ -295,7 +295,10 @@ class GTX(Module, TransceiverInterface):
|
|||||||
i_CEB=stable_clkin_n,
|
i_CEB=stable_clkin_n,
|
||||||
i_I=clock_pads.p,
|
i_I=clock_pads.p,
|
||||||
i_IB=clock_pads.n,
|
i_IB=clock_pads.n,
|
||||||
o_O=refclk
|
o_O=refclk,
|
||||||
|
p_CLKCM_CFG="0b1",
|
||||||
|
p_CLKRCV_TRST="0b1",
|
||||||
|
p_CLKSWING_CFG="0b11"
|
||||||
)
|
)
|
||||||
|
|
||||||
rtio_tx_clk = Signal()
|
rtio_tx_clk = Signal()
|
||||||
|
@ -3,7 +3,7 @@ from migen.build.generic_platform import *
|
|||||||
from migen.genlib.io import DifferentialOutput
|
from migen.genlib.io import DifferentialOutput
|
||||||
|
|
||||||
from artiq.gateware import rtio
|
from artiq.gateware import rtio
|
||||||
from artiq.gateware.rtio.phy import spi2, ad53xx_monitor, grabber
|
from artiq.gateware.rtio.phy import spi2, ad53xx_monitor, dds, grabber
|
||||||
from artiq.gateware.suservo import servo, pads as servo_pads
|
from artiq.gateware.suservo import servo, pads as servo_pads
|
||||||
from artiq.gateware.rtio.phy import servo as rtservo, fastino, phaser
|
from artiq.gateware.rtio.phy import servo as rtservo, fastino, phaser
|
||||||
|
|
||||||
@ -222,13 +222,13 @@ class Urukul(_EEM):
|
|||||||
return ios
|
return ios
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def add_std(cls, target, eem, eem_aux, ttl_out_cls, sync_gen_cls=None, iostandard=default_iostandard):
|
def add_std(cls, target, eem, eem_aux, ttl_out_cls, dds_type, sync_gen_cls=None, iostandard=default_iostandard):
|
||||||
cls.add_extension(target, eem, eem_aux, iostandard=iostandard)
|
cls.add_extension(target, eem, eem_aux, iostandard=iostandard)
|
||||||
|
|
||||||
phy = spi2.SPIMaster(target.platform.request("urukul{}_spi_p".format(eem)),
|
spi_phy = spi2.SPIMaster(target.platform.request("urukul{}_spi_p".format(eem)),
|
||||||
target.platform.request("urukul{}_spi_n".format(eem)))
|
target.platform.request("urukul{}_spi_n".format(eem)))
|
||||||
target.submodules += phy
|
target.submodules += spi_phy
|
||||||
target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4))
|
target.rtio_channels.append(rtio.Channel.from_phy(spi_phy, ififo_depth=4))
|
||||||
|
|
||||||
pads = target.platform.request("urukul{}_dds_reset_sync_in".format(eem))
|
pads = target.platform.request("urukul{}_dds_reset_sync_in".format(eem))
|
||||||
if sync_gen_cls is not None: # AD9910 variant and SYNC_IN from EEM
|
if sync_gen_cls is not None: # AD9910 variant and SYNC_IN from EEM
|
||||||
@ -237,9 +237,14 @@ class Urukul(_EEM):
|
|||||||
target.rtio_channels.append(rtio.Channel.from_phy(phy))
|
target.rtio_channels.append(rtio.Channel.from_phy(phy))
|
||||||
|
|
||||||
pads = target.platform.request("urukul{}_io_update".format(eem))
|
pads = target.platform.request("urukul{}_io_update".format(eem))
|
||||||
phy = ttl_out_cls(pads.p, pads.n)
|
io_upd_phy = ttl_out_cls(pads.p, pads.n)
|
||||||
target.submodules += phy
|
target.submodules += io_upd_phy
|
||||||
target.rtio_channels.append(rtio.Channel.from_phy(phy))
|
target.rtio_channels.append(rtio.Channel.from_phy(io_upd_phy))
|
||||||
|
|
||||||
|
dds_monitor = dds.UrukulMonitor(spi_phy, io_upd_phy, dds_type)
|
||||||
|
target.submodules += dds_monitor
|
||||||
|
spi_phy.probes.extend(dds_monitor.probes)
|
||||||
|
|
||||||
if eem_aux is not None:
|
if eem_aux is not None:
|
||||||
for signal in "sw0 sw1 sw2 sw3".split():
|
for signal in "sw0 sw1 sw2 sw3".split():
|
||||||
pads = target.platform.request("urukul{}_{}".format(eem, signal))
|
pads = target.platform.request("urukul{}_{}".format(eem, signal))
|
||||||
@ -247,6 +252,7 @@ class Urukul(_EEM):
|
|||||||
target.submodules += phy
|
target.submodules += phy
|
||||||
target.rtio_channels.append(rtio.Channel.from_phy(phy))
|
target.rtio_channels.append(rtio.Channel.from_phy(phy))
|
||||||
|
|
||||||
|
|
||||||
class Sampler(_EEM):
|
class Sampler(_EEM):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def io(eem, eem_aux, iostandard):
|
def io(eem, eem_aux, iostandard):
|
||||||
|
@ -47,7 +47,7 @@ def peripheral_urukul(module, peripheral, **kwargs):
|
|||||||
else:
|
else:
|
||||||
sync_gen_cls = None
|
sync_gen_cls = None
|
||||||
eem.Urukul.add_std(module, port, port_aux, ttl_serdes_7series.Output_8X,
|
eem.Urukul.add_std(module, port, port_aux, ttl_serdes_7series.Output_8X,
|
||||||
sync_gen_cls, **kwargs)
|
peripheral["dds"], sync_gen_cls, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
def peripheral_sampler(module, peripheral, **kwargs):
|
def peripheral_sampler(module, peripheral, **kwargs):
|
||||||
|
@ -3,6 +3,11 @@ from migen import *
|
|||||||
from artiq.gateware import ad9_dds
|
from artiq.gateware import ad9_dds
|
||||||
from artiq.gateware.rtio.phy.wishbone import RT2WB
|
from artiq.gateware.rtio.phy.wishbone import RT2WB
|
||||||
|
|
||||||
|
from artiq.coredevice.spi2 import SPI_CONFIG_ADDR, SPI_DATA_ADDR, SPI_END
|
||||||
|
from artiq.coredevice.urukul import CS_DDS_CH0, CS_DDS_MULTI, CFG_IO_UPDATE, CS_CFG
|
||||||
|
|
||||||
|
from artiq.coredevice.ad9912_reg import AD9912_POW1
|
||||||
|
from artiq.coredevice.ad9910 import _AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7, _AD9910_REG_FTW
|
||||||
|
|
||||||
class AD9914(Module):
|
class AD9914(Module):
|
||||||
def __init__(self, pads, nchannels, onehot=False, **kwargs):
|
def __init__(self, pads, nchannels, onehot=False, **kwargs):
|
||||||
@ -54,3 +59,121 @@ class AD9914(Module):
|
|||||||
self.sync.rio_phy += If(current_address == 2**len(pads.a), [
|
self.sync.rio_phy += If(current_address == 2**len(pads.a), [
|
||||||
If(selected(c), probe.eq(ftw))
|
If(selected(c), probe.eq(ftw))
|
||||||
for c, (probe, ftw) in enumerate(zip(self.probes, ftws))])
|
for c, (probe, ftw) in enumerate(zip(self.probes, ftws))])
|
||||||
|
|
||||||
|
|
||||||
|
class UrukulMonitor(Module):
|
||||||
|
def __init__(self, spi_phy, io_update_phy, dds, nchannels=4):
|
||||||
|
self.spi_phy = spi_phy
|
||||||
|
self.io_update_phy = io_update_phy
|
||||||
|
|
||||||
|
self.probes = [Signal(32) for i in range(nchannels)]
|
||||||
|
|
||||||
|
self.cs = Signal(8)
|
||||||
|
self.current_data = Signal.like(self.spi_phy.rtlink.o.data)
|
||||||
|
current_address = Signal.like(self.spi_phy.rtlink.o.address)
|
||||||
|
data_length = Signal(8)
|
||||||
|
flags = Signal(8)
|
||||||
|
|
||||||
|
self.sync.rio += If(self.spi_phy.rtlink.o.stb, [
|
||||||
|
current_address.eq(self.spi_phy.rtlink.o.address),
|
||||||
|
self.current_data.eq(self.spi_phy.rtlink.o.data),
|
||||||
|
If(self.spi_phy.rtlink.o.address == SPI_CONFIG_ADDR, [
|
||||||
|
self.cs.eq(self.spi_phy.rtlink.o.data[24:]),
|
||||||
|
data_length.eq(self.spi_phy.rtlink.o.data[8:16] + 1),
|
||||||
|
flags.eq(self.spi_phy.rtlink.o.data[0:8])
|
||||||
|
])
|
||||||
|
])
|
||||||
|
|
||||||
|
for i in range(nchannels):
|
||||||
|
ch_sel = Signal()
|
||||||
|
self.comb += ch_sel.eq(
|
||||||
|
((self.cs == CS_DDS_MULTI) | (self.cs == i + CS_DDS_CH0))
|
||||||
|
& (current_address == SPI_DATA_ADDR)
|
||||||
|
)
|
||||||
|
|
||||||
|
if dds == "ad9912":
|
||||||
|
mon_cls = _AD9912Monitor
|
||||||
|
elif dds == "ad9910":
|
||||||
|
mon_cls = _AD9910Monitor
|
||||||
|
else:
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
monitor = mon_cls(self.current_data, data_length, flags, ch_sel)
|
||||||
|
self.submodules += monitor
|
||||||
|
|
||||||
|
self.sync.rio_phy += [
|
||||||
|
If(ch_sel & self.is_io_update(), self.probes[i].eq(monitor.ftw))
|
||||||
|
]
|
||||||
|
|
||||||
|
def is_io_update(self):
|
||||||
|
# shifted 8 bits left for 32-bit bus
|
||||||
|
reg_io_upd = (self.cs == CS_CFG) & self.current_data[8 + CFG_IO_UPDATE]
|
||||||
|
phy_io_upd = False
|
||||||
|
if self.io_update_phy:
|
||||||
|
phy_io_upd = self.io_update_phy.rtlink.o.stb & self.io_update_phy.rtlink.o.data
|
||||||
|
return phy_io_upd | reg_io_upd
|
||||||
|
|
||||||
|
|
||||||
|
class _AD9912Monitor(Module):
|
||||||
|
def __init__(self, current_data, data_length, flags, ch_sel):
|
||||||
|
self.ftw = Signal(32, reset_less=True)
|
||||||
|
|
||||||
|
fsm = ClockDomainsRenamer("rio_phy")(FSM(reset_state="IDLE"))
|
||||||
|
self.submodules += fsm
|
||||||
|
|
||||||
|
reg_addr = current_data[16:29]
|
||||||
|
reg_write = ~current_data[31]
|
||||||
|
|
||||||
|
fsm.act("IDLE",
|
||||||
|
If(ch_sel & reg_write,
|
||||||
|
If((data_length == 16) & (reg_addr == AD9912_POW1),
|
||||||
|
NextState("READ")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fsm.act("READ",
|
||||||
|
If(ch_sel,
|
||||||
|
If(flags & SPI_END,
|
||||||
|
# lower 16 bits (16-32 from 48-bit transfer)
|
||||||
|
NextValue(self.ftw[:16], current_data[16:]),
|
||||||
|
NextState("IDLE")
|
||||||
|
).Else(
|
||||||
|
NextValue(self.ftw[16:], current_data[:16])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class _AD9910Monitor(Module):
|
||||||
|
def __init__(self, current_data, data_length, flags, ch_sel):
|
||||||
|
self.ftw = Signal(32, reset_less=True)
|
||||||
|
|
||||||
|
fsm = ClockDomainsRenamer("rio_phy")(FSM(reset_state="IDLE"))
|
||||||
|
self.submodules += fsm
|
||||||
|
|
||||||
|
reg_addr = current_data[24:29]
|
||||||
|
reg_write = ~current_data[31]
|
||||||
|
|
||||||
|
fsm.act("IDLE",
|
||||||
|
If(ch_sel & reg_write,
|
||||||
|
If((data_length == 8) & (_AD9910_REG_PROFILE7 >= reg_addr) & (reg_addr >= _AD9910_REG_PROFILE0),
|
||||||
|
NextState("READ")
|
||||||
|
).Elif(reg_addr == _AD9910_REG_FTW,
|
||||||
|
If((data_length == 24) & (flags & SPI_END),
|
||||||
|
NextValue(self.ftw[:16], current_data[8:24])
|
||||||
|
).Elif(data_length == 8,
|
||||||
|
NextState("READ")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fsm.act("READ",
|
||||||
|
If(ch_sel,
|
||||||
|
If(flags & SPI_END,
|
||||||
|
NextValue(self.ftw, current_data),
|
||||||
|
NextState("IDLE")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@ -282,6 +282,9 @@ class MasterBase(MiniSoC, AMPSoC):
|
|||||||
platform = self.platform
|
platform = self.platform
|
||||||
|
|
||||||
if platform.hw_rev == "v2.0":
|
if platform.hw_rev == "v2.0":
|
||||||
|
self.submodules.error_led = gpio.GPIOOut(Cat(
|
||||||
|
self.platform.request("error_led")))
|
||||||
|
self.csr_devices.append("error_led")
|
||||||
self.submodules += SMAClkinForward(platform)
|
self.submodules += SMAClkinForward(platform)
|
||||||
|
|
||||||
i2c = self.platform.request("i2c")
|
i2c = self.platform.request("i2c")
|
||||||
@ -459,6 +462,11 @@ class SatelliteBase(BaseSoC):
|
|||||||
|
|
||||||
platform = self.platform
|
platform = self.platform
|
||||||
|
|
||||||
|
if self.platform.hw_rev == "v2.0":
|
||||||
|
self.submodules.error_led = gpio.GPIOOut(Cat(
|
||||||
|
self.platform.request("error_led")))
|
||||||
|
self.csr_devices.append("error_led")
|
||||||
|
|
||||||
disable_cdr_clk_ibuf = Signal(reset=1)
|
disable_cdr_clk_ibuf = Signal(reset=1)
|
||||||
disable_cdr_clk_ibuf.attr.add("no_retiming")
|
disable_cdr_clk_ibuf.attr.add("no_retiming")
|
||||||
if self.platform.hw_rev == "v2.0":
|
if self.platform.hw_rev == "v2.0":
|
||||||
|
@ -209,18 +209,28 @@ class TraceArgumentManager:
|
|||||||
self.requested_args[key] = processor, group, tooltip
|
self.requested_args[key] = processor, group, tooltip
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
def check_unprocessed_arguments(self):
|
||||||
|
pass
|
||||||
|
|
||||||
class ProcessArgumentManager:
|
class ProcessArgumentManager:
|
||||||
def __init__(self, unprocessed_arguments):
|
def __init__(self, unprocessed_arguments):
|
||||||
self.unprocessed_arguments = unprocessed_arguments
|
self.unprocessed_arguments = unprocessed_arguments
|
||||||
|
self._processed_arguments = set()
|
||||||
|
|
||||||
def get(self, key, processor, group, tooltip):
|
def get(self, key, processor, group, tooltip):
|
||||||
if key in self.unprocessed_arguments:
|
if key in self.unprocessed_arguments:
|
||||||
r = processor.process(self.unprocessed_arguments[key])
|
r = processor.process(self.unprocessed_arguments[key])
|
||||||
|
self._processed_arguments.add(key)
|
||||||
else:
|
else:
|
||||||
r = processor.default()
|
r = processor.default()
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
def check_unprocessed_arguments(self):
|
||||||
|
unprocessed = set(self.unprocessed_arguments.keys()) -\
|
||||||
|
self._processed_arguments
|
||||||
|
if unprocessed:
|
||||||
|
raise AttributeError("Invalid argument(s): " +
|
||||||
|
", ".join(unprocessed))
|
||||||
|
|
||||||
class HasEnvironment:
|
class HasEnvironment:
|
||||||
"""Provides methods to manage the environment of an experiment (arguments,
|
"""Provides methods to manage the environment of an experiment (arguments,
|
||||||
@ -242,6 +252,8 @@ class HasEnvironment:
|
|||||||
self.__in_build = True
|
self.__in_build = True
|
||||||
self.build(*args, **kwargs)
|
self.build(*args, **kwargs)
|
||||||
self.__in_build = False
|
self.__in_build = False
|
||||||
|
if self.__argument_mgr is not None:
|
||||||
|
self.__argument_mgr.check_unprocessed_arguments()
|
||||||
|
|
||||||
def register_child(self, child):
|
def register_child(self, child):
|
||||||
self.children.append(child)
|
self.children.append(child)
|
||||||
@ -485,7 +497,7 @@ def is_experiment(o):
|
|||||||
|
|
||||||
|
|
||||||
def is_public_experiment(o):
|
def is_public_experiment(o):
|
||||||
"""Checks if a Pyhton object is a top-level,
|
"""Checks if a Python object is a top-level,
|
||||||
non underscore-prefixed, experiment class.
|
non underscore-prefixed, experiment class.
|
||||||
"""
|
"""
|
||||||
return is_experiment(o) and not o.__name__.startswith("_")
|
return is_experiment(o) and not o.__name__.startswith("_")
|
||||||
|
@ -490,3 +490,13 @@ class Scheduler:
|
|||||||
return False
|
return False
|
||||||
return r.priority_key() > run.priority_key()
|
return r.priority_key() > run.priority_key()
|
||||||
raise KeyError("RID not found")
|
raise KeyError("RID not found")
|
||||||
|
|
||||||
|
def check_termination(self, rid):
|
||||||
|
"""Returns ``True`` if termination is requested."""
|
||||||
|
for pipeline in self._pipelines.values():
|
||||||
|
if rid in pipeline.pool.runs:
|
||||||
|
run = pipeline.pool.runs[rid]
|
||||||
|
if run.termination_requested:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
@ -111,6 +111,12 @@ class Scheduler:
|
|||||||
rid = self.rid
|
rid = self.rid
|
||||||
return self._check_pause(rid)
|
return self._check_pause(rid)
|
||||||
|
|
||||||
|
_check_termination = staticmethod(make_parent_action("scheduler_check_termination"))
|
||||||
|
def check_termination(self, rid=None) -> bool:
|
||||||
|
if rid is None:
|
||||||
|
rid = self.rid
|
||||||
|
return self._check_termination(rid)
|
||||||
|
|
||||||
_submit = staticmethod(make_parent_action("scheduler_submit"))
|
_submit = staticmethod(make_parent_action("scheduler_submit"))
|
||||||
def submit(self, pipeline_name=None, expid=None, priority=None, due_date=None, flush=False):
|
def submit(self, pipeline_name=None, expid=None, priority=None, due_date=None, flush=False):
|
||||||
if pipeline_name is None:
|
if pipeline_name is None:
|
||||||
|
11
flake.nix
11
flake.nix
@ -15,6 +15,7 @@
|
|||||||
outputs = { self, mozilla-overlay, sipyco, nac3, artiq-comtools, src-migen, src-misoc }:
|
outputs = { self, mozilla-overlay, sipyco, nac3, artiq-comtools, src-migen, src-misoc }:
|
||||||
let
|
let
|
||||||
pkgs = import nac3.inputs.nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; };
|
pkgs = import nac3.inputs.nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; };
|
||||||
|
pkgs-aarch64 = import nac3.inputs.nixpkgs { system = "aarch64-linux"; };
|
||||||
|
|
||||||
artiqVersionMajor = 9;
|
artiqVersionMajor = 9;
|
||||||
artiqVersionMinor = self.sourceInfo.revCount or 0;
|
artiqVersionMinor = self.sourceInfo.revCount or 0;
|
||||||
@ -76,6 +77,7 @@
|
|||||||
pname = "artiq";
|
pname = "artiq";
|
||||||
version = artiqVersion;
|
version = artiqVersion;
|
||||||
src = self;
|
src = self;
|
||||||
|
doCheck = false;
|
||||||
|
|
||||||
preBuild =
|
preBuild =
|
||||||
''
|
''
|
||||||
@ -228,7 +230,7 @@
|
|||||||
dontFixup = true;
|
dontFixup = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
openocd-bscanspi = let
|
openocd-bscanspi-f = pkgs: let
|
||||||
bscan_spi_bitstreams-pkg = pkgs.stdenv.mkDerivation {
|
bscan_spi_bitstreams-pkg = pkgs.stdenv.mkDerivation {
|
||||||
name = "bscan_spi_bitstreams";
|
name = "bscan_spi_bitstreams";
|
||||||
src = pkgs.fetchFromGitHub {
|
src = pkgs.fetchFromGitHub {
|
||||||
@ -301,8 +303,9 @@
|
|||||||
in rec {
|
in rec {
|
||||||
packages.x86_64-linux = rec {
|
packages.x86_64-linux = rec {
|
||||||
inherit (nac3.packages.x86_64-linux) python3-mimalloc;
|
inherit (nac3.packages.x86_64-linux) python3-mimalloc;
|
||||||
inherit qasync openocd-bscanspi artiq;
|
inherit qasync artiq;
|
||||||
inherit migen misoc asyncserial microscope vivadoEnv vivado;
|
inherit migen misoc asyncserial microscope vivadoEnv vivado;
|
||||||
|
openocd-bscanspi = openocd-bscanspi-f pkgs;
|
||||||
artiq-board-kc705-nist_clock = makeArtiqBoardPackage {
|
artiq-board-kc705-nist_clock = makeArtiqBoardPackage {
|
||||||
target = "kc705";
|
target = "kc705";
|
||||||
variant = "nist_clock";
|
variant = "nist_clock";
|
||||||
@ -377,6 +380,10 @@
|
|||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
packages.aarch64-linux = {
|
||||||
|
openocd-bscanspi = openocd-bscanspi-f pkgs-aarch64;
|
||||||
|
};
|
||||||
|
|
||||||
hydraJobs = {
|
hydraJobs = {
|
||||||
inherit (packages.x86_64-linux) artiq artiq-board-kc705-nist_clock openocd-bscanspi;
|
inherit (packages.x86_64-linux) artiq artiq-board-kc705-nist_clock openocd-bscanspi;
|
||||||
sipyco-msys2-pkg = packages.x86_64-w64-mingw32.sipyco-pkg;
|
sipyco-msys2-pkg = packages.x86_64-w64-mingw32.sipyco-pkg;
|
||||||
|
Loading…
Reference in New Issue
Block a user