Merge branch 'master' into nac3

This commit is contained in:
Sebastien Bourdeauducq 2022-08-18 14:35:09 +08:00
commit 0953a07582
15 changed files with 484 additions and 64 deletions

View File

@ -3,48 +3,66 @@
Release notes
=============
Unreleased
----------
Highlights:
* Implemented Phaser-servo. This requires recent gateware on Phaser.
ARTIQ-7
-------
Highlights:
* 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
- 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).
* Gateware FPU is supported on KC705 and Kasli 2.0.
* Faster compilation for large arrays/lists.
* Phaser:
- Improved documentation
- Expose the DAC coarse mixer and ``sif_sync``
- Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs.
- Add helpers to align Phaser updates to the RTIO timeline (``get_next_frame_mu()``)
* Core device moninj is now proxied via the ``aqctl_moninj_proxy`` controller.
* Faster exception handling.
* Several exception handling bugs fixed.
* Support for a simpler shared library system with faster calls into the runtime. This is only used by the NAC3
compiler (nac3ld) and improves RTIO output performance (test_pulse_rate) by 9-10%.
* Moninj improvements:
- 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
of compile-time options.
* Packaging via Nix Flakes.
* Firmware and gateware can now be built on-demand on the M-Labs server using ``afws_client``
(subscribers only).
* Added support for 100MHz RTIO clock in DRTIO.
* Previously detected RTIO async errors are reported to the host after each kernel terminates and a
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.
* ``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
the JSON hardware description file.
the JSON.
* ``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``).
* ``artiq_master`` now has an ``--experiment-subdir`` option to scan only a subdirectory of the
repository when building the list of experiments.
* Added support for 100MHz RTIO clock in DRTIO.
* Previously detected RTIO async errors are reported to the host after each kernel terminates and a
warning is logged. The warning is additional to the one already printed in the core device log upon
detection of the error.
* Experiments can now be submitted by-content.
* The master can now optionally log all experiments submitted into a CSV file.
* 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
possibly other switches in future. Readback has been removed, and now only one channel per
switch is supported.
* The "ip" config option can now be set to "use_dhcp" in order to use DHCP to obtain an IP address.
DHCP will also be used if no "ip" config option is set.
* Experiments can now call ``scheduler.check_termination()`` to test if the user
has requested graceful termination.
* ARTIQ command-line programs and controllers now exit cleanly on Ctrl-C.
* ``artiq_coremgmt reboot`` now reloads gateware as well, providing a more thorough and reliable
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:
@ -59,6 +77,10 @@ Breaking changes:
* Mirny: Added extra delays in ``ADF5356.sync()``. This avoids the need of an extra delay before
calling `ADF5356.init()`.
* 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
-------

View File

@ -41,6 +41,14 @@ PHASER_ADDR_DUC1_P = 0x26
PHASER_ADDR_DAC1_DATA = 0x28
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_TRF0 = 1 << 1
PHASER_SEL_TRF1 = 1 << 2
@ -59,6 +67,11 @@ PHASER_DAC_SEL_TEST = 1
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
class Phaser:
@ -114,6 +127,31 @@ class Phaser:
configured through a shared SPI bus that is accessed and controlled via
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
upconverters are available to be modified through the `dac`, `trf0`,
`trf1` dictionaries. These can be set through the device database
@ -322,6 +360,8 @@ class Phaser:
self.core.delay(.1*ms)
channel.set_att_mu(0x00) # minimum attenuation
channel.set_servo(profile=0, enable=False, hold=True)
# test oscillators and DUC
for i in range(len(channel.oscillator)):
oscillator = channel.oscillator[i]
@ -394,6 +434,12 @@ class Phaser:
response = rtio_input_data(self.channel_base)
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
def write32(self, addr: int32, data: int32):
"""Write 32 bit to a sequence of FPGA registers."""
@ -1059,6 +1105,133 @@ class PhaserChannel:
data = data ^ ((1 << 12) | (1 << 13))
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
class PhaserOscillator:

View File

