forked from M-Labs/artiq
1
0
Fork 0

Merge branch 'master' into nac3

This commit is contained in:
Sebastien Bourdeauducq 2024-07-27 23:33:01 +08:00
commit f6800bdba2
112 changed files with 3062 additions and 2403 deletions

View File

@ -7,22 +7,18 @@
ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is a leading-edge control and data acquisition system for quantum information experiments. ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is a leading-edge control and data acquisition system for quantum information experiments.
It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Many laboratories around the world have adopted ARTIQ as their control system and some have `contributed <https://m-labs.hk/experiment-control/funding/>`_ to it. It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Many laboratories around the world have adopted ARTIQ as their control system and some have `contributed <https://m-labs.hk/experiment-control/funding/>`_ to it.
The system features a high-level programming language that helps describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results. The system features a high-level programming language, capable of describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results.
ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware <https://github.com/sinara-hw>`_, and in particular the Kasli FPGA carrier, is designed to work with ARTIQ. ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware <https://github.com/sinara-hw>`_, and in particular the Kasli FPGA carrier, are designed to work with ARTIQ. ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers. Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort.
ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers.
Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and of a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort.
ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions. ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions. Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration. Like any open-source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_.
Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration.
Like any open source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_.
ARTIQ is supported by M-Labs and developed openly. ARTIQ is supported by M-Labs and developed openly. Components, features, fixes, improvements, and extensions are often `funded <https://m-labs.hk/experiment-control/funding/>`_ by and developed for the partnering research groups.
Components, features, fixes, improvements, and extensions are often `funded <https://m-labs.hk/experiment-control/funding/>`_ by and developed for the partnering research groups.
Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt5 <https://www.qt.io/>`_. Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt6 <https://www.qt.io/>`_.
Website: https://m-labs.hk/artiq | Website: https://m-labs.hk/experiment-control/artiq
| (US-hosted mirror: https://m-labs-intl.com/experiment-control/artiq)
`Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``. `Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``.

View File

@ -3,9 +3,20 @@
Release notes Release notes
============= =============
ARTIQ-8 (Unreleased) ARTIQ-9 (Unreleased)
-------------------- --------------------
* Zotino monitoring in the dashboard now displays the values in volts.
* afws_client now uses the "happy eyeballs" algorithm (RFC 6555) for a faster and more
reliable connection to the server.
* The Zadig driver installer was added to the MSYS2 offline installer.
* Fastino monitoring with Moninj is now supported.
* Qt6 support.
* Python 3.12 support.
ARTIQ-8
-------
Highlights: Highlights:
* New hardware support: * New hardware support:

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from PyQt5 import QtWidgets, QtCore, QtGui from PyQt6 import QtWidgets, QtCore, QtGui
from artiq.applets.simple import SimpleApplet from artiq.applets.simple import SimpleApplet
from artiq.tools import scale_from_metadata from artiq.tools import scale_from_metadata
from artiq.gui.tools import LayoutWidget from artiq.gui.tools import LayoutWidget
@ -17,7 +17,7 @@ class QCancellableLineEdit(QtWidgets.QLineEdit):
editCancelled = QtCore.pyqtSignal() editCancelled = QtCore.pyqtSignal()
def keyPressEvent(self, event): def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape: if event.key() == QtCore.Qt.Key.Key_Escape:
self.editCancelled.emit() self.editCancelled.emit()
else: else:
super().keyPressEvent(event) super().keyPressEvent(event)
@ -44,7 +44,7 @@ class NumberWidget(LayoutWidget):
self.edit_widget = QCancellableLineEdit() self.edit_widget = QCancellableLineEdit()
self.edit_widget.setValidator(QtGui.QDoubleValidator()) self.edit_widget.setValidator(QtGui.QDoubleValidator())
self.edit_widget.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter) self.edit_widget.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
self.edit_widget.editCancelled.connect(self.cancel_edit) self.edit_widget.editCancelled.connect(self.cancel_edit)
self.edit_widget.returnPressed.connect(self.confirm_edit) self.edit_widget.returnPressed.connect(self.confirm_edit)
self.number_area.addWidget(self.edit_widget) self.number_area.addWidget(self.edit_widget)

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import PyQt5 # make sure pyqtgraph imports Qt5 import PyQt6 # make sure pyqtgraph imports Qt6
import pyqtgraph import pyqtgraph
from artiq.applets.simple import SimpleApplet from artiq.applets.simple import SimpleApplet

View File

@ -1,7 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import PyQt5 # make sure pyqtgraph imports Qt5 import PyQt6 # make sure pyqtgraph imports Qt6
from PyQt5.QtCore import QTimer from PyQt6.QtCore import QTimer
import pyqtgraph import pyqtgraph
from artiq.applets.simple import TitleApplet from artiq.applets.simple import TitleApplet

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import numpy as np import numpy as np
import PyQt5 # make sure pyqtgraph imports Qt5 import PyQt6 # make sure pyqtgraph imports Qt6
from PyQt5.QtCore import QTimer from PyQt6.QtCore import QTimer
import pyqtgraph import pyqtgraph
from artiq.applets.simple import TitleApplet from artiq.applets.simple import TitleApplet

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import numpy as np import numpy as np
from PyQt5 import QtWidgets from PyQt6 import QtWidgets
from PyQt5.QtCore import QTimer from PyQt6.QtCore import QTimer
import pyqtgraph import pyqtgraph
from artiq.applets.simple import SimpleApplet from artiq.applets.simple import SimpleApplet

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from PyQt5 import QtWidgets from PyQt6 import QtWidgets
from artiq.applets.simple import SimpleApplet from artiq.applets.simple import SimpleApplet

View File

@ -24,21 +24,21 @@ class _AppletRequestInterface:
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None): def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
""" """
Set a dataset. Set a dataset.
See documentation of ``artiq.language.environment.set_dataset``. See documentation of :meth:`~artiq.language.environment.HasEnvironment.set_dataset`.
""" """
raise NotImplementedError raise NotImplementedError
def mutate_dataset(self, key, index, value): def mutate_dataset(self, key, index, value):
""" """
Mutate a dataset. Mutate a dataset.
See documentation of ``artiq.language.environment.mutate_dataset``. See documentation of :meth:`~artiq.language.environment.HasEnvironment.mutate_dataset`.
""" """
raise NotImplementedError raise NotImplementedError
def append_to_dataset(self, key, value): def append_to_dataset(self, key, value):
""" """
Append to a dataset. Append to a dataset.
See documentation of ``artiq.language.environment.append_to_dataset``. See documentation of :meth:`~artiq.language.environment.HasEnvironment.append_to_dataset`.
""" """
raise NotImplementedError raise NotImplementedError
@ -49,8 +49,9 @@ class _AppletRequestInterface:
:param expurl: Experiment URL identifying the experiment in the dashboard. Example: 'repo:ArgumentsDemo'. :param expurl: Experiment URL identifying the experiment in the dashboard. Example: 'repo:ArgumentsDemo'.
:param key: Name of the argument in the experiment. :param key: Name of the argument in the experiment.
:param value: Object representing the new temporary value of the argument. For ``Scannable`` arguments, this parameter :param value: Object representing the new temporary value of the argument. For :class:`~artiq.language.scan.Scannable` arguments,
should be a ``ScanObject``. The type of the ``ScanObject`` will be set as the selected type when this function is called. this parameter should be a :class:`~artiq.language.scan.ScanObject`. The type of the :class:`~artiq.language.scan.ScanObject`
will be set as the selected type when this function is called.
""" """
raise NotImplementedError raise NotImplementedError
@ -136,9 +137,8 @@ class AppletIPCClient(AsyncioChildComm):
logger.error("unexpected action reply to embed request: %s", logger.error("unexpected action reply to embed request: %s",
reply["action"]) reply["action"])
self.close_cb() self.close_cb()
else:
def fix_initial_size(self): return reply["size_w"], reply["size_h"]
self.write_pyon({"action": "fix_initial_size"})
async def listen(self): async def listen(self):
data = None data = None
@ -272,7 +272,7 @@ class SimpleApplet:
# HACK: if the window has a frame, there will be garbage # HACK: if the window has a frame, there will be garbage
# (usually white) displayed at its right and bottom borders # (usually white) displayed at its right and bottom borders
# after it is embedded. # after it is embedded.
self.main_widget.setWindowFlags(QtCore.Qt.FramelessWindowHint) self.main_widget.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint)
self.main_widget.show() self.main_widget.show()
win_id = int(self.main_widget.winId()) win_id = int(self.main_widget.winId())
self.loop.run_until_complete(self.ipc.embed(win_id)) self.loop.run_until_complete(self.ipc.embed(win_id))
@ -285,12 +285,13 @@ class SimpleApplet:
# 2. applet creates native window without showing it, and # 2. applet creates native window without showing it, and
# gets its ID # gets its ID
# 3. applet sends the ID to host, host embeds the widget # 3. applet sends the ID to host, host embeds the widget
# 4. applet shows the widget # and returns embedded size
# 5. parent resizes the widget # 4. applet is resized to that given size
# 5. applet shows the widget
win_id = int(self.main_widget.winId()) win_id = int(self.main_widget.winId())
self.loop.run_until_complete(self.ipc.embed(win_id)) size_w, size_h = self.loop.run_until_complete(self.ipc.embed(win_id))
self.main_widget.resize(size_w, size_h)
self.main_widget.show() self.main_widget.show()
self.ipc.fix_initial_size()
else: else:
self.main_widget.show() self.main_widget.show()

View File

@ -1,12 +1,12 @@
import logging import logging
import asyncio import asyncio
from PyQt5 import QtCore, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from sipyco.pc_rpc import AsyncioClient as RPCClient from sipyco.pc_rpc import AsyncioClient as RPCClient
from artiq.tools import short_format from artiq.tools import short_format
from artiq.gui.tools import LayoutWidget, QRecursiveFilterProxyModel from artiq.gui.tools import LayoutWidget
from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.models import DictSyncTreeSepModel
# reduced read-only version of artiq.dashboard.datasets # reduced read-only version of artiq.dashboard.datasets
@ -62,8 +62,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
def __init__(self, dataset_sub, dataset_ctl): def __init__(self, dataset_sub, dataset_ctl):
QtWidgets.QDockWidget.__init__(self, "Datasets") QtWidgets.QDockWidget.__init__(self, "Datasets")
self.setObjectName("Datasets") self.setObjectName("Datasets")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | self.setFeatures(self.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable) self.DockWidgetFeature.DockWidgetFloatable)
grid = LayoutWidget() grid = LayoutWidget()
self.setWidget(grid) self.setWidget(grid)
@ -74,9 +74,9 @@ class DatasetsDock(QtWidgets.QDockWidget):
grid.addWidget(self.search, 0, 0) grid.addWidget(self.search, 0, 0)
self.table = QtWidgets.QTreeView() self.table = QtWidgets.QTreeView()
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self.table.setSelectionMode( self.table.setSelectionMode(
QtWidgets.QAbstractItemView.SingleSelection) QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
grid.addWidget(self.table, 1, 0) grid.addWidget(self.table, 1, 0)
metadata_grid = LayoutWidget() metadata_grid = LayoutWidget()
@ -85,13 +85,13 @@ class DatasetsDock(QtWidgets.QDockWidget):
"rid start_time".split()): "rid start_time".split()):
metadata_grid.addWidget(QtWidgets.QLabel(label), i, 0) metadata_grid.addWidget(QtWidgets.QLabel(label), i, 0)
v = QtWidgets.QLabel() v = QtWidgets.QLabel()
v.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) v.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse)
metadata_grid.addWidget(v, i, 1) metadata_grid.addWidget(v, i, 1)
self.metadata[label] = v self.metadata[label] = v
grid.addWidget(metadata_grid, 2, 0) grid.addWidget(metadata_grid, 2, 0)
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
upload_action = QtWidgets.QAction("Upload dataset to master", upload_action = QtGui.QAction("Upload dataset to master",
self.table) self.table)
upload_action.triggered.connect(self.upload_clicked) upload_action.triggered.connect(self.upload_clicked)
self.table.addAction(upload_action) self.table.addAction(upload_action)
@ -112,7 +112,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
def set_model(self, model): def set_model(self, model):
self.table_model = model self.table_model = model
self.table_model_filter = QRecursiveFilterProxyModel() self.table_model_filter = QtCore.QSortFilterProxyModel()
self.table_model_filter.setRecursiveFilteringEnabled(True)
self.table_model_filter.setSourceModel(self.table_model) self.table_model_filter.setSourceModel(self.table_model)
self.table.setModel(self.table_model_filter) self.table.setModel(self.table_model_filter)

View File

@ -4,7 +4,7 @@ import os
from functools import partial from functools import partial
from collections import OrderedDict from collections import OrderedDict
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
import h5py import h5py
from sipyco import pyon from sipyco import pyon
@ -33,13 +33,13 @@ class _ArgumentEditor(EntryTreeWidget):
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments") recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
recompute_arguments.setIcon( recompute_arguments.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_BrowserReload)) QtWidgets.QStyle.StandardPixmap.SP_BrowserReload))
recompute_arguments.clicked.connect(self._recompute_arguments_clicked) recompute_arguments.clicked.connect(self._recompute_arguments_clicked)
load = QtWidgets.QPushButton("Set arguments from HDF5") load = QtWidgets.QPushButton("Set arguments from HDF5")
load.setToolTip("Set arguments from currently selected HDF5 file") load.setToolTip("Set arguments from currently selected HDF5 file")
load.setIcon(QtWidgets.QApplication.style().standardIcon( load.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogApplyButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogApplyButton))
load.clicked.connect(self._load_clicked) load.clicked.connect(self._load_clicked)
buttons = LayoutWidget() buttons = LayoutWidget()
@ -86,7 +86,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
self.resize(100*qfm.averageCharWidth(), 30*qfm.lineSpacing()) self.resize(100*qfm.averageCharWidth(), 30*qfm.lineSpacing())
self.setWindowTitle(expurl) self.setWindowTitle(expurl)
self.setWindowIcon(QtWidgets.QApplication.style().standardIcon( self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogContentsView)) QtWidgets.QStyle.StandardPixmap.SP_FileDialogContentsView))
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.layout = QtWidgets.QGridLayout() self.layout = QtWidgets.QGridLayout()
@ -126,22 +126,22 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
run = QtWidgets.QPushButton("Analyze") run = QtWidgets.QPushButton("Analyze")
run.setIcon(QtWidgets.QApplication.style().standardIcon( run.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
run.setToolTip("Run analysis stage (Ctrl+Return)") run.setToolTip("Run analysis stage (Ctrl+Return)")
run.setShortcut("CTRL+RETURN") run.setShortcut("CTRL+RETURN")
run.setSizePolicy(QtWidgets.QSizePolicy.Expanding, run.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Expanding) QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(run, 2, 4) self.layout.addWidget(run, 2, 4)
run.clicked.connect(self._run_clicked) run.clicked.connect(self._run_clicked)
self._run = run self._run = run
terminate = QtWidgets.QPushButton("Terminate") terminate = QtWidgets.QPushButton("Terminate")
terminate.setIcon(QtWidgets.QApplication.style().standardIcon( terminate.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogCancelButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton))
terminate.setToolTip("Terminate analysis (Ctrl+Backspace)") terminate.setToolTip("Terminate analysis (Ctrl+Backspace)")
terminate.setShortcut("CTRL+BACKSPACE") terminate.setShortcut("CTRL+BACKSPACE")
terminate.setSizePolicy(QtWidgets.QSizePolicy.Expanding, terminate.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Expanding) QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(terminate, 3, 4) self.layout.addWidget(terminate, 3, 4)
terminate.clicked.connect(self._terminate_clicked) terminate.clicked.connect(self._terminate_clicked)
terminate.setEnabled(False) terminate.setEnabled(False)
@ -316,7 +316,7 @@ class ExperimentsArea(QtWidgets.QMdiArea):
asyncio.ensure_future(sub.load_hdf5_task(path)) asyncio.ensure_future(sub.load_hdf5_task(path))
def mousePressEvent(self, ev): def mousePressEvent(self, ev):
if ev.button() == QtCore.Qt.LeftButton: if ev.button() == QtCore.Qt.MouseButton.LeftButton:
self.select_experiment() self.select_experiment()
def paintEvent(self, event): def paintEvent(self, event):
@ -406,7 +406,7 @@ class ExperimentsArea(QtWidgets.QMdiArea):
exc_info=True) exc_info=True)
dock = _ExperimentDock(self, expurl, {}) dock = _ExperimentDock(self, expurl, {})
asyncio.ensure_future(dock._recompute_arguments()) asyncio.ensure_future(dock._recompute_arguments())
dock.setAttribute(QtCore.Qt.WA_DeleteOnClose) dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
self.addSubWindow(dock) self.addSubWindow(dock)
dock.show() dock.show()
dock.sigClosed.connect(partial(self.on_dock_closed, dock)) dock.sigClosed.connect(partial(self.on_dock_closed, dock))

View File

@ -3,7 +3,7 @@ import os
from datetime import datetime from datetime import datetime
import h5py import h5py
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt6 import QtCore, QtWidgets, QtGui
from sipyco import pyon from sipyco import pyon
@ -69,35 +69,35 @@ class ZoomIconView(QtWidgets.QListView):
def __init__(self): def __init__(self):
QtWidgets.QListView.__init__(self) QtWidgets.QListView.__init__(self)
self._char_width = QtGui.QFontMetrics(self.font()).averageCharWidth() self._char_width = QtGui.QFontMetrics(self.font()).averageCharWidth()
self.setViewMode(self.IconMode) self.setViewMode(self.ViewMode.IconMode)
w = self._char_width*self.default_size w = self._char_width*self.default_size
self.setIconSize(QtCore.QSize(w, int(w*self.aspect))) self.setIconSize(QtCore.QSize(w, int(w*self.aspect)))
self.setFlow(self.LeftToRight) self.setFlow(self.Flow.LeftToRight)
self.setResizeMode(self.Adjust) self.setResizeMode(self.ResizeMode.Adjust)
self.setWrapping(True) self.setWrapping(True)
def wheelEvent(self, ev): def wheelEvent(self, ev):
if ev.modifiers() & QtCore.Qt.ControlModifier: if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
a = self._char_width*self.min_size a = self._char_width*self.min_size
b = self._char_width*self.max_size b = self._char_width*self.max_size
w = self.iconSize().width()*self.zoom_step**( w = self.iconSize().width()*self.zoom_step**(
ev.angleDelta().y()/120.) ev.angleDelta().y()/120.)
if a <= w <= b: if a <= w <= b:
self.setIconSize(QtCore.QSize(w, w*self.aspect)) self.setIconSize(QtCore.QSize(int(w), int(w*self.aspect)))
else: else:
QtWidgets.QListView.wheelEvent(self, ev) QtWidgets.QListView.wheelEvent(self, ev)
class Hdf5FileSystemModel(QtWidgets.QFileSystemModel): class Hdf5FileSystemModel(QtGui.QFileSystemModel):
def __init__(self): def __init__(self):
QtWidgets.QFileSystemModel.__init__(self) QtGui.QFileSystemModel.__init__(self)
self.setFilter(QtCore.QDir.Drives | QtCore.QDir.NoDotAndDotDot | self.setFilter(QtCore.QDir.Filter.Drives | QtCore.QDir.Filter.NoDotAndDotDot |
QtCore.QDir.AllDirs | QtCore.QDir.Files) QtCore.QDir.Filter.AllDirs | QtCore.QDir.Filter.Files)
self.setNameFilterDisables(False) self.setNameFilterDisables(False)
self.setIconProvider(ThumbnailIconProvider()) self.setIconProvider(ThumbnailIconProvider())
def data(self, idx, role): def data(self, idx, role):
if role == QtCore.Qt.ToolTipRole: if role == QtCore.Qt.ItemDataRole.ToolTipRole:
info = self.fileInfo(idx) info = self.fileInfo(idx)
h5 = open_h5(info) h5 = open_h5(info)
if h5 is not None: if h5 is not None:
@ -114,7 +114,7 @@ class Hdf5FileSystemModel(QtWidgets.QFileSystemModel):
except: except:
logger.warning("unable to read metadata from %s", logger.warning("unable to read metadata from %s",
info.filePath(), exc_info=True) info.filePath(), exc_info=True)
return QtWidgets.QFileSystemModel.data(self, idx, role) return QtGui.QFileSystemModel.data(self, idx, role)
class FilesDock(QtWidgets.QDockWidget): class FilesDock(QtWidgets.QDockWidget):
@ -125,7 +125,7 @@ class FilesDock(QtWidgets.QDockWidget):
def __init__(self, datasets, browse_root=""): def __init__(self, datasets, browse_root=""):
QtWidgets.QDockWidget.__init__(self, "Files") QtWidgets.QDockWidget.__init__(self, "Files")
self.setObjectName("Files") self.setObjectName("Files")
self.setFeatures(self.DockWidgetMovable | self.DockWidgetFloatable) self.setFeatures(self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable)
self.splitter = QtWidgets.QSplitter() self.splitter = QtWidgets.QSplitter()
self.setWidget(self.splitter) self.setWidget(self.splitter)
@ -147,8 +147,8 @@ class FilesDock(QtWidgets.QDockWidget):
self.rt.setRootIndex(rt_model.mapFromSource( self.rt.setRootIndex(rt_model.mapFromSource(
self.model.setRootPath(browse_root))) self.model.setRootPath(browse_root)))
self.rt.setHeaderHidden(True) self.rt.setHeaderHidden(True)
self.rt.setSelectionBehavior(self.rt.SelectRows) self.rt.setSelectionBehavior(self.rt.SelectionBehavior.SelectRows)
self.rt.setSelectionMode(self.rt.SingleSelection) self.rt.setSelectionMode(self.rt.SelectionMode.SingleSelection)
self.rt.selectionModel().currentChanged.connect( self.rt.selectionModel().currentChanged.connect(
self.tree_current_changed) self.tree_current_changed)
self.rt.setRootIsDecorated(False) self.rt.setRootIsDecorated(False)
@ -252,7 +252,7 @@ class FilesDock(QtWidgets.QDockWidget):
100, 100,
lambda: self.rt.scrollTo( lambda: self.rt.scrollTo(
self.rt.model().mapFromSource(self.model.index(path)), self.rt.model().mapFromSource(self.model.index(path)),
self.rt.PositionAtCenter) self.rt.ScrollHint.PositionAtCenter)
) )
self.model.directoryLoaded.connect(scroll_when_loaded) self.model.directoryLoaded.connect(scroll_when_loaded)
idx = self.rt.model().mapFromSource(idx) idx = self.rt.model().mapFromSource(idx)

View File

@ -1,8 +1,8 @@
""""RTIO driver for the Analog Devices AD53[67][0123] family of multi-channel """RTIO driver for the Analog Devices AD53[67][0123] family of multi-channel
Digital to Analog Converters. Digital to Analog Converters.
Output event replacement is not supported and issuing commands at the same Output event replacement is not supported and issuing commands at the same
time is an error. time results in a collision error.
""" """
# Designed from the data sheets and somewhat after the linux kernel # Designed from the data sheets and somewhat after the linux kernel
@ -135,10 +135,10 @@ class AD53xx:
optimized for speed; datasheet says t22: 25ns min SCLK edge to SDO optimized for speed; datasheet says t22: 25ns min SCLK edge to SDO
valid, and suggests the SPI speed for reads should be <=20 MHz) valid, and suggests the SPI speed for reads should be <=20 MHz)
:param vref: DAC reference voltage (default: 5.) :param vref: DAC reference voltage (default: 5.)
:param offset_dacs: Initial register value for the two offset DACs, device :param offset_dacs: Initial register value for the two offset DACs
dependent and must be set correctly for correct voltage to mu (default: 8192). Device dependent and must be set correctly for
conversions. Knowledge of his state is not transferred between correct voltage-to-mu conversions. Knowledge of this state is
experiments. (default: 8192) not transferred between experiments.
:param core_device: Core device name (default: "core") :param core_device: Core device name (default: "core")
""" """
core: KernelInvariant[Core] core: KernelInvariant[Core]
@ -213,7 +213,7 @@ class AD53xx:
:param op: Operation to perform, one of :const:`AD53XX_READ_X1A`, :param op: Operation to perform, one of :const:`AD53XX_READ_X1A`,
:const:`AD53XX_READ_X1B`, :const:`AD53XX_READ_OFFSET`, :const:`AD53XX_READ_X1B`, :const:`AD53XX_READ_OFFSET`,
:const:`AD53XX_READ_GAIN` etc. (default: :const:`AD53XX_READ_X1A`). :const:`AD53XX_READ_GAIN` etc. (default: :const:`AD53XX_READ_X1A`).
:return: The 16 bit register value :return: The 16-bit register value
""" """
self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8) self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8)
self.bus.set_config_mu(SPI_AD53XX_CONFIG | SPI_INPUT, 24, self.bus.set_config_mu(SPI_AD53XX_CONFIG | SPI_INPUT, 24,
@ -319,7 +319,7 @@ class AD53xx:
This method does not advance the timeline; write events are scheduled This method does not advance the timeline; write events are scheduled
in the past. The DACs will synchronously start changing their output in the past. The DACs will synchronously start changing their output
levels `now`. levels ``now``.
If no LDAC device was defined, the LDAC pulse is skipped. If no LDAC device was defined, the LDAC pulse is skipped.
@ -375,8 +375,8 @@ class AD53xx:
high) can be calibrated in this fashion. high) can be calibrated in this fashion.
:param channel: The number of the calibrated channel :param channel: The number of the calibrated channel
:params vzs: Measured voltage with the DAC set to zero-scale (0x0000) :param vzs: Measured voltage with the DAC set to zero-scale (0x0000)
:params vfs: Measured voltage with the DAC set to full-scale (0xffff) :param vfs: Measured voltage with the DAC set to full-scale (0xffff)
""" """
offset_err = voltage_to_mu(vzs, self.offset_dacs, self.vref) offset_err = voltage_to_mu(vzs, self.offset_dacs, self.vref)
gain_err = voltage_to_mu(vfs, self.offset_dacs, self.vref) - ( gain_err = voltage_to_mu(vfs, self.offset_dacs, self.vref) - (

View File

@ -122,27 +122,27 @@ class AD9910:
(as configured through CFG_MASK_NU), 4-7 for individual channels. (as configured through CFG_MASK_NU), 4-7 for individual channels.
:param cpld_device: Name of the Urukul CPLD this device is on. :param cpld_device: Name of the Urukul CPLD this device is on.
:param sw_device: Name of the RF switch device. The RF switch is a :param sw_device: Name of the RF switch device. The RF switch is a
TTLOut channel available as the :attr:`sw` attribute of this instance. TTLOut channel available as the ``sw`` attribute of this instance.
:param pll_n: DDS PLL multiplier. The DDS sample clock is :param pll_n: DDS PLL multiplier. The DDS sample clock is
f_ref/clk_div*pll_n where f_ref is the reference frequency and ``f_ref / clk_div * pll_n`` where ``f_ref`` is the reference frequency and
clk_div is the reference clock divider (both set in the parent ``clk_div`` is the reference clock divider (both set in the parent
Urukul CPLD instance). Urukul CPLD instance).
:param pll_en: PLL enable bit, set to False to bypass PLL (default: True). :param pll_en: PLL enable bit, set to False to bypass PLL (default: True).
Note that when bypassing the PLL the red front panel LED may remain on. Note that when bypassing the PLL the red front panel LED may remain on.
:param pll_cp: DDS PLL charge pump setting. :param pll_cp: DDS PLL charge pump setting.
:param pll_vco: DDS PLL VCO range selection. :param pll_vco: DDS PLL VCO range selection.
:param sync_delay_seed: SYNC_IN delay tuning starting value. :param sync_delay_seed: ``SYNC_IN`` delay tuning starting value.
To stabilize the SYNC_IN delay tuning, run :meth:`tune_sync_delay` once 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 and set this to the delay tap number returned (default: -1 to signal no
synchronization and no tuning during :meth:`init`). synchronization and no tuning during :meth:`init`).
Can be a string of the form "eeprom_device:byte_offset" to read the Can be a string of the form ``eeprom_device:byte_offset`` to read the
value from a I2C EEPROM; in which case, `io_update_delay` must be set value from a I2C EEPROM, in which case ``io_update_delay`` must be set
to the same string value. to the same string value.
:param io_update_delay: IO_UPDATE pulse alignment delay. :param io_update_delay: ``IO_UPDATE`` pulse alignment delay.
To align IO_UPDATE to SYNC_CLK, run :meth:`tune_io_update_delay` and To align ``IO_UPDATE`` to ``SYNC_CLK``, run :meth:`tune_io_update_delay` and
set this to the delay tap number returned. set this to the delay tap number returned.
Can be a string of the form "eeprom_device:byte_offset" to read the Can be a string of the form ``eeprom_device:byte_offset`` to read the
value from a I2C EEPROM; in which case, `sync_delay_seed` must be set value from a I2C EEPROM, in which case ``sync_delay_seed`` must be set
to the same string value. to the same string value.
""" """
@ -213,9 +213,7 @@ class AD9910:
@kernel @kernel
def set_phase_mode(self, phase_mode: int32): def set_phase_mode(self, phase_mode: int32):
r"""Set the default phase mode. r"""Set the default phase mode for future calls to :meth:`set` and
for future calls to :meth:`set` and
:meth:`set_mu`. Supported phase modes are: :meth:`set_mu`. Supported phase modes are:
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged * :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged
@ -258,7 +256,7 @@ class AD9910:
@kernel @kernel
def write16(self, addr: int32, data: int32): def write16(self, addr: int32, data: int32):
"""Write to 16 bit register. """Write to 16-bit register.
:param addr: Register address :param addr: Register address
:param data: Data to be written :param data: Data to be written
@ -269,7 +267,7 @@ class AD9910:
@kernel @kernel
def write32(self, addr: int32, data: int32): def write32(self, addr: int32, data: int32):
"""Write to 32 bit register. """Write to 32-bit register.
:param addr: Register address :param addr: Register address
:param data: Data to be written :param data: Data to be written
@ -283,7 +281,7 @@ class AD9910:
@kernel @kernel
def read16(self, addr: int32) -> int32: def read16(self, addr: int32) -> int32:
"""Read from 16 bit register. """Read from 16-bit register.
:param addr: Register address :param addr: Register address
""" """
@ -298,7 +296,7 @@ class AD9910:
@kernel @kernel
def read32(self, addr: int32) -> int32: def read32(self, addr: int32) -> int32:
"""Read from 32 bit register. """Read from 32-bit register.
:param addr: Register address :param addr: Register address
""" """
@ -313,10 +311,10 @@ class AD9910:
@kernel @kernel
def read64(self, addr: int32) -> int64: def read64(self, addr: int32) -> int64:
"""Read from 64 bit register. """Read from 64-bit register.
:param addr: Register address :param addr: Register address
:return: 64 bit integer register value :return: 64-bit integer register value
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
SPI_CONFIG, 8, SPI_CONFIG, 8,
@ -336,10 +334,10 @@ class AD9910:
@kernel @kernel
def write64(self, addr: int32, data_high: int32, data_low: int32): def write64(self, addr: int32, data_high: int32, data_low: int32):
"""Write to 64 bit register. """Write to 64-bit register.
:param addr: Register address :param addr: Register address
:param data_high: High (MSB) 32 bits of the data :param data_high: High (MSB) 32 data bits
:param data_low: Low (LSB) 32 data bits :param data_low: Low (LSB) 32 data bits
""" """
self.bus.set_config_mu(SPI_CONFIG, 8, self.bus.set_config_mu(SPI_CONFIG, 8,
@ -357,8 +355,9 @@ class AD9910:
"""Write data to RAM. """Write data to RAM.
The profile to write to and the step, start, and end address The profile to write to and the step, start, and end address
need to be configured before and separately using need to be configured in advance and separately using
:meth:`set_profile_ram` and the parent CPLD `set_profile`. :meth:`set_profile_ram` and the parent CPLD
:meth:`~artiq.coredevice.urukul.CPLD.set_profile`.
:param data: Data to be written to RAM. :param data: Data to be written to RAM.
""" """
@ -379,7 +378,8 @@ class AD9910:
The profile to read from and the step, start, and end address The profile to read from and the step, start, and end address
need to be configured before and separately using need to be configured before and separately using
:meth:`set_profile_ram` and the parent CPLD `set_profile`. :meth:`set_profile_ram` and the parent CPLD
:meth:`~artiq.coredevice.urukul.CPLD.set_profile`.
:param data: List to be filled with data read from RAM. :param data: List to be filled with data read from RAM.
""" """
@ -415,9 +415,9 @@ class AD9910:
manual_osk_external: int32 = 0, manual_osk_external: int32 = 0,
osk_enable: int32 = 0, osk_enable: int32 = 0,
select_auto_osk: int32 = 0): select_auto_osk: int32 = 0):
"""Set CFR1. See the AD9910 datasheet for parameter meanings. """Set CFR1. See the AD9910 datasheet for parameter meanings and sizes.
This method does not pulse IO_UPDATE. This method does not pulse ``IO_UPDATE.``
:param power_down: Power down bits. :param power_down: Power down bits.
:param phase_autoclear: Autoclear phase accumulator. :param phase_autoclear: Autoclear phase accumulator.
@ -454,9 +454,9 @@ class AD9910:
effective_ftw: int32 = 1, effective_ftw: int32 = 1,
sync_validation_disable: int32 = 0, sync_validation_disable: int32 = 0,
matched_latency_enable: int32 = 0): matched_latency_enable: int32 = 0):
"""Set CFR2. See the AD9910 datasheet for parameter meanings. """Set CFR2. See the AD9910 datasheet for parameter meanings and sizes.
This method does not pulse IO_UPDATE. This method does not pulse ``IO_UPDATE``.
:param asf_profile_enable: Enable amplitude scale from single tone profiles. :param asf_profile_enable: Enable amplitude scale from single tone profiles.
:param drg_enable: Digital ramp enable. :param drg_enable: Digital ramp enable.
@ -481,14 +481,14 @@ class AD9910:
"""Initialize and configure the DDS. """Initialize and configure the DDS.
Sets up SPI mode, confirms chip presence, powers down unused blocks, Sets up SPI mode, confirms chip presence, powers down unused blocks,
configures the PLL, waits for PLL lock. Uses the configures the PLL, waits for PLL lock. Uses the ``IO_UPDATE``
IO_UPDATE signal multiple times. signal multiple times.
:param blind: Do not read back DDS identity and do not wait for lock. :param blind: Do not read back DDS identity and do not wait for lock.
""" """
self.sync_data.init() self.sync_data.init()
if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div: if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div:
raise ValueError("parent cpld does not drive SYNC") raise ValueError("parent CPLD does not drive SYNC")
if self.sync_data.sync_delay_seed >= 0: if self.sync_data.sync_delay_seed >= 0:
if float(self.sysclk_per_mu) != self.sysclk * self.core.ref_period: if float(self.sysclk_per_mu) != self.sysclk * self.core.ref_period:
raise ValueError("incorrect clock ratio for synchronization") raise ValueError("incorrect clock ratio for synchronization")
@ -539,7 +539,7 @@ class AD9910:
def power_down(self, bits: int32 = 0b1111): def power_down(self, bits: int32 = 0b1111):
"""Power down DDS. """Power down DDS.
:param bits: Power down bits, see datasheet :param bits: Power-down bits, see datasheet
""" """
self.set_cfr1(power_down=bits) self.set_cfr1(power_down=bits)
self.cpld.io_update.pulse(1. * us) self.cpld.io_update.pulse(1. * us)
@ -559,23 +559,23 @@ class AD9910:
After the SPI transfer, the shared IO update pin is pulsed to After the SPI transfer, the shared IO update pin is pulsed to
activate the data. activate the data.
.. seealso: :meth:`set_phase_mode` for a definition of the different .. seealso: :meth:`AD9910.set_phase_mode` for a definition of the different
phase modes. phase modes.
:param ftw: Frequency tuning word: 32 bit. :param ftw: Frequency tuning word: 32-bit.
:param pow_: Phase tuning word: 16 bit unsigned. :param pow_: Phase tuning word: 16-bit unsigned.
:param asf: Amplitude scale factor: 14 bit unsigned. :param asf: Amplitude scale factor: 14-bit unsigned.
:param phase_mode: If specified, overrides the default phase mode set :param phase_mode: If specified, overrides the default phase mode set
by :meth:`set_phase_mode` for this call. by :meth:`set_phase_mode` for this call.
:param ref_time_mu: Fiducial time used to compute absolute or tracking :param ref_time_mu: Fiducial time used to compute absolute or tracking
phase updates. In machine units as obtained by `now_mu()`. phase updates. In machine units as obtained by :meth:`~artiq.language.core.now_mu()`.
:param profile: Single tone profile number to set (0-7, default: 7). :param profile: Single tone profile number to set (0-7, default: 7).
Ineffective if `ram_destination` is specified. Ineffective if ``ram_destination`` is specified.
:param ram_destination: RAM destination (:const:`RAM_DEST_FTW`, :param ram_destination: RAM destination (:const:`RAM_DEST_FTW`,
:const:`RAM_DEST_POW`, :const:`RAM_DEST_ASF`, :const:`RAM_DEST_POW`, :const:`RAM_DEST_ASF`,
:const:`RAM_DEST_POWASF`). If specified, write free DDS parameters :const:`RAM_DEST_POWASF`). If specified, write free DDS parameters
to the ASF/FTW/POW registers instead of to the single tone profile to the ASF/FTW/POW registers instead of to the single tone profile
register (default behaviour, see `profile`). register (default behaviour, see ``profile``).
:return: Resulting phase offset word after application of phase :return: Resulting phase offset word after application of phase
tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in
subsequent calls, use this value as the "current" phase. subsequent calls, use this value as the "current" phase.
@ -623,10 +623,10 @@ class AD9910:
"""Get the frequency tuning word, phase offset word, """Get the frequency tuning word, phase offset word,
and amplitude scale factor. and amplitude scale factor.
.. seealso:: :meth:`get` See also :meth:`AD9910.get`.
:param profile: Profile number to get (0-7, default: 7) :param profile: Profile number to get (0-7, default: 7)
:return: A tuple ``(ftw, pow, asf)`` :return: A tuple (FTW, POW, ASF)
""" """
# Read data # Read data
@ -642,12 +642,12 @@ class AD9910:
profile: int32 = _DEFAULT_PROFILE_RAM, profile: int32 = _DEFAULT_PROFILE_RAM,
nodwell_high: int32 = 0, zero_crossing: int32 = 0, nodwell_high: int32 = 0, zero_crossing: int32 = 0,
mode: int32 = 1): mode: int32 = 1):
"""Set the RAM profile settings. """Set the RAM profile settings. See also AD9910 datasheet.
:param start: Profile start address in RAM. :param start: Profile start address in RAM (10-bit).
:param end: Profile end address in RAM (last address). :param end: Profile end address in RAM, inclusive (10-bit).
:param step: Profile time step in units of t_DDS, typically 4 ns :param step: Profile time step, counted in DDS sample clock
(default: 1). cycles, typically 4 ns (16-bit, default: 1)
:param profile: Profile index (0 to 7) (default: 0). :param profile: Profile index (0 to 7) (default: 0).
:param nodwell_high: No-dwell high bit (default: 0, :param nodwell_high: No-dwell high bit (default: 0,
see AD9910 documentation). see AD9910 documentation).
@ -875,7 +875,7 @@ class AD9910:
ram_destination: int32 = -1) -> float: ram_destination: int32 = -1) -> float:
"""Set DDS data in SI units. """Set DDS data in SI units.
.. seealso:: :meth:`set_mu` See also :meth:`AD9910.set_mu`.
:param frequency: Frequency in Hz :param frequency: Frequency in Hz
:param phase: Phase tuning word in turns :param phase: Phase tuning word in turns
@ -896,10 +896,10 @@ class AD9910:
) -> tuple[float, float, float]: ) -> tuple[float, float, float]:
"""Get the frequency, phase, and amplitude. """Get the frequency, phase, and amplitude.
.. seealso:: :meth:`get_mu` See also :meth:`AD9910.get_mu`.
:param profile: Profile number to get (0-7, default: 7) :param profile: Profile number to get (0-7, default: 7)
:return: A tuple ``(frequency, phase, amplitude)`` :return: A tuple (frequency, phase, amplitude)
""" """
# Get values # Get values
@ -912,11 +912,10 @@ class AD9910:
def set_att_mu(self, att: int32): def set_att_mu(self, att: int32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels. See also
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.set_att_mu>`.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.set_att_mu` :param att: Attenuation setting, 8-bit digital.
:param att: Attenuation setting, 8 bit digital.
""" """
self.cpld.set_att_mu(self.chip_select - 4, att) self.cpld.set_att_mu(self.chip_select - 4, att)
@ -924,9 +923,8 @@ class AD9910:
def set_att(self, att: float): def set_att(self, att: float):
"""Set digital step attenuator in SI units. """Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels. See also
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.set_att>`.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.set_att`
:param att: Attenuation in dB. :param att: Attenuation in dB.
""" """
@ -934,19 +932,17 @@ class AD9910:
@kernel @kernel
def get_att_mu(self) -> int32: def get_att_mu(self) -> int32:
"""Get digital step attenuator value in machine units. """Get digital step attenuator value in machine units. See also
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.get_channel_att_mu>`.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu` :return: Attenuation setting, 8-bit digital.
:return: Attenuation setting, 8 bit digital.
""" """
return self.cpld.get_channel_att_mu(self.chip_select - 4) return self.cpld.get_channel_att_mu(self.chip_select - 4)
@kernel @kernel
def get_att(self) -> float: def get_att(self) -> float:
"""Get digital step attenuator value in SI units. """Get digital step attenuator value in SI units. See also
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.get_channel_att>`.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att`
:return: Attenuation in dB. :return: Attenuation in dB.
""" """
@ -968,16 +964,16 @@ class AD9910:
window: int32, window: int32,
en_sync_gen: int32 = 0): en_sync_gen: int32 = 0):
"""Set the relevant parameters in the multi device synchronization """Set the relevant parameters in the multi device synchronization
register. See the AD9910 datasheet for details. The SYNC clock register. See the AD9910 datasheet for details. The ``SYNC`` clock
generator preset value is set to zero, and the SYNC_OUT generator is generator preset value is set to zero, and the ``SYNC_OUT`` generator is
disabled by default. disabled by default.
:param in_delay: SYNC_IN delay tap (0-31) in steps of ~75ps :param in_delay: ``SYNC_IN`` delay tap (0-31) in steps of ~75ps
:param window: Symmetric SYNC_IN validation window (0-15) in :param window: Symmetric ``SYNC_IN`` validation window (0-15) in
steps of ~75ps for both hold and setup margin. steps of ~75ps for both hold and setup margin.
:param en_sync_gen: Whether to enable the DDS-internal sync generator :param en_sync_gen: Whether to enable the DDS-internal sync generator
(SYNC_OUT, cf. sync_sel == 1). Should be left off for the normal (``SYNC_OUT``, cf. ``sync_sel == 1``). Should be left off for the normal
use case, where the SYNC clock is supplied by the core device. use case, where the ``SYNC`` clock is supplied by the core device.
""" """
self.write32(_AD9910_REG_SYNC, self.write32(_AD9910_REG_SYNC,
(window << 28) | # SYNC S/H validation delay (window << 28) | # SYNC S/H validation delay
@ -990,9 +986,9 @@ class AD9910:
@kernel @kernel
def clear_smp_err(self): def clear_smp_err(self):
"""Clear the SMP_ERR flag and enables SMP_ERR validity monitoring. """Clear the ``SMP_ERR`` flag and enables ``SMP_ERR`` validity monitoring.
Violations of the SYNC_IN sample and hold margins will result in Violations of the ``SYNC_IN`` sample and hold margins will result in
SMP_ERR being asserted. This then also activates the red LED on SMP_ERR being asserted. This then also activates the red LED on
the respective Urukul channel. the respective Urukul channel.
@ -1007,9 +1003,9 @@ class AD9910:
@kernel @kernel
def tune_sync_delay(self, def tune_sync_delay(self,
search_seed: int32 = 15) -> tuple[int32, int32]: search_seed: int32 = 15) -> tuple[int32, int32]:
"""Find a stable SYNC_IN delay. """Find a stable ``SYNC_IN`` delay.
This method first locates a valid SYNC_IN delay at zero validation This method first locates a valid ``SYNC_IN`` delay at zero validation
window size (setup/hold margin) by scanning around `search_seed`. It window size (setup/hold margin) by scanning around `search_seed`. It
then looks for similar valid delays at successively larger validation then looks for similar valid delays at successively larger validation
window sizes until none can be found. It then decreases the validation window sizes until none can be found. It then decreases the validation
@ -1018,7 +1014,7 @@ class AD9910:
This method and :meth:`tune_io_update_delay` can be run in any order. This method and :meth:`tune_io_update_delay` can be run in any order.
:param search_seed: Start value for valid SYNC_IN delay search. :param search_seed: Start value for valid ``SYNC_IN`` delay search.
Defaults to 15 (half range). Defaults to 15 (half range).
:return: Tuple of optimal delay and window size. :return: Tuple of optimal delay and window size.
""" """
@ -1065,16 +1061,16 @@ class AD9910:
def measure_io_update_alignment(self, delay_start: int64, def measure_io_update_alignment(self, delay_start: int64,
delay_stop: int64) -> int32: delay_stop: int64) -> int32:
"""Use the digital ramp generator to locate the alignment between """Use the digital ramp generator to locate the alignment between
IO_UPDATE and SYNC_CLK. ``IO_UPDATE`` and ``SYNC_CLK``.
The ramp generator is set up to a linear frequency ramp The ramp generator is set up to a linear frequency ramp
(dFTW/t_SYNC_CLK=1) and started at a coarse RTIO time stamp plus ``(dFTW/t_SYNC_CLK=1)`` and started at a coarse RTIO time stamp plus
`delay_start` and stopped at a coarse RTIO time stamp plus ``delay_start`` and stopped at a coarse RTIO time stamp plus
`delay_stop`. ``delay_stop``.
:param delay_start: Start IO_UPDATE delay in machine units. :param delay_start: Start ``IO_UPDATE`` delay in machine units.
:param delay_stop: Stop IO_UPDATE delay in machine units. :param delay_stop: Stop ``IO_UPDATE`` delay in machine units.
:return: Odd/even SYNC_CLK cycle indicator. :return: Odd/even ``SYNC_CLK`` cycle indicator.
""" """
# set up DRG # set up DRG
self.set_cfr1(drg_load_lrr=1, drg_autoclear=1) self.set_cfr1(drg_load_lrr=1, drg_autoclear=1)
@ -1106,19 +1102,19 @@ class AD9910:
@kernel @kernel
def tune_io_update_delay(self) -> int32: def tune_io_update_delay(self) -> int32:
"""Find a stable IO_UPDATE delay alignment. """Find a stable ``IO_UPDATE`` delay alignment.
Scan through increasing IO_UPDATE delays until a delay is found that 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 lets ``IO_UPDATE`` be registered in the next ``SYNC_CLK`` cycle. Return a
IO_UPDATE delay that is as far away from that SYNC_CLK edge ``IO_UPDATE`` delay that is as far away from that ``SYNC_CLK`` edge
as possible. as possible.
This method assumes that the IO_UPDATE TTLOut device has one machine This method assumes that the ``IO_UPDATE`` TTLOut device has one machine
unit resolution (SERDES). unit resolution (SERDES).
This method and :meth:`tune_sync_delay` can be run in any order. This method and :meth:`tune_sync_delay` can be run in any order.
:return: Stable IO_UPDATE delay to be passed to the constructor :return: Stable ``IO_UPDATE`` delay to be passed to the constructor
:class:`AD9910` via the device database. :class:`AD9910` via the device database.
""" """
period = self.sysclk_per_mu * 4 # SYNC_CLK period period = self.sysclk_per_mu * 4 # SYNC_CLK period

View File

@ -13,7 +13,7 @@ from artiq.coredevice.ttl import TTLOut
@nac3 @nac3
class AD9912: class AD9912:
""" """
AD9912 DDS channel on Urukul AD9912 DDS channel on Urukul.
This class supports a single DDS channel and exposes the DDS, This class supports a single DDS channel and exposes the DDS,
the digital step attenuator, and the RF switch. the digital step attenuator, and the RF switch.
@ -24,9 +24,9 @@ class AD9912:
:param sw_device: Name of the RF switch device. The RF switch is a :param sw_device: Name of the RF switch device. The RF switch is a
TTLOut channel available as the :attr:`sw` attribute of this instance. TTLOut channel available as the :attr:`sw` attribute of this instance.
:param pll_n: DDS PLL multiplier. The DDS sample clock is :param pll_n: DDS PLL multiplier. The DDS sample clock is
f_ref/clk_div*pll_n where f_ref is the reference frequency and clk_div ``f_ref / clk_div * pll_n`` where ``f_ref`` is the reference frequency and
is the reference clock divider (both set in the parent Urukul CPLD ``clk_div`` is the reference clock divider (both set in the parent
instance). Urukul CPLD instance).
:param pll_en: PLL enable bit, set to False to bypass PLL (default: True). :param pll_en: PLL enable bit, set to False to bypass PLL (default: True).
Note that when bypassing the PLL the red front panel LED may remain on. Note that when bypassing the PLL the red front panel LED may remain on.
""" """
@ -54,7 +54,11 @@ class AD9912:
self.pll_en = pll_en self.pll_en = pll_en
self.pll_n = pll_n self.pll_n = pll_n
if pll_en: if pll_en:
sysclk = self.cpld.refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n refclk = self.cpld.refclk
if refclk < 11e6:
# use SYSCLK PLL Doubler
refclk = refclk * 2
sysclk = refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n
else: else:
sysclk = self.cpld.refclk sysclk = self.cpld.refclk
assert sysclk <= 1e9 assert sysclk <= 1e9
@ -107,7 +111,7 @@ class AD9912:
Sets up SPI mode, confirms chip presence, powers down unused blocks, Sets up SPI mode, confirms chip presence, powers down unused blocks,
and configures the PLL. Does not wait for PLL lock. Uses the and configures the PLL. Does not wait for PLL lock. Uses the
IO_UPDATE signal multiple times. ``IO_UPDATE`` signal multiple times.
""" """
# SPI mode # SPI mode
self.write(AD9912_SER_CONF, 0x99, 1) self.write(AD9912_SER_CONF, 0x99, 1)
@ -125,7 +129,11 @@ class AD9912:
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, 1) self.write(AD9912_N_DIV, self.pll_n // 2 - 2, 1)
self.cpld.io_update.pulse(2. * us) self.cpld.io_update.pulse(2. * us)
# I_cp = 375 µA, VCO high range # I_cp = 375 µA, VCO high range
self.write(AD9912_PLLCFG, 0b00000101, 1) if self.cpld.refclk < 11e6:
# enable SYSCLK PLL Doubler
self.write(AD9912_PLLCFG, 0b00001101, 1)
else:
self.write(AD9912_PLLCFG, 0b00000101, 1)
self.cpld.io_update.pulse(2. * us) self.cpld.io_update.pulse(2. * us)
self.core.delay(1. * ms) self.core.delay(1. * ms)
@ -135,9 +143,9 @@ class AD9912:
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.set_att_mu` See also :meth:`~artiq.coredevice.urukul.CPLD.set_att_mu`.
:param att: Attenuation setting, 8 bit digital. :param att: Attenuation setting, 8-bit digital.
""" """
self.cpld.set_att_mu(self.chip_select - 4, att) self.cpld.set_att_mu(self.chip_select - 4, att)
@ -147,7 +155,7 @@ class AD9912:
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.set_att` See also :meth:`~artiq.coredevice.urukul.CPLD.set_att`.
:param att: Attenuation in dB. Higher values mean more attenuation. :param att: Attenuation in dB. Higher values mean more attenuation.
""" """
@ -157,9 +165,9 @@ class AD9912:
def get_att_mu(self) -> int32: def get_att_mu(self) -> int32:
"""Get digital step attenuator value in machine units. """Get digital step attenuator value in machine units.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu` See also :meth:`~artiq.coredevice.urukul.CPLD.get_channel_att_mu`.
:return: Attenuation setting, 8 bit digital. :return: Attenuation setting, 8-bit digital.
""" """
return self.cpld.get_channel_att_mu(self.chip_select - 4) return self.cpld.get_channel_att_mu(self.chip_select - 4)
@ -167,7 +175,7 @@ class AD9912:
def get_att(self) -> float: def get_att(self) -> float:
"""Get digital step attenuator value in SI units. """Get digital step attenuator value in SI units.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att` See also :meth:`~artiq.coredevice.urukul.CPLD.get_channel_att`.
:return: Attenuation in dB. :return: Attenuation in dB.
""" """
@ -180,8 +188,8 @@ class AD9912:
After the SPI transfer, the shared IO update pin is pulsed to After the SPI transfer, the shared IO update pin is pulsed to
activate the data. activate the data.
:param ftw: Frequency tuning word: 48 bit unsigned. :param ftw: Frequency tuning word: 48-bit unsigned.
:param pow_: Phase tuning word: 16 bit unsigned. :param pow_: Phase tuning word: 16-bit unsigned.
""" """
# streaming transfer of FTW and POW # streaming transfer of FTW and POW
self.bus.set_config_mu(SPI_CONFIG, 16, self.bus.set_config_mu(SPI_CONFIG, 16,
@ -199,9 +207,9 @@ class AD9912:
def get_mu(self) -> tuple[int64, int32]: def get_mu(self) -> tuple[int64, int32]:
"""Get the frequency tuning word and phase offset word. """Get the frequency tuning word and phase offset word.
.. seealso:: :meth:`get` See also :meth:`AD9912.get`.
:return: A tuple ``(ftw, pow)``. :return: A tuple (FTW, POW).
""" """
# Read data # Read data
@ -249,7 +257,7 @@ class AD9912:
def set(self, frequency: float, phase: float = 0.0): def set(self, frequency: float, phase: float = 0.0):
"""Set profile 0 data in SI units. """Set profile 0 data in SI units.
.. seealso:: :meth:`set_mu` See also :meth:`AD9912.set_mu`.
:param frequency: Frequency in Hz :param frequency: Frequency in Hz
:param phase: Phase tuning word in turns :param phase: Phase tuning word in turns
@ -261,9 +269,9 @@ class AD9912:
def get(self) -> tuple[float, float]: def get(self) -> tuple[float, float]:
"""Get the frequency and phase. """Get the frequency and phase.
.. seealso:: :meth:`get_mu` See also :meth:`AD9912.get_mu`.
:return: A tuple ``(frequency, phase)``. :return: A tuple (frequency, phase).
""" """
# Get values # Get values

View File

@ -49,7 +49,7 @@ class AD9914:
The time cursor is not modified by any function in this class. The time cursor is not modified by any function in this class.
Output event replacement is not supported and issuing commands at the same Output event replacement is not supported and issuing commands at the same
time is an error. time results in collision errors.
:param sysclk: DDS system frequency. The DDS system clock must be a :param sysclk: DDS system frequency. The DDS system clock must be a
phase-locked multiple of the RTIO clock. phase-locked multiple of the RTIO clock.
@ -145,7 +145,7 @@ class AD9914:
timing margin. timing margin.
:param sync_delay: integer from 0 to 0x3f that sets the value of :param sync_delay: integer from 0 to 0x3f that sets the value of
SYNC_OUT (bits 3-5) and SYNC_IN (bits 0-2) delay ADJ bits. ``SYNC_OUT`` (bits 3-5) and ``SYNC_IN`` (bits 0-2) delay ADJ bits.
""" """
delay_mu(-self.init_sync_duration_mu) delay_mu(-self.init_sync_duration_mu)
self.write(AD9914_GPIO, (1 << self.channel) << 1) self.write(AD9914_GPIO, (1 << self.channel) << 1)

View File

@ -122,7 +122,7 @@ class ADF5356:
This method will write the attenuator settings of the channel. This method will write the attenuator settings of the channel.
.. seealso:: :meth:`artiq.coredevice.mirny.Mirny.set_att` See also :meth:`Mirny.set_att<artiq.coredevice.mirny.Mirny.set_att>`.
:param att: Attenuation in dB. :param att: Attenuation in dB.
""" """
@ -132,7 +132,7 @@ class ADF5356:
def set_att_mu(self, att: int32): def set_att_mu(self, att: int32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
:param att: Attenuation setting, 8 bit digital. :param att: Attenuation setting, 8-bit digital.
""" """
self.cpld.set_att_mu(self.channel, att) self.cpld.set_att_mu(self.channel, att)
@ -541,14 +541,14 @@ class ADF5356:
@portable @portable
def _compute_pfd_frequency(self, r: int32, d: int32, t: int32) -> int64: def _compute_pfd_frequency(self, r: int32, d: int32, t: int32) -> int64:
""" """
Calculate the PFD frequency from the given reference path parameters Calculate the PFD frequency from the given reference path parameters.
""" """
return round64(self.sysclk * (float(1 + d) / float(r * (1 + t)))) return round64(self.sysclk * (float(1 + d) / float(r * (1 + t))))
@portable @portable
def _compute_reference_counter(self) -> int32: def _compute_reference_counter(self) -> int32:
""" """
Determine the reference counter R that maximizes the PFD frequency Determine the reference counter R that maximizes the PFD frequency.
""" """
d = ADF5356_REG4_R_DOUBLER_GET(self.regs[4]) d = ADF5356_REG4_R_DOUBLER_GET(self.regs[4])
t = ADF5356_REG4_R_DIVIDER_GET(self.regs[4]) t = ADF5356_REG4_R_DIVIDER_GET(self.regs[4])
@ -575,14 +575,15 @@ def calculate_pll(f_vco: int64, f_pfd: int64) -> tuple[int32, int32, tuple[int32
""" """
Calculate fractional-N PLL parameters such that Calculate fractional-N PLL parameters such that
``f_vco`` = ``f_pfd`` * (``n`` + (``frac1`` + ``frac2``/``mod2``) / ``mod1``) ``f_vco = f_pfd * (n + (frac1 + frac2/mod2) / mod1)``
where where
``mod1 = 2**24`` and ``mod2 <= 2**28``
``mod1 = 2**24`` and ``mod2 <= 2**28``
:param f_vco: target VCO frequency :param f_vco: target VCO frequency
:param f_pfd: PFD frequency :param f_pfd: PFD frequency
:return: ``(n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb))`` :return: (``n``, ``frac1``, ``(frac2_msb, frac2_lsb)``, ``(mod2_msb, mod2_lsb)``)
""" """
# integral part # integral part

View File

@ -22,12 +22,12 @@ ALMAZNY_LEGACY_SPIT_WR = 32
@nac3 @nac3
class AlmaznyLegacy: class AlmaznyLegacy:
""" """
Almazny (High frequency mezzanine board for Mirny) Almazny (High-frequency mezzanine board for Mirny)
This applies to Almazny hardware v1.1 and earlier. This applies to Almazny hardware v1.1 and earlier.
Use :class:`artiq.coredevice.almazny.AlmaznyChannel` for Almazny v1.2 and later. Use :class:`~artiq.coredevice.almazny.AlmaznyChannel` for Almazny v1.2 and later.
:param host_mirny: Mirny device Almazny is connected to :param host_mirny: :class:`~artiq.coredevice.mirny.Mirny` device Almazny is connected to
""" """
core: KernelInvariant[Core] core: KernelInvariant[Core]
@ -134,12 +134,13 @@ class AlmaznyLegacy:
@nac3 @nac3
class AlmaznyChannel: class AlmaznyChannel:
""" """
One Almazny channel Driver for one Almazny channel.
Almazny is a mezzanine for the Quad PLL RF source Mirny that exposes and Almazny is a mezzanine for the Quad PLL RF source Mirny that exposes and
controls the frequency-doubled outputs. controls the frequency-doubled outputs.
This driver requires Almazny hardware revision v1.2 or later This driver requires Almazny hardware revision v1.2 or later
and Mirny CPLD gateware v0.3 or later. and Mirny CPLD gateware v0.3 or later.
Use :class:`artiq.coredevice.almazny.AlmaznyLegacy` for Almazny hardware v1.1 and earlier. Use :class:`~artiq.coredevice.almazny.AlmaznyLegacy` for Almazny hardware v1.1 and earlier.
:param host_mirny: Mirny CPLD device name :param host_mirny: Mirny CPLD device name
:param channel: channel index (0-3) :param channel: channel index (0-3)

View File

@ -28,9 +28,9 @@ class CoreCache:
# """Extract a value from the core device cache. # """Extract a value from the core device cache.
# After a value is extracted, it cannot be replaced with another value using # After a value is extracted, it cannot be replaced with another value using
# :meth:`put` until all kernel functions finish executing; attempting # :meth:`put` until all kernel functions finish executing; attempting
# to replace it will result in a :class:`artiq.coredevice.exceptions.CacheError`. # to replace it will result in a :class:`~artiq.coredevice.exceptions.CacheError`.
# #
# If the cache does not contain any value associated with ``key``, an empty list # If the cache does not contain any value associated with `key`, an empty list
# is returned. # is returned.
# #
# The value is not copied, so mutating it will change what's stored in the cache. # The value is not copied, so mutating it will change what's stored in the cache.

View File

@ -181,25 +181,25 @@ class AnalyzerProxyReceiver:
async def _receive_cr(self): async def _receive_cr(self):
try: try:
while True: while True:
endian_byte = await self.reader.read(1) data = bytearray()
if endian_byte == b"E": data.extend(await self.reader.read(1))
endian = '>' if len(data) == 0:
elif endian_byte == b"e":
endian = '<'
elif endian_byte == b"":
# EOF reached, connection lost # EOF reached, connection lost
return return
if data[0] == ord("E"):
endian = '>'
elif data[0] == ord("e"):
endian = '<'
else: else:
raise ValueError raise ValueError
payload_length_word = await self.reader.readexactly(4) data.extend(await self.reader.readexactly(4))
payload_length = struct.unpack(endian + "I", payload_length_word)[0] payload_length = struct.unpack(endian + "I", data[1:5])[0]
if payload_length > 10 * 512 * 1024: if payload_length > 10 * 512 * 1024:
# 10x buffer size of firmware # 10x buffer size of firmware
raise ValueError raise ValueError
# The remaining header length is 11 bytes. # The remaining header length is 11 bytes.
remaining_data = await self.reader.readexactly(payload_length + 11) data.extend(await self.reader.readexactly(payload_length + 11))
data = endian_byte + payload_length_word + remaining_data
self.receive_cb(data) self.receive_cb(data)
except Exception: except Exception:
logger.error("analyzer receiver connection terminating with exception", exc_info=True) logger.error("analyzer receiver connection terminating with exception", exc_info=True)

View File

@ -42,8 +42,8 @@ class Core:
On platforms that use clock multiplication and SERDES-based PHYs, On platforms that use clock multiplication and SERDES-based PHYs,
this is the period after multiplication. For example, with a RTIO core this is the period after multiplication. For example, with a RTIO core
clocked at 125MHz and a SERDES multiplication factor of 8, the clocked at 125MHz and a SERDES multiplication factor of 8, the
reference period is 1ns. reference period is ``1 ns``.
The time machine unit is equal to this period. The machine time unit (``mu``) is equal to this period.
:param ref_multiplier: ratio between the RTIO fine timestamp frequency :param ref_multiplier: ratio between the RTIO fine timestamp frequency
and the RTIO coarse timestamp frequency (e.g. SERDES multiplication and the RTIO coarse timestamp frequency (e.g. SERDES multiplication
factor). factor).
@ -88,6 +88,8 @@ class Core:
self.trigger_analyzer_proxy() self.trigger_analyzer_proxy()
def close(self): def close(self):
"""Disconnect core device and close sockets.
"""
self.comm.close() self.comm.close()
def compile(self, method, args, kwargs, embedding_map, file_output=None, target=None): def compile(self, method, args, kwargs, embedding_map, file_output=None, target=None):
@ -128,7 +130,7 @@ class Core:
@portable @portable
def seconds_to_mu(self, seconds: float) -> int64: def seconds_to_mu(self, seconds: float) -> int64:
"""Convert seconds to the corresponding number of machine units """Convert seconds to the corresponding number of machine units
(RTIO cycles). (fine RTIO cycles).
:param seconds: time (in seconds) to convert. :param seconds: time (in seconds) to convert.
""" """
@ -136,7 +138,7 @@ class Core:
@portable @portable
def mu_to_seconds(self, mu: int64) -> float: def mu_to_seconds(self, mu: int64) -> float:
"""Convert machine units (RTIO cycles) to seconds. """Convert machine units (fine RTIO cycles) to seconds.
:param mu: cycle count to convert. :param mu: cycle count to convert.
""" """
@ -155,7 +157,7 @@ class Core:
for the actual value of the hardware register at the instant when for the actual value of the hardware register at the instant when
execution resumes in the caller. execution resumes in the caller.
For a more detailed description of these concepts, see :doc:`/rtio`. For a more detailed description of these concepts, see :doc:`rtio`.
""" """
return rtio_get_counter() return rtio_get_counter()
@ -174,7 +176,7 @@ class Core:
def get_rtio_destination_status(self, destination: int32) -> bool: def get_rtio_destination_status(self, destination: int32) -> bool:
"""Returns whether the specified RTIO destination is up. """Returns whether the specified RTIO destination is up.
This is particularly useful in startup kernels to delay This is particularly useful in startup kernels to delay
startup until certain DRTIO destinations are up.""" startup until certain DRTIO destinations are available."""
return rtio_get_destination_status(destination) return rtio_get_destination_status(destination)
@kernel @kernel
@ -202,7 +204,7 @@ class Core:
Returns only after the dump has been retrieved from the device. Returns only after the dump has been retrieved from the device.
Raises IOError if no analyzer proxy has been configured, or if the Raises :exc:`IOError` if no analyzer proxy has been configured, or if the
analyzer proxy fails. In the latter case, more details would be analyzer proxy fails. In the latter case, more details would be
available in the proxy log. available in the proxy log.
""" """

View File

@ -84,11 +84,11 @@ class CoreDMA:
@kernel @kernel
def record(self, name: str, enable_ddma: bool = False) -> DMARecordContextManager: def record(self, name: str, enable_ddma: bool = False) -> DMARecordContextManager:
"""Returns a context manager that will record a DMA trace called ``name``. """Returns a context manager that will record a DMA trace called `name`.
Any previously recorded trace with the same name is overwritten. Any previously recorded trace with the same name is overwritten.
The trace will persist across kernel switches. The trace will persist across kernel switches.
In DRTIO context, distributed DMA can be toggled with ``enable_ddma``. In DRTIO context, distributed DMA can be toggled with `enable_ddma`.
Enabling it allows running DMA on satellites, rather than sending all Enabling it allows running DMA on satellites, rather than sending all
events from the master. events from the master.
@ -124,7 +124,7 @@ class CoreDMA:
def playback_handle(self, handle: tuple[int32, int64, int32]): def playback_handle(self, handle: tuple[int32, int64, int32]):
"""Replays a handle obtained with :meth:`get_handle`. Using this function """Replays a handle obtained with :meth:`get_handle`. Using this function
is much faster than :meth:`playback` for replaying a set of traces repeatedly, is much faster than :meth:`playback` for replaying a set of traces repeatedly,
but incurs the overhead of managing the handles onto the programmer.""" but offloads the overhead of managing the handles onto the programmer."""
(epoch, advance_mu, ptr, uses_ddma) = handle (epoch, advance_mu, ptr, uses_ddma) = handle
if self.epoch != epoch: if self.epoch != epoch:
raise DMAError("Invalid handle") raise DMAError("Invalid handle")

View File

@ -1,9 +1,9 @@
"""Driver for RTIO-enabled TTL edge counter. """Driver for RTIO-enabled TTL edge counter.
Like for the TTL input PHYs, sensitivity can be configured over RTIO As for the TTL input PHYs, sensitivity can be configured over RTIO
(``gate_rising()``, etc.). In contrast to the former, however, the count is (:meth:`gate_rising<EdgeCounter.gate_rising>`, etc.). In contrast to the former, however, the count is
accumulated in gateware, and only a single input event is generated at the end accumulated in gateware, and only a single input event is generated at the end
of each gate period:: of each gate period: ::
with parallel: with parallel:
doppler_cool() doppler_cool()
@ -17,12 +17,12 @@ of each gate period::
print("Readout counts:", self.pmt_counter.fetch_count()) print("Readout counts:", self.pmt_counter.fetch_count())
For applications where the timestamps of the individual input events are not For applications where the timestamps of the individual input events are not
required, this has two advantages over ``TTLInOut.count()`` beyond raw required, this has two advantages over :meth:`TTLInOut.count<artiq.coredevice.ttl.TTLInOut.count>`
throughput. First, it is easy to count events during multiple separate periods beyond raw throughput. First, it is easy to count events during multiple separate
without blocking to read back counts in between, as illustrated in the above periods without blocking to read back counts in between, as illustrated in the
example. Secondly, as each count total only takes up a single input event, it above example. Secondly, as each count total only takes up a single input event,
is much easier to acquire counts on several channels in parallel without it is much easier to acquire counts on several channels in parallel without
risking input FIFO overflows:: risking input RTIO overflows: ::
# Using the TTLInOut driver, pmt_1 input events are only processed # Using the TTLInOut driver, pmt_1 input events are only processed
# after pmt_0 is done counting. To avoid RTIOOverflows, a round-robin # after pmt_0 is done counting. To avoid RTIOOverflows, a round-robin
@ -35,8 +35,6 @@ risking input FIFO overflows::
counts_0 = self.pmt_0.count(now_mu()) # blocks counts_0 = self.pmt_0.count(now_mu()) # blocks
counts_1 = self.pmt_1.count(now_mu()) counts_1 = self.pmt_1.count(now_mu())
#
# Using gateware counters, only a single input event each is # Using gateware counters, only a single input event each is
# generated, greatly reducing the load on the input FIFOs: # generated, greatly reducing the load on the input FIFOs:
@ -47,7 +45,7 @@ risking input FIFO overflows::
counts_0 = self.pmt_0_counter.fetch_count() # blocks counts_0 = self.pmt_0_counter.fetch_count() # blocks
counts_1 = self.pmt_1_counter.fetch_count() counts_1 = self.pmt_1_counter.fetch_count()
See :mod:`artiq.gateware.rtio.phy.edge_counter` and See the sources of :mod:`artiq.gateware.rtio.phy.edge_counter` and
:meth:`artiq.gateware.eem.DIO.add_std` for the gateware components. :meth:`artiq.gateware.eem.DIO.add_std` for the gateware components.
""" """
@ -182,13 +180,13 @@ class EdgeCounter:
"""Emit an RTIO event at the current timeline position to set the """Emit an RTIO event at the current timeline position to set the
gateware configuration. gateware configuration.
For most use cases, the `gate_*` wrappers will be more convenient. For most use cases, the ``gate_*`` wrappers will be more convenient.
:param count_rising: Whether to count rising signal edges. :param count_rising: Whether to count rising signal edges.
:param count_falling: Whether to count falling signal edges. :param count_falling: Whether to count falling signal edges.
:param send_count_event: If `True`, an input event with the current :param send_count_event: If ``True``, an input event with the current
counter value is generated on the next clock cycle (once). counter value is generated on the next clock cycle (once).
:param reset_to_zero: If `True`, the counter value is reset to zero on :param reset_to_zero: If ``True``, the counter value is reset to zero on
the next clock cycle (once). the next clock cycle (once).
""" """
config = int32(0) config = int32(0)

View File

@ -26,7 +26,7 @@ class RTIOOverflow(Exception):
@nac3 @nac3
class RTIODestinationUnreachable(Exception): class RTIODestinationUnreachable(Exception):
"""Raised with a RTIO operation could not be completed due to a DRTIO link """Raised when a RTIO operation could not be completed due to a DRTIO link
being down. being down.
""" """
artiq_builtin = True artiq_builtin = True

View File

@ -1,4 +1,4 @@
"""RTIO driver for the Fastino 32channel, 16 bit, 2.5 MS/s per channel, """RTIO driver for the Fastino 32-channel, 16-bit, 2.5 MS/s per channel
streaming DAC. streaming DAC.
""" """
from numpy import int32, int64 from numpy import int32, int64
@ -18,22 +18,22 @@ class Fastino:
to the DAC RTIO addresses, if a channel is not "held" by setting its bit to the DAC RTIO addresses, if a channel is not "held" by setting its bit
using :meth:`set_hold`, the next frame will contain the update. For the using :meth:`set_hold`, the next frame will contain the update. For the
DACs held, the update is triggered explicitly by setting the corresponding DACs held, the update is triggered explicitly by setting the corresponding
bit using :meth:`set_update`. Update is self-clearing. This enables atomic bit using :meth:`update`. Update is self-clearing. This enables atomic
DAC updates synchronized to a frame edge. DAC updates synchronized to a frame edge.
The `log2_width=0` RTIO layout uses one DAC channel per RTIO address and a The ``log2_width=0`` RTIO layout uses one DAC channel per RTIO address and a
dense RTIO address space. The RTIO words are narrow (32 bit) and dense RTIO address space. The RTIO words are narrow (32-bit) and
few-channel updates are efficient. There is the least amount of DAC state few-channel updates are efficient. There is the least amount of DAC state
tracking in kernels, at the cost of more DMA and RTIO data. tracking in kernels, at the cost of more DMA and RTIO data.
The setting here and in the RTIO PHY (gateware) must match. The setting here and in the RTIO PHY (gateware) must match.
Other `log2_width` (up to `log2_width=5`) settings pack multiple Other ``log2_width`` (up to ``log2_width=5``) settings pack multiple
(in powers of two) DAC channels into one group and into one RTIO write. (in powers of two) DAC channels into one group and into one RTIO write.
The RTIO data width increases accordingly. The `log2_width` The RTIO data width increases accordingly. The ``log2_width``
LSBs of the RTIO address for a DAC channel write must be zero and the LSBs of the RTIO address for a DAC channel write must be zero and the
address space is sparse. For `log2_width=5` the RTIO data is 512 bit wide. address space is sparse. For ``log2_width=5`` the RTIO data is 512-bit wide.
If `log2_width` is zero, the :meth:`set_dac`/:meth:`set_dac_mu` interface If ``log2_width`` is zero, the :meth:`set_dac`/:meth:`set_dac_mu` interface
must be used. If non-zero, the :meth:`set_group`/:meth:`set_group_mu` must be used. If non-zero, the :meth:`set_group`/:meth:`set_group_mu`
interface must be used. interface must be used.
@ -68,15 +68,16 @@ class Fastino:
* disables RESET, DAC_CLR, enables AFE_PWR * disables RESET, DAC_CLR, enables AFE_PWR
* clears error counters, enables error counting * clears error counters, enables error counting
* turns LEDs off * turns LEDs off
* clears `hold` and `continuous` on all channels * clears ``hold`` and ``continuous`` on all channels
* clear and resets interpolators to unit rate change on all * clear and resets interpolators to unit rate change on all
channels channels
It does not change set channel voltages and does not reset the PLLs or clock It does not change set channel voltages and does not reset the PLLs or clock
domains. domains.
Note: On Fastino gateware before v0.2 this may lead to 0 voltage being emitted .. warning::
transiently. On Fastino gateware before v0.2 this may lead to 0 voltage being emitted
transiently.
""" """
self.set_cfg(reset=False, afe_power_down=False, dac_clr=False, clr_err=True) self.set_cfg(reset=False, afe_power_down=False, dac_clr=False, clr_err=True)
delay_mu(self.t_frame) delay_mu(self.t_frame)
@ -120,7 +121,7 @@ class Fastino:
"""Write DAC data in machine units. """Write DAC data in machine units.
:param dac: DAC channel to write to (0-31). :param dac: DAC channel to write to (0-31).
:param data: DAC word to write, 16 bit unsigned integer, in machine :param data: DAC word to write, 16-bit unsigned integer, in machine
units. units.
""" """
self.write(dac, data) self.write(dac, data)
@ -129,9 +130,9 @@ class Fastino:
def set_group_mu(self, dac: int32, data: list[int32]): def set_group_mu(self, dac: int32, data: list[int32]):
"""Write a group of DAC channels in machine units. """Write a group of DAC channels in machine units.
:param dac: First channel in DAC channel group (0-31). The `log2_width` :param dac: First channel in DAC channel group (0-31). The ``log2_width``
LSBs must be zero. LSBs must be zero.
:param data: List of DAC data pairs (2x16 bit unsigned) to write, :param data: List of DAC data pairs (2x16-bit unsigned) to write,
in machine units. Data exceeding group size is ignored. in machine units. Data exceeding group size is ignored.
If the list length is less than group size, the remaining If the list length is less than group size, the remaining
DAC channels within the group are cleared to 0 (machine units). DAC channels within the group are cleared to 0 (machine units).
@ -142,10 +143,10 @@ class Fastino:
@portable @portable
def voltage_to_mu(self, voltage: float) -> int32: def voltage_to_mu(self, voltage: float) -> int32:
"""Convert SI Volts to DAC machine units. """Convert SI volts to DAC machine units.
:param voltage: Voltage in SI Volts. :param voltage: Voltage in SI volts.
:return: DAC data word in machine units, 16 bit integer. :return: DAC data word in machine units, 16-bit integer.
""" """
data = int32(round((float(0x8000)/10.)*voltage)) + int32(0x8000) data = int32(round((float(0x8000)/10.)*voltage)) + int32(0x8000)
if data < 0 or data > 0xffff: if data < 0 or data > 0xffff:
@ -154,9 +155,9 @@ class Fastino:
@portable @portable
def voltage_group_to_mu(self, voltage: list[float], data: list[int32]): def voltage_group_to_mu(self, voltage: list[float], data: list[int32]):
"""Convert SI Volts to packed DAC channel group machine units. """Convert SI volts to packed DAC channel group machine units.
:param voltage: List of SI Volt voltages. :param voltage: List of SI volt voltages.
:param data: List of DAC channel data pairs to write to. :param data: List of DAC channel data pairs to write to.
Half the length of `voltage`. Half the length of `voltage`.
""" """
@ -190,7 +191,7 @@ class Fastino:
def update(self, update: int32): def update(self, update: int32):
"""Schedule channels for update. """Schedule channels for update.
:param update: Bit mask of channels to update (32 bit). :param update: Bit mask of channels to update (32-bit).
""" """
self.write(0x20, update) self.write(0x20, update)
@ -198,7 +199,7 @@ class Fastino:
def set_hold(self, hold: int32): def set_hold(self, hold: int32):
"""Set channels to manual update. """Set channels to manual update.
:param hold: Bit mask of channels to hold (32 bit). :param hold: Bit mask of channels to hold (32-bit).
""" """
self.write(0x21, hold) self.write(0x21, hold)
@ -219,9 +220,9 @@ class Fastino:
@kernel @kernel
def set_leds(self, leds: int32): def set_leds(self, leds: int32):
"""Set the green user-defined LEDs """Set the green user-defined LEDs.
:param leds: LED status, 8 bit integer each bit corresponding to one :param leds: LED status, 8-bit integer each bit corresponding to one
green LED. green LED.
""" """
self.write(0x23, leds) self.write(0x23, leds)
@ -250,16 +251,16 @@ class Fastino:
def stage_cic(self, rate: int32) -> int32: def stage_cic(self, rate: int32) -> int32:
"""Compute and stage interpolator configuration. """Compute and stage interpolator configuration.
This method approximates the desired interpolation rate using a 10 bit This method approximates the desired interpolation rate using a 10-bit
floating point representation (6 bit mantissa, 4 bit exponent) and floating point representation (6-bit mantissa, 4-bit exponent) and
then determines an optimal interpolation gain compensation exponent then determines an optimal interpolation gain compensation exponent
to avoid clipping. Gains for rates that are powers of two are accurately to avoid clipping. Gains for rates that are powers of two are accurately
compensated. Other rates lead to overall less than unity gain (but more compensated. Other rates lead to overall less than unity gain (but more
than 0.5 gain). than 0.5 gain).
The overall gain including gain compensation is The overall gain including gain compensation is ``actual_rate ** order /
`actual_rate**order/2**ceil(log2(actual_rate**order))` 2 ** ceil(log2(actual_rate ** order))``
where `order = 3`. where ``order = 3``.
Returns the actual interpolation rate. Returns the actual interpolation rate.
""" """
@ -298,7 +299,7 @@ class Fastino:
their output is supposed to be constant. their output is supposed to be constant.
This method resets and settles the affected interpolators. There will be This method resets and settles the affected interpolators. There will be
no output updates for the next `order = 3` input samples. no output updates for the next ``order = 3`` input samples.
Affected channels will only accept one input sample per input sample Affected channels will only accept one input sample per input sample
period. This method synchronizes the input sample period to the current period. This method synchronizes the input sample period to the current
frame on the affected channels. frame on the affected channels.

View File

@ -107,7 +107,7 @@ class Grabber:
this call or the next. this call or the next.
If the timeout is reached before data is available, the exception If the timeout is reached before data is available, the exception
GrabberTimeoutException is raised. :exc:`GrabberTimeoutException` is raised.
:param timeout_mu: Timestamp at which a timeout will occur. Set to -1 :param timeout_mu: Timestamp at which a timeout will occur. Set to -1
(default) to disable timeout. (default) to disable timeout.

View File

@ -44,7 +44,7 @@ def i2c_poll(busno: int32, busaddr: int32) -> bool:
"""Poll I2C device at address. """Poll I2C device at address.
:param busno: I2C bus number :param busno: I2C bus number
:param busaddr: 8 bit I2C device address (LSB=0) :param busaddr: 8-bit I2C device address (LSB=0)
:returns: True if the poll was ACKed :returns: True if the poll was ACKed
""" """
i2c_start(busno) i2c_start(busno)
@ -58,7 +58,7 @@ def i2c_write_byte(busno: int32, busaddr: int32, data: int32, ack: bool = True):
"""Write one byte to a device. """Write one byte to a device.
:param busno: I2C bus number :param busno: I2C bus number
:param busaddr: 8 bit I2C device address (LSB=0) :param busaddr: 8-bit I2C device address (LSB=0)
:param data: Data byte to be written :param data: Data byte to be written
:param nack: Allow NACK :param nack: Allow NACK
""" """
@ -77,7 +77,7 @@ def i2c_read_byte(busno: int32, busaddr: int32) -> int32:
"""Read one byte from a device. """Read one byte from a device.
:param busno: I2C bus number :param busno: I2C bus number
:param busaddr: 8 bit I2C device address (LSB=0) :param busaddr: 8-bit I2C device address (LSB=0)
:returns: Byte read :returns: Byte read
""" """
i2c_start(busno) i2c_start(busno)
@ -96,10 +96,10 @@ def i2c_write_many(busno: int32, busaddr: int32, addr: int32, data: list[int32],
"""Transfer multiple bytes to a device. """Transfer multiple bytes to a device.
:param busno: I2c bus number :param busno: I2c bus number
:param busaddr: 8 bit I2C device address (LSB=0) :param busaddr: 8-bit I2C device address (LSB=0)
:param addr: 8 bit data address :param addr: 8-bit data address
:param data: Data bytes to be written :param data: Data bytes to be written
:param ack_last: Expect I2C ACK of the last byte written. If `False`, :param ack_last: Expect I2C ACK of the last byte written. If ``False``,
the last byte may be NACKed (e.g. EEPROM full page writes). the last byte may be NACKed (e.g. EEPROM full page writes).
""" """
n = len(data) n = len(data)
@ -122,8 +122,8 @@ def i2c_read_many(busno: int32, busaddr: int32, addr: int32, data: list[int32]):
"""Transfer multiple bytes from a device. """Transfer multiple bytes from a device.
:param busno: I2c bus number :param busno: I2c bus number
:param busaddr: 8 bit I2C device address (LSB=0) :param busaddr: 8-bit I2C device address (LSB=0)
:param addr: 8 bit data address :param addr: 8-bit data address
:param data: List of integers to be filled with the data read. :param data: List of integers to be filled with the data read.
One entry ber byte. One entry ber byte.
""" """
@ -149,7 +149,7 @@ class I2CSwitch:
PCA954X (or other) type detection is done by the CPU during I2C init. PCA954X (or other) type detection is done by the CPU during I2C init.
I2C transactions not real-time, and are performed by the CPU without I2C transactions are not real-time, and are performed by the CPU without
involving RTIO. involving RTIO.
On the KC705, this chip is used for selecting the I2C buses on the two FMC On the KC705, this chip is used for selecting the I2C buses on the two FMC
@ -184,7 +184,7 @@ class I2CSwitch:
class TCA6424A: class TCA6424A:
"""Driver for the TCA6424A I2C I/O expander. """Driver for the TCA6424A I2C I/O expander.
I2C transactions not real-time, and are performed by the CPU without I2C transactions are not real-time, and are performed by the CPU without
involving RTIO. involving RTIO.
On the NIST QC2 hardware, this chip is used for switching the directions On the NIST QC2 hardware, this chip is used for switching the directions
@ -227,7 +227,7 @@ class TCA6424A:
class PCF8574A: class PCF8574A:
"""Driver for the PCF8574 I2C remote 8-bit I/O expander. """Driver for the PCF8574 I2C remote 8-bit I/O expander.
I2C transactions not real-time, and are performed by the CPU without I2C transactions are not real-time, and are performed by the CPU without
involving RTIO. involving RTIO.
""" """

View File

@ -1,4 +1,4 @@
"""RTIO driver for Mirny (4 channel GHz PLLs) """RTIO driver for Mirny (4-channel GHz PLLs)
""" """
from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable
@ -88,7 +88,7 @@ class Mirny:
@kernel @kernel
def read_reg(self, addr: int32) -> int32: def read_reg(self, addr: int32) -> int32:
"""Read a register""" """Read a register."""
self.bus.set_config_mu( self.bus.set_config_mu(
SPI_CONFIG | SPI_INPUT | SPI_END, 24, SPIT_RD, SPI_CS SPI_CONFIG | SPI_INPUT | SPI_END, 24, SPIT_RD, SPI_CS
) )
@ -97,7 +97,7 @@ class Mirny:
@kernel @kernel
def write_reg(self, addr: int32, data: int32): def write_reg(self, addr: int32, data: int32):
"""Write a register""" """Write a register."""
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24, SPIT_WR, SPI_CS) self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24, SPIT_WR, SPI_CS)
self.bus.write((addr << 25) | WE | ((data & 0xFFFF) << 8)) self.bus.write((addr << 25) | WE | ((data & 0xFFFF) << 8))
@ -107,9 +107,9 @@ class Mirny:
Initialize and detect Mirny. Initialize and detect Mirny.
Select the clock source based the board's hardware revision. Select the clock source based the board's hardware revision.
Raise ValueError if the board's hardware revision is not supported. Raise :exc:`ValueError` if the board's hardware revision is not supported.
:param blind: Verify presence and protocol compatibility. Raise ValueError on failure. :param blind: Verify presence and protocol compatibility. Raise :exc:`ValueError` on failure.
""" """
reg0 = self.read_reg(0) reg0 = self.read_reg(0)
self.hw_rev = reg0 & 0x3 self.hw_rev = reg0 & 0x3
@ -144,7 +144,7 @@ class Mirny:
def set_att_mu(self, channel: int32, att: int32): def set_att_mu(self, channel: int32, att: int32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
:param att: Attenuation setting, 8 bit digital. :param att: Attenuation setting, 8-bit digital.
""" """
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 16, SPIT_WR, SPI_CS) self.bus.set_config_mu(SPI_CONFIG | SPI_END, 16, SPIT_WR, SPI_CS)
self.bus.write(((channel | 8) << 25) | (att << 16)) self.bus.write(((channel | 8) << 25) | (att << 16))
@ -155,7 +155,7 @@ class Mirny:
This method will write the attenuator settings of the selected channel. This method will write the attenuator settings of the selected channel.
.. seealso:: :meth:`set_att_mu` See also :meth:`Mirny.set_att_mu`.
:param channel: Attenuator channel (0-3). :param channel: Attenuator channel (0-3).
:param att: Attenuation setting in dB. Higher value is more :param att: Attenuation setting in dB. Higher value is more
@ -166,7 +166,7 @@ class Mirny:
@kernel @kernel
def write_ext(self, addr: int32, length: int32, data: int32, ext_div: int32 = SPIT_WR): def write_ext(self, addr: int32, length: int32, data: int32, ext_div: int32 = SPIT_WR):
"""Perform SPI write to a prefixed address""" """Perform SPI write to a prefixed address."""
self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_WR, SPI_CS) self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_WR, SPI_CS)
self.bus.write(addr << 25) self.bus.write(addr << 25)
self.bus.set_config_mu(SPI_CONFIG | SPI_END, length, ext_div, SPI_CS) self.bus.set_config_mu(SPI_CONFIG | SPI_END, length, ext_div, SPI_CS)

View File

@ -87,7 +87,7 @@ SERVO_T_CYCLE = (32+12+192+24+4)*ns # Must match gateware ADC parameters
class Phaser: class Phaser:
"""Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver. """Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver.
Phaser contains a 4 channel, 1 GS/s DAC chip with integrated upconversion, Phaser contains a 4-channel, 1 GS/s DAC chip with integrated upconversion,
quadrature modulation compensation and interpolation features. quadrature modulation compensation and interpolation features.
The coredevice RTIO PHY and the Phaser gateware come in different modes The coredevice RTIO PHY and the Phaser gateware come in different modes
@ -111,9 +111,9 @@ class Phaser:
**Base mode** **Base mode**
The coredevice produces 2 IQ (in-phase and quadrature) data streams with 25 The coredevice produces 2 IQ (in-phase and quadrature) data streams with 25
MS/s and 14 bit per quadrature. Each data stream supports 5 independent MS/s and 14 bits per quadrature. Each data stream supports 5 independent
numerically controlled IQ oscillators (NCOs, DDSs with 32 bit frequency, 16 numerically controlled IQ oscillators (NCOs, DDSs with 32-bit frequency,
bit phase, 15 bit amplitude, and phase accumulator clear functionality) 16-bit phase, 15-bit amplitude, and phase accumulator clear functionality)
added together. See :class:`PhaserChannel` and :class:`PhaserOscillator`. added together. See :class:`PhaserChannel` and :class:`PhaserOscillator`.
Together with a data clock, framing marker, a checksum and metadata for Together with a data clock, framing marker, a checksum and metadata for
@ -121,30 +121,28 @@ class Phaser:
FastLink via a single EEM connector from coredevice to Phaser. FastLink via a single EEM connector from coredevice to Phaser.
On Phaser in the FPGA the data streams are buffered and interpolated On Phaser in the FPGA the data streams are buffered and interpolated
from 25 MS/s to 500 MS/s 16 bit followed by a 500 MS/s digital upconverter from 25 MS/s to 500 MS/s 16-bit followed by a 500 MS/s digital upconverter
with adjustable frequency and phase. The interpolation passband is 20 MHz with adjustable frequency and phase. The interpolation passband is 20 MHz
wide, passband ripple is less than 1e-3 amplitude, stopband attenuation wide, passband ripple is less than 1e-3 amplitude, stopband attenuation
is better than 75 dB at offsets > 15 MHz and better than 90 dB at offsets is better than 75 dB at offsets > 15 MHz and better than 90 dB at offsets
> 30 MHz. > 30 MHz.
The four 16 bit 500 MS/s DAC data streams are sent via a 32 bit parallel The four 16-bit 500 MS/s DAC data streams are sent via a 32-bit parallel
LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC (Texas LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC (Texas
Instruments DAC34H84). On the DAC 2x interpolation, sinx/x compensation, Instruments DAC34H84). On the DAC 2x interpolation, sinx/x compensation,
quadrature modulator compensation, fine and coarse mixing as well as group quadrature modulator compensation, fine and coarse mixing as well as group
delay capabilities are available. If desired, these features my be delay capabilities are available. If desired, these features my be
configured via the `dac` dictionary. configured via the ``dac`` dictionary.
The latency/group delay from the RTIO events setting The latency/group delay from the RTIO events setting
:class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the :class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the
way to the DAC outputs is deterministic. This enables deterministic way to the DAC outputs is deterministic. This enables deterministic
absolute phase with respect to other RTIO input and output events absolute phase with respect to other RTIO input and output events
(see `get_next_frame_mu()`). (see :meth:`get_next_frame_mu()`).
**Miqro mode** **Miqro mode**
See :class:`Miqro` See :class:`Miqro`. Here the DAC operates in 4x interpolation.
Here the DAC operates in 4x interpolation.
**Analog flow** **Analog flow**
@ -173,7 +171,7 @@ class Phaser:
and Q datastreams from the DUC by the IIR output. The IIR state is updated at and Q datastreams from the DUC by the IIR output. The IIR state is updated at
the 3.788 MHz ADC sampling rate. the 3.788 MHz ADC sampling rate.
Each channel IIR features 4 profiles, each consisting of the [b0, b1, a1] filter 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 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``, set for each profile individually and the profiles each have their own ``y0``,
``y1`` output registers (the ``x0``, ``x1`` inputs are shared). To avoid ``y1`` output registers (the ``x0``, ``x1`` inputs are shared). To avoid
@ -187,25 +185,25 @@ class Phaser:
still ingests samples and updates its input ``x0`` and ``x1`` registers, but still ingests samples and updates its input ``x0`` and ``x1`` registers, but
does not update the ``y0``, ``y1`` output registers. does not update the ``y0``, ``y1`` output registers.
After power-up the servo is disabled, in profile 0, with coefficients [0, 0, 0] 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 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 Phaser FPGA, the device simply behaves as if the servo is disabled and none of
the servo functions have any effect. 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
(`device_db.py`). The settings are frozen during instantiation of the (``device_db.py``). The settings are frozen during instantiation of the
class and applied during `init()`. See the :class:`DAC34H84` and class and applied during ``init()``. See the :class:`dac34H84` and
:class:`TRF372017` source for details. :class:`trf372017` source for details.
.. note:: To establish deterministic latency between RTIO time base and DAC .. note:: To establish deterministic latency between RTIO time base and DAC
output, the DAC FIFO read pointer value (`fifo_offset`) must be output, the DAC FIFO read pointer value (``fifo_offset``) must be
fixed. If `tune_fifo_offset=True` (the default) a value with maximum fixed. If `tune_fifo_offset` = ``True`` (the default) a value with maximum
margin is determined automatically by `dac_tune_fifo_offset` each time margin is determined automatically by `dac_tune_fifo_offset` each time
`init()` is called. This value should be used for the `fifo_offset` key :meth:`init` is called. This value should be used for the ``fifo_offset`` key
of the `dac` settings of Phaser in `device_db.py` and automatic of the ``dac`` settings of Phaser in ``device_db.py`` and automatic
tuning should be disabled by `tune_fifo_offset=False`. tuning should be disabled by `tune_fifo_offset` = ``False```.
:param channel: Base RTIO channel number :param channel: Base RTIO channel number
:param core_device: Core device name (default: "core") :param core_device: Core device name (default: "core")
@ -221,9 +219,9 @@ class Phaser:
:param trf1: Channel 1 TRF372017 quadrature upconverter settings as a :param trf1: Channel 1 TRF372017 quadrature upconverter settings as a
dictionary. dictionary.
Attributes: **Attributes:**
* :attr:`channel`: List of two :class:`PhaserChannel` * :attr:`channel`: List of two instances of :class:`PhaserChannel`
To access oscillators, digital upconverters, PLL/VCO analog To access oscillators, digital upconverters, PLL/VCO analog
quadrature upconverters and attenuators. quadrature upconverters and attenuators.
""" """
@ -476,8 +474,8 @@ class Phaser:
def write8(self, addr: int32, data: int32): def write8(self, addr: int32, data: int32):
"""Write data to FPGA register. """Write data to FPGA register.
:param addr: Address to write to (7 bit) :param addr: Address to write to (7-bit)
:param data: Data to write (8 bit) :param data: Data to write (8-bit)
""" """
rtio_output((self.channel_base << 8) | (addr & 0x7f) | 0x80, data) rtio_output((self.channel_base << 8) | (addr & 0x7f) | 0x80, data)
delay_mu(int64(self.t_frame)) delay_mu(int64(self.t_frame))
@ -486,8 +484,8 @@ class Phaser:
def read8(self, addr: int32) -> int32: def read8(self, addr: int32) -> int32:
"""Read from FPGA register. """Read from FPGA register.
:param addr: Address to read from (7 bit) :param addr: Address to read from (7-bit)
:return: Data read (8 bit) :return: Data read (8-bit)
""" """
rtio_output((self.channel_base << 8) | (addr & 0x7f), 0) rtio_output((self.channel_base << 8) | (addr & 0x7f), 0)
response = rtio_input_data(self.channel_base) response = rtio_input_data(self.channel_base)
@ -495,13 +493,13 @@ class Phaser:
@kernel @kernel
def write16(self, addr: int32, data: int32): def write16(self, addr: int32, data: int32):
"""Write 16 bit to a sequence of FPGA registers.""" """Write 16 bits to a sequence of FPGA registers."""
self.write8(addr, data >> 8) self.write8(addr, data >> 8)
self.write8(addr + 1, data) 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 bits to a sequence of FPGA registers."""
for offset in range(4): for offset in range(4):
byte = data >> 24 byte = data >> 24
self.write8(addr + offset, byte) self.write8(addr + offset, byte)
@ -509,7 +507,7 @@ class Phaser:
@kernel @kernel
def read32(self, addr: int32) -> int32: def read32(self, addr: int32) -> int32:
"""Read 32 bit from a sequence of FPGA registers.""" """Read 32 bits from a sequence of FPGA registers."""
data = 0 data = 0
for offset in range(4): for offset in range(4):
data <<= 8 data <<= 8
@ -521,15 +519,15 @@ class Phaser:
def set_leds(self, leds: int32): def set_leds(self, leds: int32):
"""Set the front panel LEDs. """Set the front panel LEDs.
:param leds: LED settings (6 bit) :param leds: LED settings (6-bit)
""" """
self.write8(PHASER_ADDR_LED, leds) self.write8(PHASER_ADDR_LED, leds)
@kernel @kernel
def set_fan_mu(self, pwm: int32): def set_fan_mu(self, pwm: int32):
"""Set the fan duty cycle. """Set the fan duty cycle in machine units.
:param pwm: Duty cycle in machine units (8 bit) :param pwm: Duty cycle in machine units (8-bit)
""" """
self.write8(PHASER_ADDR_FAN, pwm) self.write8(PHASER_ADDR_FAN, pwm)
@ -594,10 +592,10 @@ class Phaser:
@kernel @kernel
def measure_frame_timestamp(self): def measure_frame_timestamp(self):
"""Measure the timestamp of an arbitrary frame and store it in `self.frame_tstamp`. """Measure the timestamp of an arbitrary frame and store it in ``self.frame_tstamp``.
To be used as reference for aligning updates to the FastLink frames. To be used as reference for aligning updates to the FastLink frames.
See `get_next_frame_mu()`. See :meth:`get_next_frame_mu()`.
""" """
rtio_output(self.channel_base << 8, 0) # read any register rtio_output(self.channel_base << 8, 0) # read any register
self.frame_tstamp = rtio_input_timestamp(now_mu() + int64(4) * int64(self.t_frame), self.channel_base) self.frame_tstamp = rtio_input_timestamp(now_mu() + int64(4) * int64(self.t_frame), self.channel_base)
@ -605,10 +603,10 @@ class Phaser:
@kernel @kernel
def get_next_frame_mu(self) -> int64: def get_next_frame_mu(self) -> int64:
"""Return the timestamp of the frame strictly after `now_mu()`. """Return the timestamp of the frame strictly after :meth:`~artiq.language.core.now_mu()`.
Register updates (DUC, DAC, TRF, etc.) scheduled at this timestamp and multiples Register updates (DUC, DAC, TRF, etc.) scheduled at this timestamp and multiples
of `self.t_frame` later will have deterministic latency to output. of ``self.t_frame`` later will have deterministic latency to output.
""" """
n = int64((now_mu() - self.frame_tstamp) / int64(self.t_frame)) n = int64((now_mu() - self.frame_tstamp) / int64(self.t_frame))
return self.frame_tstamp + (n + int64(1)) * int64(self.t_frame) return self.frame_tstamp + (n + int64(1)) * int64(self.t_frame)
@ -671,7 +669,7 @@ class Phaser:
@kernel @kernel
def dac_write(self, addr: int32, data: int32): def dac_write(self, addr: int32, data: int32):
"""Write 16 bit to a DAC register. """Write 16 bits to a DAC register.
:param addr: Register address :param addr: Register address
:param data: Register data to write :param data: Register data to write
@ -721,16 +719,16 @@ class Phaser:
def dac_sync(self): def dac_sync(self):
"""Trigger DAC synchronisation for both output channels. """Trigger DAC synchronisation for both output channels.
The DAC sif_sync is de-asserts, then asserted. The synchronisation is The DAC ``sif_sync`` is de-asserted, then asserted. The synchronisation is
triggered on assertion. triggered on assertion.
By default, the fine-mixer (NCO) and QMC are synchronised. This By default, the fine-mixer (NCO) and QMC are synchronised. This
includes applying the latest register settings. includes applying the latest register settings.
The synchronisation sources may be configured through the `syncsel_x` The synchronisation sources may be configured through the ``syncsel_x``
fields in the `dac` configuration dictionary (see `__init__()`). fields in the ``dac`` configuration dictionary (see :class:`Phaser`).
.. note:: Synchronising the NCO clears the phase-accumulator .. note:: Synchronising the NCO clears the phase-accumulator.
""" """
config1f = self.dac_read(0x1f) config1f = self.dac_read(0x1f)
self.core.delay(.4*ms) self.core.delay(.4*ms)
@ -739,11 +737,11 @@ class Phaser:
@kernel @kernel
def set_dac_cmix(self, fs_8_step: int32): def set_dac_cmix(self, fs_8_step: int32):
"""Set the DAC coarse mixer frequency for both channels """Set the DAC coarse mixer frequency for both channels.
Use of the coarse mixer requires the DAC mixer to be enabled. The mixer Use of the coarse mixer requires the DAC mixer to be enabled. The mixer
can be configured via the `dac` configuration dictionary (see can be configured via the ``dac`` configuration dictionary (see
`__init__()`). :class:`Phaser`).
The selected coarse mixer frequency becomes active without explicit The selected coarse mixer frequency becomes active without explicit
synchronisation. synchronisation.
@ -776,8 +774,8 @@ class Phaser:
def dac_iotest(self, pattern: list[int32]) -> int32: def dac_iotest(self, pattern: list[int32]) -> int32:
"""Performs a DAC IO test according to the datasheet. """Performs a DAC IO test according to the datasheet.
:param pattern: List of four int32 containing the pattern :param pattern: List of four int32s containing the pattern
:return: Bit error mask (16 bits) :return: Bit error mask (16-bit)
""" """
if len(pattern) != 4: if len(pattern) != 4:
raise ValueError("pattern length out of bounds") raise ValueError("pattern length out of bounds")
@ -816,9 +814,9 @@ class Phaser:
@kernel @kernel
def dac_tune_fifo_offset(self) -> int32: def dac_tune_fifo_offset(self) -> int32:
"""Scan through `fifo_offset` and configure midpoint setting. """Scan through ``fifo_offset`` and configure midpoint setting.
:return: Optimal `fifo_offset` setting with maximum margin to write :return: Optimal ``fifo_offset`` setting with maximum margin to write
pointer. pointer.
""" """
# expect two or three error free offsets: # expect two or three error free offsets:
@ -879,7 +877,7 @@ class PhaserChannel:
Attributes: Attributes:
* :attr:`oscillator`: List of five :class:`PhaserOscillator`. * :attr:`oscillator`: List of five instances of :class:`PhaserOscillator`.
* :attr:`miqro`: A :class:`Miqro`. * :attr:`miqro`: A :class:`Miqro`.
.. note:: The amplitude sum of the oscillators must be less than one to .. note:: The amplitude sum of the oscillators must be less than one to
@ -893,7 +891,7 @@ class PhaserChannel:
changes in oscillator parameters, the overshoot can lead to clipping changes in oscillator parameters, the overshoot can lead to clipping
or overflow after the interpolation. Either band-limit any changes or overflow after the interpolation. Either band-limit any changes
in the oscillator parameters or back off the amplitude sufficiently. in the oscillator parameters or back off the amplitude sufficiently.
Miqro is not affected by this. But both the oscillators and Miqro can Miqro is not affected by this, but both the oscillators and Miqro can
be affected by intrinsic overshoot of the interpolator on the DAC. be affected by intrinsic overshoot of the interpolator on the DAC.
""" """
@ -921,7 +919,7 @@ class PhaserChannel:
The data is split accross multiple registers and thus the data The data is split accross multiple registers and thus the data
is only valid if constant. is only valid if constant.
:return: DAC data as 32 bit IQ. I/DACA/DACC in the 16 LSB, :return: DAC data as 32-bit IQ. I/DACA/DACC in the 16 LSB,
Q/DACB/DACD in the 16 MSB Q/DACB/DACD in the 16 MSB
""" """
return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.index << 4)) return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.index << 4))
@ -930,7 +928,7 @@ class PhaserChannel:
def set_dac_test(self, data: int32): def set_dac_test(self, data: int32):
"""Set the DAC test data. """Set the DAC test data.
:param data: 32 bit IQ test data, I/DACA/DACC in the 16 LSB, :param data: 32-bit IQ test data, I/DACA/DACC in the 16 LSB,
Q/DACB/DACD in the 16 MSB Q/DACB/DACD in the 16 MSB
""" """
self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.index << 4), data) self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.index << 4), data)
@ -952,7 +950,7 @@ class PhaserChannel:
def set_duc_frequency_mu(self, ftw: int32): def set_duc_frequency_mu(self, ftw: int32):
"""Set the DUC frequency. """Set the DUC frequency.
:param ftw: DUC frequency tuning word (32 bit) :param ftw: DUC frequency tuning word (32-bit)
""" """
self.phaser.write32(PHASER_ADDR_DUC0_F + (self.index << 4), ftw) self.phaser.write32(PHASER_ADDR_DUC0_F + (self.index << 4), ftw)
@ -970,7 +968,7 @@ class PhaserChannel:
def set_duc_phase_mu(self, pow: int32): def set_duc_phase_mu(self, pow: int32):
"""Set the DUC phase offset. """Set the DUC phase offset.
:param pow: DUC phase offset word (16 bit) :param pow: DUC phase offset word (16-bit)
""" """
addr = PHASER_ADDR_DUC0_P + (self.index << 4) addr = PHASER_ADDR_DUC0_P + (self.index << 4)
self.phaser.write8(addr, pow >> 8) self.phaser.write8(addr, pow >> 8)
@ -992,10 +990,10 @@ class PhaserChannel:
This method stages the new NCO frequency, but does not apply it. This method stages the new NCO frequency, but does not apply it.
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
can be configured via the `dac` configuration dictionary (see can be configured via the ``dac`` configuration dictionary (see
`__init__()`). :class:`Phaser`).
:param ftw: NCO frequency tuning word (32 bit) :param ftw: NCO frequency tuning word (32-bit)
""" """
self.phaser.dac_write(0x15 + (self.index << 1), ftw >> 16) self.phaser.dac_write(0x15 + (self.index << 1), ftw >> 16)
self.phaser.dac_write(0x14 + (self.index << 1), ftw) self.phaser.dac_write(0x14 + (self.index << 1), ftw)
@ -1007,8 +1005,8 @@ class PhaserChannel:
This method stages the new NCO frequency, but does not apply it. This method stages the new NCO frequency, but does not apply it.
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
can be configured via the `dac` configuration dictionary (see can be configured via the ``dac`` configuration dictionary (see
`__init__()`). :class:`Phaser`).
:param frequency: NCO frequency in Hz (passband from -400 MHz :param frequency: NCO frequency in Hz (passband from -400 MHz
to 400 MHz, wrapping around at +- 500 MHz) to 400 MHz, wrapping around at +- 500 MHz)
@ -1023,14 +1021,13 @@ class PhaserChannel:
By default, the new NCO phase applies on completion of the SPI By default, the new NCO phase applies on completion of the SPI
transfer. This also causes a staged NCO frequency to be applied. transfer. This also causes a staged NCO frequency to be applied.
Different triggers for applying NCO settings may be configured through Different triggers for applying NCO settings may be configured through
the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see the ``syncsel_mixerxx`` fields in the ``dac`` configuration dictionary (see
`__init__()`). :class:`Phaser`).
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
can be configured via the `dac` configuration dictionary (see can be configured via the ``dac`` configuration dictionary.
`__init__()`).
:param pow: NCO phase offset word (16 bit) :param pow: NCO phase offset word (16-bit)
""" """
self.phaser.dac_write(0x12 + self.index, pow) self.phaser.dac_write(0x12 + self.index, pow)
@ -1041,12 +1038,11 @@ class PhaserChannel:
By default, the new NCO phase applies on completion of the SPI By default, the new NCO phase applies on completion of the SPI
transfer. This also causes a staged NCO frequency to be applied. transfer. This also causes a staged NCO frequency to be applied.
Different triggers for applying NCO settings may be configured through Different triggers for applying NCO settings may be configured through
the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see the ``syncsel_mixerxx`` fields in the ``dac`` configuration dictionary (see
`__init__()`). :class:`Phaser`).
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
can be configured via the `dac` configuration dictionary (see can be configured via the ``dac`` configuration dictionary.
`__init__()`).
:param phase: NCO phase in turns :param phase: NCO phase in turns
""" """
@ -1057,7 +1053,7 @@ class PhaserChannel:
def set_att_mu(self, data: int32): def set_att_mu(self, data: int32):
"""Set channel attenuation. """Set channel attenuation.
:param data: Attenuator data in machine units (8 bit) :param data: Attenuator data in machine units (8-bit)
""" """
div = 34 # 30 ns min period div = 34 # 30 ns min period
t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns) t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns)
@ -1104,7 +1100,7 @@ class PhaserChannel:
def trf_write(self, data: int32, readback: bool = False) -> int32: def trf_write(self, data: int32, readback: bool = False) -> int32:
"""Write 32 bits to quadrature upconverter register. """Write 32 bits to quadrature upconverter register.
:param data: Register data (32 bit) containing encoded address :param data: Register data (32-bit) containing encoded address
:param readback: Whether to return the read back MISO data :param readback: Whether to return the read back MISO data
""" """
div = 34 # 50 ns min period div = 34 # 50 ns min period
@ -1136,7 +1132,7 @@ class PhaserChannel:
:param addr: Register address to read (0 to 7) :param addr: Register address to read (0 to 7)
:param cnt_mux_sel: Report VCO counter min or max frequency :param cnt_mux_sel: Report VCO counter min or max frequency
:return: Register data (32 bit) :return: Register data (32-bit)
""" """
self.trf_write(int32(int64(0x80000008)) | (addr << 28) | (cnt_mux_sel << 27)) self.trf_write(int32(int64(0x80000008)) | (addr << 28) | (cnt_mux_sel << 27))
# single clk pulse with ~LE to start readback # single clk pulse with ~LE to start readback
@ -1211,13 +1207,13 @@ class PhaserChannel:
* :math:`b_0` and :math:`b_1` are the feedforward gains for the two * :math:`b_0` and :math:`b_1` are the feedforward gains for the two
delays delays
.. seealso:: :meth:`set_iir` See also :meth:`PhaserChannel.set_iir`.
:param profile: Profile to set (0 to 3) :param profile: Profile to set (0 to 3)
:param b0: b0 filter coefficient (16 bit signed) :param b0: b0 filter coefficient (16-bit signed)
:param b1: b1 filter coefficient (16 bit signed) :param b1: b1 filter coefficient (16-bit signed)
:param a1: a1 filter coefficient (16 bit signed) :param a1: a1 filter coefficient (16-bit signed)
:param offset: Output offset (16 bit signed) :param offset: Output offset (16-bit signed)
""" """
if (profile < 0) or (profile > 3): if (profile < 0) or (profile > 3):
raise ValueError("invalid profile index") raise ValueError("invalid profile index")
@ -1262,7 +1258,7 @@ class PhaserChannel:
integrator gain limit is infinite. Same sign as ``ki``. integrator gain limit is infinite. Same sign as ``ki``.
:param x_offset: IIR input offset. Used as the negative :param x_offset: IIR input offset. Used as the negative
setpoint when stabilizing to a desired input setpoint. Will setpoint when stabilizing to a desired input setpoint. Will
be converted to an equivalent output offset and added to y_offset. be converted to an equivalent output offset and added to ``y_offset``.
:param y_offset: IIR output offset. :param y_offset: IIR output offset.
""" """
NORM = 1 << SERVO_COEFF_SHIFT NORM = 1 << SERVO_COEFF_SHIFT
@ -1323,7 +1319,7 @@ class PhaserOscillator:
def set_frequency_mu(self, ftw: int32): def set_frequency_mu(self, ftw: int32):
"""Set Phaser MultiDDS frequency tuning word. """Set Phaser MultiDDS frequency tuning word.
:param ftw: Frequency tuning word (32 bit) :param ftw: Frequency tuning word (32-bit)
""" """
rtio_output(self.base_addr, ftw) rtio_output(self.base_addr, ftw)
@ -1341,8 +1337,8 @@ class PhaserOscillator:
def set_amplitude_phase_mu(self, asf: int32 = 0x7fff, pow: int32 = 0, clr: bool = False): def set_amplitude_phase_mu(self, asf: int32 = 0x7fff, pow: int32 = 0, clr: bool = False):
"""Set Phaser MultiDDS amplitude, phase offset and accumulator clear. """Set Phaser MultiDDS amplitude, phase offset and accumulator clear.
:param asf: Amplitude (15 bit) :param asf: Amplitude (15-bit)
:param pow: Phase offset word (16 bit) :param pow: Phase offset word (16-bit)
:param clr: Clear the phase accumulator (persistent) :param clr: Clear the phase accumulator (persistent)
""" """
data = (asf & 0x7fff) | (int32(clr) << 15) | (pow << 16) data = (asf & 0x7fff) | (int32(clr) << 15) | (pow << 16)
@ -1374,38 +1370,42 @@ class Miqro:
**Oscillators** **Oscillators**
* There are n_osc = 16 oscillators with oscillator IDs 0..n_osc-1. * There are ``n_osc = 16`` oscillators with oscillator IDs ``0``... ``n_osc-1``.
* Each oscillator outputs one tone at any given time * Each oscillator outputs one tone at any given time
* I/Q (quadrature, a.k.a. complex) 2x16 bit signed data * I/Q (quadrature, a.k.a. complex) 2x16-bit signed data
at tau = 4 ns sample intervals, 250 MS/s, Nyquist 125 MHz, bandwidth 200 MHz at tau = 4 ns sample intervals, 250 MS/s, Nyquist 125 MHz, bandwidth 200 MHz
(from f = -100..+100 MHz, taking into account the interpolation anti-aliasing (from f = -100..+100 MHz, taking into account the interpolation anti-aliasing
filters in subsequent interpolators), filters in subsequent interpolators),
* 32 bit frequency (f) resolution (~ 1/16 Hz), * 32-bit frequency (f) resolution (~ 1/16 Hz),
* 16 bit unsigned amplitude (a) resolution * 16-bit unsigned amplitude (a) resolution
* 16 bit phase offset (p) resolution * 16-bit phase offset (p) resolution
* The output phase p' of each oscillator at time t (boot/reset/initialization of the * The output phase ``p'`` of each oscillator at time ``t`` (boot/reset/initialization of the
device at t=0) is then p' = f*t + p (mod 1 turn) where f and p are the (currently device at ``t=0``) is then ``p' = f*t + p (mod 1 turn)`` where ``f`` and ``p`` are the (currently
active) profile frequency and phase offset. active) profile frequency and phase offset.
* Note: The terms "phase coherent" and "phase tracking" are defined to refer to this
choice of oscillator output phase p'. Note that the phase offset p is not relative to .. note ::
(on top of previous phase/profiles/oscillator history). The terms "phase coherent" and "phase tracking" are defined to refer to this
It is "absolute" in the sense that frequency f and phase offset p fully determine choice of oscillator output phase ``p'``. Note that the phase offset ``p`` is not relative to
oscillator output phase p' at time t. This is unlike typical DDS behavior. (on top of previous phase/profiles/oscillator history).
It is "absolute" in the sense that frequency ``f`` and phase offset ``p`` fully determine
oscillator output phase ``p'`` at time ``t``. This is unlike typical DDS behavior.
* Frequency, phase, and amplitude of each oscillator are configurable by selecting one of * Frequency, phase, and amplitude of each oscillator are configurable by selecting one of
n_profile = 32 profiles 0..n_profile-1. This selection is fast and can be done for ``n_profiles = 32`` profiles ``0``... ``n_profile-1``. This selection is fast and can be
each pulse. The phase coherence defined above is guaranteed for each done for each pulse. The phase coherence defined above is guaranteed for each
profile individually. profile individually.
* Note: one profile per oscillator (usually profile index 0) should be reserved * Note: one profile per oscillator (usually profile index 0) should be reserved
for the NOP (no operation, identity) profile, usually with zero amplitude. for the NOP (no operation, identity) profile, usually with zero amplitude.
* Data for each profile for each oscillator can be configured * Data for each profile for each oscillator can be configured
individually. Storing profile data should be considered "expensive". individually. Storing profile data should be considered "expensive".
* Note: The annotation that some operation is "expensive" does not mean it is
impossible, just that it may take a significant amount of time and .. note::
resources to execute such that it may be impractical when used often or To refer to an operation as "expensive" does not mean it is impossible,
during fast pulse sequences. They are intended for use in calibration and merely that it may take a significant amount of time and resources to
initialization. execute, such that it may be impractical when used often or during fast
pulse sequences. They are intended for use in calibration and initialization.
**Summation** **Summation**
@ -1422,18 +1422,18 @@ class Miqro:
the RF output. the RF output.
* Selected profiles become active simultaneously (on the same output sample) when * Selected profiles become active simultaneously (on the same output sample) when
triggering the shaper with the first shaper output sample. triggering the shaper with the first shaper output sample.
* The shaper reads (replays) window samples from a memory of size n_window = 1 << 10. * The shaper reads (replays) window samples from a memory of size ``n_window = 1 << 10``.
* The window memory can be segmented by choosing different start indices * The window memory can be segmented by choosing different start indices
to support different windows. to support different windows.
* Each window memory segment starts with a header determining segment * Each window memory segment starts with a header determining segment
length and interpolation parameters. length and interpolation parameters.
* The window samples are interpolated by a factor (rate change) between 1 and * The window samples are interpolated by a factor (rate change) between 1 and
r = 1 << 12. ``r = 1 << 12``.
* The interpolation order is constant, linear, quadratic, or cubic. This * The interpolation order is constant, linear, quadratic, or cubic. This
corresponds to interpolation modes from rectangular window (1st order CIC) corresponds to interpolation modes from rectangular window (1st order CIC)
or zero order hold) to Parzen window (4th order CIC or cubic spline). or zero order hold) to Parzen window (4th order CIC or cubic spline).
* This results in support for single shot pulse lengths (envelope support) between * This results in support for single shot pulse lengths (envelope support) between
tau and a bit more than r * n_window * tau = (1 << 12 + 10) tau ~ 17 ms. tau and a bit more than ``r * n_window * tau = (1 << 12 + 10) tau ~ 17 ms``.
* Windows can be configured to be head-less and/or tail-less, meaning, they * Windows can be configured to be head-less and/or tail-less, meaning, they
do not feed zero-amplitude samples into the shaper before and after do not feed zero-amplitude samples into the shaper before and after
each window respectively. This is used to implement pulses with arbitrary each window respectively. This is used to implement pulses with arbitrary
@ -1441,18 +1441,18 @@ class Miqro:
**Overall properties** **Overall properties**
* The DAC may upconvert the signal by applying a frequency offset f1 with * The DAC may upconvert the signal by applying a frequency offset ``f1`` with
phase p1. phase ``p1``.
* In the Upconverter Phaser variant, the analog quadrature upconverter * In the Upconverter Phaser variant, the analog quadrature upconverter
applies another frequency of f2 and phase p2. applies another frequency of ``f2`` and phase ``p2``.
* The resulting phase of the signal from one oscillator at the SMA output is * The resulting phase of the signal from one oscillator at the SMA output is
(f + f1 + f2)*t + p + s(t - t0) + p1 + p2 (mod 1 turn) ``(f + f1 + f2)*t + p + s(t - t0) + p1 + p2 (mod 1 turn)``
where s(t - t0) is the phase of the interpolated where ``s(t - t0)`` is the phase of the interpolated
shaper output, and t0 is the trigger time (fiducial of the shaper). shaper output, and ``t0`` is the trigger time (fiducial of the shaper).
Unsurprisingly the frequency is the derivative of the phase. Unsurprisingly the frequency is the derivative of the phase.
* Group delays between pulse parameter updates are matched across oscillators, * Group delays between pulse parameter updates are matched across oscillators,
shapers, and channels. shapers, and channels.
* The minimum time to change profiles and phase offsets is ~128 ns (estimate, TBC). * The minimum time to change profiles and phase offsets is ``~128 ns`` (estimate, TBC).
This is the minimum pulse interval. This is the minimum pulse interval.
The sustained pulse rate of the RTIO PHY/Fastlink is one pulse per Fastlink frame The sustained pulse rate of the RTIO PHY/Fastlink is one pulse per Fastlink frame
(may be increased, TBC). (may be increased, TBC).
@ -1488,9 +1488,9 @@ class Miqro:
:param oscillator: Oscillator index (0 to 15) :param oscillator: Oscillator index (0 to 15)
:param profile: Profile index (0 to 31) :param profile: Profile index (0 to 31)
:param ftw: Frequency tuning word (32 bit signed integer on a 250 MHz clock) :param ftw: Frequency tuning word (32-bit signed integer on a 250 MHz clock)
:param asf: Amplitude scale factor (16 bit unsigned integer) :param asf: Amplitude scale factor (16-bit unsigned integer)
:param pow_: Phase offset word (16 bit integer) :param pow_: Phase offset word (16-bit integer)
""" """
if oscillator >= 16: if oscillator >= 16:
raise ValueError("invalid oscillator index") raise ValueError("invalid oscillator index")
@ -1514,7 +1514,7 @@ class Miqro:
:param amplitude: Amplitude in units of full scale (0. to 1.) :param amplitude: Amplitude in units of full scale (0. to 1.)
:param phase: Phase in turns. See :class:`Miqro` for a definition of :param phase: Phase in turns. See :class:`Miqro` for a definition of
phase in this context. phase in this context.
:return: The quantized 32 bit frequency tuning word :return: The quantized 32-bit frequency tuning word
""" """
ftw = round(frequency*(float(1 << 30)/(62.5*MHz))) ftw = round(frequency*(float(1 << 30)/(62.5*MHz)))
asf = round(amplitude*float(0xffff)) asf = round(amplitude*float(0xffff))
@ -1526,7 +1526,7 @@ class Miqro:
@kernel @kernel
def set_window_mu(self, start: int32, iq: list[int32], rate: int32 = 1, shift: int32 = 0, order: int32 = 3, head: bool = True, tail: bool = True) -> int32: def set_window_mu(self, start: int32, iq: list[int32], rate: int32 = 1, shift: int32 = 0, order: int32 = 3, head: bool = True, tail: bool = True) -> int32:
"""Store a window segment (machine units) """Store a window segment (machine units).
:param start: Window start address (0 to 0x3ff) :param start: Window start address (0 to 0x3ff)
:param iq: List of IQ window samples. Each window sample is an integer :param iq: List of IQ window samples. Each window sample is an integer
@ -1573,7 +1573,7 @@ class Miqro:
@kernel @kernel
def set_window(self, start: int32, iq: list[tuple[float, float]], period: float = 4e-9, order: int32 = 3, head: bool = True, tail: bool = True) -> float: def set_window(self, start: int32, iq: list[tuple[float, float]], period: float = 4e-9, order: int32 = 3, head: bool = True, tail: bool = True) -> float:
"""Store a window segment """Store a window segment.
:param start: Window start address (0 to 0x3ff) :param start: Window start address (0 to 0x3ff)
:param iq: List of IQ window samples. Each window sample is a pair of :param iq: List of IQ window samples. Each window sample is a pair of
@ -1610,7 +1610,7 @@ class Miqro:
@kernel @kernel
def encode(self, window: int32, profiles: list[int32], data: list[int32]) -> int32: def encode(self, window: int32, profiles: list[int32], data: list[int32]) -> int32:
"""Encode window and profile selection """Encode window and profile selection.
:param window: Window start address (0 to 0x3ff) :param window: Window start address (0 to 0x3ff)
:param profiles: List of profile indices for the oscillators. Maximum :param profiles: List of profile indices for the oscillators. Maximum

View File

@ -26,7 +26,7 @@ def rtio_input_data(channel: int32) -> int32:
@extern @extern
def rtio_input_timestamped_data(timeout_mu: int64, def rtio_input_timestamped_data(timeout_mu: int64,
channel: int32) -> tuple[int64, int32]: channel: int32) -> tuple[int64, int32]:
"""Wait for an input event up to timeout_mu on the given channel, and """Wait for an input event up to ``timeout_mu`` on the given channel, and
return a tuple of timestamp and attached data, or (-1, 0) if the timeout is return a tuple of timestamp and attached data, or (-1, 0) if the timeout is
reached.""" reached."""
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")

View File

@ -20,13 +20,13 @@ SPI_CS_PGIA = 1 # separate SPI bus, CS used as RCLK
@portable @portable
def adc_mu_to_volt(data: int32, gain: int32 = 0, corrected_fs: bool = True) -> float: def adc_mu_to_volt(data: int32, gain: int32 = 0, corrected_fs: bool = True) -> float:
"""Convert ADC data in machine units to Volts. """Convert ADC data in machine units to volts.
:param data: 16 bit signed ADC word :param data: 16-bit signed ADC word
:param gain: PGIA gain setting (0: 1, ..., 3: 1000) :param gain: PGIA gain setting (0: 1, ..., 3: 1000)
:param corrected_fs: use corrected ADC FS reference. :param corrected_fs: use corrected ADC FS reference.
Should be True for Samplers' revisions after v2.1. False for v2.1 and earlier. Should be ``True`` for Sampler revisions after v2.1. ``False`` for v2.1 and earlier.
:return: Voltage in Volts :return: Voltage in volts
""" """
volt_per_lsb = 0. volt_per_lsb = 0.
if gain == 0: if gain == 0:
@ -46,7 +46,7 @@ def adc_mu_to_volt(data: int32, gain: int32 = 0, corrected_fs: bool = True) -> f
class Sampler: class Sampler:
"""Sampler ADC. """Sampler ADC.
Controls the LTC2320-16 8 channel 16 bit ADC with SPI interface and Controls the LTC2320-16 8-channel 16-bit ADC with SPI interface and
the switchable gain instrumentation amplifiers. the switchable gain instrumentation amplifiers.
:param spi_adc_device: ADC SPI bus device name :param spi_adc_device: ADC SPI bus device name
@ -131,12 +131,12 @@ class Sampler:
Perform a conversion and transfer the samples. Perform a conversion and transfer the samples.
This assumes that the input FIFO of the ADC SPI RTIO channel is deep This assumes that the input FIFO of the ADC SPI RTIO channel is deep
enough to buffer the samples (half the length of `data` deep). enough to buffer the samples (half the length of ``data`` deep).
If it is not, there will be RTIO input overflows. If it is not, there will be RTIO input overflows.
:param data: List of data samples to fill. Must have even length. :param data: List of data samples to fill. Must have even length.
Samples are always read from the last channel (channel 7) down. Samples are always read from the last channel (channel 7) down.
The `data` list will always be filled with the last item The ``data`` list will always be filled with the last item
holding to the sample from channel 7. holding to the sample from channel 7.
""" """
self.cnv.pulse(30.*ns) # t_CNVH self.cnv.pulse(30.*ns) # t_CNVH
@ -154,7 +154,7 @@ class Sampler:
def sample(self, data: list[float]): def sample(self, data: list[float]):
"""Acquire a set of samples. """Acquire a set of samples.
.. seealso:: :meth:`sample_mu` See also :meth:`Sampler.sample_mu`.
:param data: List of floating point data samples to fill. :param data: List of floating point data samples to fill.
""" """

View File

@ -19,8 +19,8 @@ def shuttler_volt_to_mu(volt: float) -> int32:
class Config: class Config:
"""Shuttler configuration registers interface. """Shuttler configuration registers interface.
The configuration registers control waveform phase auto-clear, and pre-DAC The configuration registers control waveform phase auto-clear, pre-DAC
gain & offset values for calibration with ADC on the Shuttler AFE card. gain and offset values for calibration with ADC on the Shuttler AFE card.
To find the calibrated DAC code, the Shuttler Core first multiplies the To find the calibrated DAC code, the Shuttler Core first multiplies the
output data with pre-DAC gain, then adds the offset. output data with pre-DAC gain, then adds the offset.
@ -90,8 +90,7 @@ class Config:
def set_offset(self, channel: int32, offset: int32): def set_offset(self, channel: int32, offset: int32):
"""Set the 16-bits pre-DAC offset register of a Shuttler Core channel. """Set the 16-bits pre-DAC offset register of a Shuttler Core channel.
.. seealso:: See also :meth:`shuttler_volt_to_mu`.
:meth:`shuttler_volt_to_mu`
:param channel: Shuttler Core channel to be configured. :param channel: Shuttler Core channel to be configured.
:param offset: Shuttler Core channel offset. :param offset: Shuttler Core channel offset.
@ -121,13 +120,13 @@ class DCBias:
.. math:: .. math::
w(t) = a(t) + b(t) * cos(c(t)) w(t) = a(t) + b(t) * cos(c(t))
And `t` corresponds to time in seconds. and `t` corresponds to time in seconds.
This class controls the cubic spline `a(t)`, in which This class controls the cubic spline `a(t)`, in which
.. math:: .. math::
a(t) = p_0 + p_1t + \\frac{p_2t^2}{2} + \\frac{p_3t^3}{6} a(t) = p_0 + p_1t + \\frac{p_2t^2}{2} + \\frac{p_3t^3}{6}
And `a(t)` is in Volt. and `a(t)` is measured in volts.
:param channel: RTIO channel number of this DC-bias spline interface. :param channel: RTIO channel number of this DC-bias spline interface.
:param core_device: Core device name. :param core_device: Core device name.
@ -146,7 +145,7 @@ class DCBias:
"""Set the DC-bias spline waveform. """Set the DC-bias spline waveform.
Given `a(t)` as defined in :class:`DCBias`, the coefficients should be Given `a(t)` as defined in :class:`DCBias`, the coefficients should be
configured by the following formulae. configured by the following formulae:
.. math:: .. math::
T &= 8*10^{-9} T &= 8*10^{-9}
@ -163,8 +162,10 @@ class DCBias:
and 48 bits in width respectively. See :meth:`shuttler_volt_to_mu` for and 48 bits in width respectively. See :meth:`shuttler_volt_to_mu` for
machine unit conversion. machine unit conversion.
Note: The waveform is not updated to the Shuttler Core until .. note::
triggered. See :class:`Trigger` for the update triggering mechanism. The waveform is not updated to the Shuttler Core until
triggered. See :class:`Trigger` for the update triggering
mechanism.
:param a0: The :math:`a_0` coefficient in machine unit. :param a0: The :math:`a_0` coefficient in machine unit.
:param a1: The :math:`a_1` coefficient in machine unit. :param a1: The :math:`a_1` coefficient in machine unit.
@ -199,7 +200,7 @@ class DDS:
.. math:: .. math::
w(t) = a(t) + b(t) * cos(c(t)) w(t) = a(t) + b(t) * cos(c(t))
And `t` corresponds to time in seconds. and `t` corresponds to time in seconds.
This class controls the cubic spline `b(t)` and quadratic spline `c(t)`, This class controls the cubic spline `b(t)` and quadratic spline `c(t)`,
in which in which
@ -208,7 +209,7 @@ class DDS:
c(t) &= r_0 + r_1t + \\frac{r_2t^2}{2} c(t) &= r_0 + r_1t + \\frac{r_2t^2}{2}
And `b(t)` is in Volt, `c(t)` is in number of turns. Note that `b(t)` `b(t)` is in volts, `c(t)` is in number of turns. Note that `b(t)`
contributes to a constant gain of :math:`g=1.64676`. contributes to a constant gain of :math:`g=1.64676`.
:param channel: RTIO channel number of this DC-bias spline interface. :param channel: RTIO channel number of this DC-bias spline interface.
@ -256,13 +257,13 @@ class DDS:
Note: The waveform is not updated to the Shuttler Core until Note: The waveform is not updated to the Shuttler Core until
triggered. See :class:`Trigger` for the update triggering mechanism. triggered. See :class:`Trigger` for the update triggering mechanism.
:param b0: The :math:`b_0` coefficient in machine unit. :param b0: The :math:`b_0` coefficient in machine units.
:param b1: The :math:`b_1` coefficient in machine unit. :param b1: The :math:`b_1` coefficient in machine units.
:param b2: The :math:`b_2` coefficient in machine unit. :param b2: The :math:`b_2` coefficient in machine units.
:param b3: The :math:`b_3` coefficient in machine unit. :param b3: The :math:`b_3` coefficient in machine units.
:param c0: The :math:`c_0` coefficient in machine unit. :param c0: The :math:`c_0` coefficient in machine units.
:param c1: The :math:`c_1` coefficient in machine unit. :param c1: The :math:`c_1` coefficient in machine units.
:param c2: The :math:`c_2` coefficient in machine unit. :param c2: The :math:`c_2` coefficient in machine units.
""" """
coef_words = [ coef_words = [
b0, b0,
@ -307,8 +308,8 @@ class Trigger:
"""Triggers coefficient update of (a) Shuttler Core channel(s). """Triggers coefficient update of (a) Shuttler Core channel(s).
Each bit corresponds to a Shuttler waveform generator core. Setting Each bit corresponds to a Shuttler waveform generator core. Setting
`trig_out` bits commits the pending coefficient update (from ``trig_out`` bits commits the pending coefficient update (from
`set_waveform` in :class:`DCBias` and :class:`DDS`) to the Shuttler Core ``set_waveform`` in :class:`DCBias` and :class:`DDS`) to the Shuttler Core
synchronously. synchronously.
:param trig_out: Coefficient update trigger bits. The MSB corresponds :param trig_out: Coefficient update trigger bits. The MSB corresponds
@ -352,8 +353,8 @@ _AD4115_REG_SETUPCON0 = 0x20
class Relay: class Relay:
"""Shuttler AFE relay switches. """Shuttler AFE relay switches.
It controls the AFE relay switches and the LEDs. Switch on the relay to This class controls the AFE relay switches and the LEDs. Switch the relay on to
enable AFE output; And off to disable the output. The LEDs indicates the enable AFE output; off to disable the output. The LEDs indicate the
relay status. relay status.
.. note:: .. note::
@ -374,7 +375,7 @@ class Relay:
def init(self): def init(self):
"""Initialize SPI device. """Initialize SPI device.
Configures the SPI bus to 16-bits, write-only, simultaneous relay Configures the SPI bus to 16 bits, write-only, simultaneous relay
switches and LED control. switches and LED control.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
@ -382,10 +383,10 @@ class Relay:
@kernel @kernel
def enable(self, en: int32): def enable(self, en: int32):
"""Enable/Disable relay switches of corresponding channels. """Enable/disable relay switches of corresponding channels.
Each bit corresponds to the relay switch of a channel. Asserting a bit Each bit corresponds to the relay switch of a channel. Asserting a bit
turns on the corresponding relay switch; Deasserting the same bit turns on the corresponding relay switch; deasserting the same bit
turns off the switch instead. turns off the switch instead.
:param en: Switch enable bits. The MSB corresponds to Channel 15, LSB :param en: Switch enable bits. The MSB corresponds to Channel 15, LSB
@ -422,12 +423,12 @@ class ADC:
def reset(self): def reset(self):
"""AD4115 reset procedure. """AD4115 reset procedure.
This performs a write operation of 96 serial clock cycles with DIN Performs a write operation of 96 serial clock cycles with DIN
held at high. It resets the entire device, including the register held at high. This resets the entire device, including the register
contents. contents.
.. note:: .. note::
The datasheet only requires 64 cycles, but reasserting `CS_n` right The datasheet only requires 64 cycles, but reasserting ``CS_n`` right
after the transfer appears to interrupt the start-up sequence. after the transfer appears to interrupt the start-up sequence.
""" """
self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC) self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC)
@ -439,7 +440,7 @@ class ADC:
@kernel @kernel
def read8(self, addr: int32) -> int32: def read8(self, addr: int32) -> int32:
"""Read from 8 bit register. """Read from 8-bit register.
:param addr: Register address. :param addr: Register address.
:return: Read-back register content. :return: Read-back register content.
@ -452,7 +453,7 @@ class ADC:
@kernel @kernel
def read16(self, addr: int32) -> int32: def read16(self, addr: int32) -> int32:
"""Read from 16 bit register. """Read from 16-bit register.
:param addr: Register address. :param addr: Register address.
:return: Read-back register content. :return: Read-back register content.
@ -465,7 +466,7 @@ class ADC:
@kernel @kernel
def read24(self, addr: int32) -> int32: def read24(self, addr: int32) -> int32:
"""Read from 24 bit register. """Read from 24-bit register.
:param addr: Register address. :param addr: Register address.
:return: Read-back register content. :return: Read-back register content.
@ -478,7 +479,7 @@ class ADC:
@kernel @kernel
def write8(self, addr: int32, data: int32): def write8(self, addr: int32, data: int32):
"""Write to 8 bit register. """Write to 8-bit register.
:param addr: Register address. :param addr: Register address.
:param data: Data to be written. :param data: Data to be written.
@ -489,7 +490,7 @@ class ADC:
@kernel @kernel
def write16(self, addr: int32, data: int32): def write16(self, addr: int32, data: int32):
"""Write to 16 bit register. """Write to 16-bit register.
:param addr: Register address. :param addr: Register address.
:param data: Data to be written. :param data: Data to be written.
@ -500,7 +501,7 @@ class ADC:
@kernel @kernel
def write24(self, addr: int32, data: int32): def write24(self, addr: int32, data: int32):
"""Write to 24 bit register. """Write to 24-bit register.
:param addr: Register address. :param addr: Register address.
:param data: Data to be written. :param data: Data to be written.
@ -513,11 +514,11 @@ class ADC:
def read_ch(self, channel: int32) -> float: def read_ch(self, channel: int32) -> float:
"""Sample a Shuttler channel on the AFE. """Sample a Shuttler channel on the AFE.
It performs a single conversion using profile 0 and setup 0, on the Performs a single conversion using profile 0 and setup 0 on the
selected channel. The sample is then recovered and converted to volt. selected channel. The sample is then recovered and converted to volts.
:param channel: Shuttler channel to be sampled. :param channel: Shuttler channel to be sampled.
:return: Voltage sample in volt. :return: Voltage sample in volts.
""" """
# Always configure Profile 0 for single conversion # Always configure Profile 0 for single conversion
self.write16(_AD4115_REG_CH0, 0x8000 | ((channel * 2 + 1) << 4)) self.write16(_AD4115_REG_CH0, 0x8000 | ((channel * 2 + 1) << 4))
@ -538,7 +539,7 @@ class ADC:
@kernel @kernel
def standby(self): def standby(self):
"""Place the ADC in standby mode and disables power down the clock. """Place the ADC in standby mode and disable power down the clock.
The ADC can be returned to single conversion mode by calling The ADC can be returned to single conversion mode by calling
:meth:`single_conversion`. :meth:`single_conversion`.
@ -555,13 +556,7 @@ class ADC:
.. note:: .. note::
The AD4115 datasheet suggests placing the ADC in standby mode The AD4115 datasheet suggests placing the ADC in standby mode
before power-down. This is to prevent accidental entry into the before power-down. This is to prevent accidental entry into the
power-down mode. power-down mode. See also :meth:`standby` and :meth:`power_up`.
.. seealso::
:meth:`standby`
:meth:`power_up`
""" """
self.write16(_AD4115_REG_ADCMODE, 0x8030) self.write16(_AD4115_REG_ADCMODE, 0x8030)
@ -571,8 +566,7 @@ class ADC:
The ADC should be in power-down mode before calling this method. The ADC should be in power-down mode before calling this method.
.. seealso:: See also :meth:`power_down`.
:meth:`power_down`
""" """
self.reset() self.reset()
# Although the datasheet claims 500 us reset wait time, only waiting # Although the datasheet claims 500 us reset wait time, only waiting
@ -583,22 +577,18 @@ class ADC:
def calibrate(self, volts: list[DCBias], trigger: Trigger, config: Config, samples: Option[list[float]] = none): def calibrate(self, volts: list[DCBias], trigger: Trigger, config: Config, samples: Option[list[float]] = none):
"""Calibrate the Shuttler waveform generator using the ADC on the AFE. """Calibrate the Shuttler waveform generator using the ADC on the AFE.
It finds the average slope rate and average offset by samples, and Finds the average slope rate and average offset by samples, and
compensate by writing the pre-DAC gain and offset registers in the compensates by writing the pre-DAC gain and offset registers in the
configuration registers. configuration registers.
.. note:: .. note::
If the pre-calibration slope rate < 1, the calibration procedure If the pre-calibration slope rate is less than 1, the calibration
will introduce a pre-DAC gain compensation. However, this may procedure will introduce a pre-DAC gain compensation. However, this
saturate the pre-DAC voltage code. (See :class:`Config` notes). may saturate the pre-DAC voltage code (see :class:`Config` notes).
Shuttler cannot cover the entire +/- 10 V range in this case. Shuttler cannot cover the entire +/- 10 V range in this case.
See also :meth:`Config.set_gain` and :meth:`Config.set_offset`.
.. seealso:: :param volts: A list of all 16 cubic DC-bias splines.
:meth:`Config.set_gain`
:meth:`Config.set_offset`
:param volts: A list of all 16 cubic DC-bias spline.
(See :class:`DCBias`) (See :class:`DCBias`)
:param trigger: The Shuttler spline coefficient update trigger. :param trigger: The Shuttler spline coefficient update trigger.
:param config: The Shuttler Core configuration registers. :param config: The Shuttler Core configuration registers.

View File

@ -4,7 +4,7 @@ Driver for generic SPI on RTIO.
This ARTIQ coredevice driver corresponds to the "new" MiSoC SPI core (v2). This ARTIQ coredevice driver corresponds to the "new" MiSoC SPI core (v2).
Output event replacement is not supported and issuing commands at the same Output event replacement is not supported and issuing commands at the same
time is an error. time results in collision errors.
""" """
from numpy import int32, int64 from numpy import int32, int64
@ -54,7 +54,7 @@ class SPIMaster:
event (``SPI_INPUT`` set), then :meth:`read` the ``data``. event (``SPI_INPUT`` set), then :meth:`read` the ``data``.
* If ``SPI_END`` was not set, repeat the transfer sequence. * If ``SPI_END`` was not set, repeat the transfer sequence.
A **transaction** consists of one or more **transfers**. The chip select A *transaction* consists of one or more *transfers*. The chip select
pattern is asserted for the entire length of the transaction. All but the pattern is asserted for the entire length of the transaction. All but the
last transfer are submitted with ``SPI_END`` cleared in the configuration last transfer are submitted with ``SPI_END`` cleared in the configuration
register. register.
@ -143,10 +143,10 @@ class SPIMaster:
* :const:`SPI_LSB_FIRST`: LSB is the first bit on the wire (reset=0) * :const:`SPI_LSB_FIRST`: LSB is the first bit on the wire (reset=0)
* :const:`SPI_HALF_DUPLEX`: 3-wire SPI, in/out on ``mosi`` (reset=0) * :const:`SPI_HALF_DUPLEX`: 3-wire SPI, in/out on ``mosi`` (reset=0)
:param flags: A bit map of `SPI_*` flags. :param flags: A bit map of :const:`SPI_*` flags.
:param length: Number of bits to write during the next transfer. :param length: Number of bits to write during the next transfer.
(reset=1) (reset=1)
:param freq: Desired SPI clock frequency. (reset=f_rtio/2) :param freq: Desired SPI clock frequency. (reset= ``f_rtio/2``)
:param cs: Bit pattern of chip selects to assert. :param cs: Bit pattern of chip selects to assert.
Or number of the chip select to assert if ``cs`` is decoded Or number of the chip select to assert if ``cs`` is decoded
downstream. (reset=0) downstream. (reset=0)
@ -157,16 +157,15 @@ class SPIMaster:
def set_config_mu(self, flags: int32, length: int32, div: int32, cs: int32): def set_config_mu(self, flags: int32, length: int32, div: int32, cs: int32):
"""Set the ``config`` register (in SPI bus machine units). """Set the ``config`` register (in SPI bus machine units).
.. seealso:: :meth:`set_config` See also :meth:`set_config`.
:param flags: A bit map of `SPI_*` flags. :param flags: A bit map of `SPI_*` flags.
:param length: Number of bits to write during the next transfer. :param length: Number of bits to write during the next transfer.
(reset=1) (reset=1)
:param div: Counter load value to divide the RTIO :param div: Counter load value to divide the RTIO
clock by to generate the SPI clock. (minimum=2, reset=2) clock by to generate the SPI clock; ``f_rtio_clk/f_spi == div``.
``f_rtio_clk/f_spi == div``. If ``div`` is odd, If ``div`` is odd, the setup phase of the SPI clock is one
the setup phase of the SPI clock is one coarse RTIO clock cycle coarse RTIO clock cycle longer than the hold phase. (minimum=2, reset=2)
longer than the hold phase.
:param cs: Bit pattern of chip selects to assert. :param cs: Bit pattern of chip selects to assert.
Or number of the chip select to assert if ``cs`` is decoded Or number of the chip select to assert if ``cs`` is decoded
downstream. (reset=0) downstream. (reset=0)
@ -193,7 +192,7 @@ class SPIMaster:
experiments and are known. experiments and are known.
This method is portable and can also be called from e.g. This method is portable and can also be called from e.g.
:meth:`__init__`. ``__init__``.
.. warning:: If this method is called while recording a DMA .. warning:: If this method is called while recording a DMA
sequence, the playback of the sequence will not update the sequence, the playback of the sequence will not update the
@ -213,7 +212,7 @@ class SPIMaster:
* The ``data`` register and the shift register are 32 bits wide. * The ``data`` register and the shift register are 32 bits wide.
* Data writes take one ``ref_period`` cycle. * Data writes take one ``ref_period`` cycle.
* A transaction consisting of a single transfer (``SPI_END``) takes * A transaction consisting of a single transfer (``SPI_END``) takes
:attr:`xfer_duration_mu` ``=(n + 1)*div`` cycles RTIO time where :attr:`xfer_duration_mu` `` = (n + 1) * div`` cycles RTIO time, where
``n`` is the number of bits and ``div`` is the SPI clock divider. ``n`` is the number of bits and ``div`` is the SPI clock divider.
* Transfers in a multi-transfer transaction take up to one SPI clock * Transfers in a multi-transfer transaction take up to one SPI clock
cycle less time depending on multiple parameters. Advanced users may cycle less time depending on multiple parameters. Advanced users may

View File

@ -29,7 +29,7 @@ def y_mu_to_full_scale(y: int32) -> float:
@portable @portable
def adc_mu_to_volts(x: int32, gain: int32, corrected_fs: bool = True) -> float: def adc_mu_to_volts(x: int32, gain: int32, corrected_fs: bool = True) -> float:
"""Convert servo ADC data from machine units to Volt.""" """Convert servo ADC data from machine units to volts."""
val = (x >> 1) & 0xffff val = (x >> 1) & 0xffff
mask = 1 << 15 mask = 1 << 15
val = -(val & mask) + (val & ~mask) val = -(val & mask) + (val & ~mask)
@ -168,7 +168,7 @@ class SUServo:
This method advances the timeline by one servo memory access. This method advances the timeline by one servo memory access.
It does not support RTIO event replacement. It does not support RTIO event replacement.
:param enable (int): Enable servo operation. Enabling starts servo :param int enable: Enable servo operation. Enabling starts servo
iterations beginning with the ADC sampling stage. The first DDS iterations beginning with the ADC sampling stage. The first DDS
update will happen about two servo cycles (~2.3 µs) after enabling update will happen about two servo cycles (~2.3 µs) after enabling
the servo. The delay is deterministic. the servo. The delay is deterministic.
@ -211,7 +211,7 @@ class SUServo:
consistent and valid data, stop the servo before using this method. consistent and valid data, stop the servo before using this method.
:param adc: ADC channel number (0-7) :param adc: ADC channel number (0-7)
:return: 17 bit signed X0 :return: 17-bit signed X0
""" """
# State memory entries are 25 bits. Due to the pre-adder dynamic # State memory entries are 25 bits. Due to the pre-adder dynamic
# range, X0/X1/OFFSET are only 24 bits. Finally, the RTIO interface # range, X0/X1/OFFSET are only 24 bits. Finally, the RTIO interface
@ -307,12 +307,12 @@ class Channel:
def set_dds_mu(self, profile: int32, ftw: int32, offs: int32, pow_: int32 = 0): def set_dds_mu(self, profile: int32, ftw: int32, offs: int32, pow_: int32 = 0):
"""Set profile DDS coefficients in machine units. """Set profile DDS coefficients in machine units.
.. seealso:: :meth:`set_amplitude` See also :meth:`Channel.set_dds`.
:param profile: Profile number (0-31) :param profile: Profile number (0-31)
:param ftw: Frequency tuning word (32 bit unsigned) :param ftw: Frequency tuning word (32-bit unsigned)
:param offs: IIR offset (17 bit signed) :param offs: IIR offset (17-bit signed)
:param pow_: Phase offset word (16 bit) :param pow_: Phase offset word (16-bit)
""" """
base = (self.servo_channel << 8) | (profile << 3) base = (self.servo_channel << 8) | (profile << 3)
self.servo.write(base + 0, ftw >> 16) self.servo.write(base + 0, ftw >> 16)
@ -346,7 +346,7 @@ class Channel:
See :meth:`set_dds_mu` for setting the complete DDS profile. See :meth:`set_dds_mu` for setting the complete DDS profile.
:param profile: Profile number (0-31) :param profile: Profile number (0-31)
:param offs: IIR offset (17 bit signed) :param offs: IIR offset (17-bit signed)
""" """
base = (self.servo_channel << 8) | (profile << 3) base = (self.servo_channel << 8) | (profile << 3)
self.servo.write(base + 4, offs) self.servo.write(base + 4, offs)
@ -394,15 +394,15 @@ class Channel:
* :math:`b_0` and :math:`b_1` are the feedforward gains for the two * :math:`b_0` and :math:`b_1` are the feedforward gains for the two
delays delays
.. seealso:: :meth:`set_iir` See also :meth:`Channel.set_iir`.
:param profile: Profile number (0-31) :param profile: Profile number (0-31)
:param adc: ADC channel to take IIR input from (0-7) :param adc: ADC channel to take IIR input from (0-7)
:param a1: 18 bit signed A1 coefficient (Y1 coefficient, :param a1: 18-bit signed A1 coefficient (Y1 coefficient,
feedback, integrator gain) feedback, integrator gain)
:param b0: 18 bit signed B0 coefficient (recent, :param b0: 18-bit signed B0 coefficient (recent,
X0 coefficient, feed forward, proportional gain) X0 coefficient, feed forward, proportional gain)
:param b1: 18 bit signed B1 coefficient (old, :param b1: 18-bit signed B1 coefficient (old,
X1 coefficient, feed forward, proportional gain) X1 coefficient, feed forward, proportional gain)
:param dly: IIR update suppression time. In units of IIR cycles :param dly: IIR update suppression time. In units of IIR cycles
(~1.2 µs, 0-255). (~1.2 µs, 0-255).
@ -518,7 +518,7 @@ class Channel:
consistent and valid data, stop the servo before using this method. consistent and valid data, stop the servo before using this method.
:param profile: Profile number (0-31) :param profile: Profile number (0-31)
:return: 17 bit unsigned Y0 :return: 17-bit unsigned Y0
""" """
return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile) return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile)
@ -554,7 +554,7 @@ class Channel:
This method advances the timeline by one servo memory access. This method advances the timeline by one servo memory access.
:param profile: Profile number (0-31) :param profile: Profile number (0-31)
:param y: 17 bit unsigned Y0 :param y: 17-bit unsigned Y0
""" """
# State memory is 25 bits wide and signed. # State memory is 25 bits wide and signed.
# Reads interact with the 18 MSBs (coefficient memory width) # Reads interact with the 18 MSBs (coefficient memory width)

View File

@ -28,7 +28,7 @@ class TTLOut:
This should be used with output-only channels. This should be used with output-only channels.
:param channel: channel number :param channel: Channel number
""" """
core: KernelInvariant[Core] core: KernelInvariant[Core]
channel: KernelInvariant[int32] channel: KernelInvariant[int32]
@ -113,7 +113,7 @@ class TTLInOut:
API is active (e.g. the gate is open, or the input events have not been API is active (e.g. the gate is open, or the input events have not been
fully read out), another API must not be used simultaneously. fully read out), another API must not be used simultaneously.
:param channel: channel number :param channel: Channel number
""" """
core: KernelInvariant[Core] core: KernelInvariant[Core]
channel: KernelInvariant[int32] channel: KernelInvariant[int32]
@ -154,7 +154,7 @@ class TTLInOut:
"""Set the direction to output at the current position of the time """Set the direction to output at the current position of the time
cursor. cursor.
There must be a delay of at least one RTIO clock cycle before any A delay of at least one RTIO clock cycle is necessary before any
other command can be issued. other command can be issued.
This method only configures the direction at the FPGA. When using This method only configures the direction at the FPGA. When using
@ -167,7 +167,7 @@ class TTLInOut:
"""Set the direction to input at the current position of the time """Set the direction to input at the current position of the time
cursor. cursor.
There must be a delay of at least one RTIO clock cycle before any A delay of at least one RTIO clock cycle is necessary before any
other command can be issued. other command can be issued.
This method only configures the direction at the FPGA. When using This method only configures the direction at the FPGA. When using
@ -335,17 +335,18 @@ class TTLInOut:
:return: The number of events before the timeout elapsed (0 if none :return: The number of events before the timeout elapsed (0 if none
observed). observed).
Examples: **Examples:**
To count events on channel ``ttl_input``, up to the current timeline To count events on channel ``ttl_input``, up to the current timeline
position:: position: ::
ttl_input.count(now_mu()) ttl_input.count(now_mu())
If other events are scheduled between the end of the input gate If other events are scheduled between the end of the input gate
period and when the number of events is counted, using ``now_mu()`` period and when the number of events is counted, using
as timeout consumes an unnecessary amount of timeline slack. In :meth:`~artiq.language.core.now_mu()` as timeout consumes an
such cases, it can be beneficial to pass a more precise timestamp, unnecessary amount of timeline slack. In such cases, it can be
for example:: beneficial to pass a more precise timestamp, for example: ::
gate_end_mu = ttl_input.gate_rising(100 * us) gate_end_mu = ttl_input.gate_rising(100 * us)
@ -359,7 +360,7 @@ class TTLInOut:
num_rising_edges = ttl_input.count(gate_end_mu) num_rising_edges = ttl_input.count(gate_end_mu)
The ``gate_*()`` family of methods return the cursor at the end The ``gate_*()`` family of methods return the cursor at the end
of the window, allowing this to be expressed in a compact fashion:: of the window, allowing this to be expressed in a compact fashion: ::
ttl_input.count(ttl_input.gate_rising(100 * us)) ttl_input.count(ttl_input.gate_rising(100 * us))
""" """
@ -450,7 +451,7 @@ class TTLInOut:
was being watched. was being watched.
The time cursor is not modified by this function. This function The time cursor is not modified by this function. This function
always makes the slack negative. always results in negative slack.
""" """
rtio_output(self.target_sens, 0) rtio_output(self.target_sens, 0)
success = True success = True

View File

@ -114,7 +114,7 @@ class CPLD:
:param spi_device: SPI bus device name :param spi_device: SPI bus device name
:param io_update_device: IO update RTIO TTLOut channel name :param io_update_device: IO update RTIO TTLOut channel name
:param dds_reset_device: DDS reset 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 sync_device: AD9910 ``SYNC_IN`` RTIO TTLClockGen channel name
:param refclk: Reference clock (SMA, MMCX or on-board 100 MHz oscillator) :param refclk: Reference clock (SMA, MMCX or on-board 100 MHz oscillator)
frequency in Hz frequency in Hz
:param clk_sel: Reference clock selection. For hardware revision >= 1.3 :param clk_sel: Reference clock selection. For hardware revision >= 1.3
@ -127,9 +127,9 @@ class CPLD:
1: divide-by-1; 2: divide-by-2; 3: divide-by-4. 1: divide-by-1; 2: divide-by-2; 3: divide-by-4.
On Urukul boards with CPLD gateware before v1.3.1 only the default On Urukul boards with CPLD gateware before v1.3.1 only the default
(0, i.e. variant dependent divider) is valid. (0, i.e. variant dependent divider) is valid.
:param sync_sel: SYNC (multi-chip synchronisation) signal source selection. :param sync_sel: ``SYNC`` (multi-chip synchronisation) signal source selection.
0 corresponds to SYNC_IN being supplied by the FPGA via the EEM 0 corresponds to ``SYNC_IN`` being supplied by the FPGA via the EEM
connector. 1 corresponds to SYNC_OUT from DDS0 being distributed to the connector. 1 corresponds to ``SYNC_OUT`` from DDS0 being distributed to the
other chips. other chips.
:param rf_sw: Initial CPLD RF switch register setting (default: 0x0). :param rf_sw: Initial CPLD RF switch register setting (default: 0x0).
Knowledge of this state is not transferred between experiments. Knowledge of this state is not transferred between experiments.
@ -137,8 +137,8 @@ class CPLD:
0x00000000). See also :meth:`get_att_mu` which retrieves the hardware 0x00000000). See also :meth:`get_att_mu` which retrieves the hardware
state without side effects. Knowledge of this state is not transferred state without side effects. Knowledge of this state is not transferred
between experiments. between experiments.
:param sync_div: SYNC_IN generator divider. The ratio between the coarse :param sync_div: ``SYNC_IN`` generator divider. The ratio between the coarse
RTIO frequency and the SYNC_IN generator frequency (default: 2 if RTIO frequency and the ``SYNC_IN`` generator frequency (default: 2 if
`sync_device` was specified). `sync_device` was specified).
:param core_device: Core device name :param core_device: Core device name
@ -202,7 +202,7 @@ class CPLD:
See :func:`urukul_cfg` for possible flags. See :func:`urukul_cfg` for possible flags.
:param cfg: 24 bit data to be written. Will be stored at :param cfg: 24-bit data to be written. Will be stored at
:attr:`cfg_reg`. :attr:`cfg_reg`.
""" """
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24, self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24,
@ -235,7 +235,7 @@ class CPLD:
Resets the DDS I/O interface and verifies correct CPLD gateware Resets the DDS I/O interface and verifies correct CPLD gateware
version. version.
Does not pulse the DDS MASTER_RESET as that confuses the AD9910. Does not pulse the DDS ``MASTER_RESET`` as that confuses the AD9910.
:param blind: Do not attempt to verify presence and compatibility. :param blind: Do not attempt to verify presence and compatibility.
""" """
@ -281,7 +281,7 @@ class CPLD:
def cfg_switches(self, state: int32): def cfg_switches(self, state: int32):
"""Configure all four RF switches through the configuration register. """Configure all four RF switches through the configuration register.
:param state: RF switch state as a 4 bit integer. :param state: RF switch state as a 4-bit integer.
""" """
self.cfg_write((self.cfg_reg & ~0xf) | state) self.cfg_write((self.cfg_reg & ~0xf) | state)
@ -325,10 +325,9 @@ class CPLD:
@kernel @kernel
def set_all_att_mu(self, att_reg: int32): def set_all_att_mu(self, att_reg: int32):
"""Set all four digital step attenuators (in machine units). """Set all four digital step attenuators (in machine units).
See also :meth:`set_att_mu`.
.. seealso:: :meth:`set_att_mu` :param att_reg: Attenuator setting string (32-bit)
:param att_reg: Attenuator setting string (32 bit)
""" """
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32, self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
SPIT_ATT_WR, CS_ATT) SPIT_ATT_WR, CS_ATT)
@ -340,8 +339,7 @@ class CPLD:
"""Set digital step attenuator in SI units. """Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
See also :meth:`set_att_mu`.
.. seealso:: :meth:`set_att_mu`
:param channel: Attenuator channel (0-3). :param channel: Attenuator channel (0-3).
:param att: Attenuation setting in dB. Higher value is more :param att: Attenuation setting in dB. Higher value is more
@ -357,9 +355,9 @@ class CPLD:
The result is stored and will be used in future calls of The result is stored and will be used in future calls of
:meth:`set_att_mu` and :meth:`set_att`. :meth:`set_att_mu` and :meth:`set_att`.
.. seealso:: :meth:`get_channel_att_mu` See also :meth:`get_channel_att_mu`.
:return: 32 bit attenuator settings :return: 32-bit attenuator settings
""" """
self.bus.set_config_mu(SPI_CONFIG | SPI_INPUT, 32, self.bus.set_config_mu(SPI_CONFIG | SPI_INPUT, 32,
SPIT_ATT_RD, CS_ATT) SPIT_ATT_RD, CS_ATT)
@ -378,7 +376,7 @@ class CPLD:
The result is stored and will be used in future calls of The result is stored and will be used in future calls of
:meth:`set_att_mu` and :meth:`set_att`. :meth:`set_att_mu` and :meth:`set_att`.
.. seealso:: :meth:`get_att_mu` See also :meth:`get_att_mu`.
:param channel: Attenuator channel (0-3). :param channel: Attenuator channel (0-3).
:return: 8-bit digital attenuation setting: :return: 8-bit digital attenuation setting:
@ -390,7 +388,7 @@ class CPLD:
def get_channel_att(self, channel: int32) -> float: def get_channel_att(self, channel: int32) -> float:
"""Get digital step attenuator value for a channel in SI units. """Get digital step attenuator value for a channel in SI units.
.. seealso:: :meth:`get_channel_att_mu` See also :meth:`get_channel_att_mu`.
:param channel: Attenuator channel (0-3). :param channel: Attenuator channel (0-3).
:return: Attenuation setting in dB. Higher value is more :return: Attenuation setting in dB. Higher value is more
@ -401,14 +399,14 @@ class CPLD:
@kernel @kernel
def set_sync_div(self, div: int32): def set_sync_div(self, div: int32):
"""Set the SYNC_IN AD9910 pulse generator frequency """Set the ``SYNC_IN`` AD9910 pulse generator frequency
and align it to the current RTIO timestamp. and align it to the current RTIO timestamp.
The SYNC_IN signal is derived from the coarse RTIO clock The ``SYNC_IN`` signal is derived from the coarse RTIO clock
and the divider must be a power of two. and the divider must be a power of two.
Configure ``sync_sel == 0``. Configure ``sync_sel == 0``.
:param div: SYNC_IN frequency divider. Must be a power of two. :param div: ``SYNC_IN`` frequency divider. Must be a power of two.
Minimum division ratio is 2. Maximum division ratio is 16. Minimum division ratio is 2. Maximum division ratio is 16.
""" """
ftw_max = 1 << 4 ftw_max = 1 << 4

View File

@ -1,7 +1,7 @@
"""RTIO driver for the Zotino 32-channel, 16-bit 1MSPS DAC. """RTIO driver for the Zotino 32-channel, 16-bit 1MSPS DAC.
Output event replacement is not supported and issuing commands at the same Output event replacement is not supported and issuing commands at the same
time is an error. time results in a collision error.
""" """
from numpy import int32 from numpy import int32

View File

@ -1,7 +1,7 @@
import asyncio import asyncio
import logging import logging
from PyQt5 import QtCore, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from artiq.gui import applets from artiq.gui import applets
@ -13,58 +13,58 @@ class AppletsCCBDock(applets.AppletsDock):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
applets.AppletsDock.__init__(self, *args, **kwargs) applets.AppletsDock.__init__(self, *args, **kwargs)
sep = QtWidgets.QAction(self.table) sep = QtGui.QAction(self.table)
sep.setSeparator(True) sep.setSeparator(True)
self.table.addAction(sep) self.table.addAction(sep)
ccbp_group_menu = QtWidgets.QMenu() ccbp_group_menu = QtWidgets.QMenu(self.table)
actiongroup = QtWidgets.QActionGroup(self.table) actiongroup = QtGui.QActionGroup(self.table)
actiongroup.setExclusive(True) actiongroup.setExclusive(True)
self.ccbp_group_none = QtWidgets.QAction("No policy", self.table) self.ccbp_group_none = QtGui.QAction("No policy", self.table)
self.ccbp_group_none.setCheckable(True) self.ccbp_group_none.setCheckable(True)
self.ccbp_group_none.triggered.connect(lambda: self.set_ccbp("")) self.ccbp_group_none.triggered.connect(lambda: self.set_ccbp(""))
ccbp_group_menu.addAction(self.ccbp_group_none) ccbp_group_menu.addAction(self.ccbp_group_none)
actiongroup.addAction(self.ccbp_group_none) actiongroup.addAction(self.ccbp_group_none)
self.ccbp_group_ignore = QtWidgets.QAction("Ignore requests", self.table) self.ccbp_group_ignore = QtGui.QAction("Ignore requests", self.table)
self.ccbp_group_ignore.setCheckable(True) self.ccbp_group_ignore.setCheckable(True)
self.ccbp_group_ignore.triggered.connect(lambda: self.set_ccbp("ignore")) self.ccbp_group_ignore.triggered.connect(lambda: self.set_ccbp("ignore"))
ccbp_group_menu.addAction(self.ccbp_group_ignore) ccbp_group_menu.addAction(self.ccbp_group_ignore)
actiongroup.addAction(self.ccbp_group_ignore) actiongroup.addAction(self.ccbp_group_ignore)
self.ccbp_group_create = QtWidgets.QAction("Create applets", self.table) self.ccbp_group_create = QtGui.QAction("Create applets", self.table)
self.ccbp_group_create.setCheckable(True) self.ccbp_group_create.setCheckable(True)
self.ccbp_group_create.triggered.connect(lambda: self.set_ccbp("create")) self.ccbp_group_create.triggered.connect(lambda: self.set_ccbp("create"))
ccbp_group_menu.addAction(self.ccbp_group_create) ccbp_group_menu.addAction(self.ccbp_group_create)
actiongroup.addAction(self.ccbp_group_create) actiongroup.addAction(self.ccbp_group_create)
self.ccbp_group_enable = QtWidgets.QAction("Create and enable/disable applets", self.ccbp_group_enable = QtGui.QAction("Create and enable/disable applets",
self.table) self.table)
self.ccbp_group_enable.setCheckable(True) self.ccbp_group_enable.setCheckable(True)
self.ccbp_group_enable.triggered.connect(lambda: self.set_ccbp("enable")) self.ccbp_group_enable.triggered.connect(lambda: self.set_ccbp("enable"))
ccbp_group_menu.addAction(self.ccbp_group_enable) ccbp_group_menu.addAction(self.ccbp_group_enable)
actiongroup.addAction(self.ccbp_group_enable) actiongroup.addAction(self.ccbp_group_enable)
self.ccbp_group_action = QtWidgets.QAction("Group CCB policy", self.table) self.ccbp_group_action = QtGui.QAction("Group CCB policy", self.table)
self.ccbp_group_action.setMenu(ccbp_group_menu) self.ccbp_group_action.setMenu(ccbp_group_menu)
self.table.addAction(self.ccbp_group_action) self.table.addAction(self.ccbp_group_action)
self.table.itemSelectionChanged.connect(self.update_group_ccbp_menu) self.table.itemSelectionChanged.connect(self.update_group_ccbp_menu)
self.update_group_ccbp_menu() self.update_group_ccbp_menu()
ccbp_global_menu = QtWidgets.QMenu() ccbp_global_menu = QtWidgets.QMenu(self.table)
actiongroup = QtWidgets.QActionGroup(self.table) actiongroup = QtGui.QActionGroup(self.table)
actiongroup.setExclusive(True) actiongroup.setExclusive(True)
self.ccbp_global_ignore = QtWidgets.QAction("Ignore requests", self.table) self.ccbp_global_ignore = QtGui.QAction("Ignore requests", self.table)
self.ccbp_global_ignore.setCheckable(True) self.ccbp_global_ignore.setCheckable(True)
ccbp_global_menu.addAction(self.ccbp_global_ignore) ccbp_global_menu.addAction(self.ccbp_global_ignore)
actiongroup.addAction(self.ccbp_global_ignore) actiongroup.addAction(self.ccbp_global_ignore)
self.ccbp_global_create = QtWidgets.QAction("Create applets", self.table) self.ccbp_global_create = QtGui.QAction("Create applets", self.table)
self.ccbp_global_create.setCheckable(True) self.ccbp_global_create.setCheckable(True)
self.ccbp_global_create.setChecked(True) self.ccbp_global_create.setChecked(True)
ccbp_global_menu.addAction(self.ccbp_global_create) ccbp_global_menu.addAction(self.ccbp_global_create)
actiongroup.addAction(self.ccbp_global_create) actiongroup.addAction(self.ccbp_global_create)
self.ccbp_global_enable = QtWidgets.QAction("Create and enable/disable applets", self.ccbp_global_enable = QtGui.QAction("Create and enable/disable applets",
self.table) self.table)
self.ccbp_global_enable.setCheckable(True) self.ccbp_global_enable.setCheckable(True)
ccbp_global_menu.addAction(self.ccbp_global_enable) ccbp_global_menu.addAction(self.ccbp_global_enable)
actiongroup.addAction(self.ccbp_global_enable) actiongroup.addAction(self.ccbp_global_enable)
ccbp_global_action = QtWidgets.QAction("Global CCB policy", self.table) ccbp_global_action = QtGui.QAction("Global CCB policy", self.table)
ccbp_global_action.setMenu(ccbp_global_menu) ccbp_global_action.setMenu(ccbp_global_menu)
self.table.addAction(ccbp_global_action) self.table.addAction(ccbp_global_action)
@ -196,7 +196,7 @@ class AppletsCCBDock(applets.AppletsDock):
logger.debug("Applet %s already exists and no update required", name) logger.debug("Applet %s already exists and no update required", name)
if ccbp == "enable": if ccbp == "enable":
applet.setCheckState(0, QtCore.Qt.Checked) applet.setCheckState(0, QtCore.Qt.CheckState.Checked)
def ccb_disable_applet(self, name, group=None): def ccb_disable_applet(self, name, group=None):
"""Disables an applet. """Disables an applet.
@ -216,7 +216,7 @@ class AppletsCCBDock(applets.AppletsDock):
return return
parent, applet = self.locate_applet(name, group, False) parent, applet = self.locate_applet(name, group, False)
if applet is not None: if applet is not None:
applet.setCheckState(0, QtCore.Qt.Unchecked) applet.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
def ccb_disable_applet_group(self, group): def ccb_disable_applet_group(self, group):
"""Disables all the applets in a group. """Disables all the applets in a group.
@ -246,7 +246,7 @@ class AppletsCCBDock(applets.AppletsDock):
return return
else: else:
wi = nwi wi = nwi
wi.setCheckState(0, QtCore.Qt.Unchecked) wi.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
def ccb_notify(self, message): def ccb_notify(self, message):
try: try:

View File

@ -2,11 +2,11 @@ import asyncio
import logging import logging
import numpy as np import numpy as np
from PyQt5 import QtCore, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from sipyco import pyon from sipyco import pyon
from artiq.tools import scale_from_metadata, short_format, exc_to_warning from artiq.tools import scale_from_metadata, short_format, exc_to_warning
from artiq.gui.tools import LayoutWidget, QRecursiveFilterProxyModel from artiq.gui.tools import LayoutWidget
from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.models import DictSyncTreeSepModel
@ -63,11 +63,11 @@ class CreateEditDialog(QtWidgets.QDialog):
self.cancel = QtWidgets.QPushButton('&Cancel') self.cancel = QtWidgets.QPushButton('&Cancel')
self.buttons = QtWidgets.QDialogButtonBox(self) self.buttons = QtWidgets.QDialogButtonBox(self)
self.buttons.addButton( self.buttons.addButton(
self.ok, QtWidgets.QDialogButtonBox.AcceptRole) self.ok, QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole)
self.buttons.addButton( self.buttons.addButton(
self.cancel, QtWidgets.QDialogButtonBox.RejectRole) self.cancel, QtWidgets.QDialogButtonBox.ButtonRole.RejectRole)
grid.setRowStretch(6, 1) grid.setRowStretch(6, 1)
grid.addWidget(self.buttons, 7, 0, 1, 3, alignment=QtCore.Qt.AlignHCenter) grid.addWidget(self.buttons, 7, 0, 1, 3, alignment=QtCore.Qt.AlignmentFlag.AlignHCenter)
self.buttons.accepted.connect(self.accept) self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject) self.buttons.rejected.connect(self.reject)
@ -125,7 +125,7 @@ class CreateEditDialog(QtWidgets.QDialog):
pyon.encode(result) pyon.encode(result)
except: except:
pixmap = self.style().standardPixmap( pixmap = self.style().standardPixmap(
QtWidgets.QStyle.SP_MessageBoxWarning) QtWidgets.QStyle.StandardPixmap.SP_MessageBoxWarning)
self.data_type.setPixmap(pixmap) self.data_type.setPixmap(pixmap)
self.ok.setEnabled(False) self.ok.setEnabled(False)
else: else:
@ -181,8 +181,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
def __init__(self, dataset_sub, dataset_ctl): def __init__(self, dataset_sub, dataset_ctl):
QtWidgets.QDockWidget.__init__(self, "Datasets") QtWidgets.QDockWidget.__init__(self, "Datasets")
self.setObjectName("Datasets") self.setObjectName("Datasets")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
self.dataset_ctl = dataset_ctl self.dataset_ctl = dataset_ctl
grid = LayoutWidget() grid = LayoutWidget()
@ -194,27 +194,27 @@ class DatasetsDock(QtWidgets.QDockWidget):
grid.addWidget(self.search, 0, 0) grid.addWidget(self.search, 0, 0)
self.table = QtWidgets.QTreeView() self.table = QtWidgets.QTreeView()
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self.table.setSelectionMode( self.table.setSelectionMode(
QtWidgets.QAbstractItemView.SingleSelection) QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
grid.addWidget(self.table, 1, 0) grid.addWidget(self.table, 1, 0)
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
create_action = QtWidgets.QAction("New dataset", self.table) create_action = QtGui.QAction("New dataset", self.table)
create_action.triggered.connect(self.create_clicked) create_action.triggered.connect(self.create_clicked)
create_action.setShortcut("CTRL+N") create_action.setShortcut("CTRL+N")
create_action.setShortcutContext(QtCore.Qt.WidgetShortcut) create_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.table.addAction(create_action) self.table.addAction(create_action)
edit_action = QtWidgets.QAction("Edit dataset", self.table) edit_action = QtGui.QAction("Edit dataset", self.table)
edit_action.triggered.connect(self.edit_clicked) edit_action.triggered.connect(self.edit_clicked)
edit_action.setShortcut("RETURN") edit_action.setShortcut("RETURN")
edit_action.setShortcutContext(QtCore.Qt.WidgetShortcut) edit_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.table.doubleClicked.connect(self.edit_clicked) self.table.doubleClicked.connect(self.edit_clicked)
self.table.addAction(edit_action) self.table.addAction(edit_action)
delete_action = QtWidgets.QAction("Delete dataset", self.table) delete_action = QtGui.QAction("Delete dataset", self.table)
delete_action.triggered.connect(self.delete_clicked) delete_action.triggered.connect(self.delete_clicked)
delete_action.setShortcut("DELETE") delete_action.setShortcut("DELETE")
delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut) delete_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.table.addAction(delete_action) self.table.addAction(delete_action)
self.table_model = Model(dict()) self.table_model = Model(dict())
@ -227,7 +227,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
def set_model(self, model): def set_model(self, model):
self.table_model = model self.table_model = model
self.table_model_filter = QRecursiveFilterProxyModel() self.table_model_filter = QtCore.QSortFilterProxyModel()
self.table_model_filter.setRecursiveFilteringEnabled(True)
self.table_model_filter.setSourceModel(self.table_model) self.table_model_filter.setSourceModel(self.table_model)
self.table.setModel(self.table_model_filter) self.table.setModel(self.table_model_filter)

View File

@ -4,7 +4,7 @@ import os
from functools import partial from functools import partial
from collections import OrderedDict from collections import OrderedDict
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
import h5py import h5py
from sipyco import pyon from sipyco import pyon
@ -44,12 +44,12 @@ class _ArgumentEditor(EntryTreeWidget):
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments") recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
recompute_arguments.setIcon( recompute_arguments.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_BrowserReload)) QtWidgets.QStyle.StandardPixmap.SP_BrowserReload))
recompute_arguments.clicked.connect(dock._recompute_arguments_clicked) recompute_arguments.clicked.connect(dock._recompute_arguments_clicked)
load_hdf5 = QtWidgets.QPushButton("Load HDF5") load_hdf5 = QtWidgets.QPushButton("Load HDF5")
load_hdf5.setIcon(QtWidgets.QApplication.style().standardIcon( load_hdf5.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOpenButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton))
load_hdf5.clicked.connect(dock._load_hdf5_clicked) load_hdf5.clicked.connect(dock._load_hdf5_clicked)
buttons = LayoutWidget() buttons = LayoutWidget()
@ -101,7 +101,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
self.resize(100 * qfm.averageCharWidth(), 30 * qfm.lineSpacing()) self.resize(100 * qfm.averageCharWidth(), 30 * qfm.lineSpacing())
self.setWindowTitle(expurl) self.setWindowTitle(expurl)
self.setWindowIcon(QtWidgets.QApplication.style().standardIcon( self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogContentsView)) QtWidgets.QStyle.StandardPixmap.SP_FileDialogContentsView))
self.layout = QtWidgets.QGridLayout() self.layout = QtWidgets.QGridLayout()
top_widget = QtWidgets.QWidget() top_widget = QtWidgets.QWidget()
@ -237,21 +237,21 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
submit = QtWidgets.QPushButton("Submit") submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon( submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
submit.setToolTip("Schedule the experiment (Ctrl+Return)") submit.setToolTip("Schedule the experiment (Ctrl+Return)")
submit.setShortcut("CTRL+RETURN") submit.setShortcut("CTRL+RETURN")
submit.setSizePolicy(QtWidgets.QSizePolicy.Expanding, submit.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Expanding) QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(submit, 1, 4, 2, 1) self.layout.addWidget(submit, 1, 4, 2, 1)
submit.clicked.connect(self.submit_clicked) submit.clicked.connect(self.submit_clicked)
reqterm = QtWidgets.QPushButton("Terminate instances") reqterm = QtWidgets.QPushButton("Terminate instances")
reqterm.setIcon(QtWidgets.QApplication.style().standardIcon( reqterm.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogCancelButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton))
reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)") reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)")
reqterm.setShortcut("CTRL+BACKSPACE") reqterm.setShortcut("CTRL+BACKSPACE")
reqterm.setSizePolicy(QtWidgets.QSizePolicy.Expanding, reqterm.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Expanding) QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(reqterm, 3, 4) self.layout.addWidget(reqterm, 3, 4)
reqterm.clicked.connect(self.reqterm_clicked) reqterm.clicked.connect(self.reqterm_clicked)
@ -306,7 +306,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
def contextMenuEvent(self, event): def contextMenuEvent(self, event):
menu = QtWidgets.QMenu(self) menu = QtWidgets.QMenu(self)
reset_sched = menu.addAction("Reset scheduler settings") reset_sched = menu.addAction("Reset scheduler settings")
action = menu.exec_(self.mapToGlobal(event.pos())) action = menu.exec(self.mapToGlobal(event.pos()))
if action == reset_sched: if action == reset_sched:
asyncio.ensure_future(self._recompute_sched_options_task()) asyncio.ensure_future(self._recompute_sched_options_task())
@ -423,7 +423,7 @@ class _QuickOpenDialog(QtWidgets.QDialog):
QtWidgets.QDialog.done(self, r) QtWidgets.QDialog.done(self, r)
def _open_experiment(self, exp_name, modifiers): def _open_experiment(self, exp_name, modifiers):
if modifiers & QtCore.Qt.ControlModifier: if modifiers & QtCore.Qt.KeyboardModifier.ControlModifier:
try: try:
self.manager.submit(exp_name) self.manager.submit(exp_name)
except: except:
@ -467,10 +467,10 @@ class ExperimentManager:
self.open_experiments = dict() self.open_experiments = dict()
self.is_quick_open_shown = False self.is_quick_open_shown = False
quick_open_shortcut = QtWidgets.QShortcut( quick_open_shortcut = QtGui.QShortcut(
QtCore.Qt.CTRL + QtCore.Qt.Key_P, QtGui.QKeySequence("Ctrl+P"),
main_window) main_window)
quick_open_shortcut.setContext(QtCore.Qt.ApplicationShortcut) quick_open_shortcut.setContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)
quick_open_shortcut.activated.connect(self.show_quick_open) quick_open_shortcut.activated.connect(self.show_quick_open)
def set_dataset_model(self, model): def set_dataset_model(self, model):
@ -589,7 +589,7 @@ class ExperimentManager:
del self.submission_arguments[expurl] del self.submission_arguments[expurl]
dock = _ExperimentDock(self, expurl) dock = _ExperimentDock(self, expurl)
self.open_experiments[expurl] = dock self.open_experiments[expurl] = dock
dock.setAttribute(QtCore.Qt.WA_DeleteOnClose) dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
self.main_window.centralWidget().addSubWindow(dock) self.main_window.centralWidget().addSubWindow(dock)
dock.show() dock.show()
dock.sigClosed.connect(partial(self.on_dock_closed, expurl)) dock.sigClosed.connect(partial(self.on_dock_closed, expurl))

View File

@ -3,7 +3,7 @@ import logging
import re import re
from functools import partial from functools import partial
from PyQt5 import QtCore, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from artiq.gui.tools import LayoutWidget from artiq.gui.tools import LayoutWidget
from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.models import DictSyncTreeSepModel
@ -37,7 +37,8 @@ class _OpenFileDialog(QtWidgets.QDialog):
self.file_list.doubleClicked.connect(self.accept) self.file_list.doubleClicked.connect(self.accept)
buttons = QtWidgets.QDialogButtonBox( buttons = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel) QtWidgets.QDialogButtonBox.StandardButton.Ok |
QtWidgets.QDialogButtonBox.StandardButton.Cancel)
grid.addWidget(buttons, 2, 0, 1, 2) grid.addWidget(buttons, 2, 0, 1, 2)
buttons.accepted.connect(self.accept) buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject) buttons.rejected.connect(self.reject)
@ -52,7 +53,7 @@ class _OpenFileDialog(QtWidgets.QDialog):
item = QtWidgets.QListWidgetItem() item = QtWidgets.QListWidgetItem()
item.setText("..") item.setText("..")
item.setIcon(QtWidgets.QApplication.style().standardIcon( item.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogToParent)) QtWidgets.QStyle.StandardPixmap.SP_FileDialogToParent))
self.file_list.addItem(item) self.file_list.addItem(item)
try: try:
@ -64,9 +65,9 @@ class _OpenFileDialog(QtWidgets.QDialog):
return return
for name in sorted(contents, key=lambda x: (x[-1] not in "\\/", x)): for name in sorted(contents, key=lambda x: (x[-1] not in "\\/", x)):
if name[-1] in "\\/": if name[-1] in "\\/":
icon = QtWidgets.QStyle.SP_DirIcon icon = QtWidgets.QStyle.StandardPixmap.SP_DirIcon
else: else:
icon = QtWidgets.QStyle.SP_FileIcon icon = QtWidgets.QStyle.StandardPixmap.SP_FileIcon
if name[-3:] != ".py": if name[-3:] != ".py":
continue continue
item = QtWidgets.QListWidgetItem() item = QtWidgets.QListWidgetItem()
@ -163,8 +164,8 @@ class ExplorerDock(QtWidgets.QDockWidget):
schedule_ctl, experiment_db_ctl, device_db_ctl): schedule_ctl, experiment_db_ctl, device_db_ctl):
QtWidgets.QDockWidget.__init__(self, "Explorer") QtWidgets.QDockWidget.__init__(self, "Explorer")
self.setObjectName("Explorer") self.setObjectName("Explorer")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
top_widget = LayoutWidget() top_widget = LayoutWidget()
self.setWidget(top_widget) self.setWidget(top_widget)
@ -175,7 +176,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
top_widget.addWidget(QtWidgets.QLabel("Revision:"), 0, 0) top_widget.addWidget(QtWidgets.QLabel("Revision:"), 0, 0)
self.revision = QtWidgets.QLabel() self.revision = QtWidgets.QLabel()
self.revision.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) self.revision.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse)
top_widget.addWidget(self.revision, 0, 1) top_widget.addWidget(self.revision, 0, 1)
self.stack = QtWidgets.QStackedWidget() self.stack = QtWidgets.QStackedWidget()
@ -187,14 +188,14 @@ class ExplorerDock(QtWidgets.QDockWidget):
self.el = QtWidgets.QTreeView() self.el = QtWidgets.QTreeView()
self.el.setHeaderHidden(True) self.el.setHeaderHidden(True)
self.el.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems) self.el.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems)
self.el.doubleClicked.connect( self.el.doubleClicked.connect(
partial(self.expname_action, "open_experiment")) partial(self.expname_action, "open_experiment"))
self.el_buttons.addWidget(self.el, 0, 0, colspan=2) self.el_buttons.addWidget(self.el, 0, 0, colspan=2)
open = QtWidgets.QPushButton("Open") open = QtWidgets.QPushButton("Open")
open.setIcon(QtWidgets.QApplication.style().standardIcon( open.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOpenButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton))
open.setToolTip("Open the selected experiment (Return)") open.setToolTip("Open the selected experiment (Return)")
self.el_buttons.addWidget(open, 1, 0) self.el_buttons.addWidget(open, 1, 0)
open.clicked.connect( open.clicked.connect(
@ -202,7 +203,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
submit = QtWidgets.QPushButton("Submit") submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon( submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
submit.setToolTip("Schedule the selected experiment (Ctrl+Return)") submit.setToolTip("Schedule the selected experiment (Ctrl+Return)")
self.el_buttons.addWidget(submit, 1, 1) self.el_buttons.addWidget(submit, 1, 1)
submit.clicked.connect( submit.clicked.connect(
@ -211,41 +212,41 @@ class ExplorerDock(QtWidgets.QDockWidget):
self.explist_model = Model(dict()) self.explist_model = Model(dict())
explist_sub.add_setmodel_callback(self.set_model) explist_sub.add_setmodel_callback(self.set_model)
self.el.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.el.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
open_action = QtWidgets.QAction("Open", self.el) open_action = QtGui.QAction("Open", self.el)
open_action.triggered.connect( open_action.triggered.connect(
partial(self.expname_action, "open_experiment")) partial(self.expname_action, "open_experiment"))
open_action.setShortcut("RETURN") open_action.setShortcut("RETURN")
open_action.setShortcutContext(QtCore.Qt.WidgetShortcut) open_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.el.addAction(open_action) self.el.addAction(open_action)
submit_action = QtWidgets.QAction("Submit", self.el) submit_action = QtGui.QAction("Submit", self.el)
submit_action.triggered.connect( submit_action.triggered.connect(
partial(self.expname_action, "submit")) partial(self.expname_action, "submit"))
submit_action.setShortcut("CTRL+RETURN") submit_action.setShortcut("CTRL+RETURN")
submit_action.setShortcutContext(QtCore.Qt.WidgetShortcut) submit_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.el.addAction(submit_action) self.el.addAction(submit_action)
reqterm_action = QtWidgets.QAction("Request termination of instances", self.el) reqterm_action = QtGui.QAction("Request termination of instances", self.el)
reqterm_action.triggered.connect( reqterm_action.triggered.connect(
partial(self.expname_action, "request_inst_term")) partial(self.expname_action, "request_inst_term"))
reqterm_action.setShortcut("CTRL+BACKSPACE") reqterm_action.setShortcut("CTRL+BACKSPACE")
reqterm_action.setShortcutContext(QtCore.Qt.WidgetShortcut) reqterm_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.el.addAction(reqterm_action) self.el.addAction(reqterm_action)
set_shortcut_menu = QtWidgets.QMenu() set_shortcut_menu = QtWidgets.QMenu(self.el)
for i in range(12): for i in range(12):
action = QtWidgets.QAction("F" + str(i + 1), self.el) action = QtGui.QAction("F" + str(i+1), self.el)
action.triggered.connect(partial(self.set_shortcut, i)) action.triggered.connect(partial(self.set_shortcut, i))
set_shortcut_menu.addAction(action) set_shortcut_menu.addAction(action)
set_shortcut_action = QtWidgets.QAction("Set shortcut", self.el) set_shortcut_action = QtGui.QAction("Set shortcut", self.el)
set_shortcut_action.setMenu(set_shortcut_menu) set_shortcut_action.setMenu(set_shortcut_menu)
self.el.addAction(set_shortcut_action) self.el.addAction(set_shortcut_action)
sep = QtWidgets.QAction(self.el) sep = QtGui.QAction(self.el)
sep.setSeparator(True) sep.setSeparator(True)
self.el.addAction(sep) self.el.addAction(sep)
scan_repository_action = QtWidgets.QAction("Scan repository HEAD", scan_repository_action = QtGui.QAction("Scan repository HEAD",
self.el) self.el)
def scan_repository(): def scan_repository():
@ -253,15 +254,14 @@ class ExplorerDock(QtWidgets.QDockWidget):
scan_repository_action.triggered.connect(scan_repository) scan_repository_action.triggered.connect(scan_repository)
self.el.addAction(scan_repository_action) self.el.addAction(scan_repository_action)
scan_ddb_action = QtWidgets.QAction("Scan device database", self.el) scan_ddb_action = QtGui.QAction("Scan device database", self.el)
def scan_ddb(): def scan_ddb():
asyncio.ensure_future(device_db_ctl.scan()) asyncio.ensure_future(device_db_ctl.scan())
scan_ddb_action.triggered.connect(scan_ddb) scan_ddb_action.triggered.connect(scan_ddb)
self.el.addAction(scan_ddb_action) self.el.addAction(scan_ddb_action)
self.current_directory = "" self.current_directory = ""
open_file_action = QtWidgets.QAction("Open file outside repository", open_file_action = QtGui.QAction("Open file outside repository",
self.el) self.el)
open_file_action.triggered.connect( open_file_action.triggered.connect(
lambda: _OpenFileDialog(self, self.exp_manager, lambda: _OpenFileDialog(self, self.exp_manager,

View File

@ -1,7 +1,7 @@
import logging import logging
import asyncio import asyncio
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt6 import QtCore, QtWidgets, QtGui
from artiq.gui.models import DictSyncModel from artiq.gui.models import DictSyncModel
from artiq.gui.entries import EntryTreeWidget, procdesc_to_entry from artiq.gui.entries import EntryTreeWidget, procdesc_to_entry
@ -44,11 +44,11 @@ class _InteractiveArgsRequest(EntryTreeWidget):
self.quickStyleClicked.connect(self.supply) self.quickStyleClicked.connect(self.supply)
cancel_btn = QtWidgets.QPushButton("Cancel") cancel_btn = QtWidgets.QPushButton("Cancel")
cancel_btn.setIcon(QtWidgets.QApplication.style().standardIcon( cancel_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogCancelButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton))
cancel_btn.clicked.connect(self.cancel) cancel_btn.clicked.connect(self.cancel)
supply_btn = QtWidgets.QPushButton("Supply") supply_btn = QtWidgets.QPushButton("Supply")
supply_btn.setIcon(QtWidgets.QApplication.style().standardIcon( supply_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
supply_btn.clicked.connect(self.supply) supply_btn.clicked.connect(self.supply)
buttons = LayoutWidget() buttons = LayoutWidget()
buttons.addWidget(cancel_btn, 1, 1) buttons.addWidget(cancel_btn, 1, 1)
@ -78,7 +78,7 @@ class _InteractiveArgsView(QtWidgets.QStackedWidget):
QtWidgets.QStackedWidget.__init__(self) QtWidgets.QStackedWidget.__init__(self)
self.tabs = QtWidgets.QTabWidget() self.tabs = QtWidgets.QTabWidget()
self.default_label = QtWidgets.QLabel("No pending interactive arguments requests.") self.default_label = QtWidgets.QLabel("No pending interactive arguments requests.")
self.default_label.setAlignment(QtCore.Qt.AlignCenter) self.default_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
font = QtGui.QFont(self.default_label.font()) font = QtGui.QFont(self.default_label.font())
font.setItalic(True) font.setItalic(True)
self.default_label.setFont(font) self.default_label.setFont(font)
@ -99,9 +99,12 @@ class _InteractiveArgsView(QtWidgets.QStackedWidget):
self._insert_widget(i) self._insert_widget(i)
def _insert_widget(self, row): def _insert_widget(self, row):
rid = self.model.data(self.model.index(row, 0), QtCore.Qt.DisplayRole) rid = self.model.data(self.model.index(row, 0),
title = self.model.data(self.model.index(row, 1), QtCore.Qt.DisplayRole) QtCore.Qt.ItemDataRole.DisplayRole)
arglist_desc = self.model.data(self.model.index(row, 2), QtCore.Qt.DisplayRole) title = self.model.data(self.model.index(row, 1),
QtCore.Qt.ItemDataRole.DisplayRole)
arglist_desc = self.model.data(self.model.index(row, 2),
QtCore.Qt.ItemDataRole.DisplayRole)
inter_args_request = _InteractiveArgsRequest(rid, arglist_desc) inter_args_request = _InteractiveArgsRequest(rid, arglist_desc)
inter_args_request.supplied.connect(self.supplied) inter_args_request.supplied.connect(self.supplied)
inter_args_request.cancelled.connect(self.cancelled) inter_args_request.cancelled.connect(self.cancelled)
@ -126,7 +129,7 @@ class InteractiveArgsDock(QtWidgets.QDockWidget):
QtWidgets.QDockWidget.__init__(self, "Interactive Args") QtWidgets.QDockWidget.__init__(self, "Interactive Args")
self.setObjectName("Interactive Args") self.setObjectName("Interactive Args")
self.setFeatures( self.setFeatures(
QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable) self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable)
self.interactive_args_rpc = interactive_args_rpc self.interactive_args_rpc = interactive_args_rpc
self.request_view = _InteractiveArgsView() self.request_view = _InteractiveArgsView()
self.request_view.supplied.connect(self.supply) self.request_view.supplied.connect(self.supply)

View File

@ -4,13 +4,14 @@ import textwrap
from collections import namedtuple from collections import namedtuple
from functools import partial from functools import partial
from PyQt5 import QtCore, QtWidgets from PyQt6 import QtCore, QtWidgets, QtGui
from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe
from artiq.coredevice.ad9912_reg import AD9912_SER_CONF from artiq.coredevice.ad9912_reg import AD9912_SER_CONF
from artiq.gui.tools import LayoutWidget, QDockWidgetCloseDetect, DoubleClickLineEdit from artiq.gui.tools import LayoutWidget, QDockWidgetCloseDetect, DoubleClickLineEdit
from artiq.gui.dndwidgets import VDragScrollArea, DragDropFlowLayoutWidget from artiq.gui.dndwidgets import VDragScrollArea, DragDropFlowLayoutWidget
from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.models import DictSyncTreeSepModel
from artiq.tools import elide
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -22,39 +23,45 @@ class _CancellableLineEdit(QtWidgets.QLineEdit):
def keyPressEvent(self, event): def keyPressEvent(self, event):
key = event.key() key = event.key()
if key == QtCore.Qt.Key_Escape: if key == QtCore.Qt.Key.Key_Escape:
self.esc_cb(event) self.esc_cb(event)
QtWidgets.QLineEdit.keyPressEvent(self, event) QtWidgets.QLineEdit.keyPressEvent(self, event)
class _TTLWidget(QtWidgets.QFrame): class _MoninjWidget(QtWidgets.QFrame):
def __init__(self, dm, channel, force_out, title): def __init__(self, title):
QtWidgets.QFrame.__init__(self) QtWidgets.QFrame.__init__(self)
self.setFrameShape(QtWidgets.QFrame.Shape.Box)
self.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.setFixedHeight(100)
self.setFixedWidth(150)
self.grid = QtWidgets.QGridLayout()
self.grid.setContentsMargins(0, 0, 0, 0)
self.grid.setHorizontalSpacing(0)
self.grid.setVerticalSpacing(0)
self.setLayout(self.grid)
title = elide(title, 20)
label = QtWidgets.QLabel(title)
label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
label.setSizePolicy(QtWidgets.QSizePolicy.Policy.Ignored,
QtWidgets.QSizePolicy.Policy.Preferred)
self.grid.addWidget(label, 1, 1)
class _TTLWidget(_MoninjWidget):
def __init__(self, dm, channel, force_out, title):
_MoninjWidget.__init__(self, title)
self.channel = channel self.channel = channel
self.set_mode = dm.ttl_set_mode self.set_mode = dm.ttl_set_mode
self.force_out = force_out self.force_out = force_out
self.title = title self.title = title
self.setFrameShape(QtWidgets.QFrame.Box)
self.setFrameShadow(QtWidgets.QFrame.Raised)
grid = QtWidgets.QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(0)
self.setLayout(grid)
label = QtWidgets.QLabel(title)
label.setAlignment(QtCore.Qt.AlignCenter)
label.setSizePolicy(QtWidgets.QSizePolicy.Ignored,
QtWidgets.QSizePolicy.Preferred)
grid.addWidget(label, 1, 1)
self.stack = QtWidgets.QStackedWidget() self.stack = QtWidgets.QStackedWidget()
grid.addWidget(self.stack, 2, 1) self.grid.addWidget(self.stack, 2, 1)
self.direction = QtWidgets.QLabel() self.direction = QtWidgets.QLabel()
self.direction.setAlignment(QtCore.Qt.AlignCenter) self.direction.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.stack.addWidget(self.direction) self.stack.addWidget(self.direction)
grid_cb = LayoutWidget() grid_cb = LayoutWidget()
@ -74,13 +81,13 @@ class _TTLWidget(QtWidgets.QFrame):
self.stack.addWidget(grid_cb) self.stack.addWidget(grid_cb)
self.value = QtWidgets.QLabel() self.value = QtWidgets.QLabel()
self.value.setAlignment(QtCore.Qt.AlignCenter) self.value.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
grid.addWidget(self.value, 3, 1) self.grid.addWidget(self.value, 3, 1)
grid.setRowStretch(1, 1) self.grid.setRowStretch(1, 1)
grid.setRowStretch(2, 0) self.grid.setRowStretch(2, 0)
grid.setRowStretch(3, 0) self.grid.setRowStretch(3, 0)
grid.setRowStretch(4, 1) self.grid.setRowStretch(4, 1)
self.programmatic_change = False self.programmatic_change = False
self.override.clicked.connect(self.override_toggled) self.override.clicked.connect(self.override_toggled)
@ -186,7 +193,7 @@ class _DDSModel:
return ftw / self.ftw_per_hz return ftw / self.ftw_per_hz
class _DDSWidget(QtWidgets.QFrame): class _DDSWidget(_MoninjWidget):
def __init__(self, dm, title, bus_channel, channel, def __init__(self, dm, title, bus_channel, channel,
dds_type, ref_clk, cpld=None, pll=1, clk_div=0): dds_type, ref_clk, cpld=None, pll=1, clk_div=0):
self.dm = dm self.dm = dm
@ -197,19 +204,7 @@ class _DDSWidget(QtWidgets.QFrame):
self.dds_model = _DDSModel(dds_type, ref_clk, cpld, pll, clk_div) self.dds_model = _DDSModel(dds_type, ref_clk, cpld, pll, clk_div)
self.title = title self.title = title
QtWidgets.QFrame.__init__(self) _MoninjWidget.__init__(self, title)
self.setFrameShape(QtWidgets.QFrame.Box)
self.setFrameShadow(QtWidgets.QFrame.Raised)
grid = QtWidgets.QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(0)
self.setLayout(grid)
label = QtWidgets.QLabel(title)
label.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(label, 1, 1)
# FREQ DATA/EDIT FIELD # FREQ DATA/EDIT FIELD
self.data_stack = QtWidgets.QStackedWidget() self.data_stack = QtWidgets.QStackedWidget()
@ -221,11 +216,11 @@ class _DDSWidget(QtWidgets.QFrame):
grid_disp.layout.setVerticalSpacing(0) grid_disp.layout.setVerticalSpacing(0)
self.value_label = QtWidgets.QLabel() self.value_label = QtWidgets.QLabel()
self.value_label.setAlignment(QtCore.Qt.AlignCenter) self.value_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
grid_disp.addWidget(self.value_label, 0, 1, 1, 2) grid_disp.addWidget(self.value_label, 0, 1, 1, 2)
unit = QtWidgets.QLabel("MHz") unit = QtWidgets.QLabel("MHz")
unit.setAlignment(QtCore.Qt.AlignCenter) unit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
grid_disp.addWidget(unit, 0, 3, 1, 1) grid_disp.addWidget(unit, 0, 3, 1, 1)
self.data_stack.addWidget(grid_disp) self.data_stack.addWidget(grid_disp)
@ -237,14 +232,14 @@ class _DDSWidget(QtWidgets.QFrame):
grid_edit.layout.setVerticalSpacing(0) grid_edit.layout.setVerticalSpacing(0)
self.value_edit = _CancellableLineEdit(self) self.value_edit = _CancellableLineEdit(self)
self.value_edit.setAlignment(QtCore.Qt.AlignRight) self.value_edit.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
grid_edit.addWidget(self.value_edit, 0, 1, 1, 2) grid_edit.addWidget(self.value_edit, 0, 1, 1, 2)
unit = QtWidgets.QLabel("MHz") unit = QtWidgets.QLabel("MHz")
unit.setAlignment(QtCore.Qt.AlignCenter) unit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
grid_edit.addWidget(unit, 0, 3, 1, 1) grid_edit.addWidget(unit, 0, 3, 1, 1)
self.data_stack.addWidget(grid_edit) self.data_stack.addWidget(grid_edit)
grid.addWidget(self.data_stack, 2, 1) self.grid.addWidget(self.data_stack, 2, 1)
# BUTTONS # BUTTONS
self.button_stack = QtWidgets.QStackedWidget() self.button_stack = QtWidgets.QStackedWidget()
@ -277,11 +272,11 @@ class _DDSWidget(QtWidgets.QFrame):
cancel.setToolTip("Cancel changes") cancel.setToolTip("Cancel changes")
apply_grid.addWidget(cancel, 0, 2, 1, 1) apply_grid.addWidget(cancel, 0, 2, 1, 1)
self.button_stack.addWidget(apply_grid) self.button_stack.addWidget(apply_grid)
grid.addWidget(self.button_stack, 3, 1) self.grid.addWidget(self.button_stack, 3, 1)
grid.setRowStretch(1, 1) self.grid.setRowStretch(1, 1)
grid.setRowStretch(2, 1) self.grid.setRowStretch(2, 1)
grid.setRowStretch(3, 1) self.grid.setRowStretch(3, 1)
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)
@ -332,39 +327,31 @@ class _DDSWidget(QtWidgets.QFrame):
return "dds/{}".format(self.title) return "dds/{}".format(self.title)
class _DACWidget(QtWidgets.QFrame): class _DACWidget(_MoninjWidget):
def __init__(self, dm, spi_channel, channel, title): def __init__(self, dm, spi_channel, channel, title, vref, offset_dacs):
QtWidgets.QFrame.__init__(self) _MoninjWidget.__init__(self, "{}_ch{}".format(title, channel))
self.spi_channel = spi_channel self.spi_channel = spi_channel
self.channel = channel self.channel = channel
self.cur_value = 0 self.cur_value = 0x8000
self.title = title self.title = title
self.vref = vref
self.setFrameShape(QtWidgets.QFrame.Box) self.offset_dacs = offset_dacs
self.setFrameShadow(QtWidgets.QFrame.Raised)
grid = QtWidgets.QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(0)
self.setLayout(grid)
label = QtWidgets.QLabel("{} ch{}".format(title, channel))
label.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(label, 1, 1)
self.value = QtWidgets.QLabel() self.value = QtWidgets.QLabel()
self.value.setAlignment(QtCore.Qt.AlignCenter) self.value.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop)
grid.addWidget(self.value, 2, 1, 6, 1) self.grid.addWidget(self.value, 2, 1, 6, 1)
grid.setRowStretch(1, 1) self.grid.setRowStretch(1, 1)
grid.setRowStretch(2, 0) self.grid.setRowStretch(2, 1)
grid.setRowStretch(3, 1)
self.refresh_display() self.refresh_display()
def mu_to_voltage(self, code):
return ((code - self.offset_dacs * 0x4) / (1 << 16)) * (4. * self.vref)
def refresh_display(self): def refresh_display(self):
self.value.setText("<font size=\"4\">{:.3f}</font><font size=\"2\"> %</font>" self.value.setText("<font size=\"4\">{:+.3f} V</font>"
.format(self.cur_value * 100 / 2**16)) .format(self.mu_to_voltage(self.cur_value)))
def sort_key(self): def sort_key(self):
return (2, self.spi_channel, self.channel) return (2, self.spi_channel, self.channel)
@ -373,7 +360,7 @@ class _DACWidget(QtWidgets.QFrame):
return (self.title, self.channel) return (self.title, self.channel)
def to_model_path(self): def to_model_path(self):
return "dac/{} ch{}".format(self.title, self.channel) return "dac/{}_ch{}".format(self.title, self.channel)
_WidgetDesc = namedtuple("_WidgetDesc", "uid comment cls arguments") _WidgetDesc = namedtuple("_WidgetDesc", "uid comment cls arguments")
@ -391,8 +378,6 @@ def setup_from_ddb(ddb):
comment = v.get("comment") comment = v.get("comment")
if v["type"] == "local": if v["type"] == "local":
if v["module"] == "artiq.coredevice.ttl": if v["module"] == "artiq.coredevice.ttl":
if "ttl_urukul" in k:
continue
channel = v["arguments"]["channel"] channel = v["arguments"]["channel"]
force_out = v["class"] == "TTLOut" force_out = v["class"] == "TTLOut"
widget = _WidgetDesc(k, comment, _TTLWidget, (channel, force_out, k)) widget = _WidgetDesc(k, comment, _TTLWidget, (channel, force_out, k))
@ -426,9 +411,17 @@ def setup_from_ddb(ddb):
while isinstance(spi_device, str): while isinstance(spi_device, str):
spi_device = ddb[spi_device] spi_device = ddb[spi_device]
spi_channel = spi_device["arguments"]["channel"] spi_channel = spi_device["arguments"]["channel"]
vref = v["arguments"].get("vref", 5.)
offset_dacs = v["arguments"].get("offset_dacs", 8192)
for channel in range(32): for channel in range(32):
widget = _WidgetDesc((k, channel), comment, _DACWidget, widget = _WidgetDesc((k, channel), comment, _DACWidget,
(spi_channel, channel, k)) (spi_channel, channel, k, vref, offset_dacs))
description.add(widget)
elif (v["module"] == "artiq.coredevice.fastino" and v["class"] == "Fastino"):
bus_channel = v["arguments"]["channel"]
for channel in range(0, 32):
widget = _WidgetDesc((k, channel), comment, _DACWidget,
(bus_channel, channel, k, 5, 8192))
description.add(widget) description.add(widget)
elif v["type"] == "controller" and k == "core_moninj": elif v["type"] == "controller" and k == "core_moninj":
mi_addr = v["host"] mi_addr = v["host"]
@ -768,7 +761,7 @@ class Model(DictSyncTreeSepModel):
class _AddChannelDialog(QtWidgets.QDialog): class _AddChannelDialog(QtWidgets.QDialog):
def __init__(self, parent, model): def __init__(self, parent, model):
QtWidgets.QDialog.__init__(self, parent=parent) QtWidgets.QDialog.__init__(self, parent=parent)
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
self.setWindowTitle("Add channels") self.setWindowTitle("Add channels")
layout = QtWidgets.QVBoxLayout() layout = QtWidgets.QVBoxLayout()
@ -778,14 +771,15 @@ class _AddChannelDialog(QtWidgets.QDialog):
self._tree_view = QtWidgets.QTreeView() self._tree_view = QtWidgets.QTreeView()
self._tree_view.setHeaderHidden(True) self._tree_view.setHeaderHidden(True)
self._tree_view.setSelectionBehavior( self._tree_view.setSelectionBehavior(
QtWidgets.QAbstractItemView.SelectItems) QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems)
self._tree_view.setSelectionMode( self._tree_view.setSelectionMode(
QtWidgets.QAbstractItemView.ExtendedSelection) QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
self._tree_view.setModel(self._model) self._tree_view.setModel(self._model)
layout.addWidget(self._tree_view) layout.addWidget(self._tree_view)
self._button_box = QtWidgets.QDialogButtonBox( self._button_box = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel QtWidgets.QDialogButtonBox.StandardButton.Ok | \
QtWidgets.QDialogButtonBox.StandardButton.Cancel
) )
self._button_box.setCenterButtons(True) self._button_box.setCenterButtons(True)
self._button_box.accepted.connect(self.add_channels) self._button_box.accepted.connect(self.add_channels)
@ -807,8 +801,8 @@ class _MonInjDock(QDockWidgetCloseDetect):
def __init__(self, name, manager): def __init__(self, name, manager):
QtWidgets.QDockWidget.__init__(self, "MonInj") QtWidgets.QDockWidget.__init__(self, "MonInj")
self.setObjectName(name) self.setObjectName(name)
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | self.setFeatures(self.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable) self.DockWidgetFeature.DockWidgetFloatable)
grid = LayoutWidget() grid = LayoutWidget()
self.setWidget(grid) self.setWidget(grid)
self.manager = manager self.manager = manager
@ -817,7 +811,7 @@ class _MonInjDock(QDockWidgetCloseDetect):
newdock = QtWidgets.QToolButton() newdock = QtWidgets.QToolButton()
newdock.setToolTip("Create new moninj dock") newdock.setToolTip("Create new moninj dock")
newdock.setIcon(QtWidgets.QApplication.style().standardIcon( newdock.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogNewFolder)) QtWidgets.QStyle.StandardPixmap.SP_FileDialogNewFolder))
newdock.clicked.connect(lambda: self.manager.create_new_dock()) newdock.clicked.connect(lambda: self.manager.create_new_dock())
grid.addWidget(newdock, 0, 0) grid.addWidget(newdock, 0, 0)
@ -828,7 +822,7 @@ class _MonInjDock(QDockWidgetCloseDetect):
dialog_btn.setToolTip("Add channels") dialog_btn.setToolTip("Add channels")
dialog_btn.setIcon( dialog_btn.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogListView)) QtWidgets.QStyle.StandardPixmap.SP_FileDialogListView))
dialog_btn.clicked.connect(self.channel_dialog.open) dialog_btn.clicked.connect(self.channel_dialog.open)
grid.addWidget(dialog_btn, 0, 1) grid.addWidget(dialog_btn, 0, 1)
@ -841,7 +835,8 @@ class _MonInjDock(QDockWidgetCloseDetect):
self.flow = DragDropFlowLayoutWidget() self.flow = DragDropFlowLayoutWidget()
scroll_area.setWidgetResizable(True) scroll_area.setWidgetResizable(True)
scroll_area.setWidget(self.flow) scroll_area.setWidget(self.flow)
self.flow.setContextMenuPolicy(QtCore.Qt.CustomContextMenu) self.flow.setContextMenuPolicy(
QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.flow.customContextMenuRequested.connect(self.custom_context_menu) self.flow.customContextMenuRequested.connect(self.custom_context_menu)
def custom_context_menu(self, pos): def custom_context_menu(self, pos):
@ -849,7 +844,7 @@ class _MonInjDock(QDockWidgetCloseDetect):
if index == -1: if index == -1:
return return
menu = QtWidgets.QMenu() menu = QtWidgets.QMenu()
delete_action = QtWidgets.QAction("Delete widget", menu) delete_action = QtGui.QAction("Delete widget", menu)
delete_action.triggered.connect(partial(self.delete_widget, index)) delete_action.triggered.connect(partial(self.delete_widget, index))
menu.addAction(delete_action) menu.addAction(delete_action)
menu.exec_(self.flow.mapToGlobal(pos)) menu.exec_(self.flow.mapToGlobal(pos))
@ -861,9 +856,10 @@ class _MonInjDock(QDockWidgetCloseDetect):
def delete_widget(self, index, checked): def delete_widget(self, index, checked):
widget = self.flow.itemAt(index).widget() widget = self.flow.itemAt(index).widget()
widget.hide()
self.manager.dm.setup_monitoring(False, widget) self.manager.dm.setup_monitoring(False, widget)
self.flow.layout.takeAt(index) self.flow.layout.takeAt(index)
widget.setParent(self.manager.main_window)
widget.hide()
def add_channels(self): def add_channels(self):
channels = self.channel_dialog.channels channels = self.channel_dialog.channels
@ -871,9 +867,9 @@ class _MonInjDock(QDockWidgetCloseDetect):
def layout_widgets(self, widgets): def layout_widgets(self, widgets):
for widget in sorted(widgets, key=lambda w: w.sort_key()): for widget in sorted(widgets, key=lambda w: w.sort_key()):
widget.show()
self.manager.dm.setup_monitoring(True, widget) self.manager.dm.setup_monitoring(True, widget)
self.flow.addWidget(widget) self.flow.addWidget(widget)
widget.show()
def restore_widgets(self): def restore_widgets(self):
if self.widget_uids is not None: if self.widget_uids is not None:
@ -937,7 +933,7 @@ class MonInj:
dock = _MonInjDock(name, self) dock = _MonInjDock(name, self)
self.docks[name] = dock self.docks[name] = dock
if add_to_area: if add_to_area:
self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) self.main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock)
dock.setFloating(True) dock.setFloating(True)
dock.sigClosed.connect(partial(self.on_dock_closed, name)) dock.sigClosed.connect(partial(self.on_dock_closed, name))
self.update_closable() self.update_closable()
@ -948,13 +944,13 @@ class MonInj:
del self.docks[name] del self.docks[name]
self.update_closable() self.update_closable()
dock.delete_all_widgets() dock.delete_all_widgets()
dock.hide() # dock may be parent, only delete on exit dock.deleteLater()
def update_closable(self): def update_closable(self):
flags = (QtWidgets.QDockWidget.DockWidgetMovable | flags = (QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
if len(self.docks) > 1: if len(self.docks) > 1:
flags |= QtWidgets.QDockWidget.DockWidgetClosable flags |= QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetClosable
for dock in self.docks.values(): for dock in self.docks.values():
dock.setFeatures(flags) dock.setFeatures(flags)
@ -974,7 +970,8 @@ class MonInj:
dock = _MonInjDock(name, self) dock = _MonInjDock(name, self)
self.docks[name] = dock self.docks[name] = dock
dock.restore_state(dock_state) dock.restore_state(dock_state)
self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) self.main_window.addDockWidget(
QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock)
dock.sigClosed.connect(partial(self.on_dock_closed, name)) dock.sigClosed.connect(partial(self.on_dock_closed, name))
self.update_closable() self.update_closable()

View File

@ -3,7 +3,7 @@ import time
from functools import partial from functools import partial
import logging import logging
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt6 import QtCore, QtWidgets, QtGui
from artiq.gui.models import DictSyncModel from artiq.gui.models import DictSyncModel
from artiq.tools import elide from artiq.tools import elide
@ -61,31 +61,31 @@ class ScheduleDock(QtWidgets.QDockWidget):
def __init__(self, schedule_ctl, schedule_sub): def __init__(self, schedule_ctl, schedule_sub):
QtWidgets.QDockWidget.__init__(self, "Schedule") QtWidgets.QDockWidget.__init__(self, "Schedule")
self.setObjectName("Schedule") self.setObjectName("Schedule")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
self.schedule_ctl = schedule_ctl self.schedule_ctl = schedule_ctl
self.table = QtWidgets.QTableView() self.table = QtWidgets.QTableView()
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) self.table.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
self.table.verticalHeader().setSectionResizeMode( self.table.verticalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.ResizeToContents) QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
self.table.verticalHeader().hide() self.table.verticalHeader().hide()
self.setWidget(self.table) self.setWidget(self.table)
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
request_termination_action = QtWidgets.QAction("Request termination", self.table) request_termination_action = QtGui.QAction("Request termination", self.table)
request_termination_action.triggered.connect(partial(self.delete_clicked, True)) request_termination_action.triggered.connect(partial(self.delete_clicked, True))
request_termination_action.setShortcut("DELETE") request_termination_action.setShortcut("DELETE")
request_termination_action.setShortcutContext(QtCore.Qt.WidgetShortcut) request_termination_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.table.addAction(request_termination_action) self.table.addAction(request_termination_action)
delete_action = QtWidgets.QAction("Delete", self.table) delete_action = QtGui.QAction("Delete", self.table)
delete_action.triggered.connect(partial(self.delete_clicked, False)) delete_action.triggered.connect(partial(self.delete_clicked, False))
delete_action.setShortcut("SHIFT+DELETE") delete_action.setShortcut("SHIFT+DELETE")
delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut) delete_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.table.addAction(delete_action) self.table.addAction(delete_action)
terminate_pipeline = QtWidgets.QAction( terminate_pipeline = QtGui.QAction(
"Gracefully terminate all in pipeline", self.table) "Gracefully terminate all in pipeline", self.table)
terminate_pipeline.triggered.connect(self.terminate_pipeline_clicked) terminate_pipeline.triggered.connect(self.terminate_pipeline_clicked)
self.table.addAction(terminate_pipeline) self.table.addAction(terminate_pipeline)

View File

@ -1,7 +1,7 @@
import logging import logging
from functools import partial from functools import partial
from PyQt5 import QtCore, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -11,8 +11,8 @@ class ShortcutsDock(QtWidgets.QDockWidget):
def __init__(self, main_window, exp_manager): def __init__(self, main_window, exp_manager):
QtWidgets.QDockWidget.__init__(self, "Shortcuts") QtWidgets.QDockWidget.__init__(self, "Shortcuts")
self.setObjectName("Shortcuts") self.setObjectName("Shortcuts")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | self.setFeatures(self.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable) self.DockWidgetFeature.DockWidgetFloatable)
layout = QtWidgets.QGridLayout() layout = QtWidgets.QGridLayout()
top_widget = QtWidgets.QWidget() top_widget = QtWidgets.QWidget()
@ -36,25 +36,25 @@ class ShortcutsDock(QtWidgets.QDockWidget):
layout.addWidget(QtWidgets.QLabel("F" + str(i + 1)), row, 0) layout.addWidget(QtWidgets.QLabel("F" + str(i + 1)), row, 0)
label = QtWidgets.QLabel() label = QtWidgets.QLabel()
label.setSizePolicy(QtWidgets.QSizePolicy.Ignored, label.setSizePolicy(QtWidgets.QSizePolicy.Policy.Ignored,
QtWidgets.QSizePolicy.Ignored) QtWidgets.QSizePolicy.Policy.Ignored)
layout.addWidget(label, row, 1) layout.addWidget(label, row, 1)
clear = QtWidgets.QToolButton() clear = QtWidgets.QToolButton()
clear.setIcon(QtWidgets.QApplication.style().standardIcon( clear.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogDiscardButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogDiscardButton))
layout.addWidget(clear, row, 2) layout.addWidget(clear, row, 2)
clear.clicked.connect(partial(self.set_shortcut, i, "")) clear.clicked.connect(partial(self.set_shortcut, i, ""))
open = QtWidgets.QToolButton() open = QtWidgets.QToolButton()
open.setIcon(QtWidgets.QApplication.style().standardIcon( open.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOpenButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton))
layout.addWidget(open, row, 3) layout.addWidget(open, row, 3)
open.clicked.connect(partial(self._open_experiment, i)) open.clicked.connect(partial(self._open_experiment, i))
submit = QtWidgets.QPushButton("Submit") submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon( submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
layout.addWidget(submit, row, 4) layout.addWidget(submit, row, 4)
submit.clicked.connect(partial(self._activated, i)) submit.clicked.connect(partial(self._activated, i))
@ -68,8 +68,8 @@ class ShortcutsDock(QtWidgets.QDockWidget):
"open": open, "open": open,
"submit": submit "submit": submit
} }
shortcut = QtWidgets.QShortcut("F" + str(i + 1), main_window) shortcut = QtGui.QShortcut("F" + str(i+1), main_window)
shortcut.setContext(QtCore.Qt.ApplicationShortcut) shortcut.setContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)
shortcut.activated.connect(partial(self._activated, i)) shortcut.activated.connect(partial(self._activated, i))
def _activated(self, nr): def _activated(self, nr):

View File

@ -5,7 +5,7 @@ import bisect
import itertools import itertools
import math import math
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt6 import QtCore, QtWidgets, QtGui
import pyqtgraph as pg import pyqtgraph as pg
import numpy as np import numpy as np
@ -130,7 +130,7 @@ class _BaseWaveform(pg.PlotWidget):
self.setMinimumHeight(WAVEFORM_MIN_HEIGHT) self.setMinimumHeight(WAVEFORM_MIN_HEIGHT)
self.setMaximumHeight(WAVEFORM_MAX_HEIGHT) self.setMaximumHeight(WAVEFORM_MAX_HEIGHT)
self.setMenuEnabled(False) self.setMenuEnabled(False)
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
self.name = name self.name = name
self.width = width self.width = width
@ -200,13 +200,13 @@ class _BaseWaveform(pg.PlotWidget):
self.cursor_y = self.y_data[ind] self.cursor_y = self.y_data[ind]
def mouseMoveEvent(self, e): def mouseMoveEvent(self, e):
if e.buttons() == QtCore.Qt.LeftButton \ if e.buttons() == QtCore.Qt.MouseButton.LeftButton \
and e.modifiers() == QtCore.Qt.ShiftModifier: and e.modifiers() == QtCore.Qt.KeyboardModifier.ShiftModifier:
drag = QtGui.QDrag(self) drag = QtGui.QDrag(self)
mime = QtCore.QMimeData() mime = QtCore.QMimeData()
drag.setMimeData(mime) drag.setMimeData(mime)
pixmapi = QtWidgets.QApplication.style().standardIcon( pixmapi = QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileIcon) QtWidgets.QStyle.StandardPixmap.SP_FileIcon)
drag.setPixmap(pixmapi.pixmap(32)) drag.setPixmap(pixmapi.pixmap(32))
drag.exec_(QtCore.Qt.MoveAction) drag.exec_(QtCore.Qt.MoveAction)
else: else:
@ -428,8 +428,9 @@ class _WaveformView(QtWidgets.QWidget):
scroll_area = VDragScrollArea(self) scroll_area = VDragScrollArea(self)
scroll_area.setWidgetResizable(True) scroll_area.setWidgetResizable(True)
scroll_area.setContentsMargins(0, 0, 0, 0) scroll_area.setContentsMargins(0, 0, 0, 0)
scroll_area.setFrameShape(QtWidgets.QFrame.NoFrame) scroll_area.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff) scroll_area.setVerticalScrollBarPolicy(
QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
layout.addWidget(scroll_area) layout.addWidget(scroll_area)
self._splitter = VDragDropSplitter(parent=scroll_area) self._splitter = VDragDropSplitter(parent=scroll_area)
@ -444,10 +445,11 @@ class _WaveformView(QtWidgets.QWidget):
) )
self.confirm_delete_dialog.setText("Delete all waveforms?") self.confirm_delete_dialog.setText("Delete all waveforms?")
self.confirm_delete_dialog.setStandardButtons( self.confirm_delete_dialog.setStandardButtons(
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel QtWidgets.QMessageBox.StandardButton.Ok |
QtWidgets.QMessageBox.StandardButton.Cancel
) )
self.confirm_delete_dialog.setDefaultButton( self.confirm_delete_dialog.setDefaultButton(
QtWidgets.QMessageBox.Ok QtWidgets.QMessageBox.StandardButton.Ok
) )
def setModel(self, model): def setModel(self, model):
@ -519,10 +521,10 @@ class _WaveformView(QtWidgets.QWidget):
w.setStoppedX(self._stopped_x) w.setStoppedX(self._stopped_x)
w.cursorMove.connect(self.cursorMove) w.cursorMove.connect(self.cursorMove)
w.onCursorMove(self._cursor_x) w.onCursorMove(self._cursor_x)
action = QtWidgets.QAction("Delete waveform", w) action = QtGui.QAction("Delete waveform", w)
action.triggered.connect(lambda: self._delete_waveform(w)) action.triggered.connect(lambda: self._delete_waveform(w))
w.addAction(action) w.addAction(action)
action = QtWidgets.QAction("Delete all waveforms", w) action = QtGui.QAction("Delete all waveforms", w)
action.triggered.connect(self.confirm_delete_dialog.open) action.triggered.connect(self.confirm_delete_dialog.open)
w.addAction(action) w.addAction(action)
return w return w
@ -548,7 +550,7 @@ class _WaveformModel(QtCore.QAbstractTableModel):
def columnCount(self, parent=QtCore.QModelIndex()): def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.headers) return len(self.headers)
def data(self, index, role=QtCore.Qt.DisplayRole): def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
if index.isValid(): if index.isValid():
return self.backing_struct[index.row()][index.column()] return self.backing_struct[index.row()][index.column()]
return None return None
@ -656,7 +658,7 @@ class Model(DictSyncTreeSepModel):
class _AddChannelDialog(QtWidgets.QDialog): class _AddChannelDialog(QtWidgets.QDialog):
def __init__(self, parent, model): def __init__(self, parent, model):
QtWidgets.QDialog.__init__(self, parent=parent) QtWidgets.QDialog.__init__(self, parent=parent)
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
self.setWindowTitle("Add channels") self.setWindowTitle("Add channels")
layout = QtWidgets.QVBoxLayout() layout = QtWidgets.QVBoxLayout()
@ -666,14 +668,14 @@ class _AddChannelDialog(QtWidgets.QDialog):
self._tree_view = QtWidgets.QTreeView() self._tree_view = QtWidgets.QTreeView()
self._tree_view.setHeaderHidden(True) self._tree_view.setHeaderHidden(True)
self._tree_view.setSelectionBehavior( self._tree_view.setSelectionBehavior(
QtWidgets.QAbstractItemView.SelectItems) QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems)
self._tree_view.setSelectionMode( self._tree_view.setSelectionMode(
QtWidgets.QAbstractItemView.ExtendedSelection) QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
self._tree_view.setModel(self._model) self._tree_view.setModel(self._model)
layout.addWidget(self._tree_view) layout.addWidget(self._tree_view)
self._button_box = QtWidgets.QDialogButtonBox( self._button_box = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
) )
self._button_box.setCenterButtons(True) self._button_box.setCenterButtons(True)
self._button_box.accepted.connect(self.add_channels) self._button_box.accepted.connect(self.add_channels)
@ -696,7 +698,7 @@ class WaveformDock(QtWidgets.QDockWidget):
QtWidgets.QDockWidget.__init__(self, "Waveform") QtWidgets.QDockWidget.__init__(self, "Waveform")
self.setObjectName("Waveform") self.setObjectName("Waveform")
self.setFeatures( self.setFeatures(
QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable) self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable)
self._channel_model = Model({}) self._channel_model = Model({})
self._waveform_model = _WaveformModel() self._waveform_model = _WaveformModel()
@ -724,14 +726,14 @@ class WaveformDock(QtWidgets.QDockWidget):
self._menu_btn = QtWidgets.QPushButton() self._menu_btn = QtWidgets.QPushButton()
self._menu_btn.setIcon( self._menu_btn.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogStart)) QtWidgets.QStyle.StandardPixmap.SP_FileDialogStart))
grid.addWidget(self._menu_btn, 0, 0) grid.addWidget(self._menu_btn, 0, 0)
self._request_dump_btn = QtWidgets.QToolButton() self._request_dump_btn = QtWidgets.QToolButton()
self._request_dump_btn.setToolTip("Fetch analyzer data from device") self._request_dump_btn.setToolTip("Fetch analyzer data from device")
self._request_dump_btn.setIcon( self._request_dump_btn.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_BrowserReload)) QtWidgets.QStyle.StandardPixmap.SP_BrowserReload))
self._request_dump_btn.clicked.connect( self._request_dump_btn.clicked.connect(
lambda: asyncio.ensure_future(exc_to_warning(self.proxy_client.trigger_proxy_task()))) lambda: asyncio.ensure_future(exc_to_warning(self.proxy_client.trigger_proxy_task())))
grid.addWidget(self._request_dump_btn, 0, 1) grid.addWidget(self._request_dump_btn, 0, 1)
@ -743,7 +745,7 @@ class WaveformDock(QtWidgets.QDockWidget):
self._add_btn.setToolTip("Add channels...") self._add_btn.setToolTip("Add channels...")
self._add_btn.setIcon( self._add_btn.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogListView)) QtWidgets.QStyle.StandardPixmap.SP_FileDialogListView))
self._add_btn.clicked.connect(self._add_channel_dialog.open) self._add_btn.clicked.connect(self._add_channel_dialog.open)
grid.addWidget(self._add_btn, 0, 2) grid.addWidget(self._add_btn, 0, 2)
@ -763,7 +765,7 @@ class WaveformDock(QtWidgets.QDockWidget):
self._reset_zoom_btn.setToolTip("Reset zoom") self._reset_zoom_btn.setToolTip("Reset zoom")
self._reset_zoom_btn.setIcon( self._reset_zoom_btn.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_TitleBarMaxButton)) QtWidgets.QStyle.StandardPixmap.SP_TitleBarMaxButton))
self._reset_zoom_btn.clicked.connect(self._waveform_view.resetZoom) self._reset_zoom_btn.clicked.connect(self._waveform_view.resetZoom)
grid.addWidget(self._reset_zoom_btn, 0, 3) grid.addWidget(self._reset_zoom_btn, 0, 3)
@ -773,7 +775,7 @@ class WaveformDock(QtWidgets.QDockWidget):
grid.addWidget(self._cursor_control, 0, 4, colspan=6) grid.addWidget(self._cursor_control, 0, 4, colspan=6)
def _add_async_action(self, label, coro): def _add_async_action(self, label, coro):
action = QtWidgets.QAction(label, self) action = QtGui.QAction(label, self)
action.triggered.connect( action.triggered.connect(
lambda: asyncio.ensure_future(exc_to_warning(coro()))) lambda: asyncio.ensure_future(exc_to_warning(coro())))
self._file_menu.addAction(action) self._file_menu.addAction(action)

View File

@ -1,4 +1,4 @@
from PyQt5 import QtWidgets from PyQt6 import QtWidgets
from artiq.applets.simple import SimpleApplet from artiq.applets.simple import SimpleApplet

View File

@ -710,11 +710,30 @@ fn read_device_map() -> DeviceMap {
device_map device_map
} }
fn toggle_sed_spread(val: u8) {
unsafe { csr::rtio_core::sed_spread_enable_write(val); }
}
fn setup_sed_spread() {
config::read_str("sed_spread_enable", |r| {
match r {
Ok("1") => { info!("SED spreading enabled"); toggle_sed_spread(1); },
Ok("0") => { info!("SED spreading disabled"); toggle_sed_spread(0); },
Ok(_) => {
warn!("sed_spread_enable value not supported (only 1, 0 allowed), disabling by default");
toggle_sed_spread(0);
},
Err(_) => { info!("SED spreading disabled by default"); toggle_sed_spread(0) },
}
});
}
pub fn startup(io: &Io, aux_mutex: &Mutex, pub fn startup(io: &Io, aux_mutex: &Mutex,
routing_table: &Urc<RefCell<drtio_routing::RoutingTable>>, routing_table: &Urc<RefCell<drtio_routing::RoutingTable>>,
up_destinations: &Urc<RefCell<[bool; drtio_routing::DEST_COUNT]>>, up_destinations: &Urc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex) { ddma_mutex: &Mutex, subkernel_mutex: &Mutex) {
set_device_map(read_device_map()); set_device_map(read_device_map());
setup_sed_spread();
drtio::startup(io, aux_mutex, routing_table, up_destinations, ddma_mutex, subkernel_mutex); drtio::startup(io, aux_mutex, routing_table, up_destinations, ddma_mutex, subkernel_mutex);
unsafe { unsafe {
csr::rtio_core::reset_phy_write(1); csr::rtio_core::reset_phy_write(1);

View File

@ -14,7 +14,7 @@ extern crate io;
extern crate eh; extern crate eh;
use core::convert::TryFrom; use core::convert::TryFrom;
use board_misoc::{csr, ident, clock, uart_logger, i2c, pmp}; use board_misoc::{csr, ident, clock, config, uart_logger, i2c, pmp};
#[cfg(has_si5324)] #[cfg(has_si5324)]
use board_artiq::si5324; use board_artiq::si5324;
#[cfg(has_si549)] #[cfg(has_si549)]
@ -70,6 +70,10 @@ fn drtiosat_tsc_loaded() -> bool {
} }
} }
fn toggle_sed_spread(val: u8) {
unsafe { csr::drtiosat::sed_spread_enable_write(val); }
}
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
pub enum RtioMaster { pub enum RtioMaster {
Drtio, Drtio,
@ -100,13 +104,13 @@ pub fn cricon_read() -> RtioMaster {
#[cfg(has_drtio_routing)] #[cfg(has_drtio_routing)]
macro_rules! forward { macro_rules! forward {
($routing_table:expr, $destination:expr, $rank:expr, $repeaters:expr, $packet:expr) => {{ ($router:expr, $routing_table:expr, $destination:expr, $rank:expr, $self_destination:expr, $repeaters:expr, $packet:expr) => {{
let hop = $routing_table.0[$destination as usize][$rank as usize]; let hop = $routing_table.0[$destination as usize][$rank as usize];
if hop != 0 { if hop != 0 {
let repno = (hop - 1) as usize; let repno = (hop - 1) as usize;
if repno < $repeaters.len() { if repno < $repeaters.len() {
if $packet.expects_response() { if $packet.expects_response() {
return $repeaters[repno].aux_forward($packet); return $repeaters[repno].aux_forward($packet, $router, $routing_table, $rank, $self_destination);
} else { } else {
let res = $repeaters[repno].aux_send($packet); let res = $repeaters[repno].aux_send($packet);
// allow the satellite to parse the packet before next // allow the satellite to parse the packet before next
@ -122,7 +126,7 @@ macro_rules! forward {
#[cfg(not(has_drtio_routing))] #[cfg(not(has_drtio_routing))]
macro_rules! forward { macro_rules! forward {
($routing_table:expr, $destination:expr, $rank:expr, $repeaters:expr, $packet:expr) => {} ($router:expr, $routing_table:expr, $destination:expr, $rank:expr, $self_destination:expr, $repeaters:expr, $packet:expr) => {}
} }
fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmgr: &mut KernelManager, fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmgr: &mut KernelManager,
@ -197,7 +201,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
let repno = hop - 1; let repno = hop - 1;
match _repeaters[repno].aux_forward(&drtioaux::Packet::DestinationStatusRequest { match _repeaters[repno].aux_forward(&drtioaux::Packet::DestinationStatusRequest {
destination: destination destination: destination
}) { }, router, _routing_table, *rank, *self_destination) {
Ok(()) => (), Ok(()) => (),
Err(drtioaux::Error::LinkDown) => drtioaux::send(0, &drtioaux::Packet::DestinationDownReply)?, Err(drtioaux::Error::LinkDown) => drtioaux::send(0, &drtioaux::Packet::DestinationDownReply)?,
Err(e) => { Err(e) => {
@ -251,7 +255,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
} }
drtioaux::Packet::MonitorRequest { destination: _destination, channel, probe } => { drtioaux::Packet::MonitorRequest { destination: _destination, channel, probe } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let value; let value;
#[cfg(has_rtio_moninj)] #[cfg(has_rtio_moninj)]
unsafe { unsafe {
@ -268,7 +272,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
drtioaux::send(0, &reply) drtioaux::send(0, &reply)
}, },
drtioaux::Packet::InjectionRequest { destination: _destination, channel, overrd, value } => { drtioaux::Packet::InjectionRequest { destination: _destination, channel, overrd, value } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
#[cfg(has_rtio_moninj)] #[cfg(has_rtio_moninj)]
unsafe { unsafe {
csr::rtio_moninj::inj_chan_sel_write(channel as _); csr::rtio_moninj::inj_chan_sel_write(channel as _);
@ -278,7 +282,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
Ok(()) Ok(())
}, },
drtioaux::Packet::InjectionStatusRequest { destination: _destination, channel, overrd } => { drtioaux::Packet::InjectionStatusRequest { destination: _destination, channel, overrd } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let value; let value;
#[cfg(has_rtio_moninj)] #[cfg(has_rtio_moninj)]
unsafe { unsafe {
@ -294,22 +298,22 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}, },
drtioaux::Packet::I2cStartRequest { destination: _destination, busno } => { drtioaux::Packet::I2cStartRequest { destination: _destination, busno } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = i2c::start(busno).is_ok(); let succeeded = i2c::start(busno).is_ok();
drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded }) drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded })
} }
drtioaux::Packet::I2cRestartRequest { destination: _destination, busno } => { drtioaux::Packet::I2cRestartRequest { destination: _destination, busno } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = i2c::restart(busno).is_ok(); let succeeded = i2c::restart(busno).is_ok();
drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded }) drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded })
} }
drtioaux::Packet::I2cStopRequest { destination: _destination, busno } => { drtioaux::Packet::I2cStopRequest { destination: _destination, busno } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = i2c::stop(busno).is_ok(); let succeeded = i2c::stop(busno).is_ok();
drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded }) drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded })
} }
drtioaux::Packet::I2cWriteRequest { destination: _destination, busno, data } => { drtioaux::Packet::I2cWriteRequest { destination: _destination, busno, data } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
match i2c::write(busno, data) { match i2c::write(busno, data) {
Ok(ack) => drtioaux::send(0, Ok(ack) => drtioaux::send(0,
&drtioaux::Packet::I2cWriteReply { succeeded: true, ack: ack }), &drtioaux::Packet::I2cWriteReply { succeeded: true, ack: ack }),
@ -318,7 +322,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
} }
} }
drtioaux::Packet::I2cReadRequest { destination: _destination, busno, ack } => { drtioaux::Packet::I2cReadRequest { destination: _destination, busno, ack } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
match i2c::read(busno, ack) { match i2c::read(busno, ack) {
Ok(data) => drtioaux::send(0, Ok(data) => drtioaux::send(0,
&drtioaux::Packet::I2cReadReply { succeeded: true, data: data }), &drtioaux::Packet::I2cReadReply { succeeded: true, data: data }),
@ -327,25 +331,25 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
} }
} }
drtioaux::Packet::I2cSwitchSelectRequest { destination: _destination, busno, address, mask } => { drtioaux::Packet::I2cSwitchSelectRequest { destination: _destination, busno, address, mask } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = i2c::switch_select(busno, address, mask).is_ok(); let succeeded = i2c::switch_select(busno, address, mask).is_ok();
drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded }) drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded })
} }
drtioaux::Packet::SpiSetConfigRequest { destination: _destination, busno, flags, length, div, cs } => { drtioaux::Packet::SpiSetConfigRequest { destination: _destination, busno, flags, length, div, cs } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = spi::set_config(busno, flags, length, div, cs).is_ok(); let succeeded = spi::set_config(busno, flags, length, div, cs).is_ok();
drtioaux::send(0, drtioaux::send(0,
&drtioaux::Packet::SpiBasicReply { succeeded: succeeded }) &drtioaux::Packet::SpiBasicReply { succeeded: succeeded })
}, },
drtioaux::Packet::SpiWriteRequest { destination: _destination, busno, data } => { drtioaux::Packet::SpiWriteRequest { destination: _destination, busno, data } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = spi::write(busno, data).is_ok(); let succeeded = spi::write(busno, data).is_ok();
drtioaux::send(0, drtioaux::send(0,
&drtioaux::Packet::SpiBasicReply { succeeded: succeeded }) &drtioaux::Packet::SpiBasicReply { succeeded: succeeded })
} }
drtioaux::Packet::SpiReadRequest { destination: _destination, busno } => { drtioaux::Packet::SpiReadRequest { destination: _destination, busno } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
match spi::read(busno) { match spi::read(busno) {
Ok(data) => drtioaux::send(0, Ok(data) => drtioaux::send(0,
&drtioaux::Packet::SpiReadReply { succeeded: true, data: data }), &drtioaux::Packet::SpiReadReply { succeeded: true, data: data }),
@ -355,7 +359,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
} }
drtioaux::Packet::AnalyzerHeaderRequest { destination: _destination } => { drtioaux::Packet::AnalyzerHeaderRequest { destination: _destination } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let header = analyzer.get_header(); let header = analyzer.get_header();
drtioaux::send(0, &drtioaux::Packet::AnalyzerHeader { drtioaux::send(0, &drtioaux::Packet::AnalyzerHeader {
total_byte_count: header.total_byte_count, total_byte_count: header.total_byte_count,
@ -365,7 +369,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
} }
drtioaux::Packet::AnalyzerDataRequest { destination: _destination } => { drtioaux::Packet::AnalyzerDataRequest { destination: _destination } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let mut data_slice: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE]; let mut data_slice: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
let meta = analyzer.get_data(&mut data_slice); let meta = analyzer.get_data(&mut data_slice);
drtioaux::send(0, &drtioaux::Packet::AnalyzerData { drtioaux::send(0, &drtioaux::Packet::AnalyzerData {
@ -376,7 +380,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
} }
drtioaux::Packet::DmaAddTraceRequest { source, destination, id, status, length, trace } => { drtioaux::Packet::DmaAddTraceRequest { source, destination, id, status, length, trace } => {
forward!(_routing_table, destination, *rank, _repeaters, &packet); forward!(router, _routing_table, destination, *rank, *self_destination, _repeaters, &packet);
*self_destination = destination; *self_destination = destination;
let succeeded = dmamgr.add(source, id, status, &trace, length as usize).is_ok(); let succeeded = dmamgr.add(source, id, status, &trace, length as usize).is_ok();
router.send(drtioaux::Packet::DmaAddTraceReply { router.send(drtioaux::Packet::DmaAddTraceReply {
@ -384,19 +388,19 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}, _routing_table, *rank, *self_destination) }, _routing_table, *rank, *self_destination)
} }
drtioaux::Packet::DmaAddTraceReply { source, destination: _destination, id, succeeded } => { drtioaux::Packet::DmaAddTraceReply { source, destination: _destination, id, succeeded } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
dmamgr.ack_upload(kernelmgr, source, id, succeeded, router, *rank, *self_destination, _routing_table); dmamgr.ack_upload(kernelmgr, source, id, succeeded, router, *rank, *self_destination, _routing_table);
Ok(()) Ok(())
} }
drtioaux::Packet::DmaRemoveTraceRequest { source, destination: _destination, id } => { drtioaux::Packet::DmaRemoveTraceRequest { source, destination: _destination, id } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = dmamgr.erase(source, id).is_ok(); let succeeded = dmamgr.erase(source, id).is_ok();
router.send(drtioaux::Packet::DmaRemoveTraceReply { router.send(drtioaux::Packet::DmaRemoveTraceReply {
destination: source, succeeded: succeeded destination: source, succeeded: succeeded
}, _routing_table, *rank, *self_destination) }, _routing_table, *rank, *self_destination)
} }
drtioaux::Packet::DmaPlaybackRequest { source, destination: _destination, id, timestamp } => { drtioaux::Packet::DmaPlaybackRequest { source, destination: _destination, id, timestamp } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
// no DMA with a running kernel // no DMA with a running kernel
let succeeded = !kernelmgr.is_running() && dmamgr.playback(source, id, timestamp).is_ok(); let succeeded = !kernelmgr.is_running() && dmamgr.playback(source, id, timestamp).is_ok();
router.send(drtioaux::Packet::DmaPlaybackReply { router.send(drtioaux::Packet::DmaPlaybackReply {
@ -404,27 +408,27 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}, _routing_table, *rank, *self_destination) }, _routing_table, *rank, *self_destination)
} }
drtioaux::Packet::DmaPlaybackReply { destination: _destination, succeeded } => { drtioaux::Packet::DmaPlaybackReply { destination: _destination, succeeded } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
if !succeeded { if !succeeded {
kernelmgr.ddma_nack(); kernelmgr.ddma_nack();
} }
Ok(()) Ok(())
} }
drtioaux::Packet::DmaPlaybackStatus { source: _, destination: _destination, id, error, channel, timestamp } => { drtioaux::Packet::DmaPlaybackStatus { source: _, destination: _destination, id, error, channel, timestamp } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
dmamgr.remote_finished(kernelmgr, id, error, channel, timestamp); dmamgr.remote_finished(kernelmgr, id, error, channel, timestamp);
Ok(()) Ok(())
} }
drtioaux::Packet::SubkernelAddDataRequest { destination, id, status, length, data } => { drtioaux::Packet::SubkernelAddDataRequest { destination, id, status, length, data } => {
forward!(_routing_table, destination, *rank, _repeaters, &packet); forward!(router, _routing_table, destination, *rank, *self_destination, _repeaters, &packet);
*self_destination = destination; *self_destination = destination;
let succeeded = kernelmgr.add(id, status, &data, length as usize).is_ok(); let succeeded = kernelmgr.add(id, status, &data, length as usize).is_ok();
drtioaux::send(0, drtioaux::send(0,
&drtioaux::Packet::SubkernelAddDataReply { succeeded: succeeded }) &drtioaux::Packet::SubkernelAddDataReply { succeeded: succeeded })
} }
drtioaux::Packet::SubkernelLoadRunRequest { source, destination: _destination, id, run } => { drtioaux::Packet::SubkernelLoadRunRequest { source, destination: _destination, id, run } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let mut succeeded = kernelmgr.load(id).is_ok(); let mut succeeded = kernelmgr.load(id).is_ok();
// allow preloading a kernel with delayed run // allow preloading a kernel with delayed run
if run { if run {
@ -441,18 +445,18 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
_routing_table, *rank, *self_destination) _routing_table, *rank, *self_destination)
} }
drtioaux::Packet::SubkernelLoadRunReply { destination: _destination, succeeded } => { drtioaux::Packet::SubkernelLoadRunReply { destination: _destination, succeeded } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
// received if local subkernel started another, remote subkernel // received if local subkernel started another, remote subkernel
kernelmgr.subkernel_load_run_reply(succeeded, *self_destination); kernelmgr.subkernel_load_run_reply(succeeded, *self_destination);
Ok(()) Ok(())
} }
drtioaux::Packet::SubkernelFinished { destination: _destination, id, with_exception, exception_src } => { drtioaux::Packet::SubkernelFinished { destination: _destination, id, with_exception, exception_src } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
kernelmgr.remote_subkernel_finished(id, with_exception, exception_src); kernelmgr.remote_subkernel_finished(id, with_exception, exception_src);
Ok(()) Ok(())
} }
drtioaux::Packet::SubkernelExceptionRequest { destination: _destination } => { drtioaux::Packet::SubkernelExceptionRequest { destination: _destination } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let mut data_slice: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE]; let mut data_slice: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
let meta = kernelmgr.exception_get_slice(&mut data_slice); let meta = kernelmgr.exception_get_slice(&mut data_slice);
drtioaux::send(0, &drtioaux::Packet::SubkernelException { drtioaux::send(0, &drtioaux::Packet::SubkernelException {
@ -462,14 +466,14 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}) })
} }
drtioaux::Packet::SubkernelMessage { source, destination: _destination, id, status, length, data } => { drtioaux::Packet::SubkernelMessage { source, destination: _destination, id, status, length, data } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
kernelmgr.message_handle_incoming(status, length as usize, id, &data); kernelmgr.message_handle_incoming(status, length as usize, id, &data);
router.send(drtioaux::Packet::SubkernelMessageAck { router.send(drtioaux::Packet::SubkernelMessageAck {
destination: source destination: source
}, _routing_table, *rank, *self_destination) }, _routing_table, *rank, *self_destination)
} }
drtioaux::Packet::SubkernelMessageAck { destination: _destination } => { drtioaux::Packet::SubkernelMessageAck { destination: _destination } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
if kernelmgr.message_ack_slice() { if kernelmgr.message_ack_slice() {
let mut data_slice: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE]; let mut data_slice: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
if let Some(meta) = kernelmgr.message_get_slice(&mut data_slice) { if let Some(meta) = kernelmgr.message_get_slice(&mut data_slice) {
@ -754,6 +758,18 @@ pub extern fn main() -> i32 {
init_rtio_crg(); init_rtio_crg();
config::read_str("sed_spread_enable", |r| {
match r {
Ok("1") => { info!("SED spreading enabled"); toggle_sed_spread(1); },
Ok("0") => { info!("SED spreading disabled"); toggle_sed_spread(0); },
Ok(_) => {
warn!("sed_spread_enable value not supported (only 1, 0 allowed), disabling by default");
toggle_sed_spread(0);
},
Err(_) => { info!("SED spreading disabled by default"); toggle_sed_spread(0) },
}
});
#[cfg(has_drtio_eem)] #[cfg(has_drtio_eem)]
drtio_eem::init(); drtio_eem::init();

View File

@ -75,6 +75,11 @@ impl Repeater {
if rep_link_rx_up(self.repno) { if rep_link_rx_up(self.repno) {
if let Ok(Some(drtioaux::Packet::EchoReply)) = drtioaux::recv(self.auxno) { if let Ok(Some(drtioaux::Packet::EchoReply)) = drtioaux::recv(self.auxno) {
info!("[REP#{}] remote replied after {} packets", self.repno, ping_count); info!("[REP#{}] remote replied after {} packets", self.repno, ping_count);
// clear the aux buffer
let max_time = clock::get_ms() + 200;
while clock::get_ms() < max_time {
let _ = drtioaux::recv(self.auxno);
}
self.state = RepeaterState::Up; self.state = RepeaterState::Up;
if let Err(e) = self.sync_tsc() { if let Err(e) = self.sync_tsc() {
error!("[REP#{}] failed to sync TSC ({})", self.repno, e); error!("[REP#{}] failed to sync TSC ({})", self.repno, e);
@ -181,10 +186,31 @@ impl Repeater {
} }
} }
pub fn aux_forward(&self, request: &drtioaux::Packet) -> Result<(), drtioaux::Error<!>> { pub fn aux_forward(&self, request: &drtioaux::Packet, router: &mut Router,
routing_table: &drtio_routing::RoutingTable, rank: u8,
self_destination: u8) -> Result<(), drtioaux::Error<!>> {
self.aux_send(request)?; self.aux_send(request)?;
let reply = self.recv_aux_timeout(200)?; loop {
drtioaux::send(0, &reply).unwrap(); let reply = self.recv_aux_timeout(200)?;
match reply {
// async/locally requested packets to be consumed or routed
// these may come while a packet would be forwarded
drtioaux::Packet::DmaPlaybackStatus { .. } |
drtioaux::Packet::SubkernelFinished { .. } |
drtioaux::Packet::SubkernelMessage { .. } |
drtioaux::Packet::SubkernelMessageAck { .. } |
drtioaux::Packet::SubkernelLoadRunReply { .. } |
drtioaux::Packet::SubkernelException { .. } |
drtioaux::Packet::DmaAddTraceReply { .. } |
drtioaux::Packet::DmaPlaybackReply { .. } => {
router.route(reply, routing_table, rank, self_destination);
}
_ => {
drtioaux::send(0, &reply).unwrap();
break;
}
}
}
Ok(()) Ok(())
} }

View File

@ -4,6 +4,7 @@ import sys
import argparse import argparse
import os import os
import socket import socket
import asyncio
import ssl import ssl
import io import io
import zipfile import zipfile
@ -41,22 +42,27 @@ def zip_unarchive(data, directory):
class Client: class Client:
def __init__(self, server, port, cafile): def __init__(self, server, port, cafile):
self.server = server
self.port = port
self.ssl_context = ssl.create_default_context(cafile=cafile) self.ssl_context = ssl.create_default_context(cafile=cafile)
self.raw_socket = socket.create_connection((server, port)) self.reader = None
self.init_websocket(server) self.writer = None
try:
self.socket = self.ssl_context.wrap_socket(self.raw_socket, server_hostname=server)
except:
self.raw_socket.close()
raise
self.fsocket = self.socket.makefile("rwb")
def init_websocket(self, server): async def connect(self):
self.raw_socket.sendall("GET / HTTP/1.1\r\nHost: {}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n" self.reader, self.writer = await asyncio.open_connection(
.format(server).encode()) host=self.server,
port=self.port,
happy_eyeballs_delay=0.25
)
await self.init_websocket()
await self.writer.start_tls(self.ssl_context)
async def init_websocket(self):
self.writer.write("GET / HTTP/1.1\r\nHost: {}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n"
.format(self.server).encode())
crlf_count = 0 crlf_count = 0
while crlf_count < 4: while crlf_count < 4:
char = self.raw_socket.recv(1) char = await self.reader.read(1)
if not char: if not char:
return ValueError("Connection closed during WebSocket initialization") return ValueError("Connection closed during WebSocket initialization")
if char == b"\r" or char == b"\n": if char == b"\r" or char == b"\n":
@ -64,30 +70,30 @@ class Client:
else: else:
crlf_count = 0 crlf_count = 0
def close(self): async def close(self):
self.socket.close() if self.writer:
self.raw_socket.close() self.writer.close()
await self.writer.wait_closed()
def send_command(self, *command): async def send_command(self, *command):
self.fsocket.write((" ".join(command) + "\n").encode()) self.writer.write((" ".join(command) + "\n").encode())
self.fsocket.flush()
def read_line(self): async def read_line(self):
return self.fsocket.readline().decode("ascii") return (await self.reader.readline()).decode("ascii")
def read_reply(self): async def read_reply(self):
return self.fsocket.readline().decode("ascii").split() return (await self.reader.readline()).decode("ascii").split()
def read_json(self): async def read_json(self):
return json.loads(self.fsocket.readline().decode("ascii")) return json.loads((await self.reader.readline()).decode("ascii"))
def login(self, username, password): async def login(self, username, password):
self.send_command("LOGIN", username, password) await self.send_command("LOGIN", username, password)
return self.read_reply() == ["HELLO"] return await self.read_reply() == ["HELLO"]
def build(self, major_ver, rev, variant, log, experimental_features): async def build(self, major_ver, rev, variant, log, experimental_features):
if not variant: if not variant:
variant = self.get_single_variant(error_msg="User can build more than 1 variant - need to specify") variant = await self.get_single_variant(error_msg="User can build more than 1 variant - need to specify")
print("Building variant: {}".format(variant)) print("Building variant: {}".format(variant))
build_args = ( build_args = (
rev, rev,
@ -96,25 +102,25 @@ class Client:
major_ver, major_ver,
*experimental_features, *experimental_features,
) )
self.send_command("BUILD", *build_args) await self.send_command("BUILD", *build_args)
reply = self.read_reply()[0] reply = (await self.read_reply())[0]
if reply != "BUILDING": if reply != "BUILDING":
return reply, None return reply, None
print("Build in progress. This may take 10-15 minutes.") print("Build in progress. This may take 10-15 minutes.")
if log: if log:
line = self.read_line() line = await self.read_line()
while line != "" and line.startswith("LOG"): while line != "" and line.startswith("LOG"):
print(line[4:], end="") print(line[4:], end="")
line = self.read_line() line = await self.read_line()
reply, status = line.split() reply, status = line.split()
else: else:
reply, status = self.read_reply() reply, status = await self.read_reply()
if reply != "DONE": if reply != "DONE":
raise ValueError("Unexpected server reply: expected 'DONE', got '{}'".format(reply)) raise ValueError("Unexpected server reply: expected 'DONE', got '{}'".format(reply))
if status != "done": if status != "done":
return status, None return status, None
print("Build completed. Downloading...") print("Build completed. Downloading...")
reply, length = self.read_reply() reply, length = await self.read_reply()
if reply != "PRODUCT": if reply != "PRODUCT":
raise ValueError("Unexpected server reply: expected 'PRODUCT', got '{}'".format(reply)) raise ValueError("Unexpected server reply: expected 'PRODUCT', got '{}'".format(reply))
length = int(length) length = int(length)
@ -123,25 +129,25 @@ class Client:
total = 0 total = 0
while total != length: while total != length:
chunk_len = min(4096, length-total) chunk_len = min(4096, length-total)
contents += self.fsocket.read(chunk_len) contents += await self.reader.read(chunk_len)
total += chunk_len total += chunk_len
progress_bar.update(chunk_len) progress_bar.update(chunk_len)
print("Download completed.") print("Download completed.")
return "OK", contents return "OK", contents
def passwd(self, password): async def passwd(self, password):
self.send_command("PASSWD", password) await self.send_command("PASSWD", password)
return self.read_reply() == ["OK"] return (await self.read_reply()) == ["OK"]
def get_variants(self): async def get_variants(self):
self.send_command("GET_VARIANTS") await self.send_command("GET_VARIANTS")
reply = self.read_reply()[0] reply = (await self.read_reply())[0]
if reply != "OK": if reply != "OK":
raise ValueError("Unexpected server reply: expected 'OK', got '{}'".format(reply)) raise ValueError("Unexpected server reply: expected 'OK', got '{}'".format(reply))
return self.read_json() return await self.read_json()
def get_single_variant(self, error_msg): async def get_single_variant(self, error_msg):
variants = self.get_variants() variants = await self.get_variants()
if len(variants) != 1: if len(variants) != 1:
print(error_msg) print(error_msg)
table = PrettyTable() table = PrettyTable()
@ -152,17 +158,17 @@ class Client:
sys.exit(1) sys.exit(1)
return variants[0][0] return variants[0][0]
def get_json(self, variant): async def get_json(self, variant):
self.send_command("GET_JSON", variant) await self.send_command("GET_JSON", variant)
reply = self.read_reply() reply = await self.read_reply()
if reply[0] != "OK": if reply[0] != "OK":
return reply[0], None return reply[0], None
length = int(reply[1]) length = int(reply[1])
json_str = self.fsocket.read(length).decode("ascii") json_str = (await self.reader.read(length)).decode("ascii")
return "OK", json_str return "OK", json_str
def main(): def get_argparser():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument("--server", default="afws.m-labs.hk", help="server to connect to (default: %(default)s)") parser.add_argument("--server", default="afws.m-labs.hk", help="server to connect to (default: %(default)s)")
parser.add_argument("--port", default=80, type=int, help="port to connect to (default: %(default)d)") parser.add_argument("--port", default=80, type=int, help="port to connect to (default: %(default)d)")
@ -183,9 +189,13 @@ def main():
act_get_json.add_argument("variant", nargs="?", default=None, help="variant to get (can be omitted if user is authorised to build only one)") act_get_json.add_argument("variant", nargs="?", default=None, help="variant to get (can be omitted if user is authorised to build only one)")
act_get_json.add_argument("-o", "--out", default=None, help="output JSON file") act_get_json.add_argument("-o", "--out", default=None, help="output JSON file")
act_get_json.add_argument("-f", "--force", action="store_true", help="overwrite file if it already exists") act_get_json.add_argument("-f", "--force", action="store_true", help="overwrite file if it already exists")
args = parser.parse_args() return parser
async def main_async():
args = get_argparser().parse_args()
client = Client(args.server, args.port, args.cert) client = Client(args.server, args.port, args.cert)
await client.connect()
try: try:
if args.action == "build": if args.action == "build":
# do this before user enters password so errors are reported without unnecessary user action # do this before user enters password so errors are reported without unnecessary user action
@ -216,7 +226,7 @@ def main():
password = getpass("Current password: ") password = getpass("Current password: ")
else: else:
password = getpass() password = getpass()
if not client.login(args.username, password): if not await client.login(args.username, password):
print("Login failed") print("Login failed")
sys.exit(1) sys.exit(1)
@ -229,12 +239,12 @@ def main():
print("Passwords do not match") print("Passwords do not match")
password = getpass("New password: ") password = getpass("New password: ")
password_confirm = getpass("New password (again): ") password_confirm = getpass("New password (again): ")
if not client.passwd(password): if not await client.passwd(password):
print("Failed to change password") print("Failed to change password")
sys.exit(1) sys.exit(1)
elif args.action == "build": elif args.action == "build":
# build dir and version variables set up above # build dir and version variables set up above
result, contents = client.build(major_ver, rev, args.variant, args.log, args.experimental) result, contents = await client.build(major_ver, rev, args.variant, args.log, args.experimental)
if result != "OK": if result != "OK":
if result == "UNAUTHORIZED": if result == "UNAUTHORIZED":
print("You are not authorized to build this variant. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.") print("You are not authorized to build this variant. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.")
@ -245,7 +255,7 @@ def main():
sys.exit(1) sys.exit(1)
zip_unarchive(contents, args.directory) zip_unarchive(contents, args.directory)
elif args.action == "get_variants": elif args.action == "get_variants":
variants = client.get_variants() variants = await client.get_variants()
table = PrettyTable() table = PrettyTable()
table.field_names = ["Variant", "Expiry date"] table.field_names = ["Variant", "Expiry date"]
for variant in variants: for variant in variants:
@ -255,8 +265,8 @@ def main():
if args.variant: if args.variant:
variant = args.variant variant = args.variant
else: else:
variant = client.get_single_variant(error_msg="User can get JSON of more than 1 variant - need to specify") variant = await client.get_single_variant(error_msg="User can get JSON of more than 1 variant - need to specify")
result, json_str = client.get_json(variant) result, json_str = await client.get_json(variant)
if result != "OK": if result != "OK":
if result == "UNAUTHORIZED": if result == "UNAUTHORIZED":
print(f"You are not authorized to get JSON of variant {variant}. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.") print(f"You are not authorized to get JSON of variant {variant}. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.")
@ -272,8 +282,10 @@ def main():
else: else:
raise ValueError raise ValueError
finally: finally:
client.close() await client.close()
def main():
asyncio.run(main_async())
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -7,7 +7,7 @@ import os
import logging import logging
import sys import sys
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from qasync import QEventLoop from qasync import QEventLoop
from sipyco.asyncio_tools import atexit_register_coroutine from sipyco.asyncio_tools import atexit_register_coroutine
@ -68,9 +68,9 @@ class Browser(QtWidgets.QMainWindow):
browse_root, dataset_sub) browse_root, dataset_sub)
smgr.register(self.experiments) smgr.register(self.experiments)
self.experiments.setHorizontalScrollBarPolicy( self.experiments.setHorizontalScrollBarPolicy(
QtCore.Qt.ScrollBarAsNeeded) QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.experiments.setVerticalScrollBarPolicy( self.experiments.setVerticalScrollBarPolicy(
QtCore.Qt.ScrollBarAsNeeded) QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setCentralWidget(self.experiments) self.setCentralWidget(self.experiments)
self.files = files.FilesDock(dataset_sub, browse_root) self.files = files.FilesDock(dataset_sub, browse_root)
@ -91,29 +91,29 @@ class Browser(QtWidgets.QMainWindow):
self.log = log.LogDock(None, "log") self.log = log.LogDock(None, "log")
smgr.register(self.log) smgr.register(self.log)
self.log.setFeatures(self.log.DockWidgetMovable | self.log.setFeatures(self.log.DockWidgetFeature.DockWidgetMovable |
self.log.DockWidgetFloatable) self.log.DockWidgetFeature.DockWidgetFloatable)
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.files) self.addDockWidget(QtCore.Qt.DockWidgetArea.LeftDockWidgetArea, self.files)
self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.applets) self.addDockWidget(QtCore.Qt.DockWidgetArea.BottomDockWidgetArea, self.applets)
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.datasets) self.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, self.datasets)
self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.log) self.addDockWidget(QtCore.Qt.DockWidgetArea.BottomDockWidgetArea, self.log)
g = self.menuBar().addMenu("&Experiment") g = self.menuBar().addMenu("&Experiment")
a = QtWidgets.QAction("&Open", self) a = QtGui.QAction("&Open", self)
a.setIcon(QtWidgets.QApplication.style().standardIcon( a.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOpenButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton))
a.setShortcuts(QtGui.QKeySequence.Open) a.setShortcuts(QtGui.QKeySequence.StandardKey.Open)
a.setStatusTip("Open an experiment") a.setStatusTip("Open an experiment")
a.triggered.connect(self.experiments.select_experiment) a.triggered.connect(self.experiments.select_experiment)
g.addAction(a) g.addAction(a)
g = self.menuBar().addMenu("&View") g = self.menuBar().addMenu("&View")
a = QtWidgets.QAction("Cascade", self) a = QtGui.QAction("Cascade", self)
a.setStatusTip("Cascade experiment windows") a.setStatusTip("Cascade experiment windows")
a.triggered.connect(self.experiments.cascadeSubWindows) a.triggered.connect(self.experiments.cascadeSubWindows)
g.addAction(a) g.addAction(a)
a = QtWidgets.QAction("Tile", self) a = QtGui.QAction("Tile", self)
a.setStatusTip("Tile experiment windows") a.setStatusTip("Tile experiment windows")
a.triggered.connect(self.experiments.tileSubWindows) a.triggered.connect(self.experiments.tileSubWindows)
g.addAction(a) g.addAction(a)

View File

@ -40,10 +40,10 @@ def get_argparser():
parser = argparse.ArgumentParser(description="ARTIQ CLI client") parser = argparse.ArgumentParser(description="ARTIQ CLI client")
parser.add_argument( parser.add_argument(
"-s", "--server", default="::1", "-s", "--server", default="::1",
help="hostname or IP of the master to connect to") help="hostname or IP of the master to connect to (default: %(default)s)")
parser.add_argument( parser.add_argument(
"--port", default=None, type=int, "--port", default=None, type=int,
help="TCP port to use to connect to the master") help="TCP port to use to connect to the master (default: %(default)s)")
parser.add_argument("--version", action="version", parser.add_argument("--version", action="version",
version="ARTIQ v{}".format(artiq_version), version="ARTIQ v{}".format(artiq_version),
help="print the ARTIQ version number") help="print the ARTIQ version number")
@ -59,7 +59,8 @@ def get_argparser():
help="priority (higher value means sooner " help="priority (higher value means sooner "
"scheduling, default: %(default)s)") "scheduling, default: %(default)s)")
parser_add.add_argument("-t", "--timed", default=None, type=str, parser_add.add_argument("-t", "--timed", default=None, type=str,
help="set a due date for the experiment") help="set a due date for the experiment "
"(default: %(default)s)")
parser_add.add_argument("-f", "--flush", default=False, parser_add.add_argument("-f", "--flush", default=False,
action="store_true", action="store_true",
help="flush the pipeline before preparing " help="flush the pipeline before preparing "

View File

@ -7,7 +7,7 @@ import importlib
import os import os
import logging import logging
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from qasync import QEventLoop from qasync import QEventLoop
from sipyco.pc_rpc import AsyncioClient, Client from sipyco.pc_rpc import AsyncioClient, Client
@ -32,31 +32,31 @@ def get_argparser():
help="print the ARTIQ version number") help="print the ARTIQ version number")
parser.add_argument( parser.add_argument(
"-s", "--server", default="::1", "-s", "--server", default="::1",
help="hostname or IP of the master to connect to") help="hostname or IP of the master to connect to (default: %(default)s)")
parser.add_argument( parser.add_argument(
"--port-notify", default=3250, type=int, "--port-notify", default=3250, type=int,
help="TCP port to connect to for notifications") help="TCP port to connect to for notifications (default: %(default)s)")
parser.add_argument( parser.add_argument(
"--port-control", default=3251, type=int, "--port-control", default=3251, type=int,
help="TCP port to connect to for control") help="TCP port to connect to for control (default: %(default)s)")
parser.add_argument( parser.add_argument(
"--port-broadcast", default=1067, type=int, "--port-broadcast", default=1067, type=int,
help="TCP port to connect to for broadcasts") help="TCP port to connect to for broadcasts (default: %(default)s)")
parser.add_argument( parser.add_argument(
"--db-file", default=None, "--db-file", default=None,
help="database file for local GUI settings") help="database file for local GUI settings (default: %(default)s)")
parser.add_argument( parser.add_argument(
"-p", "--load-plugin", dest="plugin_modules", action="append", "-p", "--load-plugin", dest="plugin_modules", action="append",
help="Python module to load on startup") help="Python module to load on startup")
parser.add_argument( parser.add_argument(
"--analyzer-proxy-timeout", default=5, type=float, "--analyzer-proxy-timeout", default=5, type=float,
help="connection timeout to core analyzer proxy") help="connection timeout to core analyzer proxy (default: %(default)s)")
parser.add_argument( parser.add_argument(
"--analyzer-proxy-timer", default=5, type=float, "--analyzer-proxy-timer", default=5, type=float,
help="retry timer to core analyzer proxy") help="retry timer to core analyzer proxy (default: %(default)s)")
parser.add_argument( parser.add_argument(
"--analyzer-proxy-timer-backoff", default=1.1, type=float, "--analyzer-proxy-timer-backoff", default=1.1, type=float,
help="retry timer backoff multiplier to core analyzer proxy") help="retry timer backoff multiplier to core analyzer proxy, (default: %(default)s)")
common_args.verbosity_args(parser) common_args.verbosity_args(parser)
return parser return parser
@ -95,14 +95,15 @@ class MdiArea(QtWidgets.QMdiArea):
self.pixmap = QtGui.QPixmap(os.path.join( self.pixmap = QtGui.QPixmap(os.path.join(
artiq_dir, "gui", "logo_ver.svg")) artiq_dir, "gui", "logo_ver.svg"))
self.setActivationOrder(self.ActivationHistoryOrder) self.setActivationOrder(
QtWidgets.QMdiArea.WindowOrder.ActivationHistoryOrder)
self.tile = QtWidgets.QShortcut( self.tile = QtGui.QShortcut(
QtGui.QKeySequence('Ctrl+Shift+T'), self) QtGui.QKeySequence('Ctrl+Shift+T'), self)
self.tile.activated.connect( self.tile.activated.connect(
lambda: self.tileSubWindows()) lambda: self.tileSubWindows())
self.cascade = QtWidgets.QShortcut( self.cascade = QtGui.QShortcut(
QtGui.QKeySequence('Ctrl+Shift+C'), self) QtGui.QKeySequence('Ctrl+Shift+C'), self)
self.cascade.activated.connect( self.cascade.activated.connect(
lambda: self.cascadeSubWindows()) lambda: self.cascadeSubWindows())
@ -186,8 +187,8 @@ def main():
main_window = MainWindow(args.server if server_name is None else server_name) main_window = MainWindow(args.server if server_name is None else server_name)
smgr.register(main_window) smgr.register(main_window)
mdi_area = MdiArea() mdi_area = MdiArea()
mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) mdi_area.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded)
mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) mdi_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded)
main_window.setCentralWidget(mdi_area) main_window.setCentralWidget(mdi_area)
# create UI components # create UI components
@ -255,12 +256,12 @@ def main():
right_docks = [ right_docks = [
d_explorer, d_shortcuts, d_explorer, d_shortcuts,
d_datasets, d_applets, d_datasets, d_applets,
d_waveform, d_interactive_args # d_waveform, d_interactive_args
] ]
main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, right_docks[0]) main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, right_docks[0])
for d1, d2 in zip(right_docks, right_docks[1:]): for d1, d2 in zip(right_docks, right_docks[1:]):
main_window.tabifyDockWidget(d1, d2) main_window.tabifyDockWidget(d1, d2)
main_window.addDockWidget(QtCore.Qt.BottomDockWidgetArea, d_schedule) main_window.addDockWidget(QtCore.Qt.DockWidgetArea.BottomDockWidgetArea, d_schedule)
# load/initialize state # load/initialize state
if os.name == "nt": if os.name == "nt":

View File

@ -805,7 +805,7 @@ def process(output, primary_description, satellites):
processor(peripheral) processor(peripheral)
def main(): def get_argparser():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description="ARTIQ device database template builder") description="ARTIQ device database template builder")
parser.add_argument("--version", action="version", parser.add_argument("--version", action="version",
@ -819,8 +819,11 @@ def main():
default=[], metavar=("DESTINATION", "DESCRIPTION"), type=str, default=[], metavar=("DESTINATION", "DESCRIPTION"), type=str,
help="add DRTIO satellite at the given destination number with " help="add DRTIO satellite at the given destination number with "
"devices from the given JSON description") "devices from the given JSON description")
return parser
args = parser.parse_args()
def main():
args = get_argparser().parse_args()
primary_description = jsondesc.load(args.primary_description) primary_description = jsondesc.load(args.primary_description)

View File

@ -30,7 +30,8 @@ Valid actions:
* firmware: write firmware to flash * firmware: write firmware to flash
* load: load main gateware bitstream into device (volatile but fast) * load: load main gateware bitstream into device (volatile but fast)
* erase: erase flash memory * erase: erase flash memory
* start: trigger the target to (re)load its gateware bitstream from flash * start: trigger the target to (re)load its gateware bitstream from flash.
If your core device is reachable by network, prefer 'artiq_coremgmt reboot'.
Prerequisites: Prerequisites:

View File

@ -40,21 +40,21 @@ def get_argparser():
group = parser.add_argument_group("databases") group = parser.add_argument_group("databases")
group.add_argument("--device-db", default="device_db.py", group.add_argument("--device-db", default="device_db.py",
help="device database file (default: '%(default)s')") help="device database file (default: %(default)s)")
group.add_argument("--dataset-db", default="dataset_db.mdb", group.add_argument("--dataset-db", default="dataset_db.mdb",
help="dataset file (default: '%(default)s')") help="dataset file (default: %(default)s)")
group = parser.add_argument_group("repository") group = parser.add_argument_group("repository")
group.add_argument( group.add_argument(
"-g", "--git", default=False, action="store_true", "-g", "--git", default=False, action="store_true",
help="use the Git repository backend") help="use the Git repository backend (default: %(default)s)")
group.add_argument( group.add_argument(
"-r", "--repository", default="repository", "-r", "--repository", default="repository",
help="path to the repository (default: '%(default)s')") help="path to the repository (default: %(default)s)")
group.add_argument( group.add_argument(
"--experiment-subdir", default="", "--experiment-subdir", default="",
help=("path to the experiment folder from the repository root " help=("path to the experiment folder from the repository root "
"(default: '%(default)s')")) "(default: %(default)s)"))
log_args(parser) log_args(parser)
parser.add_argument("--name", parser.add_argument("--name",

View File

@ -1,6 +1,7 @@
from types import SimpleNamespace from types import SimpleNamespace
from migen import * from migen import *
from migen.genlib.cdc import MultiReg
from migen.genlib.resetsync import AsyncResetSynchronizer from migen.genlib.resetsync import AsyncResetSynchronizer
from misoc.interconnect.csr import * from misoc.interconnect.csr import *
@ -51,6 +52,7 @@ class SyncRTIO(Module):
def __init__(self, tsc, channels, lane_count=8, fifo_depth=128): def __init__(self, tsc, channels, lane_count=8, fifo_depth=128):
self.cri = cri.Interface() self.cri = cri.Interface()
self.async_errors = Record(async_errors_layout) self.async_errors = Record(async_errors_layout)
self.sed_spread_enable = Signal()
chan_fine_ts_width = max(max(rtlink.get_fine_ts_width(channel.interface.o) chan_fine_ts_width = max(max(rtlink.get_fine_ts_width(channel.interface.o)
for channel in channels), for channel in channels),
@ -61,10 +63,11 @@ class SyncRTIO(Module):
self.submodules.outputs = ClockDomainsRenamer("rio")( self.submodules.outputs = ClockDomainsRenamer("rio")(
SED(channels, tsc.glbl_fine_ts_width, SED(channels, tsc.glbl_fine_ts_width,
lane_count=lane_count, fifo_depth=fifo_depth, lane_count=lane_count, fifo_depth=fifo_depth,
enable_spread=True, fifo_high_watermark=0.75, fifo_high_watermark=0.75, report_buffer_space=True,
report_buffer_space=True, interface=self.cri)) interface=self.cri))
self.comb += self.outputs.coarse_timestamp.eq(tsc.coarse_ts) self.comb += self.outputs.coarse_timestamp.eq(tsc.coarse_ts)
self.sync += self.outputs.minimum_coarse_timestamp.eq(tsc.coarse_ts + 16) self.sync += self.outputs.minimum_coarse_timestamp.eq(tsc.coarse_ts + 16)
self.specials += MultiReg(self.sed_spread_enable, self.outputs.enable_spread, "rio")
self.submodules.inputs = ClockDomainsRenamer("rio")( self.submodules.inputs = ClockDomainsRenamer("rio")(
InputCollector(tsc, channels, interface=self.cri)) InputCollector(tsc, channels, interface=self.cri))
@ -78,6 +81,7 @@ class DRTIOSatellite(Module):
self.reset = CSRStorage(reset=1) self.reset = CSRStorage(reset=1)
self.reset_phy = CSRStorage(reset=1) self.reset_phy = CSRStorage(reset=1)
self.tsc_loaded = CSR() self.tsc_loaded = CSR()
self.sed_spread_enable = CSRStorage()
# master interface in the sys domain # master interface in the sys domain
self.cri = cri.Interface() self.cri = cri.Interface()
self.async_errors = Record(async_errors_layout) self.async_errors = Record(async_errors_layout)
@ -143,7 +147,7 @@ class DRTIOSatellite(Module):
self.rt_packet, tsc, self.async_errors) self.rt_packet, tsc, self.async_errors)
def get_csrs(self): def get_csrs(self):
return ([self.reset, self.reset_phy, self.tsc_loaded] + return ([self.reset, self.sed_spread_enable, self.reset_phy, self.tsc_loaded] +
self.link_layer.get_csrs() + self.link_stats.get_csrs() + self.link_layer.get_csrs() + self.link_stats.get_csrs() +
self.rt_errors.get_csrs()) self.rt_errors.get_csrs())

View File

@ -3,7 +3,7 @@ from operator import and_
from migen import * from migen import *
from migen.genlib.resetsync import AsyncResetSynchronizer from migen.genlib.resetsync import AsyncResetSynchronizer
from migen.genlib.cdc import BlindTransfer from migen.genlib.cdc import MultiReg, BlindTransfer
from misoc.interconnect.csr import * from misoc.interconnect.csr import *
from artiq.gateware.rtio import cri from artiq.gateware.rtio import cri
@ -18,6 +18,7 @@ class Core(Module, AutoCSR):
self.cri = cri.Interface() self.cri = cri.Interface()
self.reset = CSR() self.reset = CSR()
self.reset_phy = CSR() self.reset_phy = CSR()
self.sed_spread_enable = CSRStorage()
self.async_error = CSR(3) self.async_error = CSR(3)
self.collision_channel = CSRStatus(16) self.collision_channel = CSRStatus(16)
self.busy_channel = CSRStatus(16) self.busy_channel = CSRStatus(16)
@ -67,6 +68,7 @@ class Core(Module, AutoCSR):
self.submodules += outputs self.submodules += outputs
self.comb += outputs.coarse_timestamp.eq(tsc.coarse_ts) self.comb += outputs.coarse_timestamp.eq(tsc.coarse_ts)
self.sync += outputs.minimum_coarse_timestamp.eq(tsc.coarse_ts + 12) self.sync += outputs.minimum_coarse_timestamp.eq(tsc.coarse_ts + 12)
self.specials += MultiReg(self.sed_spread_enable.storage, outputs.enable_spread, "rio")
inputs = ClockDomainsRenamer("rio")(InputCollector(tsc, channels, inputs = ClockDomainsRenamer("rio")(InputCollector(tsc, channels,
quash_channels=quash_channels, quash_channels=quash_channels,

View File

@ -27,6 +27,7 @@ class Fastino(Module):
# dac data words # dac data words
dacs = [Signal(16) for i in range(32)] dacs = [Signal(16) for i in range(32)]
self.probes = dacs
header = Record([ header = Record([
("cfg", 4), ("cfg", 4),

View File

@ -12,7 +12,7 @@ __all__ = ["SED"]
class SED(Module): class SED(Module):
def __init__(self, channels, glbl_fine_ts_width, def __init__(self, channels, glbl_fine_ts_width,
lane_count=8, fifo_depth=128, fifo_high_watermark=1.0, enable_spread=True, lane_count=8, fifo_depth=128, fifo_high_watermark=1.0,
quash_channels=[], report_buffer_space=False, interface=None): quash_channels=[], report_buffer_space=False, interface=None):
seqn_width = layouts.seqn_width(lane_count, fifo_depth) seqn_width = layouts.seqn_width(lane_count, fifo_depth)
@ -23,7 +23,6 @@ class SED(Module):
layouts.fifo_payload(channels), layouts.fifo_payload(channels),
[channel.interface.o.delay for channel in channels], [channel.interface.o.delay for channel in channels],
glbl_fine_ts_width, glbl_fine_ts_width,
enable_spread=enable_spread,
quash_channels=quash_channels, quash_channels=quash_channels,
interface=interface) interface=interface)
self.submodules.fifos = FIFOs(lane_count, fifo_depth, fifo_high_watermark, self.submodules.fifos = FIFOs(lane_count, fifo_depth, fifo_high_watermark,
@ -47,6 +46,10 @@ class SED(Module):
self.cri.o_buffer_space.eq(self.fifos.buffer_space) self.cri.o_buffer_space.eq(self.fifos.buffer_space)
] ]
@property
def enable_spread(self):
return self.lane_dist.enable_spread
@property @property
def cri(self): def cri(self):
return self.lane_dist.cri return self.lane_dist.cri

View File

@ -10,7 +10,7 @@ __all__ = ["LaneDistributor"]
class LaneDistributor(Module): class LaneDistributor(Module):
def __init__(self, lane_count, seqn_width, layout_payload, def __init__(self, lane_count, seqn_width, layout_payload,
compensation, glbl_fine_ts_width, compensation, glbl_fine_ts_width,
enable_spread=True, quash_channels=[], interface=None): quash_channels=[], interface=None):
if lane_count & (lane_count - 1): if lane_count & (lane_count - 1):
raise NotImplementedError("lane count must be a power of 2") raise NotImplementedError("lane count must be a power of 2")
@ -28,6 +28,8 @@ class LaneDistributor(Module):
self.output = [Record(layouts.fifo_ingress(seqn_width, layout_payload)) self.output = [Record(layouts.fifo_ingress(seqn_width, layout_payload))
for _ in range(lane_count)] for _ in range(lane_count)]
self.enable_spread = Signal()
# # # # # #
o_status_wait = Signal() o_status_wait = Signal()
@ -173,12 +175,11 @@ class LaneDistributor(Module):
] ]
# current lane has reached high watermark, spread events by switching to the next. # current lane has reached high watermark, spread events by switching to the next.
if enable_spread: self.sync += [
self.sync += [ If(self.enable_spread & (current_lane_high_watermark | ~current_lane_writable),
If(current_lane_high_watermark | ~current_lane_writable, force_laneB.eq(1)
force_laneB.eq(1) ),
), If(do_write,
If(do_write, force_laneB.eq(0)
force_laneB.eq(0) )
) ]
]

View File

@ -217,7 +217,10 @@ class Satellite(BaseSoC, AMPSoC):
# satellite (master-controlled) RTIO # satellite (master-controlled) RTIO
self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels, lane_count=sed_lanes) self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels, lane_count=sed_lanes)
self.comb += self.drtiosat.async_errors.eq(self.local_io.async_errors) self.comb += [
self.drtiosat.async_errors.eq(self.local_io.async_errors),
self.local_io.sed_spread_enable.eq(self.drtiosat.sed_spread_enable.storage)
]
# subkernel RTIO # subkernel RTIO
self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc)

View File

@ -597,7 +597,10 @@ class SatelliteBase(BaseSoC, AMPSoC):
# satellite (master-controlled) RTIO # satellite (master-controlled) RTIO
self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels, lane_count=sed_lanes) self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels, lane_count=sed_lanes)
self.comb += self.drtiosat.async_errors.eq(self.local_io.async_errors) self.comb += [
self.drtiosat.async_errors.eq(self.local_io.async_errors),
self.local_io.sed_spread_enable.eq(self.drtiosat.sed_spread_enable.storage)
]
# subkernel RTIO # subkernel RTIO
self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc)

View File

@ -468,7 +468,10 @@ class _SatelliteBase(BaseSoC, AMPSoC):
# DRTIO # DRTIO
self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels) self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels)
self.comb += self.drtiosat.async_errors.eq(self.local_io.async_errors) self.comb += [
self.drtiosat.async_errors.eq(self.local_io.async_errors),
self.local_io.sed_spread_enable.eq(self.drtiosat.sed_spread_enable.storage)
]
# subkernel RTIO # subkernel RTIO
self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc)

View File

@ -14,6 +14,7 @@ def simulate(input_events, compensation=None, wait=True):
if compensation is None: if compensation is None:
compensation = [0]*256 compensation = [0]*256
dut = lane_distributor.LaneDistributor(LANE_COUNT, 8, layout, compensation, 3) dut = lane_distributor.LaneDistributor(LANE_COUNT, 8, layout, compensation, 3)
dut.comb += dut.enable_spread.eq(1)
output = [] output = []
access_results = [] access_results = []

View File

@ -27,6 +27,7 @@ class DUT(Module):
self.sed.coarse_timestamp.eq(self.sed.coarse_timestamp + 1), self.sed.coarse_timestamp.eq(self.sed.coarse_timestamp + 1),
self.sed.minimum_coarse_timestamp.eq(self.sed.coarse_timestamp + 16) self.sed.minimum_coarse_timestamp.eq(self.sed.coarse_timestamp + 16)
] ]
self.comb += self.sed.enable_spread.eq(0)
def simulate(input_events, **kwargs): def simulate(input_events, **kwargs):
@ -110,6 +111,6 @@ class TestSED(unittest.TestCase):
input_events += [(now, 1), (now, 0)] input_events += [(now, 1), (now, 0)]
ttl_changes, access_results = simulate(input_events, ttl_changes, access_results = simulate(input_events,
lane_count=2, fifo_depth=2, enable_spread=False) lane_count=2, fifo_depth=2)
self.assertEqual([r[0] for r in access_results], ["ok"]*len(input_events)) self.assertEqual([r[0] for r in access_results], ["ok"]*len(input_events))
self.assertEqual(ttl_changes, list(range(40, 40+40*20, 10))) self.assertEqual(ttl_changes, list(range(40, 40+40*20, 10)))

View File

@ -9,7 +9,7 @@ from functools import partial
from itertools import count from itertools import count
from types import SimpleNamespace from types import SimpleNamespace
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from sipyco.pipe_ipc import AsyncioParentComm from sipyco.pipe_ipc import AsyncioParentComm
from sipyco.logging_tools import LogParser from sipyco.logging_tools import LogParser
@ -29,7 +29,7 @@ class EntryArea(EntryTreeWidget):
reset_all_button.setToolTip("Reset all to default values") reset_all_button.setToolTip("Reset all to default values")
reset_all_button.setIcon( reset_all_button.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_BrowserReload)) QtWidgets.QStyle.StandardPixmap.SP_BrowserReload))
reset_all_button.clicked.connect(self.reset_all) reset_all_button.clicked.connect(self.reset_all)
buttons = LayoutWidget() buttons = LayoutWidget()
buttons.layout.setColumnStretch(0, 1) buttons.layout.setColumnStretch(0, 1)
@ -137,7 +137,7 @@ class AppletIPCServer(AsyncioParentComm):
return return
self.write_pyon({"action": "mod", "mod": mod}) self.write_pyon({"action": "mod", "mod": mod})
async def serve(self, embed_cb, fix_initial_size_cb): async def serve(self, embed_cb):
self.dataset_sub.notify_cbs.append(self._on_mod) self.dataset_sub.notify_cbs.append(self._on_mod)
try: try:
while True: while True:
@ -145,10 +145,11 @@ class AppletIPCServer(AsyncioParentComm):
try: try:
action = obj["action"] action = obj["action"]
if action == "embed": if action == "embed":
embed_cb(obj["win_id"]) size = embed_cb(obj["win_id"])
self.write_pyon({"action": "embed_done"}) if size is None:
elif action == "fix_initial_size": self.write_pyon({"action": "embed_done"})
fix_initial_size_cb() else:
self.write_pyon({"action": "embed_done", "size_h": size.height(), "size_w": size.width()})
elif action == "subscribe": elif action == "subscribe":
self.datasets = obj["datasets"] self.datasets = obj["datasets"]
self.dataset_prefixes = obj["dataset_prefixes"] self.dataset_prefixes = obj["dataset_prefixes"]
@ -176,9 +177,9 @@ class AppletIPCServer(AsyncioParentComm):
finally: finally:
self.dataset_sub.notify_cbs.remove(self._on_mod) self.dataset_sub.notify_cbs.remove(self._on_mod)
def start_server(self, embed_cb, fix_initial_size_cb, *, loop=None): def start_server(self, embed_cb, *, loop=None):
self.server_task = asyncio.ensure_future( self.server_task = asyncio.ensure_future(
self.serve(embed_cb, fix_initial_size_cb), loop=loop) self.serve(embed_cb), loop=loop)
async def stop_server(self): async def stop_server(self):
if hasattr(self, "server_task"): if hasattr(self, "server_task"):
@ -239,7 +240,7 @@ class _AppletDock(QDockWidgetCloseDetect):
asyncio.ensure_future( asyncio.ensure_future(
LogParser(self._get_log_source).stream_task( LogParser(self._get_log_source).stream_task(
self.ipc.process.stderr)) self.ipc.process.stderr))
self.ipc.start_server(self.embed, self.fix_initial_size) self.ipc.start_server(self.embed)
finally: finally:
self.starting_stopping = False self.starting_stopping = False
@ -268,11 +269,9 @@ class _AppletDock(QDockWidgetCloseDetect):
self.embed_widget = QtWidgets.QWidget.createWindowContainer( self.embed_widget = QtWidgets.QWidget.createWindowContainer(
self.embed_window) self.embed_window)
self.setWidget(self.embed_widget) self.setWidget(self.embed_widget)
# return the size after embedding. Applet must resize to that,
# HACK: This function would not be needed if Qt window embedding # otherwise the applet may not fit within the dock properly.
# worked correctly. return self.embed_widget.size()
def fix_initial_size(self):
self.embed_window.resize(self.embed_widget.size())
async def terminate(self, delete_self=True): async def terminate(self, delete_self=True):
if self.starting_stopping: if self.starting_stopping:
@ -388,8 +387,8 @@ class _CompleterDelegate(QtWidgets.QStyledItemDelegate):
completer = QtWidgets.QCompleter() completer = QtWidgets.QCompleter()
completer.splitPath = lambda path: path.replace("/", ".").split(".") completer.splitPath = lambda path: path.replace("/", ".").split(".")
completer.setModelSorting( completer.setModelSorting(
QtWidgets.QCompleter.CaseSensitivelySortedModel) QtWidgets.QCompleter.ModelSorting.CaseSensitivelySortedModel)
completer.setCompletionRole(QtCore.Qt.DisplayRole) completer.setCompletionRole(QtCore.Qt.ItemDataRole.DisplayRole)
if hasattr(self, "model"): if hasattr(self, "model"):
# "TODO: Optimize updates in the source model" # "TODO: Optimize updates in the source model"
# - Qt (qcompleter.cpp), never ceasing to disappoint. # - Qt (qcompleter.cpp), never ceasing to disappoint.
@ -420,8 +419,8 @@ class AppletsDock(QtWidgets.QDockWidget):
""" """
QtWidgets.QDockWidget.__init__(self, "Applets") QtWidgets.QDockWidget.__init__(self, "Applets")
self.setObjectName("Applets") self.setObjectName("Applets")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
self.main_window = main_window self.main_window = main_window
self.dataset_sub = dataset_sub self.dataset_sub = dataset_sub
@ -435,18 +434,18 @@ class AppletsDock(QtWidgets.QDockWidget):
self.table = QtWidgets.QTreeWidget() self.table = QtWidgets.QTreeWidget()
self.table.setColumnCount(2) self.table.setColumnCount(2)
self.table.setHeaderLabels(["Name", "Command"]) self.table.setHeaderLabels(["Name", "Command"])
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) self.table.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
self.table.header().setStretchLastSection(True) self.table.header().setStretchLastSection(True)
self.table.header().setSectionResizeMode( self.table.header().setSectionResizeMode(
QtWidgets.QHeaderView.ResizeToContents) QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
self.table.setTextElideMode(QtCore.Qt.ElideNone) self.table.setTextElideMode(QtCore.Qt.TextElideMode.ElideNone)
self.table.setDragEnabled(True) self.table.setDragEnabled(True)
self.table.viewport().setAcceptDrops(True) self.table.viewport().setAcceptDrops(True)
self.table.setDropIndicatorShown(True) self.table.setDropIndicatorShown(True)
self.table.setDragDropMode(QtWidgets.QAbstractItemView.InternalMove) self.table.setDragDropMode(QtWidgets.QAbstractItemView.DragDropMode.InternalMove)
self.setWidget(self.table) self.setWidget(self.table)
@ -454,44 +453,44 @@ class AppletsDock(QtWidgets.QDockWidget):
self.table.setItemDelegateForColumn(1, completer_delegate) self.table.setItemDelegateForColumn(1, completer_delegate)
dataset_sub.add_setmodel_callback(completer_delegate.set_model) dataset_sub.add_setmodel_callback(completer_delegate.set_model)
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
new_action = QtWidgets.QAction("New applet", self.table) new_action = QtGui.QAction("New applet", self.table)
new_action.triggered.connect(partial(self.new_with_parent, self.new)) new_action.triggered.connect(partial(self.new_with_parent, self.new))
self.table.addAction(new_action) self.table.addAction(new_action)
templates_menu = QtWidgets.QMenu() templates_menu = QtWidgets.QMenu(self.table)
for name, template in _templates: for name, template in _templates:
spec = {"ty": "command", "command": template} spec = {"ty": "command", "command": template}
action = QtWidgets.QAction(name, self.table) action = QtGui.QAction(name, self.table)
action.triggered.connect(partial( action.triggered.connect(partial(
self.new_with_parent, self.new, spec=spec)) self.new_with_parent, self.new, spec=spec))
templates_menu.addAction(action) templates_menu.addAction(action)
restart_action = QtWidgets.QAction("New applet from template", self.table) restart_action = QtGui.QAction("New applet from template", self.table)
restart_action.setMenu(templates_menu) restart_action.setMenu(templates_menu)
self.table.addAction(restart_action) self.table.addAction(restart_action)
restart_action = QtWidgets.QAction("Restart selected applet or group", self.table) restart_action = QtGui.QAction("Restart selected applet or group", self.table)
restart_action.setShortcut("CTRL+R") restart_action.setShortcut("CTRL+R")
restart_action.setShortcutContext(QtCore.Qt.WidgetShortcut) restart_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
restart_action.triggered.connect(self.restart) restart_action.triggered.connect(self.restart)
self.table.addAction(restart_action) self.table.addAction(restart_action)
delete_action = QtWidgets.QAction("Delete selected applet or group", self.table) delete_action = QtGui.QAction("Delete selected applet or group", self.table)
delete_action.setShortcut("DELETE") delete_action.setShortcut("DELETE")
delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut) delete_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
delete_action.triggered.connect(self.delete) delete_action.triggered.connect(self.delete)
self.table.addAction(delete_action) self.table.addAction(delete_action)
close_nondocked_action = QtWidgets.QAction("Close non-docked applets", self.table) close_nondocked_action = QtGui.QAction("Close non-docked applets", self.table)
close_nondocked_action.setShortcut("CTRL+ALT+W") close_nondocked_action.setShortcut("CTRL+ALT+W")
close_nondocked_action.setShortcutContext(QtCore.Qt.ApplicationShortcut) close_nondocked_action.setShortcutContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)
close_nondocked_action.triggered.connect(self.close_nondocked) close_nondocked_action.triggered.connect(self.close_nondocked)
self.table.addAction(close_nondocked_action) self.table.addAction(close_nondocked_action)
new_group_action = QtWidgets.QAction("New group", self.table) new_group_action = QtGui.QAction("New group", self.table)
new_group_action.triggered.connect(partial(self.new_with_parent, self.new_group)) new_group_action.triggered.connect(partial(self.new_with_parent, self.new_group))
self.table.addAction(new_group_action) self.table.addAction(new_group_action)
self.table.itemChanged.connect(self.item_changed) self.table.itemChanged.connect(self.item_changed)
# HACK # HACK
self.table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) self.table.setEditTriggers(QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers)
self.table.itemDoubleClicked.connect(self.open_editor) self.table.itemDoubleClicked.connect(self.open_editor)
def open_editor(self, item, column): def open_editor(self, item, column):
@ -518,7 +517,7 @@ class AppletsDock(QtWidgets.QDockWidget):
del item.applet_code del item.applet_code
elif spec["ty"] == "code": elif spec["ty"] == "code":
item.setIcon(1, QtWidgets.QApplication.style().standardIcon( item.setIcon(1, QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileIcon)) QtWidgets.QStyle.StandardPixmap.SP_FileIcon))
item.applet_code = spec["code"] item.applet_code = spec["code"]
else: else:
raise ValueError raise ValueError
@ -530,7 +529,7 @@ class AppletsDock(QtWidgets.QDockWidget):
def create(self, item, name, spec): def create(self, item, name, spec):
dock = _AppletDock(self.dataset_sub, self.dataset_ctl, self.expmgr, item.applet_uid, name, spec, self.extra_substitutes) dock = _AppletDock(self.dataset_sub, self.dataset_ctl, self.expmgr, item.applet_uid, name, spec, self.extra_substitutes)
self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) self.main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock)
dock.setFloating(True) dock.setFloating(True)
asyncio.ensure_future(dock.start(), loop=self._loop) asyncio.ensure_future(dock.start(), loop=self._loop)
dock.sigClosed.connect(partial(self.on_dock_closed, item, dock)) dock.sigClosed.connect(partial(self.on_dock_closed, item, dock))
@ -547,7 +546,7 @@ class AppletsDock(QtWidgets.QDockWidget):
dock.spec = self.get_spec(item) dock.spec = self.get_spec(item)
if column == 0: if column == 0:
if item.checkState(0) == QtCore.Qt.Checked: if item.checkState(0) == QtCore.Qt.CheckState.Checked:
if item.applet_dock is None: if item.applet_dock is None:
name = item.text(0) name = item.text(0)
spec = self.get_spec(item) spec = self.get_spec(item)
@ -572,7 +571,7 @@ class AppletsDock(QtWidgets.QDockWidget):
def on_dock_closed(self, item, dock): def on_dock_closed(self, item, dock):
item.applet_geometry = dock.saveGeometry() item.applet_geometry = dock.saveGeometry()
asyncio.ensure_future(dock.terminate(), loop=self._loop) asyncio.ensure_future(dock.terminate(), loop=self._loop)
item.setCheckState(0, QtCore.Qt.Unchecked) item.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
def get_untitled(self): def get_untitled(self):
existing_names = set() existing_names = set()
@ -602,18 +601,18 @@ class AppletsDock(QtWidgets.QDockWidget):
name = self.get_untitled() name = self.get_untitled()
item = QtWidgets.QTreeWidgetItem([name, ""]) item = QtWidgets.QTreeWidgetItem([name, ""])
item.ty = "applet" item.ty = "applet"
item.setFlags(QtCore.Qt.ItemIsSelectable | item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable |
QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsUserCheckable |
QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemFlag.ItemIsEditable |
QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemFlag.ItemIsDragEnabled |
QtCore.Qt.ItemNeverHasChildren | QtCore.Qt.ItemFlag.ItemNeverHasChildren |
QtCore.Qt.ItemIsEnabled) QtCore.Qt.ItemFlag.ItemIsEnabled)
item.setCheckState(0, QtCore.Qt.Unchecked) item.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
item.applet_uid = uid item.applet_uid = uid
item.applet_dock = None item.applet_dock = None
item.applet_geometry = None item.applet_geometry = None
item.setIcon(0, QtWidgets.QApplication.style().standardIcon( item.setIcon(0, QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_ComputerIcon)) QtWidgets.QStyle.StandardPixmap.SP_ComputerIcon))
self.set_spec(item, spec) self.set_spec(item, spec)
if parent is None: if parent is None:
self.table.addTopLevelItem(item) self.table.addTopLevelItem(item)
@ -626,15 +625,15 @@ class AppletsDock(QtWidgets.QDockWidget):
name = self.get_untitled() name = self.get_untitled()
item = QtWidgets.QTreeWidgetItem([name, attr]) item = QtWidgets.QTreeWidgetItem([name, attr])
item.ty = "group" item.ty = "group"
item.setFlags(QtCore.Qt.ItemIsSelectable | item.setFlags(QtCore.Qt.ItemFlag.ItemIsSelectable |
QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemFlag.ItemIsEditable |
QtCore.Qt.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsUserCheckable |
QtCore.Qt.ItemIsTristate | QtCore.Qt.ItemFlag.ItemIsAutoTristate |
QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemFlag.ItemIsDragEnabled |
QtCore.Qt.ItemIsDropEnabled | QtCore.Qt.ItemFlag.ItemIsDropEnabled |
QtCore.Qt.ItemIsEnabled) QtCore.Qt.ItemFlag.ItemIsEnabled)
item.setIcon(0, QtWidgets.QApplication.style().standardIcon( item.setIcon(0, QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DirIcon)) QtWidgets.QStyle.StandardPixmap.SP_DirIcon))
if parent is None: if parent is None:
self.table.addTopLevelItem(item) self.table.addTopLevelItem(item)
else: else:
@ -712,7 +711,7 @@ class AppletsDock(QtWidgets.QDockWidget):
cwi = wi.child(row) cwi = wi.child(row)
if cwi.ty == "applet": if cwi.ty == "applet":
uid = cwi.applet_uid uid = cwi.applet_uid
enabled = cwi.checkState(0) == QtCore.Qt.Checked enabled = cwi.checkState(0) == QtCore.Qt.CheckState.Checked
name = cwi.text(0) name = cwi.text(0)
spec = self.get_spec(cwi) spec = self.get_spec(cwi)
geometry = cwi.applet_geometry geometry = cwi.applet_geometry
@ -744,7 +743,7 @@ class AppletsDock(QtWidgets.QDockWidget):
geometry = QtCore.QByteArray(geometry) geometry = QtCore.QByteArray(geometry)
item.applet_geometry = geometry item.applet_geometry = geometry
if enabled: if enabled:
item.setCheckState(0, QtCore.Qt.Checked) item.setCheckState(0, QtCore.Qt.CheckState.Checked)
elif wis[0] == "group": elif wis[0] == "group":
_, name, attr, expanded, state_child = wis _, name, attr, expanded, state_child = wis
item = self.new_group(name, attr, parent=parent) item = self.new_group(name, attr, parent=parent)
@ -761,11 +760,11 @@ class AppletsDock(QtWidgets.QDockWidget):
for i in range(wi.childCount()): for i in range(wi.childCount()):
cwi = wi.child(i) cwi = wi.child(i)
if cwi.ty == "applet": if cwi.ty == "applet":
if cwi.checkState(0) == QtCore.Qt.Checked: if cwi.checkState(0) == QtCore.Qt.CheckState.Checked:
if cwi.applet_dock is not None: if cwi.applet_dock is not None:
if not cwi.applet_dock.isFloating(): if not cwi.applet_dock.isFloating():
continue continue
cwi.setCheckState(0, QtCore.Qt.Unchecked) cwi.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
elif cwi.ty == "group": elif cwi.ty == "group":
walk(cwi) walk(cwi)
walk(self.table.invisibleRootItem()) walk(self.table.invisibleRootItem())

View File

@ -1,4 +1,4 @@
from PyQt5 import QtCore, QtWidgets, QtGui from PyQt6 import QtCore, QtWidgets, QtGui
from artiq.gui.flowlayout import FlowLayout from artiq.gui.flowlayout import FlowLayout
@ -10,7 +10,7 @@ class VDragDropSplitter(QtWidgets.QSplitter):
QtWidgets.QSplitter.__init__(self, parent=parent) QtWidgets.QSplitter.__init__(self, parent=parent)
self.setAcceptDrops(True) self.setAcceptDrops(True)
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
self.setOrientation(QtCore.Qt.Vertical) self.setOrientation(QtCore.Qt.Orientation.Vertical)
self.setChildrenCollapsible(False) self.setChildrenCollapsible(False)
def resetSizes(self): def resetSizes(self):
@ -78,7 +78,7 @@ class VDragScrollArea(QtWidgets.QScrollArea):
self._speed = speed self._speed = speed
def eventFilter(self, obj, e): def eventFilter(self, obj, e):
if e.type() == QtCore.QEvent.DragMove: if e.type() == QtCore.QEvent.Type.DragMove:
val = self.verticalScrollBar().value() val = self.verticalScrollBar().value()
height = self.viewport().height() height = self.viewport().height()
y = e.pos().y() y = e.pos().y()
@ -89,7 +89,7 @@ class VDragScrollArea(QtWidgets.QScrollArea):
self._direction = 1 self._direction = 1
if not self._timer.isActive(): if not self._timer.isActive():
self._timer.start() self._timer.start()
elif e.type() in (QtCore.QEvent.Drop, QtCore.QEvent.DragLeave): elif e.type() in (QtCore.QEvent.Type.Drop, QtCore.QEvent.Type.DragLeave):
self._timer.stop() self._timer.stop()
return False return False
@ -117,8 +117,8 @@ class DragDropFlowLayoutWidget(QtWidgets.QWidget):
return -1 return -1
def mousePressEvent(self, event): def mousePressEvent(self, event):
if event.buttons() == QtCore.Qt.LeftButton \ if event.buttons() == QtCore.Qt.MouseButton.LeftButton \
and event.modifiers() == QtCore.Qt.ShiftModifier: and event.modifiers() == QtCore.Qt.KeyboardModifier.ShiftModifier:
index = self._get_index(event.pos()) index = self._get_index(event.pos())
if index == -1: if index == -1:
return return
@ -127,7 +127,7 @@ class DragDropFlowLayoutWidget(QtWidgets.QWidget):
mime.setData("index", str(index).encode()) mime.setData("index", str(index).encode())
drag.setMimeData(mime) drag.setMimeData(mime)
pixmapi = QtWidgets.QApplication.style().standardIcon( pixmapi = QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileIcon) QtWidgets.QStyle.StandardPixmap.SP_FileIcon)
drag.setPixmap(pixmapi.pixmap(32)) drag.setPixmap(pixmapi.pixmap(32))
drag.exec_(QtCore.Qt.MoveAction) drag.exec_(QtCore.Qt.MoveAction)
event.accept() event.accept()

View File

@ -2,7 +2,7 @@ import logging
from collections import OrderedDict from collections import OrderedDict
from functools import partial from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from artiq.gui.tools import LayoutWidget, disable_scroll_wheel, WheelFilter from artiq.gui.tools import LayoutWidget, disable_scroll_wheel, WheelFilter
from artiq.gui.scanwidget import ScanWidget from artiq.gui.scanwidget import ScanWidget
@ -82,14 +82,14 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
reset_entry.setToolTip("Reset to default value") reset_entry.setToolTip("Reset to default value")
reset_entry.setIcon( reset_entry.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_BrowserReload)) QtWidgets.QStyle.StandardPixmap.SP_BrowserReload))
reset_entry.clicked.connect(partial(self.reset_entry, key)) reset_entry.clicked.connect(partial(self.reset_entry, key))
disable_other_scans = QtWidgets.QToolButton() disable_other_scans = QtWidgets.QToolButton()
widgets["disable_other_scans"] = disable_other_scans widgets["disable_other_scans"] = disable_other_scans
disable_other_scans.setIcon( disable_other_scans.setIcon(
QtWidgets.QApplication.style().standardIcon( QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogResetButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogResetButton))
disable_other_scans.setToolTip("Disable other scans") disable_other_scans.setToolTip("Disable other scans")
disable_other_scans.clicked.connect( disable_other_scans.clicked.connect(
partial(self._disable_other_scans, key)) partial(self._disable_other_scans, key))
@ -540,7 +540,8 @@ class _ExplicitScan(LayoutWidget):
float_regexp = r"(([+-]?\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)" float_regexp = r"(([+-]?\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?)"
regexp = "(float)?( +float)* *".replace("float", float_regexp) regexp = "(float)?( +float)* *".replace("float", float_regexp)
self.value.setValidator(QtGui.QRegExpValidator(QtCore.QRegExp(regexp))) self.value.setValidator(QtGui.QRegularExpressionValidator(
QtCore.QRegularExpression(regexp)))
self.value.setText(" ".join([str(x) for x in state["sequence"]])) self.value.setText(" ".join([str(x) for x in state["sequence"]]))
def update(text): def update(text):

View File

@ -39,9 +39,8 @@
############################################################################# #############################################################################
from PyQt5.QtCore import QPoint, QRect, QSize, Qt from PyQt6.QtCore import QPoint, QRect, QSize, Qt
from PyQt5.QtWidgets import (QApplication, QLayout, QPushButton, QSizePolicy, from PyQt6.QtWidgets import QLayout, QSizePolicy
QWidget)
class FlowLayout(QLayout): class FlowLayout(QLayout):
@ -113,8 +112,8 @@ class FlowLayout(QLayout):
for item in self.itemList: for item in self.itemList:
wid = item.widget() wid = item.widget()
spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Horizontal) spaceX = self.spacing() + wid.style().layoutSpacing(QSizePolicy.ControlType.PushButton, QSizePolicy.ControlType.PushButton, Qt.Orientation.Horizontal)
spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.PushButton, QSizePolicy.PushButton, Qt.Vertical) spaceY = self.spacing() + wid.style().layoutSpacing(QSizePolicy.ControlType.PushButton, QSizePolicy.ControlType.PushButton, Qt.Orientation.Vertical)
nextX = x + item.sizeHint().width() + spaceX nextX = x + item.sizeHint().width() + spaceX
if nextX - spaceX > rect.right() and lineHeight > 0: if nextX - spaceX > rect.right() and lineHeight > 0:
x = rect.x() x = rect.x()

View File

@ -2,7 +2,7 @@ import re
from functools import partial from functools import partial
from typing import List, Tuple from typing import List, Tuple
from PyQt5 import QtCore, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from artiq.gui.tools import LayoutWidget from artiq.gui.tools import LayoutWidget
@ -19,7 +19,7 @@ class FuzzySelectWidget(LayoutWidget):
#: Raised when an entry has been selected, giving the label of the user #: Raised when an entry has been selected, giving the label of the user
#: choice and any additional QEvent.modifiers() (e.g. Ctrl key pressed). #: choice and any additional QEvent.modifiers() (e.g. Ctrl key pressed).
finished = QtCore.pyqtSignal(str, int) finished = QtCore.pyqtSignal(str, QtCore.Qt.KeyboardModifier)
def __init__(self, def __init__(self,
choices: List[Tuple[str, int]] = [], choices: List[Tuple[str, int]] = [],
@ -138,16 +138,16 @@ class FuzzySelectWidget(LayoutWidget):
first_action = None first_action = None
last_action = None last_action = None
for choice in filtered_choices: for choice in filtered_choices:
action = QtWidgets.QAction(choice, self.menu) action = QtGui.QAction(choice, self.menu)
action.triggered.connect(partial(self._finish, action, choice)) action.triggered.connect(partial(self._finish, action, choice))
action.modifiers = 0 action.modifiers = QtCore.Qt.KeyboardModifier.NoModifier
self.menu.addAction(action) self.menu.addAction(action)
if not first_action: if not first_action:
first_action = action first_action = action
last_action = action last_action = action
if num_omitted > 0: if num_omitted > 0:
action = QtWidgets.QAction("<{} not shown>".format(num_omitted), action = QtGui.QAction("<{} not shown>".format(num_omitted),
self.menu) self.menu)
action.setEnabled(False) action.setEnabled(False)
self.menu.addAction(action) self.menu.addAction(action)
@ -239,9 +239,9 @@ class _FocusEventFilter(QtCore.QObject):
focus_lost = QtCore.pyqtSignal() focus_lost = QtCore.pyqtSignal()
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.FocusIn: if event.type() == QtCore.QEvent.Type.FocusIn:
self.focus_gained.emit() self.focus_gained.emit()
elif event.type() == QtCore.QEvent.FocusOut: elif event.type() == QtCore.QEvent.Type.FocusOut:
self.focus_lost.emit() self.focus_lost.emit()
return False return False
@ -251,8 +251,8 @@ class _EscapeKeyFilter(QtCore.QObject):
escape_pressed = QtCore.pyqtSignal() escape_pressed = QtCore.pyqtSignal()
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.KeyPress: if event.type() == QtCore.QEvent.Type.KeyPress:
if event.key() == QtCore.Qt.Key_Escape: if event.key() == QtCore.Qt.Key.Key_Escape:
self.escape_pressed.emit() self.escape_pressed.emit()
return False return False
@ -266,13 +266,13 @@ class _UpDownKeyFilter(QtCore.QObject):
self.last_item = last_item self.last_item = last_item
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.KeyPress: if event.type() == QtCore.QEvent.Type.KeyPress:
if event.key() == QtCore.Qt.Key_Down: if event.key() == QtCore.Qt.Key.Key_Down:
self.menu.setActiveAction(self.first_item) self.menu.setActiveAction(self.first_item)
self.menu.setFocus() self.menu.setFocus()
return True return True
if event.key() == QtCore.Qt.Key_Up: if event.key() == QtCore.Qt.Key.Key_Up:
self.menu.setActiveAction(self.last_item) self.menu.setActiveAction(self.last_item)
self.menu.setFocus() self.menu.setFocus()
return True return True
@ -286,16 +286,16 @@ class _NonUpDownKeyFilter(QtCore.QObject):
self.target = target self.target = target
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if event.type() == QtCore.QEvent.KeyPress: if event.type() == QtCore.QEvent.Type.KeyPress:
k = event.key() k = event.key()
if k in (QtCore.Qt.Key_Return, QtCore.Qt.Key_Enter): if k in (QtCore.Qt.Key.Key_Return, QtCore.Qt.Key.Key_Enter):
action = obj.activeAction() action = obj.activeAction()
if action is not None: if action is not None:
action.modifiers = event.modifiers() action.modifiers = event.modifiers()
return False return False
if (k != QtCore.Qt.Key_Down and k != QtCore.Qt.Key_Up if (k != QtCore.Qt.Key.Key_Down and k != QtCore.Qt.Key.Key_Up
and k != QtCore.Qt.Key_Enter and k != QtCore.Qt.Key.Key_Enter
and k != QtCore.Qt.Key_Return): and k != QtCore.Qt.Key.Key_Return):
QtWidgets.QApplication.sendEvent(self.target, event) QtWidgets.QApplication.sendEvent(self.target, event)
return True return True
return False return False

View File

@ -1,9 +1,8 @@
import logging import logging
import time import time
import re
from functools import partial from functools import partial
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
from sipyco.logging_tools import SourceFilter from sipyco.logging_tools import SourceFilter
from artiq.gui.tools import (LayoutWidget, log_level_to_name, from artiq.gui.tools import (LayoutWidget, log_level_to_name,
@ -20,7 +19,7 @@ class _ModelItem:
class _LogFilterProxyModel(QtCore.QSortFilterProxyModel): class _LogFilterProxyModel(QtCore.QSortFilterProxyModel):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive) self.setFilterCaseSensitivity(QtCore.Qt.CaseSensitivity.CaseInsensitive)
self.setRecursiveFilteringEnabled(True) self.setRecursiveFilteringEnabled(True)
self.filter_level = 0 self.filter_level = 0
@ -28,13 +27,13 @@ class _LogFilterProxyModel(QtCore.QSortFilterProxyModel):
source = self.sourceModel() source = self.sourceModel()
index0 = source.index(source_row, 0, source_parent) index0 = source.index(source_row, 0, source_parent)
index1 = source.index(source_row, 1, source_parent) index1 = source.index(source_row, 1, source_parent)
level = source.data(index0, QtCore.Qt.UserRole) level = source.data(index0, QtCore.Qt.ItemDataRole.UserRole)
if level >= self.filter_level: if level >= self.filter_level:
regex = self.filterRegExp() regex = self.filterRegularExpression()
index0_text = source.data(index0, QtCore.Qt.DisplayRole) index0_text = source.data(index0, QtCore.Qt.ItemDataRole.DisplayRole)
msg_text = source.data(index1, QtCore.Qt.DisplayRole) msg_text = source.data(index1, QtCore.Qt.ItemDataRole.DisplayRole)
return (regex.indexIn(index0_text) != -1 or regex.indexIn(msg_text) != -1) return (regex.match(index0_text).hasMatch() or regex.match(msg_text).hasMatch())
else: else:
return False return False
@ -57,7 +56,7 @@ class _Model(QtCore.QAbstractItemModel):
timer.timeout.connect(self.timer_tick) timer.timeout.connect(self.timer_tick)
timer.start(100) timer.start(100)
self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.FixedFont) self.fixed_font = QtGui.QFontDatabase.systemFont(QtGui.QFontDatabase.SystemFont.FixedFont)
self.white = QtGui.QBrush(QtGui.QColor(255, 255, 255)) self.white = QtGui.QBrush(QtGui.QColor(255, 255, 255))
self.black = QtGui.QBrush(QtGui.QColor(0, 0, 0)) self.black = QtGui.QBrush(QtGui.QColor(0, 0, 0))
@ -66,8 +65,8 @@ class _Model(QtCore.QAbstractItemModel):
self.error_bg = QtGui.QBrush(QtGui.QColor(255, 150, 150)) self.error_bg = QtGui.QBrush(QtGui.QColor(255, 150, 150))
def headerData(self, col, orientation, role): def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Horizontal if (orientation == QtCore.Qt.Orientation.Horizontal
and role == QtCore.Qt.DisplayRole): and role == QtCore.Qt.ItemDataRole.DisplayRole):
return self.headers[col] return self.headers[col]
return None return None
@ -155,9 +154,9 @@ class _Model(QtCore.QAbstractItemModel):
else: else:
msgnum = item.parent.row msgnum = item.parent.row
if role == QtCore.Qt.FontRole and index.column() == 1: if role == QtCore.Qt.ItemDataRole.FontRole and index.column() == 1:
return self.fixed_font return self.fixed_font
elif role == QtCore.Qt.BackgroundRole: elif role == QtCore.Qt.ItemDataRole.BackgroundRole:
level = self.entries[msgnum][0] level = self.entries[msgnum][0]
if level >= logging.ERROR: if level >= logging.ERROR:
return self.error_bg return self.error_bg
@ -165,13 +164,13 @@ class _Model(QtCore.QAbstractItemModel):
return self.warning_bg return self.warning_bg
else: else:
return self.white return self.white
elif role == QtCore.Qt.ForegroundRole: elif role == QtCore.Qt.ItemDataRole.ForegroundRole:
level = self.entries[msgnum][0] level = self.entries[msgnum][0]
if level <= logging.DEBUG: if level <= logging.DEBUG:
return self.debug_fg return self.debug_fg
else: else:
return self.black return self.black
elif role == QtCore.Qt.DisplayRole: elif role == QtCore.Qt.ItemDataRole.DisplayRole:
v = self.entries[msgnum] v = self.entries[msgnum]
column = index.column() column = index.column()
if item.parent is self: if item.parent is self:
@ -184,7 +183,7 @@ class _Model(QtCore.QAbstractItemModel):
return "" return ""
else: else:
return v[3][item.row+1] return v[3][item.row+1]
elif role == QtCore.Qt.ToolTipRole: elif role == QtCore.Qt.ItemDataRole.ToolTipRole:
v = self.entries[msgnum] v = self.entries[msgnum]
if item.parent is self: if item.parent is self:
lineno = 0 lineno = 0
@ -193,7 +192,7 @@ class _Model(QtCore.QAbstractItemModel):
return (log_level_to_name(v[0]) + ", " + return (log_level_to_name(v[0]) + ", " +
time.strftime("%m/%d %H:%M:%S", time.localtime(v[2])) + time.strftime("%m/%d %H:%M:%S", time.localtime(v[2])) +
"\n" + v[3][lineno]) "\n" + v[3][lineno])
elif role == QtCore.Qt.UserRole: elif role == QtCore.Qt.ItemDataRole.UserRole:
return self.entries[msgnum][0] return self.entries[msgnum][0]
@ -218,13 +217,13 @@ class LogDock(QDockWidgetCloseDetect):
scrollbottom = QtWidgets.QToolButton() scrollbottom = QtWidgets.QToolButton()
scrollbottom.setToolTip("Scroll to bottom") scrollbottom.setToolTip("Scroll to bottom")
scrollbottom.setIcon(QtWidgets.QApplication.style().standardIcon( scrollbottom.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_ArrowDown)) QtWidgets.QStyle.StandardPixmap.SP_ArrowDown))
grid.addWidget(scrollbottom, 0, 3) grid.addWidget(scrollbottom, 0, 3)
scrollbottom.clicked.connect(self.scroll_to_bottom) scrollbottom.clicked.connect(self.scroll_to_bottom)
clear = QtWidgets.QToolButton() clear = QtWidgets.QToolButton()
clear.setIcon(QtWidgets.QApplication.style().standardIcon( clear.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogResetButton)) QtWidgets.QStyle.StandardPixmap.SP_DialogResetButton))
grid.addWidget(clear, 0, 4) grid.addWidget(clear, 0, 4)
clear.clicked.connect(lambda: self.model.clear()) clear.clicked.connect(lambda: self.model.clear())
@ -232,7 +231,7 @@ class LogDock(QDockWidgetCloseDetect):
newdock = QtWidgets.QToolButton() newdock = QtWidgets.QToolButton()
newdock.setToolTip("Create new log dock") newdock.setToolTip("Create new log dock")
newdock.setIcon(QtWidgets.QApplication.style().standardIcon( newdock.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogNewFolder)) QtWidgets.QStyle.StandardPixmap.SP_FileDialogNewFolder))
# note the lambda, the default parameter is overriden otherwise # note the lambda, the default parameter is overriden otherwise
newdock.clicked.connect(lambda: manager.create_new_dock()) newdock.clicked.connect(lambda: manager.create_new_dock())
grid.addWidget(newdock, 0, 5) grid.addWidget(newdock, 0, 5)
@ -240,27 +239,27 @@ class LogDock(QDockWidgetCloseDetect):
self.log = QtWidgets.QTreeView() self.log = QtWidgets.QTreeView()
self.log.setHorizontalScrollMode( self.log.setHorizontalScrollMode(
QtWidgets.QAbstractItemView.ScrollPerPixel) QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
self.log.setVerticalScrollMode( self.log.setVerticalScrollMode(
QtWidgets.QAbstractItemView.ScrollPerPixel) QtWidgets.QAbstractItemView.ScrollMode.ScrollPerPixel)
self.log.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarAsNeeded) self.log.setHorizontalScrollBarPolicy(QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded)
grid.addWidget(self.log, 1, 0, colspan=6 if manager else 5) grid.addWidget(self.log, 1, 0, colspan=6 if manager else 5)
self.scroll_at_bottom = False self.scroll_at_bottom = False
self.scroll_value = 0 self.scroll_value = 0
self.log.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.log.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
copy_action = QtWidgets.QAction("Copy entry to clipboard", self.log) copy_action = QtGui.QAction("Copy entry to clipboard", self.log)
copy_action.triggered.connect(self.copy_to_clipboard) copy_action.triggered.connect(self.copy_to_clipboard)
self.log.addAction(copy_action) self.log.addAction(copy_action)
clear_action = QtWidgets.QAction("Clear", self.log) clear_action = QtGui.QAction("Clear", self.log)
clear_action.triggered.connect(lambda: self.model.clear()) clear_action.triggered.connect(lambda: self.model.clear())
self.log.addAction(clear_action) self.log.addAction(clear_action)
# If Qt worked correctly, this would be nice to have. Alas, resizeSections # If Qt worked correctly, this would be nice to have. Alas, resizeSections
# is broken when the horizontal scrollbar is enabled. # is broken when the horizontal scrollbar is enabled.
# sizeheader_action = QtWidgets.QAction("Resize header", self.log) # sizeheader_action = QtGui.QAction("Resize header", self.log)
# sizeheader_action.triggered.connect( # sizeheader_action.triggered.connect(
# lambda: self.log.header().resizeSections(QtWidgets.QHeaderView.ResizeToContents)) # lambda: self.log.header().resizeSections(QtWidgets.QHeaderView.ResizeMode.ResizeToContents))
# self.log.addAction(sizeheader_action) # self.log.addAction(sizeheader_action)
cw = QtGui.QFontMetrics(self.font()).averageCharWidth() cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
@ -279,7 +278,7 @@ class LogDock(QDockWidgetCloseDetect):
self.filter_level.currentIndexChanged.connect(self.apply_level_filter) self.filter_level.currentIndexChanged.connect(self.apply_level_filter)
def apply_text_filter(self): def apply_text_filter(self):
self.proxy_model.setFilterRegExp(self.filter_freetext.text()) self.proxy_model.setFilterRegularExpression(self.filter_freetext.text())
def apply_level_filter(self): def apply_level_filter(self):
self.proxy_model.apply_filter_level(self.filter_level.currentText()) self.proxy_model.apply_filter_level(self.filter_level.currentText())
@ -366,7 +365,7 @@ class LogDockManager:
dock = LogDock(self, name) dock = LogDock(self, name)
self.docks[name] = dock self.docks[name] = dock
if add_to_area: if add_to_area:
self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) self.main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock)
dock.setFloating(True) dock.setFloating(True)
dock.sigClosed.connect(partial(self.on_dock_closed, name)) dock.sigClosed.connect(partial(self.on_dock_closed, name))
self.update_closable() self.update_closable()
@ -379,8 +378,8 @@ class LogDockManager:
self.update_closable() self.update_closable()
def update_closable(self): def update_closable(self):
flags = (QtWidgets.QDockWidget.DockWidgetMovable | flags = (QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable) QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
if len(self.docks) > 1: if len(self.docks) > 1:
flags |= QtWidgets.QDockWidget.DockWidgetClosable flags |= QtWidgets.QDockWidget.DockWidgetClosable
for dock in self.docks.values(): for dock in self.docks.values():
@ -396,7 +395,7 @@ class LogDockManager:
dock = LogDock(self, name) dock = LogDock(self, name)
self.docks[name] = dock self.docks[name] = dock
dock.restore_state(dock_state) dock.restore_state(dock_state)
self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) self.main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock)
dock.sigClosed.connect(partial(self.on_dock_closed, name)) dock.sigClosed.connect(partial(self.on_dock_closed, name))
self.update_closable() self.update_closable()

View File

@ -1,4 +1,4 @@
from PyQt5 import QtCore from PyQt6 import QtCore
from sipyco.sync_struct import Subscriber, process_mod from sipyco.sync_struct import Subscriber, process_mod
@ -91,15 +91,15 @@ class DictSyncModel(QtCore.QAbstractTableModel):
return len(self.headers) return len(self.headers)
def data(self, index, role): def data(self, index, role):
if not index.isValid() or role != QtCore.Qt.DisplayRole: if not index.isValid() or role != QtCore.Qt.ItemDataRole.DisplayRole:
return None return None
else: else:
k = self.row_to_key[index.row()] k = self.row_to_key[index.row()]
return self.convert(k, self.backing_store[k], index.column()) return self.convert(k, self.backing_store[k], index.column())
def headerData(self, col, orientation, role): def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Horizontal and if (orientation == QtCore.Qt.Orientation.Horizontal and
role == QtCore.Qt.DisplayRole): role == QtCore.Qt.ItemDataRole.DisplayRole):
return self.headers[col] return self.headers[col]
return None return None
@ -170,15 +170,15 @@ class ListSyncModel(QtCore.QAbstractTableModel):
return len(self.headers) return len(self.headers)
def data(self, index, role): def data(self, index, role):
if not index.isValid() or role != QtCore.Qt.DisplayRole: if not index.isValid() or role != QtCore.Qt.ItemDataRole.DisplayRole:
return None return None
else: else:
return self.convert(self.backing_store[index.row()], return self.convert(self.backing_store[index.row()],
index.column()) index.column())
def headerData(self, col, orientation, role): def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Horizontal and if (orientation == QtCore.Qt.Orientation.Horizontal and
role == QtCore.Qt.DisplayRole): role == QtCore.Qt.ItemDataRole.DisplayRole):
return self.headers[col] return self.headers[col]
return None return None
@ -271,8 +271,8 @@ class DictSyncTreeSepModel(QtCore.QAbstractItemModel):
return len(self.headers) return len(self.headers)
def headerData(self, col, orientation, role): def headerData(self, col, orientation, role):
if (orientation == QtCore.Qt.Horizontal and if (orientation == QtCore.Qt.Orientation.Horizontal and
role == QtCore.Qt.DisplayRole): role == QtCore.Qt.ItemDataRole.DisplayRole):
return self.headers[col] return self.headers[col]
return None return None
@ -394,19 +394,19 @@ class DictSyncTreeSepModel(QtCore.QAbstractItemModel):
return key return key
def data(self, index, role): def data(self, index, role):
if not index.isValid() or (role != QtCore.Qt.DisplayRole if not index.isValid() or (role != QtCore.Qt.ItemDataRole.DisplayRole
and role != QtCore.Qt.ToolTipRole): and role != QtCore.Qt.ItemDataRole.ToolTipRole):
return None return None
else: else:
column = index.column() column = index.column()
if column == 0 and role == QtCore.Qt.DisplayRole: if column == 0 and role == QtCore.Qt.ItemDataRole.DisplayRole:
return index.internalPointer().name return index.internalPointer().name
else: else:
key = self.index_to_key(index) key = self.index_to_key(index)
if key is None: if key is None:
return None return None
else: else:
if role == QtCore.Qt.DisplayRole: if role == QtCore.Qt.ItemDataRole.DisplayRole:
convert = self.convert convert = self.convert
else: else:
convert = self.convert_tooltip convert = self.convert_tooltip

View File

@ -1,6 +1,6 @@
import logging import logging
from PyQt5 import QtGui, QtCore, QtWidgets from PyQt6 import QtGui, QtCore, QtWidgets
import numpy as np import numpy as np
from .ticker import Ticker from .ticker import Ticker
@ -23,15 +23,15 @@ class ScanWidget(QtWidgets.QWidget):
self.ticker = Ticker() self.ticker = Ticker()
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
action = QtWidgets.QAction("V&iew range", self) action = QtGui.QAction("V&iew range", self)
action.setShortcut(QtGui.QKeySequence("CTRL+i")) action.setShortcut(QtGui.QKeySequence("CTRL+i"))
action.setShortcutContext(QtCore.Qt.WidgetShortcut) action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
action.triggered.connect(self.viewRange) action.triggered.connect(self.viewRange)
self.addAction(action) self.addAction(action)
action = QtWidgets.QAction("Sna&p range", self) action = QtGui.QAction("Sna&p range", self)
action.setShortcut(QtGui.QKeySequence("CTRL+p")) action.setShortcut(QtGui.QKeySequence("CTRL+p"))
action.setShortcutContext(QtCore.Qt.WidgetShortcut) action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
action.triggered.connect(self.snapRange) action.triggered.connect(self.snapRange)
self.addAction(action) self.addAction(action)
@ -143,56 +143,56 @@ class ScanWidget(QtWidgets.QWidget):
if ev.buttons() ^ ev.button(): # buttons changed if ev.buttons() ^ ev.button(): # buttons changed
ev.ignore() ev.ignore()
return return
if ev.modifiers() & QtCore.Qt.ShiftModifier: if ev.modifiers() & QtCore.Qt.KeyboardModifier.ShiftModifier:
self._drag = "select" self._drag = "select"
self.setStart(self._pixelToAxis(ev.x())) self.setStart(self._pixelToAxis(ev.position().x()))
self.setStop(self._start) self.setStop(self._start)
elif ev.modifiers() & QtCore.Qt.ControlModifier: elif ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
self._drag = "zoom" self._drag = "zoom"
self._offset = QtCore.QPoint(ev.x(), 0) self._offset = QtCore.QPoint(ev.position().x(), 0)
self._rubber = QtWidgets.QRubberBand( self._rubber = QtWidgets.QRubberBand(
QtWidgets.QRubberBand.Rectangle, self) QtWidgets.QRubberBand.Rectangle, self)
self._rubber.setGeometry(QtCore.QRect( self._rubber.setGeometry(QtCore.QRect(
self._offset, QtCore.QPoint(ev.x(), self.height() - 1))) self._offset, QtCore.QPoint(ev.position().x(), self.height() - 1)))
self._rubber.show() self._rubber.show()
else: else:
qfm = QtGui.QFontMetrics(self.font()) qfm = QtGui.QFontMetrics(self.font())
if ev.y() <= 2.5*qfm.lineSpacing(): if ev.position().y() <= 2.5*qfm.lineSpacing():
self._drag = "axis" self._drag = "axis"
self._offset = ev.x() - self._axisView[0] self._offset = ev.position().x() - self._axisView[0]
# testing should match inverse drawing order for start/stop # testing should match inverse drawing order for start/stop
elif abs(self._axisToPixel(self._stop) - elif abs(self._axisToPixel(self._stop) -
ev.x()) < qfm.lineSpacing()/2: ev.position().x()) < qfm.lineSpacing()/2:
self._drag = "stop" self._drag = "stop"
self._offset = ev.x() - self._axisToPixel(self._stop) self._offset = ev.position().x() - self._axisToPixel(self._stop)
elif abs(self._axisToPixel(self._start) - elif abs(self._axisToPixel(self._start) -
ev.x()) < qfm.lineSpacing()/2: ev.position().x()) < qfm.lineSpacing()/2:
self._drag = "start" self._drag = "start"
self._offset = ev.x() - self._axisToPixel(self._start) self._offset = ev.position().x() - self._axisToPixel(self._start)
else: else:
self._drag = "both" self._drag = "both"
self._offset = (ev.x() - self._axisToPixel(self._start), self._offset = (ev.position().x() - self._axisToPixel(self._start),
ev.x() - self._axisToPixel(self._stop)) ev.position().x() - self._axisToPixel(self._stop))
def mouseMoveEvent(self, ev): def mouseMoveEvent(self, ev):
if not self._drag: if not self._drag:
ev.ignore() ev.ignore()
return return
if self._drag == "select": if self._drag == "select":
self.setStop(self._pixelToAxis(ev.x())) self.setStop(self._pixelToAxis(ev.position().x()))
elif self._drag == "zoom": elif self._drag == "zoom":
self._rubber.setGeometry(QtCore.QRect( self._rubber.setGeometry(QtCore.QRect(
self._offset, QtCore.QPoint(ev.x(), self.height() - 1) self._offset, QtCore.QPoint(ev.position().x(), self.height() - 1)
).normalized()) ).normalized())
elif self._drag == "axis": elif self._drag == "axis":
self._setView(ev.x() - self._offset, self._axisView[1]) self._setView(ev.position().x() - self._offset, self._axisView[1])
elif self._drag == "start": elif self._drag == "start":
self.setStart(self._pixelToAxis(ev.x() - self._offset)) self.setStart(self._pixelToAxis(ev.position().x() - self._offset))
elif self._drag == "stop": elif self._drag == "stop":
self.setStop(self._pixelToAxis(ev.x() - self._offset)) self.setStop(self._pixelToAxis(ev.position().x() - self._offset))
elif self._drag == "both": elif self._drag == "both":
self.setStart(self._pixelToAxis(ev.x() - self._offset[0])) self.setStart(self._pixelToAxis(ev.position().x() - self._offset[0]))
self.setStop(self._pixelToAxis(ev.x() - self._offset[1])) self.setStop(self._pixelToAxis(ev.position().x() - self._offset[1]))
def mouseReleaseEvent(self, ev): def mouseReleaseEvent(self, ev):
if self._drag == "zoom": if self._drag == "zoom":
@ -217,10 +217,10 @@ class ScanWidget(QtWidgets.QWidget):
y = round(ev.angleDelta().y()/120.) y = round(ev.angleDelta().y()/120.)
if not y: if not y:
return return
if ev.modifiers() & QtCore.Qt.ShiftModifier: if ev.modifiers() & QtCore.Qt.KeyboardModifier.ShiftModifier:
self.setNum(max(1, self._num + y)) self.setNum(max(1, self._num + y))
else: else:
self._zoom(self.zoomFactor**y, ev.x()) self._zoom(self.zoomFactor**y, ev.position().x())
def resizeEvent(self, ev): def resizeEvent(self, ev):
if not ev.oldSize().isValid() or not ev.oldSize().width(): if not ev.oldSize().isValid() or not ev.oldSize().width():
@ -245,8 +245,8 @@ class ScanWidget(QtWidgets.QWidget):
ticks, prefix, labels = self.ticker(self._pixelToAxis(0), ticks, prefix, labels = self.ticker(self._pixelToAxis(0),
self._pixelToAxis(self.width())) self._pixelToAxis(self.width()))
rect = QtCore.QRect(0, 0, self.width(), lineSpacing) rect = QtCore.QRect(0, 0, self.width(), lineSpacing)
painter.drawText(rect, QtCore.Qt.AlignLeft, prefix) painter.drawText(rect, QtCore.Qt.AlignmentFlag.AlignLeft, prefix)
painter.drawText(rect, QtCore.Qt.AlignRight, self.suffix) painter.drawText(rect, QtCore.Qt.AlignmentFlag.AlignRight, self.suffix)
painter.translate(0, lineSpacing + ascent) painter.translate(0, lineSpacing + ascent)
@ -264,7 +264,7 @@ class ScanWidget(QtWidgets.QWidget):
painter.drawLine(int(p), 0, int(p), int(lineSpacing/2)) painter.drawLine(int(p), 0, int(p), int(lineSpacing/2))
painter.translate(0, int(lineSpacing/2)) painter.translate(0, int(lineSpacing/2))
for x, c in (self._start, QtCore.Qt.blue), (self._stop, QtCore.Qt.red): for x, c in (self._start, QtCore.Qt.GlobalColor.blue), (self._stop, QtCore.Qt.GlobalColor.red):
x = self._axisToPixel(x) x = self._axisToPixel(x)
painter.setPen(c) painter.setPen(c)
painter.setBrush(c) painter.setBrush(c)

View File

@ -1,6 +1,6 @@
import re import re
from math import inf, copysign from math import inf, copysign
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt6 import QtCore, QtGui, QtWidgets
_float_acceptable = re.compile( _float_acceptable = re.compile(
@ -16,8 +16,8 @@ class ScientificSpinBox(QtWidgets.QDoubleSpinBox):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.setGroupSeparatorShown(False) self.setGroupSeparatorShown(False)
self.setInputMethodHints(QtCore.Qt.ImhNone) self.setInputMethodHints(QtCore.Qt.InputMethodHint.ImhNone)
self.setCorrectionMode(self.CorrectToPreviousValue) self.setCorrectionMode(self.CorrectionMode.CorrectToPreviousValue)
# singleStep: resolution for step, buttons, accelerators # singleStep: resolution for step, buttons, accelerators
# decimals: absolute rounding granularity # decimals: absolute rounding granularity
# sigFigs: number of significant digits shown # sigFigs: number of significant digits shown
@ -69,11 +69,11 @@ class ScientificSpinBox(QtWidgets.QDoubleSpinBox):
clean = clean.rsplit(self.suffix(), 1)[0] clean = clean.rsplit(self.suffix(), 1)[0]
try: try:
float(clean) # faster than matching float(clean) # faster than matching
return QtGui.QValidator.Acceptable, text, pos return QtGui.QValidator.State.Acceptable, text, pos
except ValueError: except ValueError:
if re.fullmatch(_float_intermediate, clean): if re.fullmatch(_float_intermediate, clean):
return QtGui.QValidator.Intermediate, text, pos return QtGui.QValidator.State.Intermediate, text, pos
return QtGui.QValidator.Invalid, text, pos return QtGui.QValidator.State.Invalid, text, pos
def stepBy(self, s): def stepBy(self, s):
if abs(s) < 10: # unaccelerated buttons, keys, wheel/trackpad if abs(s) < 10: # unaccelerated buttons, keys, wheel/trackpad

View File

@ -1,7 +1,7 @@
import asyncio import asyncio
import logging import logging
from PyQt5 import QtCore, QtWidgets from PyQt6 import QtCore, QtWidgets
class DoubleClickLineEdit(QtWidgets.QLineEdit): class DoubleClickLineEdit(QtWidgets.QLineEdit):
@ -56,9 +56,9 @@ class WheelFilter(QtCore.QObject):
self.ignore_with_modifier = ignore_with_modifier self.ignore_with_modifier = ignore_with_modifier
def eventFilter(self, obj, event): def eventFilter(self, obj, event):
if event.type() != QtCore.QEvent.Wheel: if event.type() != QtCore.QEvent.Type.Wheel:
return False return False
has_modifier = event.modifiers() != QtCore.Qt.NoModifier has_modifier = event.modifiers() != QtCore.Qt.KeyboardModifier.NoModifier
if has_modifier == self.ignore_with_modifier: if has_modifier == self.ignore_with_modifier:
event.ignore() event.ignore()
return True return True
@ -66,7 +66,7 @@ class WheelFilter(QtCore.QObject):
def disable_scroll_wheel(widget): def disable_scroll_wheel(widget):
widget.setFocusPolicy(QtCore.Qt.StrongFocus) widget.setFocusPolicy(QtCore.Qt.FocusPolicy.StrongFocus)
widget.installEventFilter(WheelFilter(widget)) widget.installEventFilter(WheelFilter(widget))
@ -91,8 +91,8 @@ class LayoutWidget(QtWidgets.QWidget):
async def get_open_file_name(parent, caption, dir, filter): async def get_open_file_name(parent, caption, dir, filter):
"""like QtWidgets.QFileDialog.getOpenFileName(), but a coroutine""" """like QtWidgets.QFileDialog.getOpenFileName(), but a coroutine"""
dialog = QtWidgets.QFileDialog(parent, caption, dir, filter) dialog = QtWidgets.QFileDialog(parent, caption, dir, filter)
dialog.setFileMode(dialog.ExistingFile) dialog.setFileMode(dialog.FileMode.ExistingFile)
dialog.setAcceptMode(dialog.AcceptOpen) dialog.setAcceptMode(dialog.AcceptMode.AcceptOpen)
fut = asyncio.Future() fut = asyncio.Future()
def on_accept(): def on_accept():
@ -119,20 +119,3 @@ async def get_save_file_name(parent, caption, dir, filter, suffix=None):
dialog.open() dialog.open()
return await fut return await fut
# Based on:
# http://stackoverflow.com/questions/250890/using-qsortfilterproxymodel-with-a-tree-model
class QRecursiveFilterProxyModel(QtCore.QSortFilterProxyModel):
def filterAcceptsRow(self, source_row, source_parent):
regexp = self.filterRegExp()
if not regexp.isEmpty():
source_index = self.sourceModel().index(
source_row, self.filterKeyColumn(), source_parent)
if source_index.isValid():
for i in range(self.sourceModel().rowCount(source_index)):
if self.filterAcceptsRow(i, source_index):
return True
key = self.sourceModel().data(source_index, self.filterRole())
return regexp.indexIn(key) != -1
return QtCore.QSortFilterProxyModel.filterAcceptsRow(
self, source_row, source_parent)

View File

@ -27,9 +27,9 @@ SOFTWARE.
import math import math
from PyQt5.QtCore import * from PyQt6.QtCore import *
from PyQt5.QtGui import * from PyQt6.QtGui import *
from PyQt5.QtWidgets import * from PyQt6.QtWidgets import *
class QtWaitingSpinner(QWidget): class QtWaitingSpinner(QWidget):
@ -37,7 +37,7 @@ class QtWaitingSpinner(QWidget):
super().__init__() super().__init__()
# WAS IN initialize() # WAS IN initialize()
self._color = QColor(Qt.black) self._color = Qt.GlobalColor.black
self._roundness = 100.0 self._roundness = 100.0
self._minimumTrailOpacity = 3.14159265358979323846 self._minimumTrailOpacity = 3.14159265358979323846
self._trailFadePercentage = 80.0 self._trailFadePercentage = 80.0
@ -54,17 +54,17 @@ class QtWaitingSpinner(QWidget):
self.updateTimer() self.updateTimer()
# END initialize() # END initialize()
self.setAttribute(Qt.WA_TranslucentBackground) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
def paintEvent(self, QPaintEvent): def paintEvent(self, QPaintEvent):
painter = QPainter(self) painter = QPainter(self)
painter.fillRect(self.rect(), Qt.transparent) painter.fillRect(self.rect(), Qt.GlobalColor.transparent)
painter.setRenderHint(QPainter.Antialiasing, True) painter.setRenderHint(QPainter.RenderHint.Antialiasing, True)
if self._currentCounter >= self._numberOfLines: if self._currentCounter >= self._numberOfLines:
self._currentCounter = 0 self._currentCounter = 0
painter.setPen(Qt.NoPen) painter.setPen(Qt.PenStyle.NoPen)
for i in range(0, self._numberOfLines): for i in range(0, self._numberOfLines):
painter.save() painter.save()
painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength) painter.translate(self._innerRadius + self._lineLength, self._innerRadius + self._lineLength)
@ -76,7 +76,7 @@ class QtWaitingSpinner(QWidget):
self._minimumTrailOpacity, self._color) self._minimumTrailOpacity, self._color)
painter.setBrush(color) painter.setBrush(color)
painter.drawRoundedRect(QRect(0, int(-self._lineWidth / 2), self._lineLength, self._lineWidth), self._roundness, painter.drawRoundedRect(QRect(0, int(-self._lineWidth / 2), self._lineLength, self._lineWidth), self._roundness,
self._roundness, Qt.RelativeSize) self._roundness, Qt.SizeMode.RelativeSize)
painter.restore() painter.restore()
def start(self): def start(self):
@ -136,7 +136,7 @@ class QtWaitingSpinner(QWidget):
def setRoundness(self, roundness): def setRoundness(self, roundness):
self._roundness = max(0.0, min(100.0, roundness)) self._roundness = max(0.0, min(100.0, roundness))
def setColor(self, color=Qt.black): def setColor(self, color=Qt.GlobalColor.black):
self._color = QColor(color) self._color = QColor(color)
def setRevolutionsPerSecond(self, revolutionsPerSecond): def setRevolutionsPerSecond(self, revolutionsPerSecond):

View File

@ -41,7 +41,6 @@ class _ConstGenericMarker:
def ConstGeneric(name, constraint): def ConstGeneric(name, constraint):
return TypeVar(name, _ConstGenericMarker, constraint) return TypeVar(name, _ConstGenericMarker, constraint)
def round64(x): def round64(x):
return round(x) return round(x)
@ -177,7 +176,7 @@ _time_manager = _DummyTimeManager()
def set_time_manager(time_manager): def set_time_manager(time_manager):
"""Set the time manager used for simulating kernels by running them """Set the time manager used for simulating kernels by running them
directly inside the Python interpreter. The time manager responds to the directly inside the Python interpreter. The time manager responds to the
entering and leaving of interleave/parallel/sequential blocks, delays, etc. and entering and leaving of parallel/sequential blocks, delays, etc. and
provides a time-stamped logging facility for events. provides a time-stamped logging facility for events.
""" """
global _time_manager global _time_manager
@ -265,5 +264,5 @@ def watchdog(timeout):
class TerminationRequested(Exception): class TerminationRequested(Exception):
"""Raised by ``pause`` when the user has requested termination.""" """Raised by :meth:`pause` when the user has requested termination."""
pass pass

View File

@ -28,7 +28,7 @@ class DefaultMissing(Exception):
class CancelledArgsError(Exception): class CancelledArgsError(Exception):
"""Raised by the ``interactive`` context manager when an interactive """Raised by the :meth:`~artiq.language.environment.HasEnvironment.interactive` context manager when an interactive
arguments request is cancelled.""" arguments request is cancelled."""
pass pass
@ -117,7 +117,7 @@ class NumberValue(_SimpleArgProcessor):
``int`` will also result in an error unless these conditions are met. ``int`` will also result in an error unless these conditions are met.
When ``scale`` is not specified, and the unit is a common one (i.e. When ``scale`` is not specified, and the unit is a common one (i.e.
defined in ``artiq.language.units``), then the scale is obtained from defined in :class:`~artiq.language.units`), then the scale is obtained from
the unit using a simple string match. For example, milliseconds (``"ms"``) the unit using a simple string match. For example, milliseconds (``"ms"``)
units set the scale to 0.001. No unit (default) corresponds to a scale of units set the scale to 0.001. No unit (default) corresponds to a scale of
1.0. 1.0.
@ -321,7 +321,8 @@ class HasEnvironment:
:param key: Name of the argument. :param key: Name of the argument.
:param processor: A description of how to process the argument, such :param processor: A description of how to process the argument, such
as instances of ``BooleanValue`` and ``NumberValue``. as instances of :mod:`~artiq.language.environment.BooleanValue` and
:mod:`~artiq.language.environment.NumberValue`.
:param group: An optional string that defines what group the argument :param group: An optional string that defines what group the argument
belongs to, for user interface purposes. belongs to, for user interface purposes.
:param tooltip: An optional string to describe the argument in more :param tooltip: An optional string to describe the argument in more
@ -347,7 +348,8 @@ class HasEnvironment:
"""Request arguments from the user interactively. """Request arguments from the user interactively.
This context manager returns a namespace object on which the method This context manager returns a namespace object on which the method
`setattr_argument` should be called, with the usual semantics. :meth:`~artiq.language.environment.HasEnvironment.setattr_argument` should be called,
with the usual semantics.
When the context manager terminates, the experiment is blocked When the context manager terminates, the experiment is blocked
and the user is presented with the requested argument widgets. and the user is presented with the requested argument widgets.
@ -355,7 +357,7 @@ class HasEnvironment:
the namespace contains the values of the arguments. the namespace contains the values of the arguments.
If the interactive arguments request is cancelled, raises If the interactive arguments request is cancelled, raises
``CancelledArgsError``.""" :exc:`~artiq.language.environment.CancelledArgsError`."""
interactive_arglist = [] interactive_arglist = []
namespace = SimpleNamespace() namespace = SimpleNamespace()
def setattr_argument(key, processor=None, group=None, tooltip=None): def setattr_argument(key, processor=None, group=None, tooltip=None):
@ -478,7 +480,7 @@ class HasEnvironment:
This function is used to get additional information for displaying the dataset. This function is used to get additional information for displaying the dataset.
See ``set_dataset`` for documentation of metadata items. See :meth:`set_dataset` for documentation of metadata items.
""" """
try: try:
return self.__dataset_mgr.get_metadata(key) return self.__dataset_mgr.get_metadata(key)

View File

@ -1,20 +1,6 @@
""" """
Implementation and management of scan objects. Implementation and management of scan objects.
A scan object (e.g. :class:`artiq.language.scan.RangeScan`) represents a
one-dimensional sweep of a numerical range. Multi-dimensional scans are
constructed by combining several scan objects, for example using
:class:`artiq.language.scan.MultiScanManager`.
Iterate on a scan object to scan it, e.g. ::
for variable in self.scan:
do_something(variable)
Iterating multiple times on the same scan object is possible, with the scan
yielding the same values each time. Iterating concurrently on the
same scan object (e.g. via nested loops) is also supported, and the
iterators are independent from each other.
""" """
import random import random
@ -32,6 +18,21 @@ __all__ = ["ScanObject",
class ScanObject: class ScanObject:
"""
Represents a one-dimensional sweep of a numerical range. Multi-dimensional scans are
constructed by combining several scan objects, for example using
:class:`MultiScanManager`.
Iterate on a scan object to scan it, e.g. ::
for variable in self.scan:
do_something(variable)
Iterating multiple times on the same scan object is possible, with the scan
yielding the same values each time. Iterating concurrently on the
same scan object (e.g. via nested loops) is also supported, and the
iterators are independent from each other.
"""
def __iter__(self): def __iter__(self):
raise NotImplementedError raise NotImplementedError
@ -163,7 +164,7 @@ class Scannable:
takes a scan object. takes a scan object.
When ``scale`` is not specified, and the unit is a common one (i.e. When ``scale`` is not specified, and the unit is a common one (i.e.
defined in ``artiq.language.units``), then the scale is obtained from defined in :class:`artiq.language.units`), then the scale is obtained from
the unit using a simple string match. For example, milliseconds (``"ms"``) the unit using a simple string match. For example, milliseconds (``"ms"``)
units set the scale to 0.001. No unit (default) corresponds to a scale of units set the scale to 0.001. No unit (default) corresponds to a scale of
1.0. 1.0.

View File

@ -477,16 +477,16 @@ class Scheduler:
return self.notifier.raw_view return self.notifier.raw_view
def check_pause(self, rid): def check_pause(self, rid):
"""Returns ``True`` if there is a condition that could make ``pause`` """Returns ``True`` if there is a condition that could make :meth:`pause`
not return immediately (termination requested or higher priority run). not return immediately (termination requested or higher priority run).
The typical purpose of this function is to check from a kernel The typical purpose of this function is to check from a kernel
whether returning control to the host and pausing would have an effect, whether returning control to the host and pausing would have an effect,
in order to avoid the cost of switching kernels in the common case in order to avoid the cost of switching kernels in the common case
where ``pause`` does nothing. where :meth:`pause` does nothing.
This function does not have side effects, and does not have to be This function does not have side effects, and does not have to be
followed by a call to ``pause``. followed by a call to :meth:`pause`.
""" """
for pipeline in self._pipelines.values(): for pipeline in self._pipelines.values():
if rid in pipeline.pool.runs: if rid in pipeline.pool.runs:

View File

@ -334,7 +334,13 @@ def main():
write_results() write_results()
raise raise
finally: finally:
device_mgr.notify_run_end() try:
device_mgr.notify_run_end()
except:
# Also write results immediately if the `notify_run_end`
# callbacks produce an exception
write_results()
raise
put_completed() put_completed()
elif action == "analyze": elif action == "analyze":
try: try:

View File

@ -141,132 +141,21 @@ class SchedulerCase(unittest.TestCase):
high_priority = 3 high_priority = 3
middle_priority = 2 middle_priority = 2
low_priority = 1 low_priority = 1
# "late" is far in the future, beyond any reasonable test timeout.
late = time() + 100000 late = time() + 100000
early = time() + 1 early = time() + 1
expect = [ middle_priority_done = asyncio.Event()
{ def notify(mod):
"path": [], # Watch for the "middle_priority" experiment's completion
"action": "setitem", if mod == {
"value": {
"repo_msg": None,
"priority": low_priority,
"pipeline": "main",
"due_date": None,
"status": "pending",
"expid": expid_bg,
"flush": False
},
"key": 0
},
{
"path": [],
"action": "setitem",
"value": {
"repo_msg": None,
"priority": high_priority,
"pipeline": "main",
"due_date": late,
"status": "pending",
"expid": expid_empty,
"flush": False
},
"key": 1
},
{
"path": [],
"action": "setitem",
"value": {
"repo_msg": None,
"priority": middle_priority,
"pipeline": "main",
"due_date": early,
"status": "pending",
"expid": expid_empty,
"flush": False
},
"key": 2
},
{
"path": [0],
"action": "setitem",
"value": "preparing",
"key": "status"
},
{
"path": [0],
"action": "setitem",
"value": "prepare_done",
"key": "status"
},
{
"path": [0],
"action": "setitem",
"value": "running",
"key": "status"
},
{
"path": [2],
"action": "setitem",
"value": "preparing",
"key": "status"
},
{
"path": [2],
"action": "setitem",
"value": "prepare_done",
"key": "status"
},
{
"path": [0],
"action": "setitem",
"value": "paused",
"key": "status"
},
{
"path": [2],
"action": "setitem",
"value": "running",
"key": "status"
},
{
"path": [2], "path": [2],
"action": "setitem", "action": "setitem",
"value": "run_done", "value": "run_done",
"key": "status" "key": "status"
}, }:
{ middle_priority_done.set()
"path": [0],
"action": "setitem",
"value": "running",
"key": "status"
},
{
"path": [2],
"action": "setitem",
"value": "analyzing",
"key": "status"
},
{
"path": [2],
"action": "setitem",
"value": "deleting",
"key": "status"
},
{
"path": [],
"action": "delitem",
"key": 2
},
]
done = asyncio.Event()
expect_idx = 0
def notify(mod):
nonlocal expect_idx
self.assertEqual(mod, expect[expect_idx])
expect_idx += 1
if expect_idx >= len(expect):
done.set()
scheduler.notifier.publish = notify scheduler.notifier.publish = notify
scheduler.start(loop=loop) scheduler.start(loop=loop)
@ -275,7 +164,9 @@ class SchedulerCase(unittest.TestCase):
scheduler.submit("main", expid_empty, high_priority, late) scheduler.submit("main", expid_empty, high_priority, late)
scheduler.submit("main", expid_empty, middle_priority, early) scheduler.submit("main", expid_empty, middle_priority, early)
loop.run_until_complete(done.wait()) # Check that the "middle_priority" experiment finishes. This would hang for a
# long time (until "late") if the due times were not being respected.
loop.run_until_complete(middle_priority_done.wait())
scheduler.notifier.publish = None scheduler.notifier.publish = None
loop.run_until_complete(scheduler.stop()) loop.run_until_complete(scheduler.stop())

View File

@ -0,0 +1,250 @@
Building and developing ARTIQ
=============================
.. warning::
This section is only for software or FPGA developers who want to modify ARTIQ. The steps described here are not required if you simply want to run experiments with ARTIQ. If you purchased a system from M-Labs or QUARTIQ, we usually provide board binaries for you; you can use the AFWS client to get updated versions if necessary, as described in :ref:`obtaining-binaries`. It is not necessary to build them yourself.
The easiest way to obtain an ARTIQ development environment is via the `Nix package manager <https://nixos.org/nix/>`_ on Linux. The Nix system is used on the `M-Labs Hydra server <https://nixbld.m-labs.hk/>`_ to build ARTIQ and its dependencies continuously; it ensures that all build instructions are up-to-date and allows binary packages to be used on developers' machines, in particular for large tools such as the Rust compiler.
ARTIQ itself does not depend on Nix, and it is also possible to obtain everything from source (look into the ``flake.nix`` file to see what resources are used, and run the commands manually, adapting to your system) - but Nix makes the process a lot easier.
Installing Vivado
-----------------
It is necessary to independently install AMD's `Vivado <https://www.xilinx.com/support/download.html>`_, which requires a login for download and can't be automatically obtained by package managers. The "appropriate" Vivado version to use for building gateware and firmware can vary. Some versions contain bugs that lead to hidden or visible failures, others work fine. Refer to the ``flake.nix`` file from the ARTIQ repository in order to determine which version is used at M-Labs.
.. tip::
Text-search ``flake.nix`` for a mention of ``/opt/Xilinx/Vivado``. Given e.g. the line ::
profile = "set -e; source /opt/Xilinx/Vivado/2022.2/settings64.sh"
the intended Vivado version is 2022.2.
Download and run the official installer. If using NixOS, note that this will require a FHS chroot environment; the ARTIQ flake provides such an environment, which you can enter with the command ``vivado-env`` from the development environment (i.e. after ``nix develop``). Other tips:
- Be aware that Vivado is notoriously not a lightweight piece of software and you will likely need **at least 70GB+** of free space to install it.
- If you do not want to write to ``/opt``, you can install into a folder of your home directory.
- During the Vivado installation, uncheck ``Install cable drivers`` (they are not required, as we use better open source alternatives).
- If the Vivado GUI installer crashes, you may be able to work around the problem by running it in unattended mode with a command such as ``./xsetup -a XilinxEULA,3rdPartyEULA,WebTalkTerms -b Install -e 'Vitis Unified Software Platform' -l /opt/Xilinx/``.
- Vivado installation issues are not uncommon. Searching for similar problems on `the M-Labs forum <https://forum.m-labs.hk/>`_ or `Vivado's own support forums <https://support.xilinx.com/s/topic/0TO2E000000YKXwWAO/installation-and-licensing>`_ might be helpful when looking for solutions.
.. _system-description:
System description file
-----------------------
ARTIQ gateware and firmware binaries are dependent on the system configuration. In other words, a specific set of ARTIQ binaries is bound to the exact arrangement of real-time hardware it was generated for: the core device itself, its role in a DRTIO context (master, satellite, or standalone), the (real-time) peripherals in use, the physical EEM ports they will be connected to, and various other basic specifications. This information is normally provided to the software in the form of a JSON file called the system description or system configuration file.
.. warning::
System configuration files are only used with Kasli and Kasli-SoC boards. KC705 and ZC706 ARTIQ configurations, due to their relative rarity and specialization, are handled on a case-by-case basis and selected through a variant name such as ``nist_clock``, with no system description file necessary. See below in :ref:`building` for where to find the list of supported variants. Writing new KC705 or ZC706 variants is not a trivial task, and not particularly recommended, unless you are an FPGA developer and know what you're doing.
If you already have your system configuration file on hand, you can edit it to reflect any changes in configuration. If you purchased your original system from M-Labs, or recently purchased new hardware to add to it, you can obtain your up-to-date system configuration file through AFWS at any time using the command ``$ afws_client get_json`` (see :ref:`AFWS client<afws-client>`). If you are starting from scratch, a close reading of ``coredevice_generic.schema.json`` in ``artiq/coredevice`` will be helpful.
System descriptions do not need to be very complex. At its most basic, a system description looks something like: ::
{
"target": "kasli",
"variant": "example",
"hw_rev": "v2.0",
"base": "master",
"peripherals": [
{
"type": "grabber",
"ports": [0]
}
]
}
Only these five fields are required, and the ``peripherals`` list can in principle be empty. A limited number of more extensive examples can currently be found in `the ARTIQ-Zynq repository <https://git.m-labs.hk/M-Labs/artiq-zynq/src/branch/master>`_, as well as in the main repository under ``artiq/examples/kasli_shuttler``. Once your system description file is complete, you can use ``artiq_ddb_template`` (see also :ref:`Utilities <ddb-template-tool>`) to test it and to generate a template for the corresponding :ref:`device database <device-db>`.
DRTIO descriptions
^^^^^^^^^^^^^^^^^^
Note that in DRTIO systems it is necessary to create one description file *per core device*. Satellites and their connected peripherals must be described separately. Satellites also need to be reflashed separately, albeit only if their personal system descriptions have changed. (The layout of satellites relative to the master is configurable on the fly and will be established much later, in the routing table; see :ref:`drtio-routing`. It is not necessary to rebuild or reflash if only changing the DRTIO routing table).
In contrast, only one device database should be generated even for a DRTIO system. Use a command of the form: ::
$ artiq_ddb_template -s 1 <satellite1>.json -s 2 <satellite2>.json <master>.json
The numbers designate the respective satellite's destination number, which must correspond to the destination numbers used when generating the routing table later.
Common system description changes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
To add or remove peripherals from the system, add or remove their entries from the ``peripherals`` field. When replacing hardware with upgraded versions, update the corresponding ``hw_rev`` (hardware revision) field. Other fields to consider include:
- ``enable_wrpll`` (a simple boolean, see :ref:`core-device-clocking`)
- ``sed_lanes`` (increasing the number of SED lanes can reduce sequence errors, but correspondingly consumes more FPGA resources, see :ref:`sequence-errors` )
- various defaults (e.g. ``core_addr`` defines a default IP address, which can be freely reconfigured later).
Nix development environment
---------------------------
* Install `Nix <http://nixos.org/nix/>`_ if you haven't already. Prefer a single-user installation for simplicity.
* Enable flakes in Nix, for example by adding ``experimental-features = nix-command flakes`` to ``nix.conf``; see the `NixOS Wiki on flakes <https://nixos.wiki/wiki/flakes>`_ for details and more options.
* Clone `the ARTIQ Git repository <https://github.com/m-labs/artiq>`_, or `the ARTIQ-Zynq repository <https://git.m-labs.hk/M-Labs/artiq-zynq>`__ for Zynq devices (Kasli-SoC or ZC706). By default, you are working with the ``master`` branch, which represents the beta version and is not stable (see :doc:`releases`). Checkout the most recent release (``git checkout release-[number]``) for a stable version.
* If your Vivado installation is not in its default location ``/opt``, open ``flake.nix`` and edit it accordingly (once again text-search ``/opt/Xilinx/Vivado``).
* Run ``nix develop`` at the root of the repository, where ``flake.nix`` is.
* Answer ``y``/'yes' to any Nix configuration questions if necessary, as in :ref:`installing-troubleshooting`.
.. note::
You can also target legacy versions of ARTIQ; use Git to checkout older release branches. Note however that older releases of ARTIQ required different processes for developing and building, which you are broadly more likely to figure out by (also) consulting corresponding older versions of the manual.
Once you have run ``nix develop`` you are in the ARTIQ development environment. All ARTIQ commands and utilities -- ``artiq_run``, ``artiq_master``, etc. -- should be available, as well as all the packages necessary to build or run ARTIQ itself. You can exit the environment at any time using Control+D or the ``exit`` command and re-enter it by re-running ``nix develop`` again in the same location.
.. tip::
If you are developing for Zynq, you will have noted that the ARTIQ-Zynq repository consists largely of firmware. The firmware for Zynq (NAR3) is more modern than that used for current mainline ARTIQ, and is intended to eventually replace it; for now it constitutes most of the difference between the two ARTIQ variants. The gateware for Zynq, on the other hand, is largely imported from mainline ARTIQ. If you intend to modify the gateware housed in the original ARTIQ repository, but build and test the results on a Zynq device, clone both repositories and set your ``PYTHONPATH`` after entering the ARTIQ-Zynq development shell: ::
$ export PYTHONPATH=/absolute/path/to/your/artiq:$PYTHONPATH
Note that this only applies for incremental builds. If you want to use ``nix build``, look into changing the inputs of the ``flake.nix`` instead. You can do this by replacing the URL of the GitHub ARTIQ repository with ``path:/absolute/path/to/your/artiq``; remember that Nix caches dependencies, so to incorporate new changes you will need to exit the development shell, update the Nix cache with ``nix flake update``, and re-run ``nix develop``.
Building only standard binaries
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you are working with original ARTIQ, and you only want to build a set of standard binaries (i.e. without changing the source code), you can also enter the development shell without cloning the repository, using ``nix develop`` as follows: ::
$ nix develop git+https://github.com/m-labs/artiq.git\?ref=release-[number]#boards
Leave off ``\?ref=release-[number]`` to prefer the current beta version instead of a numbered release.
.. note::
Adding ``#boards`` makes use of the ARTIQ flake's provided ``artiq-boards-shell``, a lighter environment optimized for building firmware and flashing boards, which can also be accessed by running ``nix develop .#boards`` if you have already cloned the repository. Developers should be aware that in this shell the current copy of the ARTIQ sources is not added to your ``PYTHONPATH``. Run ``nix flake show`` and read ``flake.nix`` carefully to understand the different available shells.
The parallel command does exist for ARTIQ-Zynq: ::
$ nix develop git+https://git.m-labs.hk/m-labs/artiq-zynq\?ref=release-[number]
but if you are building ARTIQ-Zynq without intention to change the source, it is not actually necessary to enter the development environment at all; Nix is capable of accessing the official flake remotely for the build itself, eliminating the requirement for any particular environment.
This is equally possible for original ARTIQ, but not as useful, as the development environment (specifically the ``#boards`` shell) is still the easiest way to access the necessary tools for flashing the board. On the other hand, with Zynq, it is normally recommended to boot from SD card, which requires no further special tools. As long as you have a functioning Nix installation with flakes enabled, you can progress directly to the building instructions below.
.. _building:
Building ARTIQ
--------------
For general troubleshooting and debugging, especially with a 'fresh' board, see also :ref:`connecting-uart`.
Kasli or KC705 (ARTIQ original)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
For Kasli, if you have your system description file on-hand, you can at this point build both firmware and gateware with a command of the form: ::
$ python -m artiq.gateware.targets.kasli <description>.json
With KC705, use: ::
$ python -m artiq.gateware.targets.kc705 -V <variant>
This will create a directory ``artiq_kasli`` or ``artiq_kc705`` containing the binaries in a subdirectory named after your description file or variant. Flash the board as described in :ref:`writing-flash`, adding the option ``--srcbuild``, e.g., assuming your board is already connected by JTAG USB: ::
$ artiq_flash --srcbuild [-t kc705] -d artiq_<board>/<variant>
.. note::
To see supported KC705 variants, run: ::
$ python -m artiq.gateware.targets.kc705 --help
Look for the option ``-V VARIANT, --variant VARIANT``.
Kasli-SoC or ZC706 (ARTIQ on Zynq)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The building process for Zynq devices is a little more complex. The easiest method is to leverage ``nix build`` and the ``makeArtiqZynqPackage`` utility provided by the official flake. The ensuing command is rather long, because it uses a multi-clause expression in the Nix language to describe the desired result; it can be executed piece-by-piece using the `Nix REPL <https://nix.dev/manual/nix/2.18/command-ref/new-cli/nix3-repl.html>`_, but ``nix build`` provides a lot of useful conveniences.
For Kasli-SoC, run: ::
$ nix build --print-build-logs --impure --expr 'let fl = builtins.getFlake "git+https://git.m-labs.hk/m-labs/artiq-zynq?ref=release-[number]"; in (fl.makeArtiqZynqPackage {target="kasli_soc"; variant="<variant>"; json=<path/to/description.json>;}).kasli_soc-<variant>-sd'
Replace ``<variant>`` with ``master``, ``satellite``, or ``standalone``, depending on your targeted DRTIO role. Remove ``?ref=release-[number]`` to use the current beta version rather than a numbered release. If you have cloned the repository and prefer to use your local copy of the flake, replace the corresponding clause with ``builtins.getFlake "/absolute/path/to/your/artiq-zynq"``.
For ZC706, you can use a command of the same form: ::
$ nix build --print-build-logs --impure --expr 'let fl = builtins.getFlake "git+https://git.m-labs.hk/m-labs/artiq-zynq?ref=release-[number]"; in (fl.makeArtiqZynqPackage {target="zc706"; variant="<variant>";}).zc706-<variant>-sd'
or you can use the more direct version: ::
$ nix build --print-build-logs git+https://git.m-labs.hk/m-labs/artiq-zynq\?ref=release-[number]#zc706-<variant>-sd
(which is possible for ZC706 because there is no need to be able to specify a system description file in the arguments.)
.. note::
To see supported ZC706 variants, you can run the following at the root of the repository: ::
$ src/gateware/zc706.py --help
Look for the option ``-V VARIANT, --variant VARIANT``. If you have not cloned the repository or are not in the development environment, try: ::
$ nix flake show git+https://git.m-labs.hk/m-labs/artiq-zynq\?ref=release-[number] | grep "package 'zc706.*sd"
to see the list of suitable build targets directly.
Any of these commands should produce a directory ``result`` which contains a file ``boot.bin``. As described in :ref:`writing-flash`, if your core device is currently accessible over the network, it can be flashed with ``artiq_coremgmt``. If it is not connected to the network:
1. Power off the board, extract the SD card and load ``boot.bin`` onto it manually.
2. Insert the SD card back into the board.
3. Ensure that the DIP switches (labeled BOOT MODE) are set correctly, to SD.
4. Power the board back on.
Optionally, the SD card may also be loaded at the same time with an additional file ``config.txt``, which can contain preset configuration values in the format ``key=value``, one per line. The keys are those used with ``artiq_coremgmt``. This allows e.g. presetting an IP address and any other configuration information.
After a successful boot, the "FPGA DONE" light should be illuminated and the board should respond to ping when plugged into Ethernet.
.. _zynq-jtag-boot :
Booting over JTAG/Ethernet
""""""""""""""""""""""""""
It is also possible to boot Zynq devices over USB and Ethernet. Flip the DIP switches to JTAG. The scripts ``remote_run.sh`` and ``local_run.sh`` in the ARTIQ-Zynq repository, intended for use with a remote JTAG server or a local connection to the core device respectively, are used at M-Labs to accomplish this. Both make use of the netboot tool ``artiq_netboot``, see also its source `here <https://git.m-labs.hk/M-Labs/artiq-netboot>`__, which is included in the ARTIQ-Zynq development environment. Adapt the relevant script to your system or read it closely to understand the options and the commands being run; note for example that ``remote_run.sh`` as written only supports ZC706.
You will need to generate the gateware, firmware and bootloader first, either through ``nix build`` or incrementally as below. After an incremental build add the option ``-i`` when running either of the scripts. If using ``nix build``, note that target names of the form ``<board>-<variant>-jtag`` (run ``nix flake show`` to see all targets) will output the three necessary files without combining them into ``boot.bin``.
.. warning::
A known Xilinx hardware bug on Zynq prevents repeatedly loading the SZL bootloader over JTAG (i.e. repeated calls of the ``*_run.sh`` scripts) without a POR reset. On Kasli-SoC, you can physically set a jumper on the ``PS_POR_B`` pins of your board and use the M-Labs `POR reset script <https://git.m-labs.hk/M-Labs/zynq-rs/src/branch/master/kasli_soc_por.py>`_.
Zynq incremental build
^^^^^^^^^^^^^^^^^^^^^^
The ``boot.bin`` file used in a Zynq SD card boot is in practice the combination of several files, normally ``top.bit`` (the gateware), ``runtime`` or ``satman`` (the firmware) and ``szl.elf`` (an open-source bootloader for Zynq `written by M-Labs <https://git.m-labs.hk/M-Labs/zynq-rs/src/branch/master/szl>`_, used in ARTIQ in place of Xilinx's FSBL). In some circumstances, especially if you are developing ARTIQ, you may prefer to construct these components separately. Be sure that you have cloned the repository and entered the development environment as described above.
To compile the gateware and firmware, enter the ``src`` directory and run two commands as follows:
For Kasli-SoC:
::
$ gateware/kasli_soc.py -g ../build/gateware <description.json>
$ make TARGET=kasli_soc GWARGS="path/to/description.json" <fw-type>
For ZC706:
::
$ gateware/zc706.py -g ../build/gateware -V <variant>
$ make TARGET=zc706 GWARGS="-V <variant>" <fw-type>
where ``fw-type`` is ``runtime`` for standalone or DRTIO master builds and ``satman`` for DRTIO satellites. Both the gateware and the firmware will generate into the ``../build`` destination directory. At this stage you can :ref:`boot from JTAG <zynq-jtag-boot>`; either of the ``*_run.sh`` scripts will expect the gateware and firmware files at their default locations, and the ``szl.elf`` bootloader is retrieved automatically.
.. warning::
Note that in between runs of ``make`` it is necessary to manually clear ``build``, even for different targets, or ``make`` will do nothing.
If you prefer to boot from SD card, you will need to construct your own ``boot.bin``. Build ``szl.elf`` from source by running a command of the form: ::
$ nix build git+https://git.m-labs.hk/m-labs/zynq-rs#<board>-szl
For easiest access run this command in the ``build`` directory. The ``szl.elf`` file will be in the subdirectory ``result``. To combine all three files into the boot image, create a file called ``boot.bif`` in ``build`` with the following contents: ::
the_ROM_image:
{
[bootloader]result/szl.elf
gateware/top.bit
firmware/armv7-none-eabihf/release/<fw-type>
}
EOF
Save this file. Now use ``mkbootimage`` to create ``boot.bin``. ::
$ mkbootimage boot.bif boot.bin
Boot from SD card as above.

View File

@ -1,34 +1,52 @@
Compiler Compiler
======== ========
The ARTIQ compiler transforms the Python code of the kernels into machine code executable on the core device. It is invoked automatically when calling a function that uses the ``@kernel`` decorator. The ARTIQ compiler transforms the Python code of the kernels into machine code executable on the core device. For limited purposes (normally, obtaining executable binaries of idle and startup kernels), it can be accessed through ``artiq_compile``. Otherwise it is invoked automatically whenever a function with an applicable decorator is called.
Supported Python features ARTIQ kernel code accepts *nearly,* but not quite, a strict subset of Python 3. The necessities of real-time operation impose a harsher set of limitations; as a result, many Python features are necessarily omitted, and there are some specific discrepancies (see also :ref:`compiler-pitfalls`).
-------------------------
A number of Python features can be used inside a kernel for compilation and execution on the core device. They include ``for`` and ``while`` loops, conditionals (``if``, ``else``, ``elif``), functions, exceptions, and statically typed variables of the following types: In general, ARTIQ Python supports only statically typed variables; it implements no heap allocation or garbage collection systems, essentially disallowing any heap-based data structures (although lists and arrays remain available in a stack-based form); and it cannot use runtime dispatch, meaning that, for example, all elements of an array must be of the same type. Nonetheless, technical details aside, a basic knowledge of Python is entirely sufficient to write useful and coherent ARTIQ experiments.
* Booleans .. note::
* 32-bit signed integers (default size) The ARTIQ compiler is now in its second iteration. The third generation, known as NAC3, is `currently in development <https://git.m-labs.hk/M-Labs/nac3>`_, and available for pre-alpha experimental use. NAC3 represents a major overhaul of ARTIQ compilation, and will feature much faster compilation speeds, a greatly improved type system, and more predictable and transparent operation. It is compatible with ARTIQ firmware starting at ARTIQ-7. Instructions for installation and basic usage differences can also be found `on the M-Labs Forum <https://forum.m-labs.hk/d/392-nac3-new-artiq-compiler-3-prealpha-release>`_. While NAC3 is a work in progress and many important features remain unimplemented, installation and feedback is welcomed.
* 64-bit signed integers (use ``numpy.int64`` to convert)
* Double-precision floating point numbers
* Lists of any supported types
* String constants
* User-defined classes, with attributes of any supported types (attributes that are not used anywhere in the kernel are ignored)
For a demonstration of some of these features, see the ``mandelbrot.py`` example. ARTIQ Python code
-----------------
When several instances of a user-defined class are referenced from the same kernel, every attribute must have the same type in every instance of the class. A variety of short experiments can be found in the subfolders of ``artiq/examples``, especially under ``kc705_nist_clock/repository`` and ``no_hardware/repository``. Reading through these will give you a general idea of what ARTIQ Python is capable of and how to use it.
Remote procedure calls Functions and decorators
---------------------- ^^^^^^^^^^^^^^^^^^^^^^^^^
Kernel code can call host functions without any additional ceremony. However, such functions are assumed to return `None`, and if a value other than `None` is returned, an exception is raised. To call a host function returning a value other than `None` its return type must be annotated using the standard Python syntax, e.g.: :: The ARTIQ compiler recognizes several specialized decorators, which determine the way the decorated function will be compiled and handled.
def return_four() -> TInt32: ``@kernel`` (see :meth:`~artiq.language.core.kernel`) designates kernel functions, which will be compiled for and wholly executed on the core device; the basic setup and background for kernels is detailed on the :doc:`getting_started_core` page. ``@subkernel`` (:meth:`~artiq.language.core.subkernel`) designates subkernel functions, which are largely similar to kernels except that they are executed on satellite devices in a DRTIO setting, with some associated limitations; they are described in more detail on the :doc:`using_drtio_subkernels` page.
return 4
The Python types correspond to ARTIQ type annotations as follows: ``@rpc`` (:meth:`~artiq.language.core.rpc`) designates functions to be executed on the host machine, which are compiled and run in regular Python, outside of the core device's real-time limitations. Notably, functions without decorators are assumed to be host-bound by default, and treated identically to an explicitly marked ``@rpc``. As a result, the explicit decorator is only really necessary when specifying additional flags (for example, ``flags={"async"}``, see below).
``@portable`` (:meth:`~artiq.language.core.portable`) designates functions to be executed *on the same device they are called.* In other words, when called from a kernel, a portable is executed as a kernel; when called from a subkernel, it is executed as a kernel, on the same satellite device as the calling subkernel; when called from a host function, it is executed on the host machine.
``@host_only`` (:meth:`~artiq.language.core.host_only`) functions are executed fully on the host, similarly to ``@rpc``, but calling them from a kernel as an RPC will be refused by the compiler. It can be used to mark functions which should only ever be called by the host.
.. warning::
ARTIQ goes to some lengths to cache code used in experiments correctly, so that experiments run according to the state of the code when they were started, even if the source is changed during the run time. Python itself annoyingly fails to implement this (see also `issue #416 <https://github.com/m-labs/artiq/issues/416>`_), necessitating a workaround on ARTIQ's part. One particular downstream limitation is that the ARTIQ compiler is unable to recognize decorators with path prefixes, i.e.: ::
import artiq.experiment as aq
[...]
@aq.kernel
def run(self):
pass
will fail to compile. As long as ``from artiq.experiment import *`` is used as in the examples, this is never an issue. If prefixes are strongly preferred, a possible workaround is to import decorators separately, as e.g. ``from artiq.language.core import kernel``.
.. _compiler-types:
ARTIQ types
^^^^^^^^^^^
Python/NumPy types correspond to ARTIQ types as follows:
+---------------+-------------------------+ +---------------+-------------------------+
| Python | ARTIQ | | Python | ARTIQ |
@ -43,6 +61,10 @@ The Python types correspond to ARTIQ type annotations as follows:
+---------------+-------------------------+ +---------------+-------------------------+
| str | TStr | | str | TStr |
+---------------+-------------------------+ +---------------+-------------------------+
| bytes | TBytes |
+---------------+-------------------------+
| bytearray | TByteArray |
+---------------+-------------------------+
| list of T | TList(T) | | list of T | TList(T) |
+---------------+-------------------------+ +---------------+-------------------------+
| NumPy array | TArray(T, num_dims) | | NumPy array | TArray(T, num_dims) |
@ -56,54 +78,107 @@ The Python types correspond to ARTIQ type annotations as follows:
| numpy.float64 | TFloat | | numpy.float64 | TFloat |
+---------------+-------------------------+ +---------------+-------------------------+
Integers are 32-bit by default but may be converted to 64-bit with ``numpy.int64``.
The ARTIQ compiler can be thought of as overriding all built-in Python types, and types in kernel code cannot always be assumed to behave as they would in host Python. In particular, normally heap-allocated types such as arrays, lists, and strings are very limited in what they support. Strings must be constant and lists and arrays must be of constant size. Methods like ``append``, ``push``, and ``pop`` are unavailable as a matter of principle, and will not compile. Certain types, notably dictionaries, have no ARTIQ implementation and cannot be used in kernels at all.
.. tip::
Instead of pushing or appending, preallocate for the maximum number of elements you expect with a list comprehension, i.e. ``x = [0 for _ in range(1024)]``, and then keep a variable ``n`` noting the last filled element of the array. Afterwards, ``x[0:n]`` will give you a list with that number of elements.
Multidimensional arrays are allowed (using NumPy syntax). Element-wise operations (e.g. ``+``, ``/``), matrix multiplication (``@``) and multidimensional indexing are supported; slices and views (currently) are not.
User-defined classes are supported, provided their attributes are of other supported types (attributes that are not used in the kernel are ignored and thus unrestricted). When several instances of a user-defined class are referenced from the same kernel, every attribute must have the same type in every instance of the class.
Basic ARTIQ Python
^^^^^^^^^^^^^^^^^^
Basic Python features can broadly be used inside kernels without further compunctions. This includes loops (``for`` / ``while`` / ``break`` / ``continue``), conditionals (``if`` / ``else`` / ``elif``), functions, exceptions, ``try`` / ``except`` / ``else`` blocks, and statically typed variables of any supported types.
Kernel code can call host functions without any additional ceremony. However, such functions are assumed to return ``None``, and if a value other than ``None`` is returned, an exception is raised. To call a host function returning a value other than ``None`` its return type must be annotated, using the standard Python syntax, e.g.: ::
def return_four() -> TInt32:
return 4
Kernels can freely modify attributes of objects shared with the host. However, by necessity, these modifications are actually applied to local copies of the objects, as the latency of immediate writeback would be unsupportable in a real-time environment. Instead, modifications are written back *when the kernel completes;* notably, this means RPCs called by a kernel itself will only have access to the unmodified host version of the object, as the kernel hasn't finished execution yet. In some cases, accessing data on the host is better handled by calling RPCs specifically to make the desired modifications.
.. warning::
Kernels *cannot and should not* return lists, arrays, or strings they have created, or any objects containing them; in the absence of a heap, the way these values are allocated means they cannot outlive the kernels they are created in. Trying to do so will normally be discovered by lifetime tracking and result in compilation errors, but in certain cases lifetime tracking will fail to detect a problem and experiments will encounter memory corruption at runtime. For example: ::
def func(a):
return a
class ProblemReturn1(EnvExperiment):
def build(self):
self.setattr_device("core")
@kernel
def run(self):
# results in memory corruption
return func([1, 2, 3])
will compile, **but corrupts at runtime.** On the other hand, lists, arrays, or strings can and should be used as inputs for RPCs, and this is the preferred method of returning data to the host. In this way the data is inherently read and sent before the kernel completes and there are no allocation issues.
Available built-in functions
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ARTIQ makes various useful built-in and mathematical functions from Python, NumPy, and SciPy available in kernel code. They are not guaranteed to be perfectly equivalent to their host namesakes (for example, ``numpy.rint()`` normally rounds-to-even, but in kernel code rounds toward zero) but their behavior should be basically predictable.
.. list-table::
:header-rows: 1
+ * Reference
* Functions
+ * `Python built-ins <https://docs.python.org/3/library/functions.html>`_
* - ``len()``, ``round()``, ``abs()``, ``min()``, ``max()``
- ``print()`` (for specifics see warning below)
- all basic type conversions (``int()``, ``float()`` etc.)
+ * `NumPy mathematic utilities <https://numpy.org/doc/stable/reference/routines.math.html>`_
* - ``sqrt()``, ``cbrt```
- ``fabs()``, ``fmax()``, ``fmin()``
- ``floor()``, ``ceil()``, ``trunc()``, ``rint()``
+ * `NumPy exponents and logarithms <https://numpy.org/doc/stable/reference/routines.math.html#exponents-and-logarithms>`_
* - ``exp()``, ``exp2()``, ``expm1()``
- ``log()``, ``log2()``, ``log10()``
+ * `NumPy trigonometric and hyperbolic functions <https://numpy.org/doc/stable/reference/routines.math.html#trigonometric-functions>`_
* - ``sin()``, ``cos()``, ``tan()``,
- ``arcsin()``, ``arccos()``, ``arctan()``
- ``sinh()``, ``cosh()``, ``tanh()``
- ``arcsinh()``, ``arccosh()``, ``arctanh()``
- ``hypot()``, ``arctan2()``
+ * `NumPy floating point routines <https://numpy.org/doc/stable/reference/routines.math.html#floating-point-routines>`_
* - ``copysign()``, ``nextafter()``
+ * `SciPy special functions <https://docs.scipy.org/doc/scipy/reference/special.html>`_
* - ``erf()``, ``erfc()``
- ``gamma()``, ``gammaln()``
- ``j0()``, ``j1()``, ``y0()``, ``y1()``
Basic NumPy array handling (``np.array()``, ``numpy.transpose()``, ``numpy.full``, ``@``, element-wise operation, etc.) is also available. NumPy functions are implicitly broadcast when applied to arrays.
.. warning::
A kernel ``print`` call normally specifically prints to the host machine, either the terminal of ``artiq_run`` or the dashboard log when using ``artiq_dashboard``. This makes it effectively an RPC, with some of the corresponding limitations. In subkernels and whenever compiled through ``artiq_compile``, where RPCs are not supported, it is instead considered a print to the local log (UART only in the case of satellites, UART and core log for master/standalone devices).
.. _compiler-pitfalls:
Pitfalls Pitfalls
-------- --------
The ARTIQ compiler accepts *nearly* a strict subset of Python 3. However, by necessity there
is a number of differences that can lead to bugs.
Arbitrary-length integers are not supported at all on the core device; all integers are
either 32-bit or 64-bit. This especially affects calculations that result in a 32-bit signed
overflow; if the compiler detects a constant that doesn't fit into 32 bits, the entire expression
will be upgraded to 64-bit arithmetics, however if all constants are small, 32-bit arithmetics
will be used even if the result will overflow. Overflows are not detected.
The result of calling the builtin ``round`` function is different when used with
the builtin ``float`` type and the ``numpy.float64`` type on the host interpreter; ``round(1.0)``
returns an integer value 1, whereas ``round(numpy.float64(1.0))`` returns a floating point value
``numpy.float64(1.0)``. Since both ``float`` and ``numpy.float64`` are mapped to
the builtin ``float`` type on the core device, this can lead to problems in functions marked
``@portable``; the workaround is to explicitly cast the argument of ``round`` to ``float``:
``round(float(numpy.float64(1.0)))`` returns an integer on the core device as well as on the host
interpreter.
Empty lists do not have valid list element types, so they cannot be used in the kernel. Empty lists do not have valid list element types, so they cannot be used in the kernel.
In kernels, lifetime of allocated values (e.g. lists or numpy arrays) might not be correctly Arbitrary-length integers are not supported at all on the core device; all integers are either 32-bit or 64-bit. This especially affects calculations that result in a 32-bit signed overflow. If the compiler detects a constant that can't fit into 32 bits, the entire expression will be upgraded to 64-bit arithmetic, but if all constants are small, 32-bit arithmetic is used even if the result will overflow. Overflows are not detected.
tracked across function calls (see `#1497 <https://github.com/m-labs/artiq/issues/1497>`_,
`#1677 <https://github.com/m-labs/artiq/issues/1677>`_) like in this example ::
@kernel The result of calling the builtin ``round`` function is different when used with the builtin ``float`` type and the ``numpy.float64`` type on the host interpreter; ``round(1.0)`` returns an integer value 1, whereas ``round(numpy.float64(1.0))`` returns a floating point value ``numpy.float64(1.0)``. Since both ``float`` and ``numpy.float64`` are mapped to the builtin ``float`` type on the core device, this can lead to problems in functions marked ``@portable``; the workaround is to explicitly cast the argument of ``round`` to ``float``: ``round(float(numpy.float64(1.0)))`` returns an integer on the core device as well as on the host interpreter.
def func(a):
return a
class ProblemReturn1(EnvExperiment): Flags and optimizations
def build(self): -----------------------
self.setattr_device("core")
@kernel The ARTIQ compiler runs many optimizations, most of which perform well on code that has pristine Python semantics. It also contains more powerful, and more invasive, optimizations that require opt-in to activate.
def run(self):
# results in memory corruption
return func([1, 2, 3])
This results in memory corruption at runtime.
Asynchronous RPCs Asynchronous RPCs
----------------- ^^^^^^^^^^^^^^^^^
If an RPC returns no value, it can be invoked in a way that does not block until the RPC finishes If an RPC returns no value, it can be invoked in a way that does not block until the RPC finishes execution, but only until it is queued. (Submitting asynchronous RPCs too rapidly, as well as submitting asynchronous RPCs with arguments that are too large, can still block until completion.)
execution, but only until it is queued. (Submitting asynchronous RPCs too rapidly, as well as
submitting asynchronous RPCs with arguments that are too large, can still block until completion.)
To define an asynchronous RPC, use the ``@rpc`` annotation with a flag: :: To define an asynchronous RPC, use the ``@rpc`` annotation with a flag: ::
@ -111,17 +186,12 @@ To define an asynchronous RPC, use the ``@rpc`` annotation with a flag: ::
def record_result(x): def record_result(x):
self.results.append(x) self.results.append(x)
Additional optimizations
------------------------
The ARTIQ compiler runs many optimizations, most of which perform well on code that has pristine Python semantics. It also contains more powerful, and more invasive, optimizations that require opt-in to activate.
Fast-math flags Fast-math flags
+++++++++++++++ ^^^^^^^^^^^^^^^
The compiler does not normally perform algebraically equivalent transformations on floating-point expressions, because this can dramatically change the result. However, it can be instructed to do so if all of the following is true: The compiler does not normally perform algebraically equivalent transformations on floating-point expressions, because this can dramatically change the result. However, it can be instructed to do so if all of the following are true:
* Arguments and results will not be not-a-number or infinity values; * Arguments and results will not be Not-a-Number or infinite;
* The sign of a zero value is insignificant; * The sign of a zero value is insignificant;
* Any algebraically equivalent transformations, such as reassociation or replacing division with multiplication by reciprocal, are legal to perform. * Any algebraically equivalent transformations, such as reassociation or replacing division with multiplication by reciprocal, are legal to perform.
@ -134,7 +204,7 @@ If this is the case for a given kernel, a ``fast-math`` flag can be specified to
This flag particularly benefits loops with I/O delays performed in fractional seconds rather than machine units, as well as updates to DDS phase and frequency. This flag particularly benefits loops with I/O delays performed in fractional seconds rather than machine units, as well as updates to DDS phase and frequency.
Kernel invariants Kernel invariants
+++++++++++++++++ ^^^^^^^^^^^^^^^^^
The compiler attempts to remove or hoist out of loops any redundant memory load operations, as well as propagate known constants into function bodies, which can enable further optimization. However, it must make conservative assumptions about code that it is unable to observe, because such code can change the value of the attribute, making the optimization invalid. The compiler attempts to remove or hoist out of loops any redundant memory load operations, as well as propagate known constants into function bodies, which can enable further optimization. However, it must make conservative assumptions about code that it is unable to observe, because such code can change the value of the attribute, making the optimization invalid.
@ -171,7 +241,7 @@ In the synthetic example above, the compiler will be able to detect that the res
delay(self.worker.interval / 5.0) delay(self.worker.interval / 5.0)
self.worker.work() self.worker.work()
In the synthetic example above, the compiler will be able to detect that the result of evaluating ``self.interval / 5.0`` never changes, even though it neither knows the value of ``self.worker.interval`` beforehand nor can it see through the ``self.worker.work()`` function call, and hoist the expensive floating-point division out of the loop, transforming the code for ``loop`` into an equivalent of the following: :: In the synthetic example above, the compiler will be able to detect that the result of evaluating ``self.interval / 5.0`` never changes, even though it neither knows the value of ``self.worker.interval`` beforehand nor can see through the ``self.worker.work()`` function call, and thus can hoist the expensive floating-point division out of the loop, transforming the code for ``loop`` into an equivalent of the following: ::
@kernel @kernel
def loop(self): def loop(self):

View File

@ -31,7 +31,7 @@ mock_modules = ["artiq.gui.waitingspinnerwidget",
"artiq.dashboard.waveform", "artiq.dashboard.waveform",
"artiq.dashboard.interactive_args", "artiq.dashboard.interactive_args",
"qasync", "pyqtgraph", "matplotlib", "lmdb", "qasync", "pyqtgraph", "matplotlib", "lmdb",
"numpy", "dateutil", "dateutil.parser", "prettytable", "PyQt5", "numpy", "dateutil", "dateutil.parser", "prettytable", "PyQt6",
"h5py", "serial", "scipy", "scipy.interpolate", "h5py", "serial", "scipy", "scipy.interpolate",
"nac3artiq", "nac3artiq",
"sipyco", "sipyco.pc_rpc", "sipyco.sync_struct", "sipyco", "sipyco.pc_rpc", "sipyco.sync_struct",
@ -42,7 +42,6 @@ mock_modules = ["artiq.gui.waitingspinnerwidget",
for module in mock_modules: for module in mock_modules:
sys.modules[module] = Mock() sys.modules[module] = Mock()
# https://stackoverflow.com/questions/29992444/sphinx-autodoc-skips-classes-inherited-from-mock # https://stackoverflow.com/questions/29992444/sphinx-autodoc-skips-classes-inherited-from-mock
class MockApplets: class MockApplets:
class AppletsDock: class AppletsDock:
@ -141,6 +140,25 @@ pygments_style = 'sphinx'
# If true, keep warnings as "system message" paragraphs in the built documents. # If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False #keep_warnings = False
# If true, Sphinx will warn about *all* references where the target cannot be found.
nitpicky = True
# (type, target) regex tuples to ignore when generating warnings in 'nitpicky' mode
nitpick_ignore_regex = [
(r'py:.*', r'numpy..*'),
(r'py:.*', r'sipyco..*'),
('py:const', r'.*'), # no constants are documented anyway
('py.attr', r'.*'), # nor attributes
(r'py:.*', r'artiq.gateware.*'),
('py:mod', r'artiq.frontend.*'),
('py:mod', r'artiq.test.*'),
('py:mod', 'artiq.experiment'),
('py:class', 'dac34H84'),
('py:class', 'trf372017'),
('py:class', r'list(.*)'),
('py.class', 'NoneType'),
('py:meth', 'pause')
]
# -- Options for HTML output ---------------------------------------------- # -- Options for HTML output ----------------------------------------------

125
doc/manual/configuring.rst Normal file
View File

@ -0,0 +1,125 @@
Networking and configuration
============================
.. _core-device-networking:
Setting up core device networking
---------------------------------
For Kasli, insert a SFP/RJ45 transceiver (normally included with purchases from M-Labs and QUARTIQ) into the SFP0 port and connect it to an Ethernet port in your network. If the port is 10Mbps or 100Mbps and not 1000Mbps, make sure that the SFP/RJ45 transceiver supports the lower rate. Many SFP/RJ45 transceivers only support the 1000Mbps rate. If you do not have a SFP/RJ45 transceiver that supports 10Mbps and 100Mbps rates, you may instead use a gigabit Ethernet switch in the middle to perform rate conversion.
You can also insert other types of SFP transceivers into Kasli if you wish to use it directly in e.g. an optical fiber Ethernet network. Kasli-SoC already directly features RJ45 10/100/1000 Ethernet.
IP address and ping
^^^^^^^^^^^^^^^^^^^
If you purchased a Kasli or Kasli-SoC device from M-Labs, it will arrive with an IP address already set, normally the address requested in the web shop at time of purchase. If you did not specify an address at purchase, the default IP M-Labs uses is ``192.168.1.75``. If you did not obtain your hardware from M-Labs, or if you have just reflashed your core device, see :ref:`networking-tips` below.
Once you know the IP, check that you can ping your device: ::
$ ping <IP_address>
If ping fails, check that the Ethernet LED is ON; on Kasli, it is the LED next to the SFP0 connector. As a next step, try connecting to the serial port to read the UART log. See :ref:`connecting-UART`.
Core management tool
^^^^^^^^^^^^^^^^^^^^
The tool used to configure the core device is the command-line utility ``artiq_coremgmt``. In order for it to connect to your core device, it is necessary to supply it somehow with the correct IP address for your core device. This can be done directly through use of the ``-D`` option, for example in: ::
$ artiq_coremgmt -D <IP_address> log
.. note::
This command reads and displays the core log. If you have recently rebooted or reflashed your core device, you should see the startup logs in your terminal.
Normally, however, the core device IP is supplied through the *device database* for your system, which comes in the form of a Python script called ``device_db.py`` (see also :ref:`device-db`). If you purchased a system from M-Labs, the ``device_db.py`` for your system will have been provided for you, either on the USB stick, inside ``~/artiq`` on your NUC, or sent by email.
Make sure the field ``core_addr`` at the top of the file is set to your core device's correct IP address, and always execute ``artiq_coremgmt`` from the same directory the device database is placed in.
Once you can reach your core device, the IP can be changed at any time by running: ::
$ artiq_coremgmt [-D old_IP] config write -s ip <new_IP>
and then rebooting the device: ::
$ artiq_coremgmt [-D old_IP] reboot
Make sure to correspondingly edit your ``device_db.py`` after rebooting.
.. _networking-tips:
Tips and troubleshooting
^^^^^^^^^^^^^^^^^^^^^^^^
For Kasli-SoC:
If the ``ip`` config is not set, Kasli-SoC firmware defaults to using the IP address ``192.168.1.56``.
For ZC706:
If the ``ip`` config is not set, ZC706 firmware defaults to using the IP address ``192.168.1.52``.
For Kasli or KC705:
If the ``ip`` config field is not set or set to ``use_dhcp``, the device will attempt to obtain an IP address and default gateway using DHCP. The chosen IP address will be in log output, which can be accessed via the :ref:`UART log <connecting-UART>`.
If a static IP address is preferred, it can be flashed directly (OpenOCD must be installed and configured, as in :doc:`flashing`), along with, as necessary, default gateway, IPv6, and/or MAC address: ::
$ artiq_mkfs flash_storage.img [-s mac xx:xx:xx:xx:xx:xx] [-s ip xx.xx.xx.xx/xx] [-s ipv4_default_route xx.xx.xx.xx] [-s ip6 xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx/xx] [-s ipv6_default_route xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx]
$ artiq_flash -t [board] -V [variant] -f flash_storage.img storage start
On Kasli or Kasli-SoC devices, specifying the MAC address is unnecessary, as they can obtain it from their EEPROM. If you only want to access the core device from the same subnet, default gateway and IPv4 prefix length may also be ommitted. On any board, once a device can be reached by ``artiq_coremgmt``, these values can be set and edited at any time, following the procedure for IP above.
Regarding IPv6, note that the device also has a link-local address that corresponds to its EUI-64, which can be used simultaneously to the (potentially unrelated) IPv6 address defined by using the ``ip6`` configuration key.
.. _core-device-config:
Configuring the core device
---------------------------
.. note::
The following steps are optional, and you only need to execute them if they are necessary for your specific system. To learn more about how ARTIQ works and how to use it first, you might skip to the next page, :doc:`rtio`. For all configuration options, the core device generally must be restarted for changes to take effect.
Flash idle and/or startup kernel
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The *idle kernel* is the kernel (that is, a piece of code running on the core device; see :doc:`the next page <rtio>` for further explanation) which the core device runs in between experiments and whenever not connected to the host. It is saved directly to the core device's flash storage in compiled form. Potential uses include cleanup of the environment between experiments, state maintenance for certain hardware, or anything else that should run continuously whenever the system is not otherwise occupied.
To flash an idle kernel, first write an idle experiment. Note that since the idle kernel runs regardless of whether the core device is connected to the host, remote procedure calls or RPCs (functions called by a kernel to run on the host) are forbidden and the ``run()`` method must be a kernel marked with ``@kernel``. Once written, you can compile and flash your idle experiment: ::
$ artiq_compile idle.py
$ artiq_coremgmt config write -f idle_kernel idle.elf
The *startup kernel* is a kernel executed once and only once immediately whenever the core device powers on. Uses include initializing DDSes and setting TTL directions. For DRTIO systems, the startup kernel should wait until the desired destinations, including local RTIO, are up, using ``self.core.get_rtio_destination_status`` (:meth:`~artiq.coredevice.core.Core.get_rtio_destination_status`).
To flash a startup kernel, proceed as with the idle kernel, but using the ``startup_kernel`` key in the ``artiq_coremgmt`` command.
.. note::
Subkernels (see :doc:`using_drtio_subkernels`) are allowed in idle (and startup) experiments without any additional ceremony. ``artiq_compile`` will produce a ``.tar`` rather than a ``.elf``; simply substitute ``idle.tar`` for ``idle.elf`` in the ``artiq_coremgmt config write`` command.
Select the RTIO clock source
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The core device may use any of an external clock signal, its internal clock with external frequency reference, or its internal clock with internal crystal reference. Clock source and timing are set at power-up. To find out what clock signal you are using, check the startup logs with ``artiq_coremgmt log``.
The default is to use an internal 125MHz clock. To select a source, use a command of the form: ::
$ artiq_coremgmt config write -s rtio_clock int_125 # internal 125MHz clock (default)
$ artiq_coremgmt config write -s rtio_clock ext0_synth0_10to125 # external 10MHz reference used to synthesize internal 125MHz
See :ref:`core-device-clocking` for availability of specific options.
Set up resolving RTIO channels to their names
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
This feature allows you to print the channels' respective names alongside with their numbers in RTIO error messages. To enable it, run the ``artiq_rtiomap`` tool and write its result into the device config at the ``device_map`` key: ::
$ artiq_rtiomap dev_map.bin
$ artiq_coremgmt config write -f device_map dev_map.bin
More information on the ``artiq_rtiomap`` utility can be found on the :ref:`Utilities <rtiomap-tool>` page.
Enable event spreading
^^^^^^^^^^^^^^^^^^^^^^
This feature changes the logic used for queueing RTIO output events in the core device for a more efficient use of FPGA resources, at the cost of introducing nondeterminism and potential unpredictability in certain timing errors (specifically gateware :ref:`sequence errors<sequence-errors>`). It can be enabled with the config key ``sed_spread_enable``. See :ref:`sed-event-spreading`.
Load the DRTIO routing table
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you are using DRTIO and the default routing table (for a star topology) is not suitable to your needs, you will first need to prepare and load a different routing table. See :ref:`Using DRTIO <drtio-routing>`.

View File

@ -8,21 +8,87 @@ While it is possible to use the other parts of ARTIQ (controllers, master, GUI,
.. _core-device-flash-storage: .. _core-device-flash-storage:
Flash storage Flash storage
^^^^^^^^^^^^^ -------------
The core device contains some flash storage space which is used to store configuration data. It is one sector (typically 64 kB) large and organized as a list of key-value records, accessible by using ``artiq_coremgmt`` (see :ref:`core-device-management-tool`). The core device IP and MAC addresses, as well as, if present, the startup and/or idle kernels (see :ref:`miscellaneous_config_core_device`) are stored here. The core device contains some flash storage space which is used to store configuration data. It is one sector (typically 64 kB) large and organized as a list of key-value records, accessible either through ``artiq_mkfs`` and ``artiq_flash`` or, preferably in most cases, the ``config`` option of the ``artiq_coremgmt`` core management tool (see below). Information can be stored to keys of any name, but the specific keys currently used and referenced by ARTIQ are summarized below:
.. _board-ports: ``idle_kernel``
Stores (compiled ``.tar`` or ``.elf`` binary of) idle kernel. See :ref:`core-device-config`.
``startup_kernel``
Stores (compiled ``.tar`` or ``.elf`` binary of) startup kernel. See :ref:`core-device-config`.
``ip``
Sets IP address of core device. For this and other networking options see also :ref:`core-device-networking`.
``mac``
Sets MAC address of core device. Unnecessary on Kasli or Kasli-SoC, which can obtain it from EEPROM.
``ipv4_default_route``
Sets IPv4 default route.
``ip6``
Sets IPv6 address of core device. Can be set irrespective of and used simultaneously as IPv4 address above.
``ipv6_default_route``
Sets IPv6 default route.
``sed_spread_enable``
If set to ``1``, will activate :ref:`sed-event-spreading` in this core device. Needs to be set separately for satellite devices in a DRTIO setting.
``log_level``
Sets core device log level. Possible levels are ``TRACE``, ``DEBUG``, ``INFO``, ``WARN``, ``ERROR``, and ``OFF``. Note that enabling higher log levels will produce some core device slowdown.
``uart_log_level``
Sets UART log level, with same options. Printing a large number of messages to UART log will produce a significant slowdown.
``rtio_clock``
Sets the RTIO clock; see :ref:`core-device-clocking`.
``routing_table``
Sets the routing table in DRTIO systems; see :ref:`drtio-routing`. If not set, a star topology is assumed.
``device_map``
If set, allows the core log to connect RTIO channels to device names and use device names as well as channel numbers in log output. A correctly formatted table can be automatically generated with ``artiq_rtiomap``, see :ref:`Utilities<rtiomap-tool>`.
``net_trace``
If set to ``1``, will activate net trace (print all packets sent and received to UART and core log). This will considerably slow down all network response from the core. Not applicable for ARTIQ-Zynq (Kasli-SoC, ZC706).
``panic_reset``
If set to ``1``, core device will restart automatically. Not applicable for ARTIQ-Zynq.
``no_flash_boot``
If set to ``1``, will disable flash boot. Network boot is attempted if possible. Not applicable for ARTIQ-Zynq.
``boot``
Allows full firmware/gateware (``boot.bin``) to be written with ``artiq_coremgmt``, on ARTIQ-Zynq systems only.
Common configuration commands
-----------------------------
To write, then read, the value ``test_value`` in the key ``my_key``::
$ artiq_coremgmt config write -s my_key test_value
$ artiq_coremgmt config read my_key
b'test_value'
You do not need to remove a record in order to change its value. Just overwrite it::
$ artiq_coremgmt config write -s my_key some_value
$ artiq_coremgmt config write -s my_key some_other_value
$ artiq_coremgmt config read my_key
b'some_other_value'
You can write several records at once::
$ artiq_coremgmt config write -s key1 value1 -f key2 filename -s key3 value3
You can also write entire files in a record using the ``-f`` option. This is useful for instance to write the startup and idle kernels into the flash storage::
$ artiq_coremgmt config write -f idle_kernel idle.elf
$ artiq_coremgmt config read idle_kernel | head -c9
b'\x7fELF
The same option is used to write ``boot.bin`` in ARTIQ-Zynq. Note that the ``boot`` key is write-only.
See also the full reference of ``artiq_coremgmt`` in :ref:`Utilities <core-device-management-tool>`.
Board details
-------------
FPGA board ports FPGA board ports
^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^
All boards have a serial interface running at 115200bps 8-N-1 that can be used for debugging. All boards have a serial interface running at 115200bps 8-N-1 that can be used for debugging.
Kasli and Kasli SoC Kasli and Kasli-SoC
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
`Kasli <https://github.com/sinara-hw/Kasli/wiki>`_ and `Kasli-SoC <https://github.com/sinara-hw/Kasli-SOC/wiki>`_ are versatile core devices designed for ARTIQ as part of the open-source `Sinara <https://github.com/sinara-hw/meta/wiki>`_ family of boards. All support interfacing to various EEM daughterboards (TTL, DDS, ADC, DAC...) through twelve onboard EEM ports. Kasli-SoC, which runs on a separate `Zynq port <https://git.m-labs.hk/M-Labs/artiq-zynq>`_ of the ARTIQ firmware, is architecturally separate, among other things being capable of performing much heavier software computations quickly locally to the board, but provides generally similar features to Kasli. Kasli itself exists in two versions, of which the improved Kasli v2.0 is now in more common use, but the original Kasli v1.0 remains supported by ARTIQ. `Kasli <https://github.com/sinara-hw/Kasli/wiki>`_ and `Kasli-SoC <https://github.com/sinara-hw/Kasli-SOC/wiki>`_ are versatile core devices designed for ARTIQ as part of the open-source `Sinara <https://github.com/sinara-hw/meta/wiki>`_ family of boards. All support interfacing to various EEM daughterboards (TTL, DDS, ADC, DAC...) through twelve onboard EEM ports. Kasli-SoC, which runs on a separate `Zynq port <https://git.m-labs.hk/M-Labs/artiq-zynq>`_ of the ARTIQ firmware, is architecturally separate, among other things being capable of performing much heavier software computations at high speeds on the board itself, but provides generally similar features to Kasli. Kasli itself exists in two versions, of which the improved Kasli v2.0 is now in more common use; the original Kasli v1.0 remains supported by ARTIQ.
Kasli can be connected to the network using a 10000Base-X SFP module, installed into the SFP0 cage. Kasli-SoC features a built-in Ethernet port to use instead. If configured as a DRTIO satellite, both boards instead reserve SFP0 for the upstream DRTIO connection; remaining SFP cages are available for downstream connections. Equally, if used as a DRTIO master, all free SFP cages are available for downstream connections (i.e. all but SFP0 on Kasli, all four on Kasli-SoC). Kasli can be connected to the network using a 10000Base-X SFP module, installed into the SFP0 cage. Kasli-SoC features a built-in Ethernet port to use instead. If configured as a DRTIO satellite, both boards instead reserve SFP0 for the upstream DRTIO connection; remaining SFP cages are available for downstream connections. Equally, if used as a DRTIO master, all free SFP cages are available for downstream connections (i.e. all but SFP0 on Kasli, all four on Kasli-SoC).
@ -34,20 +100,20 @@ KC705
An alternative target board for the ARTIQ core device is the KC705 development board from Xilinx. It supports the NIST CLOCK and QC2 hardware (FMC). An alternative target board for the ARTIQ core device is the KC705 development board from Xilinx. It supports the NIST CLOCK and QC2 hardware (FMC).
Common problems Common problems
--------------- ^^^^^^^^^^^^^^^
* The SW13 switches on the board need to be set to 00001. * The SW13 switches on the board need to be set to 00001.
* When connected, the CLOCK adapter breaks the JTAG chain due to TDI not being connected to TDO on the FMC mezzanine. * When connected, the CLOCK adapter breaks the JTAG chain due to TDI not being connected to TDO on the FMC mezzanine.
* On some boards, the JTAG USB connector is not correctly soldered. * On some boards, the JTAG USB connector is not correctly soldered.
VADJ VADJ
---- """"
With the NIST CLOCK and QC2 adapters, for safe operation of the DDS buses (to prevent damage to the IO banks of the FPGA), the FMC VADJ rail of the KC705 should be changed to 3.3V. Plug the Texas Instruments USB-TO-GPIO PMBus adapter into the PMBus connector in the corner of the KC705 and use the Fusion Digital Power Designer software to configure (requires Windows). Write to chip number U55 (address 52), channel 4, which is the VADJ rail, to make it 3.3V instead of 2.5V. Power cycle the KC705 board to check that the startup voltage on the VADJ rail is now 3.3V. With the NIST CLOCK and QC2 adapters, for safe operation of the DDS buses (to prevent damage to the IO banks of the FPGA), the FMC VADJ rail of the KC705 should be changed to 3.3V. Plug the Texas Instruments USB-TO-GPIO PMBus adapter into the PMBus connector in the corner of the KC705 and use the Fusion Digital Power Designer software to configure (requires Windows). Write to chip number U55 (address 52), channel 4, which is the VADJ rail, to make it 3.3V instead of 2.5V. Power cycle the KC705 board to check that the startup voltage on the VADJ rail is now 3.3V.
NIST CLOCK NIST CLOCK
---------- ^^^^^^^^^^
With the CLOCK hardware, the TTL lines are mapped as follows: With the CLOCK hardware, the TTL lines are mapped as follows:
@ -91,7 +157,7 @@ The DDS bus is on channel 27.
NIST QC2 NIST QC2
-------- ^^^^^^^^
With the QC2 hardware, the TTL lines are mapped as follows: With the QC2 hardware, the TTL lines are mapped as follows:
@ -137,7 +203,7 @@ See :mod:`artiq.coredevice.i2c` for more details.
.. _core-device-clocking: .. _core-device-clocking:
Clocking Clocking
^^^^^^^^ --------
The core device generates the RTIO clock using a PLL locked either to an internal crystal or to an external frequency reference. If choosing the latter, external reference must be provided (via front panel SMA input on Kasli boards). Valid configuration options include: The core device generates the RTIO clock using a PLL locked either to an internal crystal or to an external frequency reference. If choosing the latter, external reference must be provided (via front panel SMA input on Kasli boards). Valid configuration options include:
@ -151,7 +217,7 @@ The core device generates the RTIO clock using a PLL locked either to an interna
The selected option can be observed in the core device boot logs and accessed using ``artiq_coremgmt config`` with key ``rtio_clock``. The selected option can be observed in the core device boot logs and accessed using ``artiq_coremgmt config`` with key ``rtio_clock``.
As of ARTIQ 8, it is now possible for Kasli and Kasli-SoC configurations to enable WRPLL -- a clock recovery method using `DDMTD <http://white-rabbit.web.cern.ch/documents/DDMTD_for_Sub-ns_Synchronization.pdf>`_ and Si549 oscillators -- both to lock the main RTIO clock and (in DRTIO configurations) to lock satellites to master. This is set by the ``enable_wrpll`` option in the JSON description file. Because WRPLL requires slightly different gateware and firmware, it is necessary to re-flash devices to enable or disable it in extant systems. If you would like to obtain the firmware for a different WRPLL setting through ``awfs_client``, write to the helpdesk@ email. As of ARTIQ 8, it is now possible for Kasli and Kasli-SoC configurations to enable WRPLL -- a clock recovery method using `DDMTD <http://white-rabbit.web.cern.ch/documents/DDMTD_for_Sub-ns_Synchronization.pdf>`_ and Si549 oscillators -- both to lock the main RTIO clock and (in DRTIO configurations) to lock satellites to master. This is set by the ``enable_wrpll`` option in the :ref:`JSON description file <system-description>`. Because WRPLL requires slightly different gateware and firmware, it is necessary to re-flash devices to enable or disable it in extant systems. If you would like to obtain the firmware for a different WRPLL setting through AFWS, write to the helpdesk@ email.
If phase noise performance is the priority, it is recommended to use ``ext0_synth0_125to125`` over other ``ext0`` options, as this bypasses the (noisy) MMCM. If phase noise performance is the priority, it is recommended to use ``ext0_synth0_125to125`` over other ``ext0`` options, as this bypasses the (noisy) MMCM.

View File

@ -3,30 +3,29 @@ Core drivers reference
These drivers are for the core device and the peripherals closely integrated into it, which do not use the controller mechanism. These drivers are for the core device and the peripherals closely integrated into it, which do not use the controller mechanism.
System drivers System drivers
-------------- --------------
:mod:`artiq.coredevice.core` module :mod:`artiq.coredevice.core` module
+++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.core .. automodule:: artiq.coredevice.core
:members: :members:
:mod:`artiq.coredevice.exceptions` module :mod:`artiq.coredevice.exceptions` module
+++++++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.exceptions .. automodule:: artiq.coredevice.exceptions
:members: :members:
:mod:`artiq.coredevice.dma` module :mod:`artiq.coredevice.dma` module
++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.dma .. automodule:: artiq.coredevice.dma
:members: :members:
:mod:`artiq.coredevice.cache` module :mod:`artiq.coredevice.cache` module
++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.cache .. automodule:: artiq.coredevice.cache
:members: :members:
@ -36,25 +35,25 @@ Digital I/O drivers
------------------- -------------------
:mod:`artiq.coredevice.ttl` module :mod:`artiq.coredevice.ttl` module
++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.ttl .. automodule:: artiq.coredevice.ttl
:members: :members:
:mod:`artiq.coredevice.edge_counter` module :mod:`artiq.coredevice.edge_counter` module
++++++++++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.edge_counter .. automodule:: artiq.coredevice.edge_counter
:members: :members:
:mod:`artiq.coredevice.spi2` module :mod:`artiq.coredevice.spi2` module
+++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.spi2 .. automodule:: artiq.coredevice.spi2
:members: :members:
:mod:`artiq.coredevice.i2c` module :mod:`artiq.coredevice.i2c` module
++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.i2c .. automodule:: artiq.coredevice.i2c
:members: :members:
@ -63,49 +62,49 @@ RF generation drivers
--------------------- ---------------------
:mod:`artiq.coredevice.urukul` module :mod:`artiq.coredevice.urukul` module
+++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.urukul .. automodule:: artiq.coredevice.urukul
:members: :members:
:mod:`artiq.coredevice.ad9910` module :mod:`artiq.coredevice.ad9910` module
+++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.ad9910 .. automodule:: artiq.coredevice.ad9910
:members: :members:
:mod:`artiq.coredevice.ad9912` module :mod:`artiq.coredevice.ad9912` module
+++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.ad9912 .. automodule:: artiq.coredevice.ad9912
:members: :members:
:mod:`artiq.coredevice.ad9914` module :mod:`artiq.coredevice.ad9914` module
+++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.ad9914 .. automodule:: artiq.coredevice.ad9914
:members: :members:
:mod:`artiq.coredevice.mirny` module :mod:`artiq.coredevice.mirny` module
+++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.mirny .. automodule:: artiq.coredevice.mirny
:members: :members:
:mod:`artiq.coredevice.almazny` module :mod:`artiq.coredevice.almazny` module
++++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.almazny .. automodule:: artiq.coredevice.almazny
:members: :members:
:mod:`artiq.coredevice.adf5356` module :mod:`artiq.coredevice.adf5356` module
+++++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.adf5356 .. automodule:: artiq.coredevice.adf5356
:members: :members:
:mod:`artiq.coredevice.phaser` module :mod:`artiq.coredevice.phaser` module
+++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.phaser .. automodule:: artiq.coredevice.phaser
:members: :members:
@ -114,31 +113,31 @@ DAC/ADC drivers
--------------- ---------------
:mod:`artiq.coredevice.ad53xx` module :mod:`artiq.coredevice.ad53xx` module
+++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.ad53xx .. automodule:: artiq.coredevice.ad53xx
:members: :members:
:mod:`artiq.coredevice.zotino` module :mod:`artiq.coredevice.zotino` module
+++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.zotino .. automodule:: artiq.coredevice.zotino
:members: :members:
:mod:`artiq.coredevice.sampler` module :mod:`artiq.coredevice.sampler` module
++++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.sampler .. automodule:: artiq.coredevice.sampler
:members: :members:
:mod:`artiq.coredevice.fastino` module :mod:`artiq.coredevice.fastino` module
++++++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.fastino .. automodule:: artiq.coredevice.fastino
:members: :members:
:mod:`artiq.coredevice.shuttler` module :mod:`artiq.coredevice.shuttler` module
++++++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.shuttler .. automodule:: artiq.coredevice.shuttler
:members: :members:
@ -148,14 +147,14 @@ Miscellaneous
------------- -------------
:mod:`artiq.coredevice.suservo` module :mod:`artiq.coredevice.suservo` module
++++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.suservo .. automodule:: artiq.coredevice.suservo
:members: :members:
:mod:`artiq.coredevice.grabber` module :mod:`artiq.coredevice.grabber` module
++++++++++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
.. automodule:: artiq.coredevice.grabber .. automodule:: artiq.coredevice.grabber
:members: :members:

View File

@ -7,22 +7,28 @@ The most commonly used features from the ARTIQ language modules and from the cor
--------------------------------- ---------------------------------
.. automodule:: artiq.language.core .. automodule:: artiq.language.core
:member-order: bysource
:members: :members:
:mod:`artiq.language.environment` module :mod:`artiq.language.environment` module
---------------------------------------- ----------------------------------------
.. automodule:: artiq.language.environment .. automodule:: artiq.language.environment
:member-order: bysource
:members: :members:
:mod:`artiq.language.scan` module :mod:`artiq.language.scan` module
---------------------------------------- ----------------------------------------
.. automodule:: artiq.language.scan .. automodule:: artiq.language.scan
:member-order: bysource
:members: :members:
:mod:`artiq.language.units` module :mod:`artiq.language.units` module
---------------------------------- ----------------------------------
This module contains floating point constants that correspond to common physical units (ns, MHz, ...). .. displays nothing, but makes references work
They are provided for convenience (e.g write ``MHz`` instead of ``1000000.0``) and code clarity purposes. .. automodule:: artiq.language.units
:members:
This module contains floating point constants that correspond to common physical units (ns, MHz, ...). They are provided for convenience (e.g write ``MHz`` instead of ``1000000.0``) and code clarity purposes.

View File

@ -1,46 +0,0 @@
.. _developing-artiq:
Developing ARTIQ
^^^^^^^^^^^^^^^^
.. warning::
This section is only for software or FPGA developers who want to modify ARTIQ. The steps described here are not required if you simply want to run experiments with ARTIQ. If you purchased a system from M-Labs or QUARTIQ, we normally provide board binaries for you.
The easiest way to obtain an ARTIQ development environment is via the Nix package manager on Linux. The Nix system is used on the `M-Labs Hydra server <https://nixbld.m-labs.hk/>`_ to build ARTIQ and its dependencies continuously; it ensures that all build instructions are up-to-date and allows binary packages to be used on developers' machines, in particular for large tools such as the Rust compiler.
ARTIQ itself does not depend on Nix, and it is also possible to compile everything from source (look into the ``flake.nix`` file and/or nixpkgs, and run the commands manually) - but Nix makes the process a lot easier.
* Download Vivado from Xilinx and install it (by running the official installer in a FHS chroot environment if using NixOS; the ARTIQ flake provides such an environment, which can be entered with the command `vivado-env`). If you do not want to write to ``/opt``, you can install it in a folder of your home directory. The "appropriate" Vivado version to use for building the bitstream can vary. Some versions contain bugs that lead to hidden or visible failures, others work fine. Refer to `Hydra <https://nixbld.m-labs.hk/>`_ and/or the ``flake.nix`` file from the ARTIQ repository in order to determine which version is used at M-Labs. If the Vivado GUI installer crashes, you may be able to work around the problem by running it in unattended mode with a command such as ``./xsetup -a XilinxEULA,3rdPartyEULA,WebTalkTerms -b Install -e 'Vitis Unified Software Platform' -l /opt/Xilinx/``.
* During the Vivado installation, uncheck ``Install cable drivers`` (they are not required as we use better and open source alternatives).
* Install the `Nix package manager <http://nixos.org/nix/>`_, version 2.4 or later. Prefer a single-user installation for simplicity.
* If you did not install Vivado in its default location ``/opt``, clone the ARTIQ Git repository and edit ``flake.nix`` accordingly.
* Enable flakes in Nix by e.g. adding ``experimental-features = nix-command flakes`` to ``nix.conf`` (for example ``~/.config/nix/nix.conf``).
* Clone the ARTIQ Git repository and run ``nix develop`` at the root (where ``flake.nix`` is).
* Make the current source code of ARTIQ available to the Python interpreter by running ``export PYTHONPATH=`pwd`:$PYTHONPATH``.
* You can then build the firmware and gateware with a command such as ``$ python -m artiq.gateware.targets.kasli <description>.json``, using a JSON system description file.
* Flash the binaries into the FPGA board with a command such as ``$ artiq_flash --srcbuild -d artiq_kasli/<your_variant>``. You need to configure OpenOCD as explained :ref:`in the user section <installing-configuring-openocd>`. OpenOCD is already part of the flake's development environment.
* Check that the board boots and examine the UART messages by running a serial terminal program, e.g. ``$ flterm /dev/ttyUSB1`` (``flterm`` is part of MiSoC and installed in the flake's development environment). Leave the terminal running while you are flashing the board, so that you see the startup messages when the board boots immediately after flashing. You can also restart the board (without reflashing it) with ``$ artiq_flash start``.
* The communication parameters are 115200 8-N-1. Ensure that your user has access to the serial device (e.g. by adding the user account to the ``dialout`` group).
Testing Custom NAC3 Builds
^^^^^^^^^^^^^^^^^^^^^^^^^^
To test a version of NAC3 from source using ARTIQ kernels or test cases on real hardware, follow these instructions:
1. Clone or download the source code of NAC3. This path will be referred to as ``<path-to-nac3>``.
2. Build NAC3 from source, e.g.:
.. code-block::
$ cd <path-to-nac3>
$ nix develop
[nix-shell] $ cargo clean && cargo build --release
3. Symlink ``nac3artiq.so`` to ``libnac3artiq.so``, i.e. ``ln -s target/release/libnac3artiq.so target/release/nac3artiq.so``
4. Enter the development environment for ARTIQ, e.g. ``cd <path-to-artiq> && nix develop``
5. Run the Python command, overriding ``PYTHONPATH`` with the directory containing ``nac3artiq.so``, e.g.:
.. code-block::
[nix-shell] $ PYTHONPATH="<path-to-nac3>/target/release:$PYTHONPATH" python ...
This will run the ARTIQ command using the custom build of NAC3.

View File

@ -1,39 +1,41 @@
Developing a Network Device Support Package (NDSP) Developing a Network Device Support Package (NDSP)
================================================== ==================================================
Most ARTIQ devices are interfaced through "controllers" that expose RPC interfaces to the network (based on SiPyCo). The master never does direct I/O to the devices, but issues RPCs to the controllers when needed. As opposed to running everything on the master, this architecture has those main advantages: Besides the kind of specialized real-time hardware most of ARTIQ is concerned with the control and management of, ARTIQ also easily handles more conventional 'slow' devices. This is done through *controllers*, based on `SiPyCo <https://github.com/m-labs/sipyco>`_ (manual hosted `here <https://m-labs.hk/artiq/sipyco-manual/>`_), which expose remote procedure call (RPC) interfaces to the network. This allows experiments to issue RPCs to the controllers as necessary, without needing to do direct I/O to the devices. Some advantages of this architecture include:
* Each driver can be run on a different machine, which alleviates cabling issues and OS compatibility problems. * Controllers/drivers can be run on different machines, alleviating cabling issues and OS compatibility problems.
* Reduces the impact of driver crashes. * Reduces the impact of driver crashes.
* Reduces the impact of driver memory leaks. * Reduces the impact of driver memory leaks.
This mechanism is for "slow" devices that are directly controlled by a PC, typically over a non-realtime channel such as USB. Certain devices (such as the PDQ2) may still perform real-time operations by having certain controls physically connected to the core device (for example, the trigger and frame selection signals on the PDQ2). For handling such cases, parts of the support infrastructure may be kernels executed on the core device.
Certain devices (such as the PDQ2) may still perform real-time operations by having certain controls physically connected to the core device (for example, the trigger and frame selection signals on the PDQ2). For handling such cases, parts of the NDSPs may be kernels executed on the core device. .. seealso::
Some NDSPs for particular devices have already been written and made available to the public. A (non-exhaustive) list can be found on the page :doc:`list_of_ndsps`.
A network device support package (NDSP) is composed of several parts: Components of a NDSP
--------------------
1. The `driver`, which contains the Python API functions to be called over the network, and performs the I/O to the device. The top-level module of the driver is called ``artiq.devices.XXX.driver``. Full support for a specific device, called a network device support package or NDSP, requires several parts:
2. The `controller`, which instantiates, initializes and terminates the driver, and sets up the RPC server. The controller is a front-end command-line tool to the user and is called ``artiq.frontend.aqctl_XXX``. A ``setup.py`` entry must also be created to install it.
1. The `driver`, which contains the Python API functions to be called over the network and performs the I/O to the device. The top-level module of the driver should be called ``artiq.devices.XXX.driver``.
2. The `controller`, which instantiates, initializes and terminates the driver, and sets up the RPC server. The controller is a front-end command-line tool to the user and should be called ``artiq.frontend.aqctl_XXX``. A ``setup.py`` entry must also be created to install it.
3. An optional `client`, which connects to the controller and exposes the functions of the driver as a command-line interface. Clients are front-end tools (called ``artiq.frontend.aqcli_XXX``) that have ``setup.py`` entries. In most cases, a custom client is not needed and the generic ``sipyco_rpctool`` utility can be used instead. Custom clients are only required when large amounts of data must be transferred over the network API, that would be unwieldy to pass as ``sipyco_rpctool`` command-line parameters. 3. An optional `client`, which connects to the controller and exposes the functions of the driver as a command-line interface. Clients are front-end tools (called ``artiq.frontend.aqcli_XXX``) that have ``setup.py`` entries. In most cases, a custom client is not needed and the generic ``sipyco_rpctool`` utility can be used instead. Custom clients are only required when large amounts of data must be transferred over the network API, that would be unwieldy to pass as ``sipyco_rpctool`` command-line parameters.
4. An optional `mediator`, which is code executed on the client that supplements the network API. A mediator may contain kernels that control real-time signals such as TTL lines connected to the device. Simple devices use the network API directly and do not have a mediator. Mediator modules are called ``artiq.devices.XXX.mediator`` and their public classes are exported at the ``artiq.devices.XXX`` level (via ``__init__.py``) for direct import and use by the experiments. 4. An optional `mediator`, which is code executed on the client that supplements the network API. A mediator may contain kernels that control real-time signals such as TTL lines connected to the device. Simple devices use the network API directly and do not have a mediator. Mediator modules are called ``artiq.devices.XXX.mediator`` and their public classes are exported at the ``artiq.devices.XXX`` level (via ``__init__.py``) for direct import and use by the experiments.
The driver and controller The driver and controller
------------------------- -------------------------
A controller is a piece of software that receives commands from a client over the network (or the ``localhost`` interface), drives a device, and returns information about the device to the client. The mechanism used is remote procedure calls (RPCs) using ``sipyco.pc_rpc``, which makes the network layers transparent for the driver's user. As an example, we will develop a controller for a "device" that is very easy to work with: the console from which the controller is run. The operation that the driver will implement (and offer as an RPC) is writing a message to that console.
The controller we will develop is for a "device" that is very easy to work with: the console from which the controller is run. The operation that the driver will implement is writing a message to that console. To use RPCs, the functions that a driver provides must be the methods of a single object. We will thus define a class that provides our message-printing method: ::
For using RPC, the functions that a driver provides must be the methods of a single object. We will thus define a class that provides our message-printing method: ::
class Hello: class Hello:
def message(self, msg): def message(self, msg):
print("message: " + msg) print("message: " + msg)
For a more complex driver, you would put this class definition into a separate Python module called ``driver``. For a more complex driver, we would place this class definition into a separate Python module called ``driver``. In this example, for simplicity, we can include it in the controller module.
To turn it into a server, we use ``sipyco.pc_rpc``. Import the function we will use: :: For the controller itself, we will turn this method into a server using ``sipyco.pc_rpc``. Import the function we will use: ::
from sipyco.pc_rpc import simple_server_loop from sipyco.pc_rpc import simple_server_loop
@ -45,13 +47,14 @@ and add a ``main`` function that is run when the program is executed: ::
if __name__ == "__main__": if __name__ == "__main__":
main() main()
:tip: Defining the ``main`` function instead of putting its code directly in the ``if __name__ == "__main__"`` body enables the controller to be used as well as a setuptools entry point. .. tip::
Defining the ``main`` function instead of putting its code directly in the ``if __name__ == "__main__"`` body enables the controller to be used as a setuptools entry point as well.
The parameters ``::1`` and ``3249`` are respectively the address to bind the server to (IPv6 localhost) and the TCP port to use. Then add a line: :: The parameters ``::1`` and ``3249`` are respectively the address to bind the server to (in this case, we use IPv6 localhost) and the TCP port. Add a line: ::
#!/usr/bin/env python3 #!/usr/bin/env python3
at the beginning of the file, save it to ``aqctl_hello.py`` and set its execution permissions: :: at the beginning of the file, save it as ``aqctl_hello.py``, and set its execution permissions: ::
$ chmod 755 aqctl_hello.py $ chmod 755 aqctl_hello.py
@ -59,16 +62,18 @@ Run it as: ::
$ ./aqctl_hello.py $ ./aqctl_hello.py
and verify that you can connect to the TCP port: :: In a different console, verify that you can connect to the TCP port: ::
$ telnet ::1 3249 $ telnet ::1 3249
Trying ::1... Trying ::1...
Connected to ::1. Connected to ::1.
Escape character is '^]'. Escape character is '^]'.
:tip: Use the key combination Ctrl-AltGr-9 to get the ``telnet>`` prompt, and enter ``close`` to quit Telnet. Quit the controller with Ctrl-C. .. tip ::
Also verify that a target (service) named "hello" (as passed in the first argument to ``simple_server_loop``) exists using the ``sipyco_rpctool`` program from the ARTIQ front-end tools: :: To exit telnet, use the escape character combination (Ctrl + ]) to access the ``telnet>`` prompt, and then enter ``quit`` or ``close`` to close the connection.
Also verify that a target (i.e. available service for RPC) named "hello" exists: ::
$ sipyco_rpctool ::1 3249 list-targets $ sipyco_rpctool ::1 3249 list-targets
Target(s): hello Target(s): hello
@ -76,7 +81,7 @@ Also verify that a target (service) named "hello" (as passed in the first argume
The client The client
---------- ----------
Clients are small command-line utilities that expose certain functionalities of the drivers. The ``sipyco_rpctool`` utility contains a generic client that can be used in most cases, and developing a custom client is not required. Try these commands :: Clients are small command-line utilities that expose certain functionalities of the drivers. The ``sipyco_rpctool`` utility contains a generic client that can be used in most cases, and developing a custom client is not required. You have already used it above in the form of ``list-targets``. Try these commands: ::
$ sipyco_rpctool ::1 3249 list-methods $ sipyco_rpctool ::1 3249 list-methods
$ sipyco_rpctool ::1 3249 call message test $ sipyco_rpctool ::1 3249 call message test
@ -98,21 +103,54 @@ In case you are developing a NDSP that is complex enough to need a custom client
if __name__ == "__main__": if __name__ == "__main__":
main() main()
Run it as before, while the controller is running. You should see the message appearing on the controller's terminal: :: Run it as before, making sure the controller is running first. You should see the message appear in the controller's terminal: ::
$ ./aqctl_hello.py $ ./aqctl_hello.py
message: Hello World! message: Hello World!
When using the driver in an experiment, the ``Client`` instance can be returned by the environment mechanism (via the ``get_device`` and ``attr_device`` methods of :class:`artiq.language.environment.HasEnvironment`) and used normally as a device. We see that the client has made a request to the server, which has, through the driver, performed the requisite I/O with the "device" (its console), resulting in the operation we wanted. Success!
:warning: RPC servers operate on copies of objects provided by the client, and modifications to mutable types are not written back. For example, if the client passes a list as a parameter of an RPC method, and that method ``append()s`` an element to the list, the element is not appended to the client's list. .. warning::
Note that RPC servers operate on copies of objects provided by the client, and modifications to mutable types are not written back. For example, if the client passes a list as a parameter of an RPC method, and that method ``append()s`` an element to the list, the element is not appended to the client's list.
To access this driver in an experiment, we can retrieve the ``Client`` instance with the ``get_device`` and ``set_device`` methods of :class:`artiq.language.environment.HasEnvironment`, and then use it like any other device (provided the controller is running and accessible at the time).
.. _ndsp-integration:
Integration with ARTIQ experiments
----------------------------------
Generally we will want to add the device to our :ref:`device database <device-db>` so that we can add it to an experiment with ``self.setattr_device`` and so the controller can be started and stopped automatically by a controller manager (the ``artiq_ctlmgr`` utility from ``artiq-comtools``). To do so, add an entry to your device database in this format: ::
device_db.update({
"hello": {
"type": "controller",
"host": "::1",
"port": 3249,
"command": "python /abs/path/to/aqctl_hello.py -p {port}"
},
})
Now it can be added using ``self.setattr_device("hello")`` in the ``build()`` phase of the experiment, and its methods accessed via: ::
self.hello.message("Hello world!")
.. note::
In order to be correctly started and stopped by a controller manager, your controller must additionally implement a ``ping()`` method, which should simply return true, e.g. ::
def ping(self):
return True
Remote execution support
------------------------
If you wish to support remote execution in your controller, you may do so by simply replacing ``simple_server_loop`` with :class:`sipyco.remote_exec.simple_rexec_server_loop`.
Command-line arguments Command-line arguments
---------------------- ----------------------
Use the Python ``argparse`` module to make the bind address(es) and port configurable on the controller, and the server address, port and message configurable on the client. Use the Python ``argparse`` module to make the bind address(es) and port configurable on the controller, and the server address, port and message configurable on the client. We suggest naming the controller parameters ``--bind`` (which adds a bind address in addition to a default binding to localhost), ``--no-bind-localhost`` (which disables the default binding to localhost), and ``--port``, so that those parameters stay consistent across controllers. Use ``-s/--server`` and ``--port`` on the client. The :meth:`sipyco.common_args.simple_network_args` library function adds such arguments for the controller, and the :meth:`sipyco.common_args.bind_address_from_args` function processes them.
We suggest naming the controller parameters ``--bind`` (which adds a bind address in addition to a default binding to localhost), ``--no-bind-localhost`` (which disables the default binding to localhost), and ``--port``, so that those parameters stay consistent across controllers. Use ``-s/--server`` and ``--port`` on the client. The ``sipyco.common_args.simple_network_args`` library function adds such arguments for the controller, and the ``sipyco.common_args.bind_address_from_args`` function processes them.
The controller's code would contain something similar to this: :: The controller's code would contain something similar to this: ::
@ -132,7 +170,7 @@ We suggest that you define a function ``get_argparser`` that returns the argumen
Logging Logging
------- -------
For the debug, information and warning messages, use the ``logging`` Python module and print the log on the standard error output (the default setting). The logging level is by default "WARNING", meaning that only warning messages and more critical messages will get printed (and no debug nor information messages). By calling ``sipyco.common_args.verbosity_args`` with the parser as argument, you add support for the ``--verbose`` (``-v``) and ``--quiet`` (``-q``) arguments in the parser. Each occurrence of ``-v`` (resp. ``-q``) in the arguments will increase (resp. decrease) the log level of the logging module. For instance, if only one ``-v`` is present in the arguments, then more messages (info, warning and above) will get printed. If only one ``-q`` is present in the arguments, then only errors and critical messages will get printed. If ``-qq`` is present in the arguments, then only critical messages will get printed, but no debug/info/warning/error. For debug, information and warning messages, use the ``logging`` Python module and print the log on the standard error output (the default setting). As in other areas, there are five logging levels, from most to least critical, ``CRITICAL``, ``ERROR``, ``WARNING``, ``INFO``, and ``DEBUG``. By default, the logging level starts at ``WARNING``, meaning it will print messages of level WARNING and above (and no debug nor information messages). By calling ``sipyco.common_args.verbosity_args`` with the parser as argument, you add support for the ``--verbose`` (``-v``) and ``--quiet`` (``-q``) arguments in your controller. Each occurrence of ``-v`` (resp. ``-q``) in the arguments will increase (resp. decrease) the log level of the logging module. For instance, if only one ``-v`` is present, then more messages (INFO and above) will be printed. If only one ``-q`` is present in the arguments, then ERROR and above will be printed. If ``-qq`` is present in the arguments, then only CRITICAL will be printed.
The program below exemplifies how to use logging: :: The program below exemplifies how to use logging: ::
@ -168,46 +206,26 @@ The program below exemplifies how to use logging: ::
if __name__ == "__main__": if __name__ == "__main__":
main() main()
Additional guidelines
---------------------
Integration with ARTIQ experiments Command line and options
---------------------------------- ^^^^^^^^^^^^^^^^^^^^^^^^
To integrate the controller into an experiment, simply add an entry into the ``device_db.py`` following the example shown below for the ``aqctl_hello.py`` from above: :: * Controllers should be able to operate in "simulation" mode, specified with ``--simulation``, where they behave properly even if the associated hardware is not connected. For example, they can print the data to the console instead of sending it to the device, or dump it into a file.
* The device identification (e.g. serial number, or entry in ``/dev``) to attach to must be passed as a command-line parameter to the controller. We suggest using ``-d`` and ``--device`` as parameter name.
* Keep command line parameters consistent across clients/controllers. When adding new command line options, look for a client/controller that does a similar thing and follow its use of ``argparse``. If the original client/controller could use ``argparse`` in a better way, improve it.
device_db.update({ Style
"hello": { ^^^^^
"type": "controller",
"host": "::1",
"port": 3249,
"command": "python /abs/path/to/aqctl_hello.py -p {port}"
},
})
From the experiment this can now be added using ``self.setattr_device("hello")`` in the ``build()`` phase of the experiment, and methods accessed via: ::
self.hello.message("Hello world!")
To automatically initiate controllers the ``artiq_ctlmgr`` utility from ``artiq-comtools`` can be used. By default, this initiates all controllers hosted on the local address of master connection.
Remote execution support
------------------------
If you wish to support remote execution in your controller, you may do so by simply replacing ``simple_server_loop`` with :class:`sipyco.remote_exec.simple_rexec_server_loop`.
General guidelines
------------------
* Do not use ``__del__`` to implement the cleanup code of your driver. Instead, define a ``close`` method, and call it using a ``try...finally...`` block in the controller. * Do not use ``__del__`` to implement the cleanup code of your driver. Instead, define a ``close`` method, and call it using a ``try...finally...`` block in the controller.
* Format your source code according to PEP8. We suggest using ``flake8`` to check for compliance. * Format your source code according to PEP8. We suggest using ``flake8`` to check for compliance.
* Use new-style formatting (``str.format``) except for logging where it is not well supported, and double quotes for strings. * Use new-style formatting (``str.format``) except for logging where it is not well supported, and double quotes for strings.
* The device identification (e.g. serial number, or entry in ``/dev``) to attach to must be passed as a command-line parameter to the controller. We suggest using ``-d`` and ``--device`` as parameter name.
* Controllers must be able to operate in "simulation" mode, where they behave properly even if the associated hardware is not connected. For example, they can print the data to the console instead of sending it to the device, or dump it into a file.
* The simulation mode is entered whenever the ``--simulation`` option is specified.
* Keep command line parameters consistent across clients/controllers. When adding new command line options, look for a client/controller that does a similar thing and follow its use of ``argparse``. If the original client/controller could use ``argparse`` in a better way, improve it.
* Use docstrings for all public methods of the driver (note that those will be retrieved by ``sipyco_rpctool``). * Use docstrings for all public methods of the driver (note that those will be retrieved by ``sipyco_rpctool``).
* Choose a free default TCP port and add it to the default port list in this manual. * Choose a free default TCP port and add it to the :doc:`default port list<default_network_ports>` in this manual.
Hosting your code Hosting your code
----------------- -----------------
We suggest that you create a Git repository for your code, and publish it on https://git.m-labs.hk/, GitLab, GitHub, or a similar website of your choosing. Then send us a message or pull request for your NDSP to be added to the list in this manual. We suggest that you create a Git repository for your code, and publish it on https://git.m-labs.hk/, GitLab, GitHub, or a similar website of your choosing. Then send us a message or pull request for your NDSP to be added to :doc:`the list in this manual <list_of_ndsps>`.

View File

@ -1,17 +1,11 @@
Distributed Real Time Input/Output (DRTIO) DRTIO system
========================================== ============
DRTIO is a time and data transfer system that allows ARTIQ RTIO channels to be distributed among several satellite devices synchronized and controlled by a central core device. DRTIO is the time and data transfer system that allows ARTIQ RTIO channels to be distributed among several satellite devices, synchronized and controlled by a central core device. The main source of DRTIO traffic is the remote control of RTIO output and input channels. The protocol is optimized to maximize throughput and minimize latency, and handles flow control and error conditions (underflows, overflows, etc.)
The link is a high speed duplex serial line operating at 1Gbps or more, over copper or optical fiber. The DRTIO protocol also supports auxiliary traffic, which is low-priority and non-realtime, e.g., to override and monitor TTL I/Os. Auxiliary traffic never interrupts or delays the main traffic, so it cannot cause unexpected latencies or exceptions (e.g. RTIO underflows).
The main source of DRTIO traffic is the remote control of RTIO output and input channels. The protocol is optimized to maximize throughput and minimize latency, and handles flow control and error conditions (underflows, overflows, etc.) The lower layers of DRTIO are similar to `White Rabbit <https://white-rabbit.web.cern.ch/>`_ , with the following main differences:
The DRTIO protocol also supports auxiliary, low-priority and non-realtime traffic. The auxiliary channel supports overriding and monitoring TTL I/Os. Auxiliary traffic never interrupts or delays the main traffic, so that it cannot cause unexpected poor performance (e.g. RTIO underflows).
Time transfer and clock syntonization is typically done over the serial link alone. The DRTIO code is organized as much as possible to support porting to different types of transceivers (Xilinx MGTs, Altera MGTs, soft transceivers running off regular FPGA IOs, etc.) and different synchronization mechanisms.
The lower layers of DRTIO are similar to White Rabbit, with the following main differences:
* lower latency * lower latency
* deterministic latency * deterministic latency
@ -20,80 +14,31 @@ The lower layers of DRTIO are similar to White Rabbit, with the following main d
* no Ethernet compatibility * no Ethernet compatibility
* only star or tree topologies are supported * only star or tree topologies are supported
From ARTIQ kernels, DRTIO channels are used in the same way as local RTIO channels. Time transfer and clock syntonization is typically done over the serial link alone. The DRTIO code is written as much as possible to support porting to different types of transceivers (Xilinx MGTs, Altera MGTs, soft transceivers running off regular FPGA IOs, etc.) and different synchronization mechanisms.
.. _using-drtio:
Using DRTIO
-----------
Terminology Terminology
+++++++++++ -----------
In a system of interconnected DRTIO devices, each RTIO core (driving RTIO PHYs; for example a RTIO core would connect to a large bank of TTL signals) is assigned a number and is called a *destination*. One DRTIO device normally contains one RTIO core. In a system of interconnected DRTIO devices, each RTIO core (controlling a certain set of associated RTIO channels) is assigned a number and called a *destination*. One DRTIO device normally contains one RTIO core.
On one DRTIO device, the immediate path that a RTIO request must take is called a *hop*: the request can be sent to the local RTIO core, or to another device downstream. Each possible hop is assigned a number. Hop 0 is normally the local RTIO core, and hops 1 and above correspond to the respective downstream ports of the device. On one DRTIO device, the immediate path that a RTIO request must take is called a *hop*: the request can be sent to the local RTIO core, or to another device downstream. Each possible hop is assigned a number. Hop 0 is normally the local RTIO core, and hops 1 and above correspond to the respective downstream ports of the device.
DRTIO devices are arranged in a tree topology, with the core device at the root. For each device, its distance from the root (in number of devices that are crossed) is called its *rank*. The root has rank 0, the devices immediately connected to it have rank 1, and so on. DRTIO devices are arranged in a tree topology, with the core device at the root. For each device, its distance from the root (in number of devices that are crossed) is called its *rank*. The root has rank 0, the devices immediately connected to it have rank 1, and so on.
The routing table The routing table
+++++++++++++++++ -----------------
The routing table defines, for each destination, the list of hops ("route") that must be taken from the root in order to reach it. The routing table defines, for each destination, the list of hops ("route") that must be taken from the root in order to reach it.
It is stored in a binary format that can be manipulated with the :ref:`artiq_route utility <routing-table-tool>`. The binary file is then programmed into the flash storage of the core device under the ``routing_table`` key. It is automatically distributed to downstream devices when the connections are established. Modifying the routing table requires rebooting the core device for the new table to be taken into account. It is stored in a binary format that can be generated and manipulated with the :ref:`artiq_route utility <routing-table-tool>`, see :ref:`drtio-routing`. The binary file is programmed into the flash storage of the core device under the ``routing_table`` key. It is automatically distributed to downstream devices when the connections are established. Modifying the routing table requires rebooting the core device for the new table to be taken into account.
All routes must end with the local RTIO core of the last device (0).
The local RTIO core of the core device is a destination like any other, and it needs to be explicitly part of the routing table for kernels to be able to access it.
If no routing table is programmed, the core device takes a default routing table for a star topology (i.e. with no devices of rank 2 or above), with destination 0 being the core device's local RTIO core and destinations 1 and above corresponding to devices on the respective downstream ports.
Here is an example of creating and programming a routing table for a chain of 3 devices: ::
# create an empty routing table
$ artiq_route rt.bin init
# set destination 0 to the local RTIO core
$ artiq_route rt.bin set 0 0
# for destination 1, first use hop 1 (the first downstream port)
# then use the local RTIO core of that second device.
$ artiq_route rt.bin set 1 1 0
# for destination 2, use hop 1 and reach the second device as
# before, then use hop 1 on that device to reach the third
# device, and finally use the local RTIO core (hop 0) of the
# third device.
$ artiq_route rt.bin set 2 1 1 0
$ artiq_route rt.bin show
0: 0
1: 1 0
2: 1 1 0
$ artiq_coremgmt config write -f routing_table rt.bin
Addressing distributed RTIO cores from kernels
++++++++++++++++++++++++++++++++++++++++++++++
Remote RTIO channels are accessed in the same way as local ones. Bits 16-24 of the RTIO channel number define the destination. Bits 0-15 of the RTIO channel number select the channel within the destination.
Link establishment
++++++++++++++++++
After devices have booted, it takes several seconds for all links in a DRTIO system to become established (especially with the long locking times of low-bandwidth PLLs that are used for jitter reduction purposes). Kernels should not attempt to access destinations until all required links are up (when this happens, the ``RTIODestinationUnreachable`` exception is raised). ARTIQ provides the method :meth:`~artiq.coredevice.core.Core.get_rtio_destination_status` that determines whether a destination can be reached. We recommend calling it in a loop in your startup kernel for each important destination, to delay startup until they all can be reached.
Latency
+++++++
Each hop increases the RTIO latency of a destination by a significant amount; that latency is however constant and can be compensated for in kernels. To limit latency in a system, fully utilize the downstream ports of devices to reduce the depth of the tree, instead of creating chains.
Internal details Internal details
---------------- ----------------
Bits 16-24 of the RTIO channel number (assigned to a respective device in the initial :ref:`system description JSON <system-description>`, and specified again for use of the ARTIQ front-end in the device database) define the destination. Bits 0-15 of the RTIO channel number select the channel within the destination.
Real-time and auxiliary packets Real-time and auxiliary packets
+++++++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
DRTIO is a packet-based protocol that uses two types of packets: DRTIO is a packet-based protocol that uses two types of packets:
@ -101,9 +46,9 @@ DRTIO is a packet-based protocol that uses two types of packets:
* auxiliary packets, which are lower-bandwidth and are used for ancillary tasks such as housekeeping and monitoring/injection. Auxiliary packets are low-priority and their transmission has no impact on the timing of real-time packets (however, transmission of real-time packets slows down the transmission of auxiliary packets). In the ARTIQ DRTIO implementation, the contents of the auxiliary packets are read and written directly by the firmware, with the gateware simply handling the transmission of the raw data. * auxiliary packets, which are lower-bandwidth and are used for ancillary tasks such as housekeeping and monitoring/injection. Auxiliary packets are low-priority and their transmission has no impact on the timing of real-time packets (however, transmission of real-time packets slows down the transmission of auxiliary packets). In the ARTIQ DRTIO implementation, the contents of the auxiliary packets are read and written directly by the firmware, with the gateware simply handling the transmission of the raw data.
Link layer Link layer
++++++++++ ^^^^^^^^^^
The lower layer of the DRTIO protocol stack is the link layer, which is responsible for delimiting real-time and auxiliary packets, and assisting with the establishment of a fixed-latency high speed serial transceiver link. The lower layer of the DRTIO protocol stack is the link layer, which is responsible for delimiting real-time and auxiliary packets and assisting with the establishment of a fixed-latency high speed serial transceiver link.
DRTIO uses the IBM (Widmer and Franaszek) 8b/10b encoding. D characters (the encoded 8b symbols) always transmit real-time packet data, whereas K characters are used for idling and transmitting auxiliary packet data. DRTIO uses the IBM (Widmer and Franaszek) 8b/10b encoding. D characters (the encoded 8b symbols) always transmit real-time packet data, whereas K characters are used for idling and transmitting auxiliary packet data.
@ -113,7 +58,8 @@ A real-time packet is defined by a series of D characters containing the packet'
K characters, which are transmitted whenever there is no real-time data to transmit and to delimit real-time packets, are chosen using a 3-bit K selection word. If this K character is the first character in the set of N characters processed by the transceiver in the logic clock cycle, the mapping between the K selection word and the 8b/10b K space contains commas. If the K character is any of the subsequent characters processed by the transceiver, a different mapping is used that does not contain any commas. This scheme allows the receiver to align its logic clock with that of the transmitter, simply by shifting its logic clock so that commas are received into the first character position. K characters, which are transmitted whenever there is no real-time data to transmit and to delimit real-time packets, are chosen using a 3-bit K selection word. If this K character is the first character in the set of N characters processed by the transceiver in the logic clock cycle, the mapping between the K selection word and the 8b/10b K space contains commas. If the K character is any of the subsequent characters processed by the transceiver, a different mapping is used that does not contain any commas. This scheme allows the receiver to align its logic clock with that of the transmitter, simply by shifting its logic clock so that commas are received into the first character position.
.. note:: Due to the shoddy design of transceiver hardware, this simple process of clock and comma alignment is difficult to perform in practice. The paper "High-speed, fixed-latency serial links with Xilinx FPGAs" (by Xue LIU, Qing-xu DENG, Bo-ning HOU and Ze-ke WANG) discusses techniques that can be used. The ARTIQ implementation simply keeps resetting the receiver until the comma is aligned, since relatively long lock times are acceptable. .. note::
Due to the shoddy design of transceiver hardware, this simple process of clock and comma alignment is difficult to perform in practice. The paper "High-speed, fixed-latency serial links with Xilinx FPGAs" (by Xue LIU, Qing-xu DENG, Bo-ning HOU and Ze-ke WANG) discusses techniques that can be used. The ARTIQ implementation simply keeps resetting the receiver until the comma is aligned, since relatively long lock times are acceptable.
The series of K selection words is then used to form auxiliary packets and the idle pattern. When there is no auxiliary packet to transfer or to delimitate auxiliary packets, the K selection word ``100`` is used. To transfer data from an auxiliary packet, the K selection word ``0ab`` is used, with ``ab`` containing two bits of data from the packet. An auxiliary packet is delimited by at least one ``100`` K selection word. The series of K selection words is then used to form auxiliary packets and the idle pattern. When there is no auxiliary packet to transfer or to delimitate auxiliary packets, the K selection word ``100`` is used. To transfer data from an auxiliary packet, the K selection word ``0ab`` is used, with ``ab`` containing two bits of data from the packet. An auxiliary packet is delimited by at least one ``100`` K selection word.
@ -122,23 +68,23 @@ Both real-time traffic and K selection words are scrambled in order to make the
Due to the use of K characters both as delimiters for real-time packets and as information carrier for auxiliary packets, auxiliary traffic is guaranteed a minimum bandwidth simply by having a maximum size limit on real-time packets. Due to the use of K characters both as delimiters for real-time packets and as information carrier for auxiliary packets, auxiliary traffic is guaranteed a minimum bandwidth simply by having a maximum size limit on real-time packets.
Clocking Clocking
++++++++ ^^^^^^^^
At the DRTIO satellite device, the recovered and aligned transceiver clock is used for clocking RTIO channels, after appropriate jitter filtering using devices such as the Si5324. The same clock is also used for clocking the DRTIO transmitter (loop timing), which simplifies clock domain transfers and allows for precise round-trip-time measurements to be done. At the DRTIO satellite device, the recovered and aligned transceiver clock is used for clocking RTIO channels, after appropriate jitter filtering using devices such as the Si5324. The same clock is also used for clocking the DRTIO transmitter (loop timing), which simplifies clock domain transfers and allows for precise round-trip-time measurements to be done.
RTIO clock synchronization RTIO clock synchronization
++++++++++++++++++++++++++ ^^^^^^^^^^^^^^^^^^^^^^^^^^
As part of the DRTIO link initialization, a real-time packet is sent by the core device to each satellite device to make them load their respective timestamp counters with the timestamp values from their respective packets. As part of the DRTIO link initialization, a real-time packet is sent by the core device to each satellite device to make them load their respective timestamp counters with the timestamp values from their respective packets.
RTIO outputs RTIO outputs
++++++++++++ ^^^^^^^^^^^^
Controlling a remote RTIO output involves placing the RTIO event into the buffer of the destination. The core device maintains a cache of the buffer space available in each destination. If, according to the cache, there is space available, then a packet containing the event information (timestamp, address, channel, data) is sent immediately and the cached value is decremented by one. If, according to the cache, no space is available, then the core device sends a request for the space available in the destination and updates the cache. The process repeats until at least one remote buffer entry is available for the event, at which point a packet containing the event information is sent as before. Controlling a remote RTIO output involves placing the RTIO event into the buffer of the destination. The core device maintains a cache of the buffer space available in each destination. If, according to the cache, there is space available, then a packet containing the event information (timestamp, address, channel, data) is sent immediately and the cached value is decremented by one. If, according to the cache, no space is available, then the core device sends a request for the space available in the destination and updates the cache. The process repeats until at least one remote buffer entry is available for the event, at which point a packet containing the event information is sent as before.
Detecting underflow conditions is the responsibility of the core device; should an underflow occur then no DRTIO packet is transmitted. Sequence errors are handled similarly. Detecting underflow conditions is the responsibility of the core device; should an underflow occur then no DRTIO packet is transmitted. Sequence errors are handled similarly.
RTIO inputs RTIO inputs
+++++++++++ ^^^^^^^^^^^
The core device sends a request to the satellite for reading data from one of its channels. The request contains a timeout, which is the RTIO timestamp to wait for until an input event appears. The satellite then replies with either an input event (containing timestamp and data), a timeout, or an overflow error. The core device sends a request to the satellite for reading data from one of its channels. The request contains a timeout, which is the RTIO timestamp to wait for until an input event appears. The satellite then replies with either an input event (containing timestamp and data), a timeout, or an overflow error.

View File

@ -1,53 +1,98 @@
The environment Environment
=============== ===========
Experiments interact with an environment that consists of devices, arguments and datasets. Access to the environment is handled by the class :class:`artiq.language.environment.EnvExperiment` that experiments should derive from. ARTIQ experiments exist in an environment, which consists of devices, arguments, and datasets. Access to the environment is handled through the :class:`~artiq.language.environment.HasEnvironment` manager provided by the :class:`~artiq.language.environment.EnvExperiment` class that experiments should derive from.
.. _device-db: .. _device-db:
The device database The device database
------------------- -------------------
The device database contains information about the devices available in a ARTIQ installation, what drivers to use, what controllers to use and on what machine, and where the devices are connected. Information about available devices is provided to ARTIQ through a file called the device database, typically called ``device_db.py``, which many of the ARTIQ front-end tools require access to in order to run. The device database specifies:
The master (or :mod:`~artiq.frontend.artiq_run`) instantiates the device drivers (and the RPC clients in the case of controllers) for the experiments based on the contents of the device database. * what devices are available to an ARTIQ installation
* what drivers to use
* what controllers to use
* how and where to contact each device, i.e.
The device database is stored in the memory of the master and is generated by a Python script typically called ``device_db.py``. That script must define a global variable ``device_db`` with the contents of the database. The device database is a Python dictionary whose keys are the device names, and values can have several types. - at which RTIO channel(s) each local device can be reached
- at which network address each controller can be reached
as well as, if present, how and where to contact the core device itself (e.g., its IP address, often by a field named ``core_addr``).
This is stored in a Python dictionary whose keys are the device names, which the file must define as a global variable with the name ``device_db``. Examples for various system configurations can be found inside the subfolders of ``artiq/examples``. A typical device database entry looks like this: ::
"led": {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 19}
},
Note that the key (the name of the device) is ``led`` and the value is itself a Python dictionary. Names will later be used to gain access to a device through methods such as ``self.setattr_device("led")``. While in this case ``led`` can be replaced with another name, provided it is used consistently, some names (e.g. in particular ``core``) are used internally by ARTIQ and will cause problems if changed. It is often more convenient to use aliases for renaming purposes, see below.
.. note::
The device database is generated and stored in the memory of the master when the master is first started. Changes to the ``device_db.py`` file will not immediately affect a running master. In order to update the device database, right-click in the Explorer window and select 'Scan device database', or run the command ``artiq_client scan-devices``.
.. warning::
It is important to understand that the device database does not *set* your system configuration, only *describe* it. If you change the devices available to your system, it is usually necessary to edit the device database, but editing the database will not change what devices are available to your system.
Remote (normally, non-realtime) devices must have accessible, suitable controllers and drivers; see :doc:`developing_a_ndsp` for more information, including how to add entries for new remote devices to your device database. Local devices (normally, realtime, e.g. your Sinara hardware) must be factually attached to your system, and more importantly, your gateware and firmware must have been compiled to account for them, and to expect them at those ports.
While controllers can be added and removed to your device database relatively easily, in order to make new real-time hardware accessible, it is generally also necessary to recompile and reflash your gateware and firmware. (If you purchase your hardware from M-Labs, you will normally be provided with new binaries and necessary assistance.)
Adding or removing new real-time hardware is a difference in *system configuration,* which must be specified at compilation time of gateware and firmware. For Kasli and Kasli-SoC, this is managed in the form of a JSON usually called the *system description* file. The device database generally provides that information to ARTIQ which can change from instance to instance ARTIQ is run, e.g., device names and aliases, network addresses, clock frequencies, and so on. The system configuration defines that information which is *not* permitted to change, e.g., what device is associated with which EEM port or RTIO channels. Insofar as data is duplicated between the two, the device database is obliged to agree with the system description, not the other way around.
If you obtain your hardware from M-Labs, you will always be provided with a ``device_db.py`` to match your system configuration, which you can edit as necessary to add remote devices, aliases, and so on. In the relatively unlikely case that you are writing a device database from scratch, the ``artiq_ddb_template`` utility can be used to generate a template device database directly from the JSON system description used to compile your gateware and firmware. This is the easiest way to ensure that details such as the allocation of RTIO channel numbers will be represented in the device database correctly. See also the corresponding entry in :ref:`Utilities <ddb-template-tool>`.
Local devices Local devices
+++++++++++++ ^^^^^^^^^^^^^
Local device entries are dictionaries that contain a ``type`` field set to ``local``. They correspond to device drivers that are created locally on the master (as opposed to going through the controller mechanism). The fields ``module`` and ``class`` determine the location of the Python class that the driver consists of. The ``arguments`` field is another (possibly empty) dictionary that contains arguments to pass to the device driver constructor. Local device entries are dictionaries which contain a ``type`` field set to ``local``. They correspond to device drivers that are created locally on the master as opposed to using the controller mechanism; this is normally the real-time hardware of the system, including the core, which is itself considered a local device. The ``led`` example above is a local device entry.
The fields ``module`` and ``class`` determine the location of the Python class of the driver. The ``arguments`` field is another (possibly empty) dictionary that contains arguments to pass to the device driver constructor. ``arguments`` is often used to specify the RTIO channel number of a peripheral, which must match the channel number in gateware.
On Kasli and Kasli-SoC, the allocation of RTIO channels to EEM ports is done automatically when the gateware is compiled, and while conceptually simple (channels are assigned one after the other, from zero upwards, for each device entry in the system description file) it is not entirely straightforward (different devices require different numbers of RTIO channels). Again, the easiest way to handle this when writing a new device database is automatically, using ``artiq_ddb_template``.
Controllers Controllers
+++++++++++ ^^^^^^^^^^^
Controller entries are dictionaries whose ``type`` field is set to ``controller``. When an experiment requests such a device, a RPC client (see ``sipyco.pc_rpc``) is created and connected to the appropriate controller. Controller entries are also used by controller managers to determine what controllers to run. Controller entries are dictionaries which contain a ``type`` field set to ``controller``. When an experiment requests such a device, a RPC client (see ``sipyco.pc_rpc``) is created and connected to the appropriate controller. Controller entries are also used by controller managers to determine what controllers to run. For an example, see :ref:`the NDSP development page <ndsp-integration>`.
The ``best_effort`` field is a boolean that determines whether to use ``sipyco.pc_rpc.Client`` or ``sipyco.pc_rpc.BestEffortClient``. The ``host`` and ``port`` fields configure the TCP connection. The ``target`` field contains the name of the RPC target to use (you may use ``sipyco_rpctool`` on a controller to list its targets). Controller managers run the ``command`` field in a shell to launch the controller, after replacing ``{port}`` and ``{bind}`` by respectively the TCP port the controller should listen to (matches the ``port`` field) and an appropriate bind address for the controller's listening socket. The ``host`` and ``port`` fields configure the TCP connection. The ``target`` field contains the name of the RPC target to use (you may use ``sipyco_rpctool`` on a controller to list its targets). Controller managers run the ``command`` field in a shell to launch the controller, after replacing ``{port}`` and ``{bind}`` by respectively the TCP port the controller should listen to (matches the ``port`` field) and an appropriate bind address for the controller's listening socket.
An optional ``best_effort`` boolean field determines whether to use ``sipyco.pc_rpc.Client`` or ``sipyco.pc_rpc.BestEffortClient``. ``BestEffortClient`` is very similar to ``Client``, but suppresses network errors and automatically retries connections in the background. If no ``best_effort`` field is present, ``Client`` is used by default.
Aliases Aliases
+++++++ ^^^^^^^
If an entry is a string, that string is used as a key for another lookup in the device database. If an entry is a string, that string is used as a key for another lookup in the device database.
Arguments Arguments
--------- ---------
Arguments are values that parameterize the behavior of an experiment and are set before the experiment is executed. Arguments are values that parameterize the behavior of an experiment. ARTIQ supports both interactive arguments, requested and supplied at some point while an experiment is running, and submission-time arguments, requested in the build phase and set before the experiment is executed. For more on arguments in practice, see the tutorial section :ref:`mgmt-arguments`. For supported argument types and specific reference, see the relevant sections of :doc:`the core language reference <core_language_reference>`, as well as the example experiment ``examples/no_hardware/interactive.py``.
Requesting the values of arguments can only be done in the build phase of an experiment. The value requests are also used to define the GUI widgets shown in the explorer when the experiment is selected.
Datasets Datasets
-------- --------
Datasets are values (possibly arrays) that are read and written by experiments and live in a key-value store. Datasets are values that are read and written by experiments kept in a key-value store. They exist to facilitate the exchange and preservation of information between experiments, from experiments to the management system, and from experiments to long-term storage. Datasets may be either scalars (``bool``, ``int``, ``float``, or NumPy scalar) or NumPy arrays. For basic use of datasets, see the :ref:`management system tutorial <getting-started-datasets>`.
A dataset may be broadcasted, that is, distributed to all clients connected to the master. For example, the ARTIQ GUI may plot it while the experiment is in progress to give rapid feedback to the user. Broadcasted datasets live in a global key-value store; experiments should use distinctive real-time result names in order to avoid conflicts. Broadcasted datasets may be used to communicate values across experiments; for example, a periodic calibration experiment may update a dataset read by payload experiments. Broadcasted datasets are replaced when a new dataset with the same key (name) is produced. A dataset may be broadcast (``broadcast=True``), that is, distributed to all clients connected to the master. This is useful e.g. for the ARTIQ dashboard to plot results while an experiment is in progress and give rapid feedback to the user. Broadcasted datasets live in a global key-value store owned by the master. Care should be taken that experiments use distinctive real-time result names in order to avoid conflicts. Broadcasted datasets may be used to communicate values across experiments; for instance, a periodic calibration experiment might update a dataset read by payload experiments.
Datasets can attach metadata for numerical formatting with the ``unit``, ``scale`` and ``precision`` parameters. In experiment code, values are assumed to be in the SI unit. In code, setting a dataset with a value of `1000` and the unit `kV` represents the quantity `1 kV`. It is recommended to use the globals defined by `artiq.language.units` and write `1*kV` instead of `1000` for the value. In dashboards and clients these globals are not available. There, setting a dataset with a value of `1` and the unit `kV` simply represents the quantity `1 kV`. ``precision`` refers to the max number of decimal places to display. This parameter does not affect the underlying value, and is only used for display purposes. Broadcasted datasets are replaced when a new dataset with the same key (name) is produced. By default, they are erased when the master halts. Broadcasted datasets may be made persistent (``persistent=True``, which also implies ``broadcast=True``), in which case the master stores them in a LMDB database typically called ``dataset_db.mdb``, where they are saved across master restarts.
Broadcasted datasets may be persistent: the master stores them in a file typically called ``dataset_db.pyon`` so they are saved across master restarts. By default, datasets are archived in the HDF5 output for that run, although this can be opted against (``archive=False``).
Datasets and units
^^^^^^^^^^^^^^^^^^
Datasets accept metadata for numerical formatting with the ``unit``, ``scale`` and ``precision`` parameters of ``set_dataset``.
.. note::
In experiment code, values are assumed to be in the SI base unit. Setting a dataset with a value of ``1000`` and the unit ``kV`` represents the quantity ``1 kV``. It is recommended to use the globals defined by :mod:`artiq.language.units` and write ``1*kV`` instead of ``1000`` for the value.
In dashboards and clients these globals are not available. However, setting a dataset with a value of ``1`` and the unit ``kV`` simply represents the quantity ``1 kV``.
``precision`` refers to the max number of decimal places to display. This parameter does not affect the underlying value, and is only used for display purposes.
Datasets produced by an experiment run may be archived in the HDF5 output for that run.

View File

@ -1,10 +1,10 @@
.. Copyright (C) 2014, 2015 Robert Jordens <jordens@gmail.com> .. Copyright (C) 2014, 2015 Robert Jordens <jordens@gmail.com>
FAQ FAQ
### ===
How do I ... How do I ...
============ ------------
find ARTIQ examples? find ARTIQ examples?
-------------------- --------------------
@ -91,7 +91,7 @@ It will give you the ``/dev/ttyUSBxx`` (or the ``COMxx`` for Windows) device
names. names.
The ``hwid:`` field gives you the string you can pass via the ``hwgrep://`` The ``hwid:`` field gives you the string you can pass via the ``hwgrep://``
feature of pyserial feature of pyserial
`serial_for_url() <http://pyserial.sourceforge.net/pyserial_api.html#serial.serial_for_url>`_ `serial_for_url() <https://pythonhosted.org/pyserial/pyserial_api.html#serial.serial_for_url>`_
in order to open a serial device. in order to open a serial device.
The preferred way to specify a serial device is to make use of the ``hwgrep://`` The preferred way to specify a serial device is to make use of the ``hwgrep://``

129
doc/manual/flashing.rst Normal file
View File

@ -0,0 +1,129 @@
(Re)flashing your core device
=============================
.. note::
If you have purchased a pre-assembled system from M-Labs or QUARTIQ, the gateware and firmware of your devices will already be flashed to the newest version of ARTIQ. Flashing your device is only necessary if you obtained your hardware in a different way, or if you want to change your system configuration or upgrade your ARTIQ version after the original purchase. Otherwise, skip straight to :doc:`configuring`.
.. _obtaining-binaries:
Obtaining board binaries
------------------------
If you have an active firmware subscription with M-Labs or QUARTIQ, you can obtain firmware for your system that corresponds to your currently installed version of ARTIQ using the ARTIQ firmware service (AFWS). One year of subscription is included with most hardware purchases. You may purchase or extend firmware subscriptions by writing to the sales@ email. The client ``afws_client`` is included in all ARTIQ installations.
Run the command::
$ afws_client <username> build <afws_director> <variant>
Replace ``<username>`` with the login name that was given to you with the subscription, ``<variant>`` with the name of your system variant, and ``<afws_directory>`` with the name of an empty directory, which will be created by the command if it does not exist. Enter your password when prompted and wait for the build (if applicable) and download to finish. If you experience issues with the AFWS client, write to the helpdesk@ email. For more information about ``afws_client`` see also the corresponding entry on the :ref:`Utilities <afws-client>` page.
For certain configurations (KC705 or ZC706 only) it is also possible to source firmware from `the M-Labs Hydra server <https://nixbld.m-labs.hk/project/artiq>`_ (in ``main`` and ``zynq`` respectively).
Without a subscription, you may build the firmware yourself from the open source code. See the section :doc:`building_developing`.
Installing and configuring OpenOCD
----------------------------------
.. warning::
These instructions are not applicable to Zynq devices (Kasli-SoC or ZC706), which do not use the utility ``artiq_flash`` to reflash. If your core device is a Zynq device, skip straight to :ref:`writing-flash`.
ARTIQ supplies the utility ``artiq_flash``, which uses OpenOCD to write the binary images into an FPGA board's flash memory. For both Nix and MSYS2, OpenOCD are included with the installation by default. Note that in the case of Nix this is the package ``artiq.openocd-bscanspi`` and not ``pkgs.openocd``; the second is OpenOCD from the Nix package collection, which does not support ARTIQ/Sinara boards.
.. note::
With Conda, install ``openocd`` as follows: ::
$ conda install -c m-labs openocd
Some additional steps are necessary to ensure that OpenOCD can communicate with the FPGA board:
On Linux
^^^^^^^^
First ensure that the current user belongs to the ``plugdev`` group (i.e. ``plugdev`` shown when you run ``$ groups``). If it does not, run ``$ sudo adduser $USER plugdev`` and re-login.
If you installed OpenOCD on Linux using Nix, use the ``which`` command to determine the path to OpenOCD, and then copy the udev rules: ::
$ which openocd
/nix/store/2bmsssvk3d0y5hra06pv54s2324m4srs-openocd-mlabs-0.10.0/bin/openocd
$ sudo cp /nix/store/2bmsssvk3d0y5hra06pv54s2324m4srs-openocd-mlabs-0.10.0/share/openocd/contrib/60-openocd.rules /etc/udev/rules.d
$ sudo udevadm trigger
NixOS users should configure OpenOCD through ``/etc/nixos/configuration.nix`` instead.
Linux using Conda
^^^^^^^^^^^^^^^^^
If you are using a Conda environment ``artiq``, then execute the statements below. If you are using a different environment, you will have to replace ``artiq`` with the name of your environment::
$ sudo cp ~/.conda/envs/artiq/share/openocd/contrib/60-openocd.rules /etc/udev/rules.d
$ sudo udevadm trigger
On Windows
^^^^^^^^^^
A third-party tool, `Zadig <http://zadig.akeo.ie/>`_, is necessary. It is also included with the MSYS2 offline installer and available from the Start Menu as ``Zadig Driver Installer``. Use it as follows:
1. Make sure the FPGA board's JTAG USB port is connected to your computer.
2. Activate Options → List All Devices.
3. Select the "Digilent Adept USB Device (Interface 0)" or "FTDI Quad-RS232 HS" (or similar)
device from the drop-down list.
4. Select WinUSB from the spinner list.
5. Click "Install Driver" or "Replace Driver".
You may need to repeat these steps every time you plug the FPGA board into a port it has not previously been plugged into, even on the same system.
.. _writing-flash:
Writing the flash
-----------------
First ensure the board is connected to your computer. In the case of Kasli, the JTAG adapter is integrated into the Kasli board; for flashing (and debugging) you can simply connect your computer to the micro-USB connector on the Kasli front panel. For Kasli-SoC, which uses ``artiq_coremgmt`` to flash over network, an Ethernet connection and an IP address, supplied either with the ``-D`` option or in your :ref:`device database <device-db>`, are sufficient.
For Kasli-SoC or ZC706:
::
$ artiq_coremgmt [-D 192.168.1.75] config write -f boot [afws_directory]/boot.bin
$ artiq_coremgmt reboot
If the device is not reachable due to corrupted firmware or networking problems, extract the SD card and copy ``boot.bin`` onto it manually.
For Kasli:
::
$ artiq_flash -d [afws_directory]
For KC705:
::
$ artiq_flash -t kc705 -d [afws_directory]
The SW13 switches need to be set to 00001.
Flashing over network is also possible for Kasli and KC705, assuming IP networking has already been set up. In this case, the ``-H HOSTNAME`` option is used; see the entry for ``artiq_flash`` in the :ref:`Utilities <flashing-loading-tool>` reference.
.. _connecting-uart:
Connecting to the UART log
--------------------------
A UART is a peripheral device for asynchronous serial communication; in the case of core device boards, it allows the reading of the UART log, which is used for debugging, especially when problems with booting or networking disallow checking core logs with ``artiq_coremgmt log``. If you had no issues flashing your board you can proceed directly to :doc:`configuring`.
Otherwise, ensure your core device is connected to your PC with a data micro-USB cable, as above, and wait at least fifteen seconds after startup to try to connect. To help find the correct port to connect to, you can list your system's serial devices by running: ::
$ python -m serial.tools.list_ports -v
This will give you the list of ``/dev/ttyUSBx`` or ``COMx`` device names (on Linux and Windows respectively). Most commonly, the correct option is the third, i.e. index number 2, but it can vary.
On Linux:
Run the commands: ::
stty 115200 < /dev/ttyUSBx
cat /dev/ttyUSBx
When you restart or reflash the core device you should see the startup logs in the terminal. If you encounter issues, try other ``ttyUSBx`` names, and make certain that your user is part of the ``dialout`` group (run ``groups`` in a terminal to check).
On Windows:
Use a program such as PuTTY to connect to the COM port. Connect to every available COM port at first, restart the core device, see which port produces meaningful output, and close the others. It may be necessary to install the `FTDI drivers <https://ftdichip.com/drivers/>`_ first.
Note that the correct parameters for the serial port are 115200bps 8-N-1 for every core device.

View File

@ -21,17 +21,17 @@ As a very first step, we will turn on a LED on the core device. Create a file ``
self.core.reset() self.core.reset()
self.led.on() self.led.on()
The central part of our code is our ``LED`` class, which derives from :class:`artiq.language.environment.EnvExperiment`. Among other features, :class:`~artiq.language.environment.EnvExperiment` calls our :meth:`~artiq.language.environment.Experiment.build` method and provides the :meth:`~artiq.language.environment.HasEnvironment.setattr_device` method that interfaces with the device database to create the appropriate device drivers and make those drivers accessible as ``self.core`` and ``self.led``. The :func:`~artiq.language.core.kernel` decorator (``@kernel``) tells the system that the :meth:`~artiq.language.environment.Experiment.run` method must be compiled for and executed on the core device (instead of being interpreted and executed as regular Python code on the host). The decorator uses ``self.core`` internally, which is why we request the core device using :meth:`~artiq.language.environment.HasEnvironment.setattr_device` like any other. The central part of our code is our ``LED`` class, which derives from :class:`~artiq.language.environment.EnvExperiment`. Almost all experiments should derive from this class, which provides access to the environment as well as including the necessary experiment framework from the base-level :class:`~artiq.language.environment.Experiment`. It will call our :meth:`~artiq.language.environment.HasEnvironment.build` at the right time and provides the :meth:`~artiq.language.environment.HasEnvironment.setattr_device` we use to gain access to our devices ``core`` and ``led``. The :func:`~artiq.language.core.kernel` decorator (``@kernel``) tells the system that the :meth:`~artiq.language.environment.Experiment.run` method is a kernel and must be compiled for and executed on the core device (instead of being interpreted and executed as regular Python code on the host).
It is important that you supply the correct device database for your system configuration; it is generated by a Python script typically called ``device_db.py`` (see also :ref:`the device database <device-db>`). If you purchased a system from M-Labs, the ``device_db.py`` for your system will normally already have been provided to you (either on the USB stick, inside ``~/artiq`` on the NUC, or by email). If you have the JSON description file for your system on hand, you can use the ARTIQ front-end tool ``artiq_ddb_template`` to generate a matching device database file. Otherwise, you can also find examples in the ``examples`` folder of ARTIQ (sorted in corresponding subfolders per core device) which you can edit to match your system. Before you can run the example experiment, you will need to supply ARTIQ with the device database for your system, just as you did when configuring the core device. Make sure ``device_db.py`` is in the same directory as ``led.py``. Check once again that the field ``core_addr``, placed at the top of the file, matches the current IP address of your core device.
If you don't have a ``device_db.py`` for your system, consult :ref:`device-db` to find out how to construct one. You can also find example device databases in the ``examples`` folder of ARTIQ, sorted into corresponding subfolders by core device, which you can edit to match your system.
.. note:: .. note::
To access the examples, you can find where the ARTIQ package is installed on your machine with: :: To access the examples, find where the ARTIQ package is installed on your machine with: ::
python3 -c "import artiq; print(artiq.__path__[0])" python3 -c "import artiq; print(artiq.__path__[0])"
Make sure ``device_db.py`` is in the same directory as ``led.py``. The field ``core_addr``, placed at the top of the file, needs to match the current IP address of your core device in order for your host machine to contact it. If you purchased a pre-assembled system and haven't changed the IP address it is normally already set correctly.
Run your code using ``artiq_run``, which is one of the ARTIQ front-end tools: :: Run your code using ``artiq_run``, which is one of the ARTIQ front-end tools: ::
$ artiq_run led.py $ artiq_run led.py
@ -71,11 +71,11 @@ You can then turn the LED off and on by entering 0 or 1 at the prompt that appea
$ artiq_run led.py $ artiq_run led.py
Enter desired LED state: 0 Enter desired LED state: 0
What happens is that the ARTIQ compiler notices that the :meth:`input_led_state` function does not have a ``@kernel`` decorator (:func:`~artiq.language.core.kernel`) and thus must be executed on the host. When the function is called on the core device, it sends a request to the host, which executes it. The core device waits until the host returns, and then continues the kernel; in this case, the host displays the prompt, collects user input, and the core device sets the LED state accordingly. What happens is that the ARTIQ compiler notices that the ``input_led_state`` function does not have a ``@kernel`` decorator (:func:`~artiq.language.core.kernel`) and thus must be executed on the host. When the function is called on the core device, it sends a request to the host, which executes it. The core device waits until the host returns, and then continues the kernel; in this case, the host displays the prompt, collects user input, and the core device sets the LED state accordingly.
The return type of all RPC functions must be known in advance. If the return value is not ``None``, the compiler requires a type annotation, like ``-> TBool`` in the example above. The return type of all RPC functions must be known in advance. If the return value is not ``None``, the compiler requires a type annotation, like ``-> TBool`` in the example above. See also :ref:`compiler-types`.
Without the :meth:`~artiq.coredevice.core.Core.break_realtime` call, the RTIO events emitted by :func:`self.led.on()` or :func:`self.led.off()` would be scheduled at a fixed and very short delay after entering :meth:`~artiq.language.environment.Experiment.run()`. These events would fail because the RPC to :meth:`input_led_state()` can take an arbitrarily long amount of time, and therefore the deadline for the submission of RTIO events would have long passed when :func:`self.led.on()` or :func:`self.led.off()` are called (that is, the ``rtio_counter`` wall clock will have advanced far ahead of the timeline cursor ``now``, and an :exc:`~artiq.coredevice.exceptions.RTIOUnderflow` would result; see :ref:`artiq-real-time-i-o-concepts` for the full explanation of wall clock vs. timeline.) The :meth:`~artiq.coredevice.core.Core.break_realtime` call is necessary to waive the real-time requirements of the LED state change. Rather than delaying by any particular time interval, it reads ``rtio_counter`` and moves up the ``now`` cursor far enough to ensure it's once again safely ahead of the wall clock. Without the :meth:`~artiq.coredevice.core.Core.break_realtime` call, the RTIO events emitted by :meth:`self.led.on() <artiq.coredevice.ttl.TTLInOut.on>` or :meth:`self.led.off() <artiq.coredevice.ttl.TTLInOut.off>` would be scheduled at a fixed and very short delay after entering :meth:`~artiq.language.environment.Experiment.run()`. These events would fail because the RPC to ``input_led_state()`` can take an arbitrarily long amount of time, and therefore the deadline for the submission of RTIO events would have long passed when :meth:`self.led.on() <artiq.coredevice.ttl.TTLInOut.on>` or :meth:`self.led.off() <artiq.coredevice.ttl.TTLInOut.off>` are called (that is, the ``rtio_counter_mu`` wall clock will have advanced far ahead of the timeline cursor ``now_mu``, and an :exc:`~artiq.coredevice.exceptions.RTIOUnderflow` would result; see :ref:`artiq-real-time-i-o-concepts` for the full explanation of wall clock vs. timeline.) The :meth:`~artiq.coredevice.core.Core.break_realtime` call is necessary to waive the real-time requirements of the LED state change. Rather than delaying by any particular time interval, it reads ``rtio_counter_mu`` and moves up the ``now_mu`` cursor far enough to ensure it's once again safely ahead of the wall clock.
Real-time Input/Output (RTIO) Real-time Input/Output (RTIO)
----------------------------- -----------------------------
@ -100,7 +100,7 @@ Create a new file ``rtio.py`` containing the following: ::
delay(2*us) delay(2*us)
self.ttl0.pulse(2*us) self.ttl0.pulse(2*us)
In its :meth:`~artiq.language.environment.Experiment.build` method, the experiment obtains the core device and a TTL device called ``ttl0`` as defined in the device database. In its :meth:`~artiq.language.environment.HasEnvironment.build` method, the experiment obtains the core device and a TTL device called ``ttl0`` as defined in the device database.
In ARTIQ, TTL is used roughly synonymous with "a single generic digital signal" and does not refer to a specific signaling standard or voltage/current levels. In ARTIQ, TTL is used roughly synonymous with "a single generic digital signal" and does not refer to a specific signaling standard or voltage/current levels.
When :meth:`~artiq.language.environment.Experiment.run`, the experiment first ensures that ``ttl0`` is in output mode and actively driving the device it is connected to. When :meth:`~artiq.language.environment.Experiment.run`, the experiment first ensures that ``ttl0`` is in output mode and actively driving the device it is connected to.
@ -108,7 +108,7 @@ Bidirectional TTL channels (i.e. :class:`~artiq.coredevice.ttl.TTLInOut`) are in
There are no input-only TTL channels. There are no input-only TTL channels.
The experiment then drives one million 2 µs long pulses separated by 2 µs each. The experiment then drives one million 2 µs long pulses separated by 2 µs each.
Connect an oscilloscope or logic analyzer to TTL0 and run ``artiq_run.py rtio.py``. Connect an oscilloscope or logic analyzer to TTL0 and run ``artiq_run rtio.py``.
Notice that the generated signal's period is precisely 4 µs, and that it has a duty cycle of precisely 50%. Notice that the generated signal's period is precisely 4 µs, and that it has a duty cycle of precisely 50%.
This is not what one would expect if the delay and the pulse were implemented with register-based general purpose input output (GPIO) that is CPU-controlled. This is not what one would expect if the delay and the pulse were implemented with register-based general purpose input output (GPIO) that is CPU-controlled.
The signal's period would depend on CPU speed, and overhead from the loop, memory management, function calls, etc., all of which are hard to predict and variable. The signal's period would depend on CPU speed, and overhead from the loop, memory management, function calls, etc., all of which are hard to predict and variable.
@ -145,7 +145,7 @@ Try reducing the period of the generated waveform until the CPU cannot keep up w
Parallel and sequential blocks Parallel and sequential blocks
------------------------------ ------------------------------
It is often necessary for several pulses to overlap one another. This can be expressed through the use of ``with parallel`` constructs, in which the events generated by the individual statements are executed at the same time. The duration of the ``parallel`` block is the duration of its longest statement. It is often necessary for several pulses to overlap one another. This can be expressed through the use of the ``with parallel`` construct, in which the events generated by individual statements are scheduled to execute at the same time, rather than sequentially. The duration of the ``parallel`` block is the duration of its longest statement.
Try the following code and observe the generated pulses on a 2-channel oscilloscope or logic analyzer: :: Try the following code and observe the generated pulses on a 2-channel oscilloscope or logic analyzer: ::
@ -167,9 +167,12 @@ Try the following code and observe the generated pulses on a 2-channel oscillosc
delay(4*us) delay(4*us)
ARTIQ can implement ``with parallel`` blocks without having to resort to any of the typical parallel processing approaches. ARTIQ can implement ``with parallel`` blocks without having to resort to any of the typical parallel processing approaches.
It simply remembers the position on the timeline when entering the ``parallel`` block and then seeks back to that position after submitting the events generated by each statement. It simply remembers its position on the timeline (``now_mu``) when entering the ``parallel`` block and resets to that position after each individual statement.
In other words, the statements in the ``parallel`` block are actually executed sequentially, only the RTIO events generated by them are scheduled to be executed in parallel. At the end of the block, the cursor is advanced to the furthest position it reached during the block.
Note that accordingly if a statement takes a lot of CPU time to execute (which is different from -- and has nothing to do with! -- the events *scheduled* by the statement taking a long time), it may cause a subsequent statement to miss the deadline for timely submission of its events (and raise :exc:`~artiq.coredevice.exceptions.RTIOUnderflow`), while earlier statements in the parallel block would have submitted their events without problems. In other words, the statements in a ``parallel`` block are actually executed sequentially.
Only the RTIO events generated by the statements are *scheduled* in parallel.
Remember that while ``now_mu`` resets at the beginning of each statement in a ``parallel`` block, the wall clock advances regardless. If a particular statement takes a long time to execute (which is different from -- and unrelated to! -- the events *scheduled* by the statement taking a long time), the wall clock may advance past the reset value, putting any subsequent statements inside the block into a situation of negative slack (i.e., resulting in :exc:`~artiq.coredevice.exceptions.RTIOUnderflow` ). Sometimes underflows may be avoided simply by reordering statements within the parallel block. This especially applies to input methods, which generally necessarily block CPU progress until the wall clock has caught up to or overtaken the cursor.
Within a parallel block, some statements can be scheduled sequentially again using a ``with sequential`` block. Observe the pulses generated by this code: :: Within a parallel block, some statements can be scheduled sequentially again using a ``with sequential`` block. Observe the pulses generated by this code: ::
@ -182,11 +185,22 @@ Within a parallel block, some statements can be scheduled sequentially again usi
self.ttl1.pulse(4*us) self.ttl1.pulse(4*us)
delay(4*us) delay(4*us)
Particular care needs to be taken when working with ``parallel`` blocks which generate large numbers of RTIO events, as it is possible to create sequence errors. A sequence error is caused when the scalable event dispatcher (SED) cannot queue an RTIO event due to its timestamp being the same as or earlier than another event in its queue. By default, the SED has 8 lanes, which suffice in most cases to avoid sequence errors; however, if many (>8) events are queued with interlaced timestamps the problem can still surface. See :ref:`sequence-errors`. .. warning::
``with parallel`` specifically 'parallelizes' the *top-level* statements inside a block. Consider as an example: ::
Note that for performance reasons sequence errors do not halt execution of the kernel. Instead, they are reported in the core log. If the ``aqctl_corelog`` process has been started with ``artiq_ctlmgr``, then these errors will be posted to the master log. If an experiment is executed through ``artiq_run``, the errors will only be visible in the core log. for i in range(1000000):
with parallel:
self.ttl0.pulse(2*us) # 1
if True: # 2
self.ttl1.pulse(2*us) # 3
self.ttl2.pulse(2*us) # 4
delay(4*us)
Sequence errors can usually be overcome by reordering the generation of the events (again, different from and unrelated to reordering the events themselves). Alternatively, the number of SED lanes can be increased in the gateware. This code will not schedule the three pulses to ``ttl0``, ``ttl1``, and ``ttl2`` in parallel. Rather, the pulse to ``ttl1`` is 'parallelized' *with the if statement*. The timeline cursor resets once, at the beginning of statement #2; it will not repeat the reset at the deeper indentation level for #3 or #4.
In practice, the pulses to ``ttl0`` and ``ttl1`` will execute simultaneously, and the pulse to ``ttl2`` will execute after the pulse to ``ttl1``, bringing the total duration of the ``parallel`` block to 4 us. Internally, statements #3 and #4, contained within the top-level if statement, are considered an atomic sequence and executed within an implicit ``with sequential``. To execute #3 and #4 in parallel, it is necessary to place them inside a second, nested ``parallel`` block within the if statement.
Particular care needs to be taken when working with ``parallel`` blocks which generate large numbers of RTIO events, as it is possible to cause sequencing issues in the gateware; see also :ref:`sequence-errors`.
.. _rtio-analyzer-example: .. _rtio-analyzer-example:
@ -211,12 +225,14 @@ The core device records the real-time I/O waveforms into a circular buffer. It i
rtio_log("ttl0", "i", i) rtio_log("ttl0", "i", i)
delay(...) delay(...)
Afterwards, the recorded data can be extracted and written to a VCD file using ``artiq_coreanalyzer -w rtio.vcd`` (see :ref:`core-device-rtio-analyzer-tool`). VCD files can be viewed using third-party tools such as GtkWave. When using ``artiq_run``, the recorded data can be extracted using ``artiq_coreanalyzer`` (see :ref:`core-device-rtio-analyzer-tool`). To export it to VCD, which can be viewed using third-party tools such as GtkWave, use the command ``artiq_coreanalyzer -w rtio.vcd``. Recorded data can also be viewed directly with the ARTIQ dashboard, which will be presented later in :doc:`getting_started_mgmt`.
.. _getting-started-dma:
Direct Memory Access (DMA) Direct Memory Access (DMA)
-------------------------- --------------------------
DMA allows for storing fixed sequences of RTIO events in system memory and having the DMA core in the FPGA play them back at high speed. Provided that the specifications of a desired event sequence are known far enough in advance, and no other RTIO issues (collisions, sequence errors) are provoked, even extremely fast and detailed event sequences are always possible to generate and execute. However, if they are time-consuming for the CPU to generate, they may require very large amounts of positive slack in order to allow the CPU enough time to complete the generation before the wall clock 'catches up' (that is, without running into RTIO underflows). A better option is to record these sequences to the DMA core. Once recorded, events sequences are fixed and cannot be modified, but can be safely replayed at any position in the timeline, potentially repeatedly. DMA allows for storing fixed sequences of RTIO events in system memory and having the DMA core in the FPGA play them back at high speed. Provided that the specifications of a desired event sequence are known far enough in advance, and no other RTIO issues (collisions, sequence errors) are provoked, even extremely fast and detailed event sequences can always be generated and executed. RTIO underflows occur when events cannot be generated *as fast as* they need to be executed, resulting in an exception when the wall clock 'catches up'. The solution is to record these sequences to the DMA core. Once recorded, event sequences are fixed and cannot be modified, but can be safely replayed very quickly at any position in the timeline, potentially repeatedly.
Try this: :: Try this: ::
@ -232,7 +248,7 @@ Try this: ::
@kernel @kernel
def record(self): def record(self):
with self.core_dma.record("pulses"): with self.core_dma.record("pulses"):
# all RTIO operations now go to the "pulses" # all RTIO operations now_mu go to the "pulses"
# DMA buffer, instead of being executed immediately. # DMA buffer, instead of being executed immediately.
for i in range(50): for i in range(50):
self.ttl0.pulse(100*ns) self.ttl0.pulse(100*ns)
@ -251,137 +267,7 @@ Try this: ::
# each playback advances the timeline by 50*(100+100) ns # each playback advances the timeline by 50*(100+100) ns
self.core_dma.playback_handle(pulses_handle) self.core_dma.playback_handle(pulses_handle)
For more documentation on the methods used, see the :mod:`artiq.coredevice.dma` reference.
Distributed Direct Memory Access (DDMA)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
By default on DRTIO systems, all events recorded by the DMA core are kept and played back on the master.
With distributed DMA, RTIO events that should be played back on remote destinations are distributed to the corresponding satellites. In some cases (typically, large buffers on several satellites with high event throughput), it allows for better performance and higher bandwidth, as the RTIO events do not have to be sent over the DRTIO link(s) during playback.
To enable distributed DMA, simply provide an ``enable_ddma=True`` argument for the :meth:`~artiq.coredevice.dma.CoreDMA.record` method - taking a snippet from the previous example: ::
@kernel
def record(self):
with self.core_dma.record("pulses", enable_ddma=True):
# all RTIO operations now go to the "pulses"
# DMA buffer, instead of being executed immediately.
for i in range(50):
self.ttl0.pulse(100*ns)
delay(100*ns)
In standalone systems this argument is ignored and has no effect.
Enabling DDMA on a purely local sequence on a DRTIO system introduces an overhead during trace recording which comes from additional processing done on the record, so careful use is advised. Due to the extra time that communicating with relevant satellites takes, an additional delay before playback may be necessary to prevent a :exc:`~artiq.coredevice.exceptions.RTIOUnderflow` when playing back a DDMA-enabled sequence.
Subkernels
----------
Subkernels refers to kernels running on a satellite device. This allows offloading some processing and control over remote RTIO devices, freeing up resources on the master.
Subkernels behave for the most part like regular kernels; they accept arguments and can return values. However, there are few caveats:
- they do not support RPCs,
- they do not support DRTIO,
- their return value must be fully annotated with an ARTIQ type,
- their arguments should be annotated, and only basic ARTIQ types are supported,
- while ``self`` is allowed, there is no attribute writeback - any changes will be discarded when the subkernel is completed,
- they can raise exceptions, but the exceptions cannot be caught by the master (rather, they are propagated directly to the host),
- they begin execution as soon as possible when called, and can be awaited.
To define a subkernel, use the subkernel decorator (``@subkernel(destination=X)``). The destination is the satellite number as defined in the routing table, and must be between 1 and 255. To call a subkernel, call it like a normal function; and to await its result, use ``subkernel_await(function, [timeout])``.
For example, a subkernel performing integer addition: ::
from artiq.experiment import *
@subkernel(destination=1)
def subkernel_add(a: TInt32, b: TInt32) -> TInt32:
return a + b
class SubkernelExperiment(EnvExperiment):
def build(self):
self.setattr_device("core")
@kernel
def run(self):
subkernel_add(2, 2)
result = subkernel_await(subkernel_add)
assert result == 4
Sometimes subkernel execution may take large amounts of time. By default, the await function will wait as long as necessary. If a timeout is needed, it can be set using the optional argument of ``subkernel_await()``. The value given is interpreted in milliseconds. If a negative value is given, timeout is disabled.
Subkernels are compiled after the main kernel and immediately uploaded to satellites. When called, the master instructs the appropriate satellite to load the subkernel into their kernel core and run it. If the subkernel is complex, and its binary relatively large, the delay between the call and actually running the subkernel may be substantial; if it's necessary to minimize this delay, ``subkernel_preload(function)`` should be used before the call.
While ``self`` is accepted as an argument for subkernels, it is embedded into the compiled data. Any changes made by the main kernel or other subkernels will not be available.
Subkernels can call other kernels and subkernels. For a more complex example: ::
from artiq.experiment import *
class SubkernelExperiment(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("ttl0")
self.setattr_device("ttl8") # assuming it's on satellite
@subkernel(destination=1)
def add_and_pulse(self, a: TInt32, b: TInt32) -> TInt32:
c = a + b
self.pulse_ttl(c)
return c
@subkernel(destination=1)
def pulse_ttl(self, delay: TInt32) -> TNone:
self.ttl8.pulse(delay*us)
@kernel
def run(self):
subkernel_preload(self.add_and_pulse)
self.core.reset()
delay(10*ms)
self.add_and_pulse(2, 2)
self.ttl0.pulse(15*us)
result = subkernel_await(self.add_and_pulse)
assert result == 4
self.pulse_ttl(20)
Without the preload, the delay after the core reset would need to be longer. The operation may still take some time, depending on the connection. Notice that the method ``pulse_ttl()`` can be called both within a subkernel and on its own.
It is not necessary for subkernels to always be awaited, but awaiting is required to retrieve returned values and exceptions.
.. note:: .. note::
While a subkernel is running, regardless of what devices it makes use of, none of the RTIO devices on that satellite (or on any satellites downstream) will be available to the master. Control is returned to master after the subkernel completes - to be certain a device is usable, await the subkernel before performing any RTIO operations on the affected satellites. Only output events are redirected to the DMA core. Input methods inside a ``with dma`` block will be called as they would be outside of the block, in the current real-time context, and input events will be buffered normally, not to DMA.
Message passing For more documentation on the methods used, see the :mod:`artiq.coredevice.dma` reference.
^^^^^^^^^^^^^^^
Apart from arguments and returns, subkernels can also pass messages between each other or the master with built-in ``subkernel_send()`` and ``subkernel_recv()`` functions. This can be used for communication between subkernels, to pass additional data, or to send partially computed data. Consider the following example: ::
from artiq.experiment import *
@subkernel(destination=1)
def simple_message() -> TInt32:
data = subkernel_recv("message", TInt32)
return data + 20
class MessagePassing(EnvExperiment):
def build(self):
self.setattr_device("core")
@kernel
def run(self):
simple_self()
subkernel_send(1, "message", 150)
result = subkernel_await(simple_self)
assert result == 170
The ``subkernel_send(destination, name, value)`` function takes three arguments: a destination, a name for the message (to be used for identification in the corresponding ``subkernel_recv()``), and the passed value.
The ``subkernel_recv(name, type, [timeout])`` function requires two arguments: message name (matching exactly the name provided in ``subkernel_send``) and expected type. Optionally, it accepts a third argument, a timeout for the operation in milliseconds. If this value is negative, timeout is disabled. By default, it waits as long as necessary.
To avoid misinterpretation of the data the compiler type-checks the value sent by ``subkernel_send`` against the type declared in ``subkernel_recv``. To guard against common errors, it also checks that all message names are used in both a sending and receiving function.
A message can only be received while a subkernel is running, and is placed into a buffer to be retrieved when required; therefore send executes independently of any receive and never deadlocks. However, a receive function may timeout or wait forever if no message with the correct name and destination is ever sent.

View File

@ -1,24 +1,32 @@
Getting started with the management system Getting started with the management system
========================================== ==========================================
The management system is the high-level part of ARTIQ that schedules the experiments, distributes and stores the results, and manages devices and parameters. In practice, rather than managing experiments by executing ``artiq_run`` over and over, most use cases are better served by using the ARTIQ *management system.* This is the high-level part of ARTIQ, which can be used to schedule experiments, distribute and store the results, and manage devices and parameters. It possesses a detailed GUI and can be used on several machines concurrently, allowing them to coordinate with each other and with the specialized hardware over the network. As a result, multiple users on different machines can schedule experiments or retrieve results on the same ARTIQ system, potentially simultaneously.
The manipulations described in this tutorial can be carried out using a single computer, without any special hardware. The management system consists of at least two parts:
a. the **ARTIQ master,** which runs on a single machine, facilitates communication with the core device and peripherals, and is responsible for most of the actual duties of the system,
b. one or more **ARTIQ clients,** which may be local or remote and which communicate only with the master. Both a GUI (the **dashboard**) and a straightforward command line client are provided, with many of the same capabilities.
as well as, optionally,
c. one or more **controller managers**, which help coordinate the operation of certain (generally, non-realtime) classes of device.
In this tutorial, we will explore the basic operation of the management system. Because the various components of the management system run wholly on the host machine, and not on the core device (in other words, they do not inherently involve any kernel functions), it is not necessary to have a core device or any special hardware set up to use it. The examples in this tutorial can all be carried out using only your computer.
Starting your first experiment with the master Starting your first experiment with the master
---------------------------------------------- ----------------------------------------------
In the previous tutorial, we used the ``artiq_run`` utility to execute our experiments, which is a simple stand-alone tool that bypasses the ARTIQ management system. We will now see how to run an experiment using the master (the central program in the management system that schedules and executes experiments) and the dashboard (that connects to the master and controls it). In the previous tutorial, we used the ``artiq_run`` utility to execute our experiments, which is a simple standalone tool that bypasses the management system. We will now see how to run an experiment using the master and the dashboard.
First, create a folder ``~/artiq-master`` and copy your ``device_db.py`` into it (the file containing the device database, as described in :ref:`connecting-to-the-core-device`).The master uses those files in the same way as ``artiq_run``. First, create a folder ``~/artiq-master`` and copy into it the ``device_db.py`` for your system (your device database, exactly as in :ref:`connecting-to-the-core-device`.) The master uses the device database in the same way as ``artiq_run`` when communicating with the core device. Since no devices are actually used in these examples, you can also use the ``device_db.py`` found in ``examples/no_hardware``.
Then create a ``~/artiq-master/repository`` sub-folder to contain experiments. The master scans this ``repository`` folder to determine what experiments are available (the name of the folder can be changed using ``-r``). Secondly, create a subfolder ``~/artiq-master/repository`` to contain experiments. By default, the master scans for a folder of this name to determine what experiments are available. If you'd prefer to use a different name, this can be changed by running ``artiq_master -r [folder name]`` instead of ``artiq_master`` below.
Create a very simple experiment in ``~/artiq-master/repository`` and save it as ``mgmt_tutorial.py``: :: Create a very simple experiment in ``~/artiq-master/repository`` and save it as ``mgmt_tutorial.py``: ::
from artiq.experiment import * from artiq.experiment import *
class MgmtTutorial(EnvExperiment): class MgmtTutorial(EnvExperiment):
"""Management tutorial""" """Management tutorial"""
def build(self): def build(self):
@ -40,18 +48,30 @@ Now, start the dashboard with the following commands in another terminal: ::
$ cd ~ $ cd ~
$ artiq_dashboard $ artiq_dashboard
.. note:: The ``artiq_dashboard`` program uses a file called ``artiq_dashboard.pyon`` in the current directory to save and restore the GUI state (window/dock positions, last values entered by the user, etc.). .. note::
In order to connect to a master over the network, start it with the command ::
$ artiq_master --bind [hostname or IP]
and then use the option ``--server`` or ``-s`` for clients, as in: ::
$ artiq_dashboard -s [hostname or IP of the master]
$ artiq_client -s [hostname or IP of the master]
Both IPv4 and IPv6 are supported.
The dashboard should display the list of experiments from the repository folder in a dock called "Explorer". There should be only the experiment we created. Select it and click "Submit", then look at the "Log" dock for the output from this simple experiment. The dashboard should display the list of experiments from the repository folder in a dock called "Explorer". There should be only the experiment we created. Select it and click "Submit", then look at the "Log" dock for the output from this simple experiment.
.. note:: Multiple clients may be connected at the same time, possibly on different machines, and will be synchronized. See the ``-s`` option of ``artiq_dashboard`` and the ``--bind`` option of ``artiq_master`` to use the network. Both IPv4 and IPv6 are supported. .. seealso::
You may note that experiments may be submitted with a due date, a priority level, a pipeline identifier, and other specific settings. Some of these are self-explanatory. Many are scheduling-related. For more information on experiment scheduling, especially when submitting longer experiments or submitting across multiple users, see :ref:`experiment-scheduling`.
.. _mgmt-arguments:
Adding an argument Adding an argument
------------------ ------------------
Experiments may have arguments whose values can be set in the dashboard and used in the experiment's code. Modify the experiment as follows: :: Experiments may have arguments whose values can be set in the dashboard and used in the experiment's code. Modify the experiment as follows: ::
def build(self): def build(self):
self.setattr_argument("count", NumberValue(precision=0, step=1)) self.setattr_argument("count", NumberValue(precision=0, step=1))
@ -60,7 +80,7 @@ Experiments may have arguments whose values can be set in the dashboard and used
print("Hello World", i) print("Hello World", i)
``NumberValue`` represents a floating point numeric argument. There are many other types, see :class:`artiq.language.environment` and :class:`artiq.language.scan`. ``NumberValue`` represents a floating point numeric argument. There are many other types, see :class:`~artiq.language.environment` and :class:`~artiq.language.scan`.
Use the command-line client to trigger a repository rescan: :: Use the command-line client to trigger a repository rescan: ::
@ -68,12 +88,36 @@ Use the command-line client to trigger a repository rescan: ::
The dashboard should now display a spin box that allows you to set the value of the ``count`` argument. Try submitting the experiment as before. The dashboard should now display a spin box that allows you to set the value of the ``count`` argument. Try submitting the experiment as before.
Interactive arguments
---------------------
It is also possible to use interactive arguments, which may be requested and supplied while the experiment is running. This time modify the experiment as follows: ::
def build(self):
pass
def run(self):
repeat = True
while repeat:
print("Hello World")
with self.interactive(title="Repeat?") as interactive:
interactive.setattr_argument("repeat", BooleanValue(True))
repeat = interactive.repeat
Trigger a repository rescan and click the button labeled "Recompute all arguments". Now submit the experiment. It should print once, then wait; in the same dock as "Explorer", find and navigate to the tab "Interactive Args". You can now choose and supply a value for the argument mid-experiment. Every time an argument is requested, the experiment pauses until the input is supplied. If you choose to "Cancel" instead, an :exc:`~artiq.language.environment.CancelledArgsError` will be raised (which the experiment can choose to catch, rather than halting.)
While regular arguments are all requested simultaneously before submitting, interactive arguments can be requested at any point. In order to request multiple interactive arguments at once, place them within the same ``with`` block; see also the example ``interactive.py`` in the ``examples/no_hardware`` folder.
.. _master-setting-up-git:
Setting up Git integration Setting up Git integration
-------------------------- --------------------------
So far, we have used the bare filesystem for the experiment repository, without any version control. Using Git to host the experiment repository helps with the tracking of modifications to experiments and with the traceability of a result to a particular version of an experiment. So far, we have used the bare filesystem for the experiment repository, without any version control. Using Git to host the experiment repository helps with the tracking of modifications to experiments and with the traceability of a result to a particular version of an experiment.
.. note:: The workflow we will describe in this tutorial corresponds to a situation where the ARTIQ master machine is also used as a Git server where multiple users may push and pull code. The Git setup can be customized according to your needs; the main point to remember is that when scanning or submitting, the ARTIQ master uses the internal Git data (*not* any working directory that may be present) to fetch the latest *fully completed commit* at the repository's head. .. note::
The workflow we will describe in this tutorial corresponds to a situation where the ARTIQ master machine is also used as a Git server where multiple users may push and pull code. The Git setup can be customized according to your needs; the main point to remember is that when scanning or submitting, the ARTIQ master uses the internal Git data (*not* any working directory that may be present) to fetch the latest *fully completed commit* at the repository's head.
We will use the current ``repository`` folder as working directory for making local modifications to the experiments, move it away from the master data directory, and create a new ``repository`` folder that holds the Git data used by the master. Stop the master with Ctrl-C and enter the following commands: :: We will use the current ``repository`` folder as working directory for making local modifications to the experiments, move it away from the master data directory, and create a new ``repository`` folder that holds the Git data used by the master. Stop the master with Ctrl-C and enter the following commands: ::
@ -103,7 +147,8 @@ Start the master again with the ``-g`` flag, telling it to treat the contents of
$ cd ~/artiq-master $ cd ~/artiq-master
$ artiq_master -g $ artiq_master -g
.. note:: You need at least one commit in the repository before you can start the master. .. note::
You need at least one commit in the repository before you can start the master.
There should be no errors displayed, and if you start the GUI again, you will find the experiment there. There should be no errors displayed, and if you start the GUI again, you will find the experiment there.
@ -116,15 +161,15 @@ Then set the execution permission on it: ::
$ chmod 755 ~/artiq-master/repository/hooks/post-receive $ chmod 755 ~/artiq-master/repository/hooks/post-receive
.. note:: Remote machines may also push and pull into the master's bare repository using e.g. Git over SSH. .. note::
Remote machines may also push and pull into the master's bare repository using e.g. Git over SSH.
Let's now make a modification to the experiment. In the source present in the working directory, add an exclamation mark at the end of "Hello World". Before committing it, check that the experiment can still be executed correctly by running it directly from the filesystem using: :: Let's now make a modification to the experiment. In the source present in the working directory, add an exclamation mark at the end of "Hello World". Before committing it, check that the experiment can still be executed correctly by running it directly from the filesystem using: ::
$ artiq_client submit ~/artiq-work/mgmt_tutorial.py $ artiq_client submit ~/artiq-work/mgmt_tutorial.py
.. note:: You may also use the "Open file outside repository" feature of the GUI, by right-clicking on the explorer. .. note::
You may also use the "Open file outside repository" feature of the GUI, by right-clicking on the explorer.
.. note:: Submitting an experiment from the repository using the ``artiq_client`` command-line tool is done using the ``-R`` flag.
Verify the log in the GUI. If you are happy with the result, commit the new version and push it into the master's repository: :: Verify the log in the GUI. If you are happy with the result, commit the new version and push it into the master's repository: ::
@ -132,16 +177,24 @@ Verify the log in the GUI. If you are happy with the result, commit the new vers
$ git commit -a -m "More enthusiasm" $ git commit -a -m "More enthusiasm"
$ git push $ git push
.. note:: Notice that commands other than ``git push`` are not needed anymore. .. note::
Notice that commands other than ``git push`` are no longer necessary.
The master should now run the new version from its repository. The master should now run the new version from its repository.
As an exercise, add another experiment to the repository, commit and push the result, and verify that it appears in the GUI. As an exercise, add another experiment to the repository, commit and push the result, and verify that it appears in the GUI.
.. _getting-started-datasets:
Datasets Datasets
-------- --------
Modify the ``run()`` method of the experiment as follows: :: ARTIQ uses the concept of *datasets* to manage the data exchanged with experiments, both supplied *to* experiments (generally, from other experiments) and saved *from* experiments (i.e. results or records).
Modify the experiment as follows, once again using a single non-interactive argument: ::
def build(self):
self.setattr_argument("count", NumberValue(precision=0, step=1))
def run(self): def run(self):
self.set_dataset("parabola", np.full(self.count, np.nan), broadcast=True) self.set_dataset("parabola", np.full(self.count, np.nan), broadcast=True)
@ -149,16 +202,61 @@ Modify the ``run()`` method of the experiment as follows: ::
self.mutate_dataset("parabola", i, i*i) self.mutate_dataset("parabola", i, i*i)
time.sleep(0.5) time.sleep(0.5)
.. note:: You need to import the ``time`` module, and the ``numpy`` module as ``np``. .. tip::
You need to import the ``time`` module, and the ``numpy`` module as ``np``.
Commit, push and submit the experiment as before. Go to the "Datasets" dock of the GUI and observe that a new dataset has been created. We will now create a new XY plot showing this new result. Commit, push and submit the experiment as before. Go to the "Datasets" dock of the GUI and observe that a new dataset has been created. Once the experiment has finished executing, navigate to ``~/artiq-master/`` in a terminal or file manager and see that a new directory has been created called ``results``. Your dataset should be stored as an HD5 dump file in ``results`` under ``<date>/<hour>``.
Plotting in the ARTIQ dashboard is achieved by programs called "applets". Applets are independent programs that add simple GUI features and are run as separate processes (to achieve goals of modularity and resilience against poorly written applets). Users may write their own applets, or use those supplied with ARTIQ (in the ``artiq.applets`` module) that cover basic plotting. .. note::
By default, datasets are primarily attributes of the experiments that run them, and are not shared with the master or the dashboard. The ``broadcast=True`` argument specifies that an argument should be shared in real-time with the master, which is responsible for dispatching it to the clients. A more detailed description of dataset methods and their arguments can be found under :mod:`artiq.language.environment.HasEnvironment`.
Applets are configured through their command line to select parameters such as the names of the datasets to plot. The list of command-line options can be retrieved using the ``-h`` option; for example you can run ``python3 -m artiq.applets.plot_xy -h`` in a terminal. Open the file for your first dataset with HDFView, h5dump, or any similar third-party tool, and observe the data we just generated as well as the Git commit ID of the experiment (a hexadecimal hash such as ``947acb1f90ae1b8862efb489a9cc29f7d4e0c645`` which represents a particular state of the Git repository). A list of Git commit IDs can be found by running the ``git log`` command in ``~/artiq-master/``.
In our case, create a new applet from the XY template by right-clicking on the applet list, and edit the applet command line so that it retrieves the ``parabola`` dataset (the command line should now be ``${artiq_applet}plot_xy parabola``). Run the experiment again, and observe how the points are added one by one to the plot. Applets
-------
After the experiment has finished executing, the results are written to a HDF5 file that resides in ``~/artiq-master/results/<date>/<hour>``. Open that file with HDFView or h5dump, and observe the data we just generated as well as the Git commit ID of the experiment (a hexadecimal hash such as ``947acb1f90ae1b8862efb489a9cc29f7d4e0c645`` that represents the data at a particular time in the Git repository). The list of Git commit IDs can be found using the ``git log`` command in ``~/artiq-work``. Often, rather than the HDF dump, we would like to see our result datasets in readable graphical form, preferably immediately. In the ARTIQ dashboard, this is achieved by programs called "applets". Applets are independent programs that add simple GUI features and are run as separate processes (to achieve goals of modularity and resilience against poorly written applets). ARTIQ supplies several applets for basic plotting in the ``artiq.applets`` module, and users may write their own using the provided interfaces.
.. note:: HDFView and h5dump are third-party tools not supplied with ARTIQ. .. seealso::
For developing your own applets, see the references provided on the :ref:`management system page<applet-references>` of this manual.
For our ``parabola`` dataset, we will create an XY plot using the provided ``artiq.applets.plot_xy``. Applets are configured with simple command line options; we can find the list of available options using the ``-h`` flag. Try running: ::
$ python3 -m artiq.applets.plot_xy -h
In our case, we only need to supply our dataset as the y-values to be plotted. Navigate to the "Applet" dock in the dashboard. Right-click in the empty list and select "New applet from template" and "XY". This will generate a version of the applet command that shows all applicable options; edit the command so that it retrieves the ``parabola`` dataset and erase the unused options. The line should now be: ::
${artiq_applet}plot_xy parabola
Run the experiment again, and observe how the points are added one by one to the plot.
RTIO analyzer and the dashboard
-------------------------------
The :ref:`rtio-analyzer-example` is fully integrated into the dashboard. Navigate to the "Waveform" tab in the dashboard. After running the example experiment in that section, or any other experiment producing an analyzer trace, the waveform results will be directly displayed in this tab. It is also possible to save a trace, reopen it, or export it to VCD directly from the GUI.
Non-RTIO devices and the controller manager
-------------------------------------------
As described in :ref:`artiq-real-time-i-o-concepts`, there are two classes of equipment a laboratory typically finds itself needing to operate. So far, we have largely discussed ARTIQ in terms of one only: the kind of specialized hardware that requires the very high-resolution timing control ARTIQ provides. The other class comprises the broad range of regular, "slow" laboratory devices, which do *not* require nanosecond precision and can generally be operated perfectly well from a regular PC over a non-realtime channel such as USB.
To handle these "slow" devices, ARTIQ uses *controllers*, intermediate pieces of software which are responsible for the direct I/O to these devices and offer RPC interfaces to the network. Controllers can be started and run standalone, but are generally handled through the *controller manager*, available through the ``artiq-comtools`` package (normally automatically installed together with ARTIQ.) The controller manager in turn communicates with the ARTIQ master, and through it with clients or the GUI.
To start the controller manager (the master must already be running), the only command necessary is: ::
$ artiq_ctlmgr
Controllers may be run on a different machine from the master, or even on multiple different machines, alleviating cabling issues and OS compatibility problems. In this case, communication with the master happens over the network. If multiple machines are running controllers, they must each run their own controller manager (for which only ``artiq-comtools`` and its few dependencies are necessary, not the full ARTIQ installation.) Use the ``-s`` and ``--bind`` flags of ``artiq_ctlmgr`` to set IP addresses or hostnames to connect and bind to.
Note, however, that the controller for the particular device you are trying to connect to must first exist and be part of a complete Network Device Support Package, or NDSP. :doc:`Some NDSPs are already available <list_of_ndsps>`. If your device is not on this list, the system is designed to make it quite possible to write your own. For this, see the :doc:`developing_a_ndsp` page.
Once a device is correctly listed in ``device_db.py``, it can be added to an experiment using ``self.setattr_device([device_name])`` and the methods its API offers called straightforwardly as ``self.[device_name].[method_name]``. As long as the requisite controllers are running and available, the experiment can then be executed with ``artiq_run`` or through the management system.
The ARTIQ session
-----------------
If (as is often the case) you intend to mostly operate your ARTIQ system and its devices from a single machine, i.e., the networked aspects of the management system are largely unnecessary and you will be running master, dashboard, and controller manager on one computer, they can all be started simultaneously with the single command: ::
$ artiq_session
Arguments to the individuals (including ``-s`` and ``--bind``) can still be specified using the ``-m``, ``-d`` and ``-c`` options respectively.

View File

@ -1,26 +1,60 @@
ARTIQ documentation ARTIQ manual
=================== ============
.. toctree:: .. toctree::
:caption: Contents :caption: Overview
:maxdepth: 2 :maxdepth: 2
introduction introduction
release_notes releases
.. toctree::
:caption: Setting up ARTIQ
:maxdepth: 2
installing installing
developing flashing
configuring
building_developing
.. toctree::
:caption: Tutorials
:maxdepth: 2
rtio rtio
getting_started_core getting_started_core
getting_started_mgmt getting_started_mgmt
using_drtio_subkernels
.. toctree::
:caption: ARTIQ components
:maxdepth: 2
environment environment
compiler compiler
management_system management_system
drtio drtio
core_device core_device
core_language_reference
core_drivers_reference .. toctree::
utilities :caption: Network devices
:maxdepth: 2
developing_a_ndsp developing_a_ndsp
list_of_ndsps list_of_ndsps
default_network_ports default_network_ports
.. toctree::
:caption: References
:maxdepth: 2
main_frontend_reference
core_language_reference
core_drivers_reference
utilities
.. toctree::
:caption: Addenda
:maxdepth: 2
faq faq

Some files were not shown because too many files have changed in this diff Show More