@ -8,6 +8,8 @@ from PyQt5 import QtCore, QtWidgets, QtGui
from sipyco.sync_struct import Subscriber
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.flowlayout import FlowLayout
@ -179,14 +181,45 @@ class _SimpleDisplayWidget(QtWidgets.QFrame):
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):
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.bus_channel = bus_channel
self.channel = channel
self.dds_name = title
self.cpld = cpld
self.cur_frequency = 0
self.dds_model = dds_model
QtWidgets.QFrame.__init__(self)
@ -249,7 +282,7 @@ class _DDSWidget(QtWidgets.QFrame):
set_grid.addWidget(set_btn, 0, 1, 1, 1)
# for urukuls also allow switching off RF
if self.cpld:
if self.dds_model.is_urukul:
off_btn = QtWidgets.QToolButton()
off_btn.setText("Off")
off_btn.setToolTip("Switch off the output")
@ -276,7 +309,7 @@ class _DDSWidget(QtWidgets.QFrame):
set_btn.clicked.connect(self.set_clicked)
apply.clicked.connect(self.apply_changes)
if self.cpld:
if self.dds_model.is_urukul:
off_btn.clicked.connect(self.off_clicked)
self.value_edit.returnPressed.connect(lambda: self.apply_changes(None))
self.value_edit.escapePressedConnect(self.cancel_changes)
@ -293,19 +326,20 @@ class _DDSWidget(QtWidgets.QFrame):
self.value_edit.selectAll()
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):
self.data_stack.setCurrentIndex(0)
self.button_stack.setCurrentIndex(0)
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):
self.data_stack.setCurrentIndex(0)
self.button_stack.setCurrentIndex(0)
def refresh_display(self):
self.cur_frequency = self.dds_model.cur_frequency
self.value_label.setText("<font size=\"4\">{:.7f}</font>"
.format(self.cur_frequency/1e6))
self.value_edit.setText("{:.7f}"
@ -356,7 +390,8 @@ def setup_from_ddb(ddb):
bus_channel = v["arguments"]["bus_channel"]
channel = v["arguments"]["channel"]
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)
elif (v["module"] == "artiq.coredevice.ad9910"
and v["class"] == "AD9910") or \
@ -368,7 +403,11 @@ def setup_from_ddb(ddb):
dds_cpld = v["arguments"]["cpld_device"]
spi_dev = ddb[dds_cpld]["arguments"]["spi_device"]
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)
elif ( (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx")
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)
except KeyError:
pass
return mi_addr, mi_port, dds_sysclk, description
return mi_addr, mi_port, description
class _DeviceManager:
@ -415,15 +454,13 @@ class _DeviceManager:
return ddb
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):
self.mi_addr = mi_addr
self.mi_port = mi_port
self.reconnect_mi.set()
self.dds_sysclk = dds_sysclk
for to_remove in self.description - description:
widget = 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,
"content": content,
"class_name": class_name,
"arguments": []
"arguments": {}
}
scheduling = {
"pipeline_name": "main",
@ -512,24 +549,25 @@ class _DeviceManager:
scheduling["flush"])
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
if dds_cpld:
if dds_model.is_urukul:
# urukuls need CPLD init and switch to on
# keep previous config if it was set already
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")
if len(cfg) > 0:
self.{cpld}.cfg_reg = cfg[0]
else:
delay(15*ms)
self.{cpld}.init()
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
cfg = self.core_cache.get("_{cpld}_cfg")
""".format(cpld=dds_cpld)
""".format(cpld=dds_model.cpld)
cfg_sw = """self.{}.cfg_sw(True)
cfg[0] = self.{}.cfg_reg
""".format(dds_channel, dds_cpld)
""".format(dds_channel, dds_model.cpld)
else:
cpld_dev = ""
cpld_init = ""
@ -546,8 +584,8 @@ class _DeviceManager:
@kernel
def run(self):
self.core.break_realtime()
delay(2*ms)
{cpld_init}
delay(5*ms)
self.{dds_channel}.init()
self.{dds_channel}.set({freq})
{cfg_sw}
@ -560,7 +598,7 @@ class _DeviceManager:
"SetDDS",
"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
toggle_exp = textwrap.dedent("""
from artiq.experiment import *
@ -575,18 +613,19 @@ class _DeviceManager:
@kernel
def run(self):
self.core.break_realtime()
delay(2*ms)
cfg = self.core_cache.get("_{cpld}_cfg")
if len(cfg) > 0:
self.{cpld}.cfg_reg = cfg[0]
else:
delay(15*ms)
self.{cpld}.init()
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
cfg = self.core_cache.get("_{cpld}_cfg")
delay(5*ms)
self.{ch}.init()
self.{ch}.cfg_sw({sw})
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(
self._submit_by_content(
toggle_exp,
@ -619,11 +658,11 @@ class _DeviceManager:
elif probe == TTLProbe.oe.value:
widget.cur_oe = bool(value)
widget.refresh_display()
if (channel, probe) in self.dds_widgets:
elif (channel, probe) in self.dds_widgets:
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()
if (channel, probe) in self.dac_widgets:
elif (channel, probe) in self.dac_widgets:
widget = self.dac_widgets[(channel, probe)]
widget.cur_value = value
widget.refresh_display()
@ -656,11 +695,11 @@ class _DeviceManager:
logger.info("cancelled connection to moninj")
break
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.)
self.reconnect_mi.set()
else:
logger.info("ARTIQ dashboard connected to moninj proxy (%s)",
logger.info("ARTIQ dashboard connected to moninj (%s)",
self.mi_addr)
self.mi_connection = new_mi_connection
for ttl_channel in self.ttl_widgets.keys():

View File

@ -116,6 +116,9 @@ class MonitorMux:
else:
raise ValueError
def disconnect_cb(self):
self.listeners.clear()
class ProxyConnection:
def __init__(self, monitor_mux, reader, writer):
@ -203,7 +206,9 @@ def main():
signal_handler.setup()
try:
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
loop.run_until_complete(comm_moninj.connect(args.core_addr))
try:

View File

@ -114,7 +114,12 @@ class Programmer:
"telnet_port disabled"
] + preinit_script
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):
if isinstance(self._client, LocalClient):

View File

@ -128,6 +128,7 @@ def main():
"scheduler_request_termination": scheduler.request_termination,
"scheduler_get_status": scheduler.get_status,
"scheduler_check_pause": scheduler.check_pause,
"scheduler_check_termination": scheduler.check_termination,
"ccb_issue": ccb_issue,
})
experiment_db.scan_repository_async()

View File

@ -295,7 +295,10 @@ class GTX(Module, TransceiverInterface):
i_CEB=stable_clkin_n,
i_I=clock_pads.p,
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()

View File

@ -3,7 +3,7 @@ from migen.build.generic_platform import *
from migen.genlib.io import DifferentialOutput
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.rtio.phy import servo as rtservo, fastino, phaser
@ -222,13 +222,13 @@ class Urukul(_EEM):
return ios
@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)
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.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4))
target.submodules += spi_phy
target.rtio_channels.append(rtio.Channel.from_phy(spi_phy, ififo_depth=4))
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
@ -237,9 +237,14 @@ class Urukul(_EEM):
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)
target.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy))
io_upd_phy = ttl_out_cls(pads.p, pads.n)
target.submodules += io_upd_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:
for signal in "sw0 sw1 sw2 sw3".split():
pads = target.platform.request("urukul{}_{}".format(eem, signal))
@ -247,6 +252,7 @@ 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):

View File

@ -47,7 +47,7 @@ def peripheral_urukul(module, peripheral, **kwargs):
else:
sync_gen_cls = None
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):

View File

@ -3,6 +3,11 @@ from migen import *
from artiq.gateware import ad9_dds
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):
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), [
If(selected(c), probe.eq(ftw))
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")
)
)
)

View File

@ -282,6 +282,9 @@ class MasterBase(MiniSoC, AMPSoC):
platform = self.platform
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)
i2c = self.platform.request("i2c")
@ -459,6 +462,11 @@ class SatelliteBase(BaseSoC):
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.attr.add("no_retiming")
if self.platform.hw_rev == "v2.0":

View File

@ -209,18 +209,28 @@ class TraceArgumentManager:
self.requested_args[key] = processor, group, tooltip
return None
def check_unprocessed_arguments(self):
pass
class ProcessArgumentManager:
def __init__(self, unprocessed_arguments):
self.unprocessed_arguments = unprocessed_arguments
self._processed_arguments = set()
def get(self, key, processor, group, tooltip):
if key in self.unprocessed_arguments:
r = processor.process(self.unprocessed_arguments[key])
self._processed_arguments.add(key)
else:
r = processor.default()
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:
"""Provides methods to manage the environment of an experiment (arguments,
@ -242,6 +252,8 @@ class HasEnvironment:
self.__in_build = True
self.build(*args, **kwargs)
self.__in_build = False
if self.__argument_mgr is not None:
self.__argument_mgr.check_unprocessed_arguments()
def register_child(self, child):
self.children.append(child)
@ -485,7 +497,7 @@ def is_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.
"""
return is_experiment(o) and not o.__name__.startswith("_")

View File

@ -490,3 +490,13 @@ class Scheduler:
return False
return r.priority_key() > run.priority_key()
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

View File

@ -111,6 +111,12 @@ class Scheduler:
rid = self.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"))
def submit(self, pipeline_name=None, expid=None, priority=None, due_date=None, flush=False):
if pipeline_name is None:

View File

@ -15,6 +15,7 @@
outputs = { self, mozilla-overlay, sipyco, nac3, artiq-comtools, src-migen, src-misoc }:
let
pkgs = import nac3.inputs.nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; };
pkgs-aarch64 = import nac3.inputs.nixpkgs { system = "aarch64-linux"; };
artiqVersionMajor = 9;
artiqVersionMinor = self.sourceInfo.revCount or 0;
@ -76,6 +77,7 @@
pname = "artiq";
version = artiqVersion;
src = self;
doCheck = false;
preBuild =
''
@ -228,7 +230,7 @@
dontFixup = true;
};
openocd-bscanspi = let
openocd-bscanspi-f = pkgs: let
bscan_spi_bitstreams-pkg = pkgs.stdenv.mkDerivation {
name = "bscan_spi_bitstreams";
src = pkgs.fetchFromGitHub {
@ -301,8 +303,9 @@
in rec {
packages.x86_64-linux = rec {
inherit (nac3.packages.x86_64-linux) python3-mimalloc;
inherit qasync openocd-bscanspi artiq;
inherit qasync artiq;
inherit migen misoc asyncserial microscope vivadoEnv vivado;
openocd-bscanspi = openocd-bscanspi-f pkgs;
artiq-board-kc705-nist_clock = makeArtiqBoardPackage {
target = "kc705";
variant = "nist_clock";
@ -377,6 +380,10 @@
];
};
packages.aarch64-linux = {
openocd-bscanspi = openocd-bscanspi-f pkgs-aarch64;
};
hydraJobs = {
inherit (packages.x86_64-linux) artiq artiq-board-kc705-nist_clock openocd-bscanspi;
sipyco-msys2-pkg = packages.x86_64-w64-mingw32.sipyco-pkg;