forked from M-Labs/artiq
1
0
Fork 0

Compare commits

...

77 Commits

Author SHA1 Message Date
David Nadlinger 11bab7dadb gui: Update logo_ver.svg to major version 9
Opened in Inkscape, manually replaced the "8" with a text object
showing "9" (Intro, 40pt), converted object to paths, and saved
again with default settings. Only committed the part of the file
change corresponding to the text.
2024-09-01 08:06:06 +08:00
David Nadlinger a1fb6e1b70 setup.py: Add missing platformdirs dependency
This was introduced in 583a4ceadd, but only added to the
Nix flake there.
2024-08-30 15:46:56 +01:00
architeuthidae 00f2e3ae93 doc: Dashboard 'Load HDF5' button + nitpicks 2024-08-28 11:20:02 +08:00
Florian Agbuya 9aaec5db67 update default version 2024-08-27 13:59:22 +08:00
Florian Agbuya 583a4ceadd
switch from `appdirs` to `platformdirs` 2024-08-27 13:59:05 +08:00
Sebastien Bourdeauducq dba877471f flake: update dependencies 2024-08-24 10:51:34 +08:00
Sebastien Bourdeauducq 457b3edd44 test_client: make master termination easier to debug 2024-08-24 10:47:01 +08:00
Sebastien Bourdeauducq fbb1a2c25d master: migrate deprecated pygit2 commit.hex attr 2024-08-24 10:47:01 +08:00
abdul124 352cf907ee compiler: add UnwrapNoneError exception 2024-08-23 19:12:52 +08:00
abdul124 7a2b11cc54 firmware: add UnwrapNoneError exception 2024-08-23 19:12:52 +08:00
architeuthidae 792f3d1ca8 doc: Fix TikZ arrow types (again) 2024-08-23 17:22:24 +08:00
architeuthidae f6bc4d559a doc: Fix TikZ arrow types 2024-08-23 16:44:56 +08:00
architeuthidae 6b570c0484 doc: Additional keyboard shortcuts 2024-08-23 16:44:56 +08:00
architeuthidae 70dce7c1dd doc: Minor fixes 2024-08-23 16:44:56 +08:00
architeuthidae e38dc59656 doc: Add ZC706 to core device page 2024-08-22 13:52:16 +08:00
architeuthidae 70d0f930c6 doc: More FAQs 2024-08-22 13:51:39 +08:00
architeuthidae 9bfac74c3f doc: Add 'Overview' diagram 2024-08-22 13:44:20 +08:00
abdul124 3d125e76b3 firmware: add LinAlgError exception 2024-08-22 13:19:36 +08:00
abdul124 8e6fe41dc4 compiler: add LinAlgError exception 2024-08-22 13:19:36 +08:00
spaqin 61e96b37f9 doc: remove section on idle kernel info after demotion 2024-08-21 11:26:53 +08:00
spaqin 788e0cb3da session: demote idle/startup kernel messages to debug 2024-08-21 11:26:53 +08:00
Florian Agbuya 62afcdaaf6
gui/state: implement backup state file for last successful load (#2538) 2024-08-21 00:39:24 +08:00
architeuthidae 83bf984216 doc: Management expansion, suggested edits 2024-08-20 16:29:06 +08:00
architeuthidae 020fe6caf0 doc: Add Waveform/RTIO analyzer 2024-08-20 16:29:06 +08:00
architeuthidae 9e557cdbf9 doc: Add reference descriptions 2024-08-20 16:29:06 +08:00
architeuthidae b5787ac8f4 doc: GUI applet groups 2024-08-20 16:29:06 +08:00
architeuthidae e4b4657a6d doc: using_data_interfaces associated changes 2024-08-20 16:29:06 +08:00
architeuthidae 25b3553469 doc: Add 'Data and user interfaces' page 2024-08-20 16:29:06 +08:00
abdul124 7e32f00121 firmware/ksupport: improve comments and syscall name 2024-08-20 15:21:27 +08:00
abdul124 76ead047bf coredevice/test: add unittests for exceptions 2024-08-20 15:21:27 +08:00
abdul124 cd4a0bb39e firmware/ksupport: add exception unittests 2024-08-20 15:21:27 +08:00
abdul124 09128f87e6 coredevice/comm_kernel: map exceptions to correct names 2024-08-20 15:21:27 +08:00
abdul124 33d5002f39 sync exception names and ids 2024-08-20 15:21:27 +08:00
architeuthidae 0eddd2bbaa doc: More FAQs 2024-08-19 13:03:46 +08:00
Sebastien Bourdeauducq d28355541a flake: update dependencies, use rust overlay snapshot 2024-08-14 16:52:46 +08:00
architeuthidae 00b429b468 doc: Refactor management system reference 2024-08-13 14:53:13 +08:00
architeuthidae 1b75bd1448 doc: Extending RTIO, fixes 2024-08-13 14:50:13 +08:00
architeuthidae 83922cce8b doc: Add 'Extending RTIO' page 2024-08-13 14:50:13 +08:00
architeuthidae 775aff730e flake/sphinx: add tikz for diagrams 2024-08-13 14:50:13 +08:00
architeuthidae c0805b9cb9 doc: Minor link fixes 2024-08-12 16:52:45 +08:00
Sébastien Bourdeauducq 322f9f6e55 setup: minimum Python version is 3.11 (StreamWriter.start_tls) 2024-08-12 11:40:34 +08:00
spaqin 9f9acb3528 gui: force xcb instead of wayland 2024-08-07 16:01:51 +08:00
Florian Agbuya 2241a32c9a afws_client: report error on JSON data length mismatch 2024-08-02 14:26:58 +08:00
architeuthidae e627aaeda0 CONTRIBUTING: Doc section 2024-08-01 19:29:53 +08:00
architeuthidae cec24feac8 doc: Refactor FAQ slightly 2024-08-01 19:28:42 +08:00
architeuthidae c8b797c5ac doc: Add helpdesk instructions to FAQ 2024-08-01 19:28:42 +08:00
architeuthidae a547fac41b doc: Add extra resources to FAQ 2024-08-01 19:28:42 +08:00
architeuthidae 55a89b1dbb doc: Add note on nix profile deletion, garbage collect 2024-08-01 18:57:34 +08:00
architeuthidae 477320d72c doc: reST formatting 2024-08-01 18:52:11 +08:00
architeuthidae 1b28e38d51 flake/sphinx: fix errors in manual nix build 2024-08-01 18:49:21 +08:00
Sebastien Bourdeauducq 468ace0e6d Revert "flake: avoid permissions race window when setting up HITL SSH key"
This reverts commit 75ffbeba4d.
2024-08-01 07:35:20 +08:00
spaqin 4fcb7cc408 big_number: fix port to Qt6 2024-07-31 15:06:47 +02:00
architeuthidae 0623480c82 doc: Subkernel exception fix 2024-07-31 17:17:13 +08:00
mwojcik e63ac3435f satman: pass exceptions from one subkernel to another 2024-07-31 17:17:01 +08:00
mwojcik 02479e4fb3 subkernels: separate error messages 2024-07-31 17:17:01 +08:00
mwojcik c5c5708f49 compiler: add builtinInvoke for subkernel raising functions 2024-07-31 17:17:01 +08:00
mwojcik fb8dd01e8d subkernel: pass exceptions to kernel 2024-07-31 17:17:01 +08:00
architeuthidae e12bc586a5 doc: Rewrite FAQ 2024-07-31 17:15:27 +08:00
architeuthidae d69c2b6aa2 git(hub): update gitignore, pull request template 2024-07-31 13:24:40 +08:00
architeuthidae 994a936f26 doc: Document core_log() in manual 2024-07-31 13:20:15 +08:00
Egor Savkin 75ffbeba4d
flake: avoid permissions race window when setting up HITL SSH key 2024-07-30 11:24:21 +08:00
Florian Agbuya fbf11ca002 afws_client: fix unicode error in json handling 2024-07-29 18:05:15 +08:00
architeuthidae 61ac6da547 doc: Add references to command line tools 2024-07-29 13:40:11 +08:00
architeuthidae 2ec01a3c45 doc: Add artiq_session and artiq_ctlmgr to front-end tools 2024-07-29 13:40:11 +08:00
architeuthidae bac22b7163 doc: Add automodule to command line references 2024-07-29 13:40:11 +08:00
architeuthidae 937f3811d1 doc: Rename main frontend tools page 2024-07-29 13:40:11 +08:00
Simon Renblad ab090f9caf dashboard: fix pyqt6 gui bugs 2024-07-29 13:37:49 +08:00
architeuthidae 6698a6f80c
doc: Building + developing rewrite (#2496)
Co-authored-by: architeuthidae <am@m-labs.hk>
2024-07-27 22:14:25 +08:00
Sebastien Bourdeauducq 191494e430 RELEASE_NOTES: update 2024-07-27 22:10:06 +08:00
Sebastien Bourdeauducq b588295063 flake: switch to nixpkgs unstable (py3.12), update nix deps 2024-07-27 21:49:33 +08:00
spaqin e7f906e47a applets: fix initial embedded widget size 2024-07-27 21:37:19 +08:00
mwojcik d6bcc64518 flake: update qasync to support pyqt6 2024-07-27 21:37:19 +08:00
mwojcik c4c932020a port recent GUI changes to PyQt6 2024-07-27 21:37:19 +08:00
David Nadlinger f7edb7b706 gui: Remove some unused imports [nfc] 2024-07-27 21:37:19 +08:00
David Nadlinger 4bd328afe7 gui: QRecursiveFilterProxyModel is obsolete
The recursiveFilteringEnabled property, which was added
in Qt 5.10, can now be used to obtain the same behaviour.

Also fixes a PyQt6 incompatibility in the implementation.
2024-07-27 21:37:19 +08:00
David Nadlinger 5dd2f7c4e8 dashboard: Explicitly specify QMenu parents
Not sure if this is a lifetime issue or something else, but
without it, the respective submenu "triangles" would just not
be shown in PyQt6.
2024-07-27 21:37:19 +08:00
David Nadlinger 9fbd6de30c Qt{5 -> 6}
Some changes are due to deprecations in Qt6 which were outright
removed in PyQt, for instance QRegExp or the x()/y() QMouseEvent
properties. Most of the diff is due to enumeration values now no
longer being available directly in the parent namespace.

This commit is purposefully restricted to the mechanical changes,
no reformatting/… is done to keep the diff easy to validate.
2024-07-27 21:37:19 +08:00
106 changed files with 3118 additions and 2081 deletions

View File

@ -51,7 +51,7 @@ Closes #XXX
### Documentation Changes ### Documentation Changes
- [ ] Check, test, and update the documentation in [doc/](../doc/). Build documentation (`cd doc/manual/; make html`) to ensure no errors. - [ ] Check, test, and update the documentation in [doc/](../doc/). Build documentation (`nix build .#artiq-manual-html; nix build .#artiq-manual-pdf`) to ensure no errors.
### Git Logistics ### Git Logistics

4
.gitignore vendored
View File

@ -11,6 +11,7 @@ __pycache__/
.ipynb_checkpoints .ipynb_checkpoints
/doc/manual/_build /doc/manual/_build
/build /build
/result
/dist /dist
/*.egg-info /*.egg-info
/.coverage /.coverage
@ -23,7 +24,8 @@ __pycache__/
/artiq/test/results /artiq/test/results
/artiq/examples/*/results /artiq/examples/*/results
/artiq/examples/*/last_rid.pyon /artiq/examples/*/last_rid.pyon
/artiq/examples/*/dataset_db.pyon /artiq/examples/*/dataset_db.mdb
/artiq/examples/*/dataset_db.mdb-lock
# when testing ad-hoc experiments at the root: # when testing ad-hoc experiments at the root:
/repository/ /repository/

View File

@ -8,27 +8,27 @@ Reporting Issues/Bugs
Thanks for `reporting issues to ARTIQ Thanks for `reporting issues to ARTIQ
<https://github.com/m-labs/artiq/issues/new>`_! You can also discuss issues and <https://github.com/m-labs/artiq/issues/new>`_! You can also discuss issues and
ask questions on IRC (the #m-labs channel on OFTC), the `Mattermost chat ask questions on IRC (the #m-labs channel on OFTC), the `Mattermost chat
<https://chat.m-labs.hk>`_, or on the `forum <https://forum.m-labs.hk>`_. <https://chat.m-labs.hk>`_, or in the `forum <https://forum.m-labs.hk>`_.
The best bug reports are those which contain sufficient information. With The best bug reports are those which contain sufficient information. With
accurate and comprehensive context, an issue can be resolved quickly and accurate and comprehensive context, an issue can be resolved quickly and
efficiently. Please consider adding the following data to your issue efficiently. Please consider adding the following data to your issue
report if possible: report if possible:
* A clear and unique summary that fits into one line. Also check that * A clear and unique summary that fits into one line. Check that this
this issue has not yet been reported. If it has, add additional information there. issue has not yet been reported; if it has, add additional information there.
* Precise steps to reproduce (list of actions that leads to the issue) * Precise steps to reproduce (a list of actions that leads to the issue)
* Expected behavior (what should happen) * Expected behavior (what should happen)
* Actual behavior (what happens instead) * Actual behavior (what happens instead)
* Logging message, trace backs, screen shots where relevant * Logging message, tracebacks, screenshots, where applicable
* Components involved (omit irrelevant parts): * Components involved (omit irrelevant parts):
* Operating System * Operating system used
* ARTIQ version (with recent versions of ARTIQ, run ``artiq_client --version``) * ARTIQ version (run any command in the form of ``artiq_client --version``)
* Version of the gateware and runtime loaded in the core device (in the output of ``artiq_coremgmt -D .... log``) * Gateware and firmware loaded to the core device (in the output of
``artiq_coremgmt [-D ....] log``)
* Hardware involved * Hardware involved
For in-depth information on bug reporting, see: For in-depth information on bug reporting, see:
http://www.chiark.greenend.org.uk/~sgtatham/bugs.html http://www.chiark.greenend.org.uk/~sgtatham/bugs.html
@ -38,10 +38,10 @@ https://developer.mozilla.org/en-US/docs/Mozilla/QA/Bug_writing_guidelines
Contributing Code Contributing Code
================= =================
ARTIQ welcomes contributions. Write bite-sized patches that can stand alone, ARTIQ welcomes contributions. Write bite-size patches that can stand alone,
clean them up, write proper commit messages, add docstrings and unittests. Then clean them up, write proper commit messages, add docstrings and unit tests;
``git rebase`` them onto the current master or merge the current master. Verify ``git rebase`` them onto the current master or merge the current master. Verify
that the testsuite passes. Then submit a pull request. Expect your contribution that the test suite passes. Then submit a pull request. Expect your contribution
to be held up to coding standards (e.g. use ``flake8`` to check yourself). to be held up to coding standards (e.g. use ``flake8`` to check yourself).
Checklist for Code Contributions Checklist for Code Contributions
@ -51,7 +51,7 @@ Checklist for Code Contributions
- Use correct spelling and grammar. Use your code editor to help you with - Use correct spelling and grammar. Use your code editor to help you with
syntax, spelling, and style syntax, spelling, and style
- Style: PEP-8 (``flake8``) - Style: PEP-8 (``flake8``)
- Add, check docstrings and comments - Add or update docstrings and comments
- Split your contribution into logically separate changes (``git rebase - Split your contribution into logically separate changes (``git rebase
--interactive``). Merge (squash, fixup) commits that just fix previous commits --interactive``). Merge (squash, fixup) commits that just fix previous commits
or amend them. Remove unintended changes. Clean up your commits. or amend them. Remove unintended changes. Clean up your commits.
@ -63,12 +63,37 @@ Checklist for Code Contributions
- Review each of your commits for the above items (``git show``) - Review each of your commits for the above items (``git show``)
- Update ``RELEASE_NOTES.md`` if there are noteworthy changes, especially if - Update ``RELEASE_NOTES.md`` if there are noteworthy changes, especially if
there are changes to existing APIs there are changes to existing APIs
- Check, test, and update the documentation in `doc/` - Check, test, and update the documentation in ``doc/``
- Check, test, and update the unittests - Check, test, and update the unit tests
- Close and/or update issues - Close and/or update issues
Contributing Documentation
==========================
ARTIQ welcomes documentation contributions. The ARTIQ manual is hosted online in HTML
form `here <https://m-labs.hk/artiq/manual/>`__ and in PDF form
`here <https://m-labs.hk/artiq/manual.pdf>`__. It is generated from source files
in ``doc/manual``, written in a variant of the
`reStructured Text <https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html>`_
markup language processed by `Sphinx <https://www.sphinx-doc.org/en/master/>`_, with
some of the additional reference material processed from inline documentation
in the ARTIQ source itself.
Write bite-size patches that can stand alone, clean them up, write proper commit
messages. Check that your edits render properly and compile without errors: ::
$ nix build .#artiq-manual-pdf
$ nix build .#artiq-manual-html
Elaborations, improvements, clarifications and corrections to any of the material
are happily accepted, but special attention is drawn to the manual
`FAQ <https://m-labs.hk/artiq/manual/faq.html>`_, where tips and solutions
are especially easy to add. See also the FAQ's own
`section on the subject <https://m-labs.hk/artiq/manual/faq.html#build-documentation>`_.
Copyright and Sign-Off Copyright and Sign-Off
---------------------- ======================
Authors retain copyright of their contributions to ARTIQ, but whenever possible Authors retain copyright of their contributions to ARTIQ, but whenever possible
should use the GNU LGPL version 3 license for them to be merged. should use the GNU LGPL version 3 license for them to be merged.
@ -108,7 +133,7 @@ can certify the below:
maintained indefinitely and may be redistributed consistent with maintained indefinitely and may be redistributed consistent with
this project or the open source license(s) involved. this project or the open source license(s) involved.
then you just add a line saying then add a line saying
Signed-off-by: Random J Developer <random@developer.example.org> Signed-off-by: Random J Developer <random@developer.example.org>

View File

@ -15,7 +15,7 @@ ARTIQ and its dependencies are available in the form of Nix packages (for Linux)
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. 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.
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/experiment-control/artiq | Website: https://m-labs.hk/experiment-control/artiq
| (US-hosted mirror: https://m-labs-intl.com/experiment-control/artiq) | (US-hosted mirror: https://m-labs-intl.com/experiment-control/artiq)

View File

@ -6,11 +6,14 @@ Release notes
ARTIQ-9 (Unreleased) ARTIQ-9 (Unreleased)
-------------------- --------------------
* GUI state files are now automatically backed up upon successful loading.
* Zotino monitoring in the dashboard now displays the values in volts. * 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 * afws_client now uses the "happy eyeballs" algorithm (RFC 6555) for a faster and more
reliable connection to the server. reliable connection to the server.
* The Zadig driver installer was added to the MSYS2 offline installer. * The Zadig driver installer was added to the MSYS2 offline installer.
* Fastino monitoring with Moninj is now supported. * Fastino monitoring with Moninj is now supported.
* Qt6 support.
* Python 3.12 support.
ARTIQ-8 ARTIQ-8
------- -------

View File

@ -4,4 +4,4 @@ def get_rev():
return os.getenv("VERSIONEER_REV", default="unknown") return os.getenv("VERSIONEER_REV", default="unknown")
def get_version(): def get_version():
return os.getenv("VERSIONEER_OVERRIDE", default="8.0+unknown.beta") return os.getenv("VERSIONEER_OVERRIDE", default="9.0+unknown.beta")

View File

@ -1,557 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2005-2010 ActiveState Software Inc.
# Copyright (c) 2013 Eddy Petrișor
"""Utilities for determining application-specific dirs.
See <http://github.com/ActiveState/appdirs> for details and usage.
"""
# Dev Notes:
# - MSDN on where to store app data files:
# http://support.microsoft.com/default.aspx?scid=kb;en-us;310294#XSLTH3194121123120121120120
# - Mac OS X: http://developer.apple.com/documentation/MacOSX/Conceptual/BPFileSystem/index.html
# - XDG spec for Un*x: http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
__version_info__ = (1, 4, 1)
__version__ = '.'.join(map(str, __version_info__))
import sys
import os
PY3 = sys.version_info[0] == 3
if PY3:
unicode = str
if sys.platform.startswith('java'):
import platform
os_name = platform.java_ver()[3][0]
if os_name.startswith('Windows'): # "Windows XP", "Windows 7", etc.
system = 'win32'
elif os_name.startswith('Mac'): # "Mac OS X", etc.
system = 'darwin'
else: # "Linux", "SunOS", "FreeBSD", etc.
# Setting this to "linux2" is not ideal, but only Windows or Mac
# are actually checked for and the rest of the module expects
# *sys.platform* style strings.
system = 'linux2'
else:
system = sys.platform
def user_data_dir(appname=None, appauthor=None, version=None, roaming=False):
r"""Return full path to the user-specific data dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"roaming" (boolean, default False) can be set True to use the Windows
roaming appdata directory. That means that for users on a Windows
network setup for roaming profiles, this user data will be
sync'd on login. See
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
for a discussion of issues.
Typical user data directories are:
Mac OS X: ~/Library/Application Support/<AppName>
Unix: ~/.local/share/<AppName> # or in $XDG_DATA_HOME, if defined
Win XP (not roaming): C:\Documents and Settings\<username>\Application Data\<AppAuthor>\<AppName>
Win XP (roaming): C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>
Win 7 (not roaming): C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>
Win 7 (roaming): C:\Users\<username>\AppData\Roaming\<AppAuthor>\<AppName>
For Unix, we follow the XDG spec and support $XDG_DATA_HOME.
That means, by default "~/.local/share/<AppName>".
"""
if system == "win32":
if appauthor is None:
appauthor = appname
const = roaming and "CSIDL_APPDATA" or "CSIDL_LOCAL_APPDATA"
path = os.path.normpath(_get_win_folder(const))
if appname:
if appauthor is not False:
path = os.path.join(path, appauthor, appname)
else:
path = os.path.join(path, appname)
elif system == 'darwin':
path = os.path.expanduser('~/Library/Application Support/')
if appname:
path = os.path.join(path, appname)
else:
path = os.getenv('XDG_DATA_HOME', os.path.expanduser("~/.local/share"))
if appname:
path = os.path.join(path, appname)
if appname and version:
path = os.path.join(path, version)
return path
def site_data_dir(appname=None, appauthor=None, version=None, multipath=False):
"""Return full path to the user-shared data dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"multipath" is an optional parameter only applicable to *nix
which indicates that the entire list of data dirs should be
returned. By default, the first item from XDG_DATA_DIRS is
returned, or '/usr/local/share/<AppName>',
if XDG_DATA_DIRS is not set
Typical user data directories are:
Mac OS X: /Library/Application Support/<AppName>
Unix: /usr/local/share/<AppName> or /usr/share/<AppName>
Win XP: C:\Documents and Settings\All Users\Application Data\<AppAuthor>\<AppName>
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
Win 7: C:\ProgramData\<AppAuthor>\<AppName> # Hidden, but writeable on Win 7.
For Unix, this is using the $XDG_DATA_DIRS[0] default.
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
"""
if system == "win32":
if appauthor is None:
appauthor = appname
path = os.path.normpath(_get_win_folder("CSIDL_COMMON_APPDATA"))
if appname:
if appauthor is not False:
path = os.path.join(path, appauthor, appname)
else:
path = os.path.join(path, appname)
elif system == 'darwin':
path = os.path.expanduser('/Library/Application Support')
if appname:
path = os.path.join(path, appname)
else:
# XDG default for $XDG_DATA_DIRS
# only first, if multipath is False
path = os.getenv('XDG_DATA_DIRS',
os.pathsep.join(['/usr/local/share', '/usr/share']))
pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
if appname:
if version:
appname = os.path.join(appname, version)
pathlist = [os.sep.join([x, appname]) for x in pathlist]
if multipath:
path = os.pathsep.join(pathlist)
else:
path = pathlist[0]
return path
if appname and version:
path = os.path.join(path, version)
return path
def user_config_dir(appname=None, appauthor=None, version=None, roaming=False):
r"""Return full path to the user-specific config dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"roaming" (boolean, default False) can be set True to use the Windows
roaming appdata directory. That means that for users on a Windows
network setup for roaming profiles, this user data will be
sync'd on login. See
<http://technet.microsoft.com/en-us/library/cc766489(WS.10).aspx>
for a discussion of issues.
Typical user data directories are:
Mac OS X: same as user_data_dir
Unix: ~/.config/<AppName> # or in $XDG_CONFIG_HOME, if defined
Win *: same as user_data_dir
For Unix, we follow the XDG spec and support $XDG_CONFIG_HOME.
That means, by deafult "~/.config/<AppName>".
"""
if system in ["win32", "darwin"]:
path = user_data_dir(appname, appauthor, None, roaming)
else:
path = os.getenv('XDG_CONFIG_HOME', os.path.expanduser("~/.config"))
if appname:
path = os.path.join(path, appname)
if appname and version:
path = os.path.join(path, version)
return path
def site_config_dir(appname=None, appauthor=None, version=None, multipath=False):
"""Return full path to the user-shared data dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"multipath" is an optional parameter only applicable to *nix
which indicates that the entire list of config dirs should be
returned. By default, the first item from XDG_CONFIG_DIRS is
returned, or '/etc/xdg/<AppName>', if XDG_CONFIG_DIRS is not set
Typical user data directories are:
Mac OS X: same as site_data_dir
Unix: /etc/xdg/<AppName> or $XDG_CONFIG_DIRS[i]/<AppName> for each value in
$XDG_CONFIG_DIRS
Win *: same as site_data_dir
Vista: (Fail! "C:\ProgramData" is a hidden *system* directory on Vista.)
For Unix, this is using the $XDG_CONFIG_DIRS[0] default, if multipath=False
WARNING: Do not use this on Windows. See the Vista-Fail note above for why.
"""
if system in ["win32", "darwin"]:
path = site_data_dir(appname, appauthor)
if appname and version:
path = os.path.join(path, version)
else:
# XDG default for $XDG_CONFIG_DIRS
# only first, if multipath is False
path = os.getenv('XDG_CONFIG_DIRS', '/etc/xdg')
pathlist = [os.path.expanduser(x.rstrip(os.sep)) for x in path.split(os.pathsep)]
if appname:
if version:
appname = os.path.join(appname, version)
pathlist = [os.sep.join([x, appname]) for x in pathlist]
if multipath:
path = os.pathsep.join(pathlist)
else:
path = pathlist[0]
return path
def user_cache_dir(appname=None, appauthor=None, version=None, opinion=True):
r"""Return full path to the user-specific cache dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"opinion" (boolean) can be False to disable the appending of
"Cache" to the base app data dir for Windows. See
discussion below.
Typical user cache directories are:
Mac OS X: ~/Library/Caches/<AppName>
Unix: ~/.cache/<AppName> (XDG default)
Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Cache
Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Cache
On Windows the only suggestion in the MSDN docs is that local settings go in
the `CSIDL_LOCAL_APPDATA` directory. This is identical to the non-roaming
app data dir (the default returned by `user_data_dir` above). Apps typically
put cache data somewhere *under* the given dir here. Some examples:
...\Mozilla\Firefox\Profiles\<ProfileName>\Cache
...\Acme\SuperApp\Cache\1.0
OPINION: This function appends "Cache" to the `CSIDL_LOCAL_APPDATA` value.
This can be disabled with the `opinion=False` option.
"""
if system == "win32":
if appauthor is None:
appauthor = appname
path = os.path.normpath(_get_win_folder("CSIDL_LOCAL_APPDATA"))
if appname:
if appauthor is not False:
path = os.path.join(path, appauthor, appname)
else:
path = os.path.join(path, appname)
if opinion:
path = os.path.join(path, "Cache")
elif system == 'darwin':
path = os.path.expanduser('~/Library/Caches')
if appname:
path = os.path.join(path, appname)
else:
path = os.getenv('XDG_CACHE_HOME', os.path.expanduser('~/.cache'))
if appname:
path = os.path.join(path, appname)
if appname and version:
path = os.path.join(path, version)
return path
def user_log_dir(appname=None, appauthor=None, version=None, opinion=True):
r"""Return full path to the user-specific log dir for this application.
"appname" is the name of application.
If None, just the system directory is returned.
"appauthor" (only used on Windows) is the name of the
appauthor or distributing body for this application. Typically
it is the owning company name. This falls back to appname. You may
pass False to disable it.
"version" is an optional version path element to append to the
path. You might want to use this if you want multiple versions
of your app to be able to run independently. If used, this
would typically be "<major>.<minor>".
Only applied when appname is present.
"opinion" (boolean) can be False to disable the appending of
"Logs" to the base app data dir for Windows, and "log" to the
base cache dir for Unix. See discussion below.
Typical user cache directories are:
Mac OS X: ~/Library/Logs/<AppName>
Unix: ~/.cache/<AppName>/log # or under $XDG_CACHE_HOME if defined
Win XP: C:\Documents and Settings\<username>\Local Settings\Application Data\<AppAuthor>\<AppName>\Logs
Vista: C:\Users\<username>\AppData\Local\<AppAuthor>\<AppName>\Logs
On Windows the only suggestion in the MSDN docs is that local settings
go in the `CSIDL_LOCAL_APPDATA` directory. (Note: I'm interested in
examples of what some windows apps use for a logs dir.)
OPINION: This function appends "Logs" to the `CSIDL_LOCAL_APPDATA`
value for Windows and appends "log" to the user cache dir for Unix.
This can be disabled with the `opinion=False` option.
"""
if system == "darwin":
path = os.path.join(
os.path.expanduser('~/Library/Logs'),
appname)
elif system == "win32":
path = user_data_dir(appname, appauthor, version)
version = False
if opinion:
path = os.path.join(path, "Logs")
else:
path = user_cache_dir(appname, appauthor, version)
version = False
if opinion:
path = os.path.join(path, "log")
if appname and version:
path = os.path.join(path, version)
return path
class AppDirs(object):
"""Convenience wrapper for getting application dirs."""
def __init__(self, appname, appauthor=None, version=None, roaming=False,
multipath=False):
self.appname = appname
self.appauthor = appauthor
self.version = version
self.roaming = roaming
self.multipath = multipath
@property
def user_data_dir(self):
return user_data_dir(self.appname, self.appauthor,
version=self.version, roaming=self.roaming)
@property
def site_data_dir(self):
return site_data_dir(self.appname, self.appauthor,
version=self.version, multipath=self.multipath)
@property
def user_config_dir(self):
return user_config_dir(self.appname, self.appauthor,
version=self.version, roaming=self.roaming)
@property
def site_config_dir(self):
return site_config_dir(self.appname, self.appauthor,
version=self.version, multipath=self.multipath)
@property
def user_cache_dir(self):
return user_cache_dir(self.appname, self.appauthor,
version=self.version)
@property
def user_log_dir(self):
return user_log_dir(self.appname, self.appauthor,
version=self.version)
#---- internal support stuff
def _get_win_folder_from_registry(csidl_name):
"""This is a fallback technique at best. I'm not sure if using the
registry for this guarantees us the correct answer for all CSIDL_*
names.
"""
if PY3:
import winreg as _winreg
else:
import _winreg
shell_folder_name = {
"CSIDL_APPDATA": "AppData",
"CSIDL_COMMON_APPDATA": "Common AppData",
"CSIDL_LOCAL_APPDATA": "Local AppData",
}[csidl_name]
key = _winreg.OpenKey(
_winreg.HKEY_CURRENT_USER,
r"Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders"
)
dir, type = _winreg.QueryValueEx(key, shell_folder_name)
return dir
def _get_win_folder_with_pywin32(csidl_name):
from win32com.shell import shellcon, shell
dir = shell.SHGetFolderPath(0, getattr(shellcon, csidl_name), 0, 0)
# Try to make this a unicode path because SHGetFolderPath does
# not return unicode strings when there is unicode data in the
# path.
try:
dir = unicode(dir)
# Downgrade to short path name if have highbit chars. See
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
has_high_char = False
for c in dir:
if ord(c) > 255:
has_high_char = True
break
if has_high_char:
try:
import win32api
dir = win32api.GetShortPathName(dir)
except ImportError:
pass
except UnicodeError:
pass
return dir
def _get_win_folder_with_ctypes(csidl_name):
import ctypes
csidl_const = {
"CSIDL_APPDATA": 26,
"CSIDL_COMMON_APPDATA": 35,
"CSIDL_LOCAL_APPDATA": 28,
}[csidl_name]
buf = ctypes.create_unicode_buffer(1024)
ctypes.windll.shell32.SHGetFolderPathW(None, csidl_const, None, 0, buf)
# Downgrade to short path name if have highbit chars. See
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
has_high_char = False
for c in buf:
if ord(c) > 255:
has_high_char = True
break
if has_high_char:
buf2 = ctypes.create_unicode_buffer(1024)
if ctypes.windll.kernel32.GetShortPathNameW(buf.value, buf2, 1024):
buf = buf2
return buf.value
def _get_win_folder_with_jna(csidl_name):
import array
from com.sun import jna
from com.sun.jna.platform import win32
buf_size = win32.WinDef.MAX_PATH * 2
buf = array.zeros('c', buf_size)
shell = win32.Shell32.INSTANCE
shell.SHGetFolderPath(None, getattr(win32.ShlObj, csidl_name), None, win32.ShlObj.SHGFP_TYPE_CURRENT, buf)
dir = jna.Native.toString(buf.tostring()).rstrip("\0")
# Downgrade to short path name if have highbit chars. See
# <http://bugs.activestate.com/show_bug.cgi?id=85099>.
has_high_char = False
for c in dir:
if ord(c) > 255:
has_high_char = True
break
if has_high_char:
buf = array.zeros('c', buf_size)
kernel = win32.Kernel32.INSTANCE
if kernel.GetShortPathName(dir, buf, buf_size):
dir = jna.Native.toString(buf.tostring()).rstrip("\0")
return dir
if system == "win32":
try:
import win32com.shell
_get_win_folder = _get_win_folder_with_pywin32
except ImportError:
try:
from ctypes import windll
_get_win_folder = _get_win_folder_with_ctypes
except ImportError:
try:
import com.sun.jna
_get_win_folder = _get_win_folder_with_jna
except ImportError:
_get_win_folder = _get_win_folder_from_registry
#---- self test code
if __name__ == "__main__":
appname = "MyApp"
appauthor = "MyCompany"
props = ("user_data_dir", "site_data_dir",
"user_config_dir", "site_config_dir",
"user_cache_dir", "user_log_dir")
print("-- app dirs %s --" % __version__)
print("-- app dirs (with optional 'version')")
dirs = AppDirs(appname, appauthor, version="1.0")
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (without optional 'version')")
dirs = AppDirs(appname, appauthor)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (without optional 'appauthor')")
dirs = AppDirs(appname)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))
print("\n-- app dirs (with disabled 'appauthor')")
dirs = AppDirs(appname, appauthor=False)
for prop in props:
print("%s: %s" % (prop, getattr(dirs, prop)))

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)
@ -34,7 +34,7 @@ class NumberWidget(LayoutWidget):
self.addWidget(self.number_area, 0, 0) self.addWidget(self.number_area, 0, 0)
self.unit_area = QtWidgets.QLabel() self.unit_area = QtWidgets.QLabel()
self.unit_area.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop) self.unit_area.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignTop)
self.addWidget(self.unit_area, 0, 1) self.addWidget(self.unit_area, 0, 1)
self.lcd_widget = QResponsiveLCDNumber() self.lcd_widget = QResponsiveLCDNumber()
@ -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

@ -137,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
@ -273,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))
@ -286,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,15 +69,15 @@ 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**(
@ -88,16 +88,16 @@ class ZoomIconView(QtWidgets.QListView):
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

@ -88,18 +88,34 @@ class EmbeddingMap:
self.subkernel_message_map[msg_type.name] = msg_id self.subkernel_message_map[msg_type.name] = msg_id
self.object_reverse_map[obj_id] = msg_id self.object_reverse_map[obj_id] = msg_id
self.preallocate_runtime_exception_names(["RuntimeError", # Keep this list of exceptions in sync with `EXCEPTION_ID_LOOKUP` in `artiq::firmware::ksupport::eh_artiq`
"RTIOUnderflow", # The exceptions declared here must be defined in `artiq.coredevice.exceptions`
"RTIOOverflow", # Verify synchronization by running the test cases in `artiq.test.coredevice.test_exceptions`
"RTIODestinationUnreachable", self.preallocate_runtime_exception_names([
"DMAError", "RTIOUnderflow",
"I2CError", "RTIOOverflow",
"CacheError", "RTIODestinationUnreachable",
"SPIError", "DMAError",
"0:ZeroDivisionError", "I2CError",
"0:IndexError", "CacheError",
"UnwrapNoneError", "SPIError",
"SubkernelError"]) "SubkernelError",
"0:AssertionError",
"0:AttributeError",
"0:IndexError",
"0:IOError",
"0:KeyError",
"0:NotImplementedError",
"0:OverflowError",
"0:RuntimeError",
"0:TimeoutError",
"0:TypeError",
"0:ValueError",
"0:ZeroDivisionError",
"0:LinAlgError",
"UnwrapNoneError",
])
def preallocate_runtime_exception_names(self, names): def preallocate_runtime_exception_names(self, names):
for i, name in enumerate(names): for i, name in enumerate(names):

View File

@ -1047,6 +1047,42 @@ class Builtin(Instruction):
def opcode(self): def opcode(self):
return "builtin({})".format(self.op) return "builtin({})".format(self.op)
class BuiltinInvoke(Terminator):
"""
A builtin operation which can raise exceptions.
:ivar op: (string) operation name
"""
"""
:param op: (string) operation name
:param normal: (:class:`BasicBlock`) normal target
:param exn: (:class:`BasicBlock`) exceptional target
"""
def __init__(self, op, operands, typ, normal, exn, name=None):
assert isinstance(op, str)
for operand in operands: assert isinstance(operand, Value)
assert isinstance(normal, BasicBlock)
assert isinstance(exn, BasicBlock)
if name is None:
name = "BLTINV.{}".format(op)
super().__init__(operands + [normal, exn], typ, name)
self.op = op
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.op = self.op
return self_copy
def normal_target(self):
return self.operands[-2]
def exception_target(self):
return self.operands[-1]
def opcode(self):
return "builtinInvokable({})".format(self.op)
class Closure(Instruction): class Closure(Instruction):
""" """
A closure creation operation. A closure creation operation.

View File

@ -2544,10 +2544,22 @@ class ARTIQIRGenerator(algorithm.Visitor):
fn = types.get_method_function(fn) fn = types.get_method_function(fn)
sid = ir.Constant(fn.sid, builtins.TInt32()) sid = ir.Constant(fn.sid, builtins.TInt32())
if not builtins.is_none(fn.ret): if not builtins.is_none(fn.ret):
ret = self.append(ir.Builtin("subkernel_retrieve_return", [sid, timeout], fn.ret)) if self.unwind_target is None:
ret = self.append(ir.Builtin("subkernel_retrieve_return", [sid, timeout], fn.ret))
else:
after_invoke = self.add_block("invoke")
ret = self.append(ir.BuiltinInvoke("subkernel_retrieve_return", [sid, timeout],
fn.ret, after_invoke, self.unwind_target))
self.current_block = after_invoke
else: else:
ret = ir.Constant(None, builtins.TNone()) ret = ir.Constant(None, builtins.TNone())
self.append(ir.Builtin("subkernel_await_finish", [sid, timeout], builtins.TNone())) if self.unwind_target is None:
self.append(ir.Builtin("subkernel_await_finish", [sid, timeout], builtins.TNone()))
else:
after_invoke = self.add_block("invoke")
self.append(ir.BuiltinInvoke("subkernel_await_finish", [sid, timeout],
builtins.TNone(), after_invoke, self.unwind_target))
self.current_block = after_invoke
return ret return ret
elif types.is_builtin(typ, "subkernel_preload"): elif types.is_builtin(typ, "subkernel_preload"):
if len(node.args) == 1 and len(node.keywords) == 0: if len(node.args) == 1 and len(node.keywords) == 0:
@ -2594,7 +2606,14 @@ class ARTIQIRGenerator(algorithm.Visitor):
{"name": name, "recv": vartype, "send": msg.value_type}, {"name": name, "recv": vartype, "send": msg.value_type},
node.loc) node.loc)
self.engine.process(diag) self.engine.process(diag)
return self.append(ir.Builtin("subkernel_recv", [msg_id, timeout], vartype)) if self.unwind_target is None:
ret = self.append(ir.Builtin("subkernel_recv", [msg_id, timeout], vartype))
else:
after_invoke = self.add_block("invoke")
ret = self.append(ir.BuiltinInvoke("subkernel_recv", [msg_id, timeout],
vartype, after_invoke, self.unwind_target))
self.current_block = after_invoke
return ret
elif types.is_exn_constructor(typ): elif types.is_exn_constructor(typ):
return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args]) return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args])
elif types.is_constructor(typ): elif types.is_constructor(typ):
@ -2946,7 +2965,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
format_string += ")" format_string += ")"
elif builtins.is_exception(value.type): elif builtins.is_exception(value.type):
# message may not be an actual string... # message may not be an actual string...
# so we cannot really print it # so we cannot really print itInvoke
name = self.append(ir.GetAttr(value, "#__name__")) name = self.append(ir.GetAttr(value, "#__name__"))
param1 = self.append(ir.GetAttr(value, "#__param0__")) param1 = self.append(ir.GetAttr(value, "#__param0__"))
param2 = self.append(ir.GetAttr(value, "#__param1__")) param2 = self.append(ir.GetAttr(value, "#__param1__"))

View File

@ -1437,6 +1437,44 @@ class LLVMIRGenerator:
else: else:
assert False assert False
def process_BuiltinInvoke(self, insn):
llnormalblock = self.map(insn.normal_target())
llunwindblock = self.map(insn.exception_target())
if insn.op == "subkernel_retrieve_return":
llsid = self.map(insn.operands[0])
lltimeout = self.map(insn.operands[1])
lltagptr = self._build_subkernel_tags([insn.type])
llheadu = self.llbuilder.append_basic_block(name="subkernel.await.unwind")
self.llbuilder.invoke(self.llbuiltin("subkernel_await_message"),
[llsid, lltimeout, lltagptr, ll.Constant(lli8, 1), ll.Constant(lli8, 1)],
llheadu, llunwindblock,
name="subkernel.await.message")
self.llbuilder.position_at_end(llheadu)
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
name="subkernel.arg.stack")
return self._build_rpc_recv(insn.type, llstackptr, llnormalblock, llunwindblock)
elif insn.op == "subkernel_await_finish":
llsid = self.map(insn.operands[0])
lltimeout = self.map(insn.operands[1])
return self.llbuilder.invoke(self.llbuiltin("subkernel_await_finish"), [llsid, lltimeout],
llnormalblock, llunwindblock,
name="subkernel.await.finish")
elif insn.op == "subkernel_recv":
llmsgid = self.map(insn.operands[0])
lltimeout = self.map(insn.operands[1])
lltagptr = self._build_subkernel_tags([insn.type])
llheadu = self.llbuilder.append_basic_block(name="subkernel.await.unwind")
self.llbuilder.invoke(self.llbuiltin("subkernel_await_message"),
[llmsgid, lltimeout, lltagptr, ll.Constant(lli8, 1), ll.Constant(lli8, 1)],
llheadu, llunwindblock,
name="subkernel.await.message")
self.llbuilder.position_at_end(llheadu)
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
name="subkernel.arg.stack")
return self._build_rpc_recv(insn.type, llstackptr, llnormalblock, llunwindblock)
else:
assert False
def process_SubkernelAwaitArgs(self, insn): def process_SubkernelAwaitArgs(self, insn):
llmin = self.map(insn.operands[0]) llmin = self.map(insn.operands[0])
llmax = self.map(insn.operands[1]) llmax = self.map(insn.operands[1])

View File

@ -3,6 +3,7 @@ import logging
import traceback import traceback
import numpy import numpy
import socket import socket
import builtins
from enum import Enum from enum import Enum
from fractions import Fraction from fractions import Fraction
from collections import namedtuple from collections import namedtuple
@ -616,9 +617,10 @@ class CommKernel:
self._write_int32(embedding_map.store_str(function)) self._write_int32(embedding_map.store_str(function))
else: else:
exn_type = type(exn) exn_type = type(exn)
if exn_type in (ZeroDivisionError, ValueError, IndexError, RuntimeError) or \ if exn_type in builtins.__dict__.values():
hasattr(exn, "artiq_builtin"): name = "0:{}".format(exn_type.__qualname__)
name = "0:{}".format(exn_type.__name__) elif hasattr(exn, "artiq_builtin"):
name = "0:{}.{}".format(exn_type.__module__, exn_type.__qualname__)
else: else:
exn_id = embedding_map.store_object(exn_type) exn_id = embedding_map.store_object(exn_type)
name = "{}:{}.{}".format(exn_id, name = "{}:{}.{}".format(exn_id,

View File

@ -53,6 +53,9 @@ def rtio_get_destination_status(linkno: TInt32) -> TBool:
def rtio_get_counter() -> TInt64: def rtio_get_counter() -> TInt64:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall
def test_exception_id_sync(id: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
def get_target_cls(target): def get_target_cls(target):
if target == "rv32g": if target == "rv32g":

View File

@ -2,16 +2,31 @@ import builtins
import linecache import linecache
import re import re
import os import os
from numpy.linalg import LinAlgError
from artiq import __artiq_dir__ as artiq_dir from artiq import __artiq_dir__ as artiq_dir
from artiq.coredevice.runtime import source_loader from artiq.coredevice.runtime import source_loader
"""
This file provides class definition for all the exceptions declared in `EmbeddingMap` in `artiq.compiler.embedding`
For Python builtin exceptions, use the `builtins` module
For ARTIQ specific exceptions, inherit from `Exception` class
"""
ZeroDivisionError = builtins.ZeroDivisionError
ValueError = builtins.ValueError
IndexError = builtins.IndexError
RuntimeError = builtins.RuntimeError
AssertionError = builtins.AssertionError AssertionError = builtins.AssertionError
AttributeError = builtins.AttributeError
IndexError = builtins.IndexError
IOError = builtins.IOError
KeyError = builtins.KeyError
NotImplementedError = builtins.NotImplementedError
OverflowError = builtins.OverflowError
RuntimeError = builtins.RuntimeError
TimeoutError = builtins.TimeoutError
TypeError = builtins.TypeError
ValueError = builtins.ValueError
ZeroDivisionError = builtins.ZeroDivisionError
OSError = builtins.OSError
class CoreException: class CoreException:
@ -157,13 +172,18 @@ class SubkernelError(Exception):
class ClockFailure(Exception): class ClockFailure(Exception):
"""Raised when RTIO PLL has lost lock.""" """Raised when RTIO PLL has lost lock."""
artiq_builtin = True
class I2CError(Exception): class I2CError(Exception):
"""Raised when a I2C transaction fails.""" """Raised when a I2C transaction fails."""
pass artiq_builtin = True
class SPIError(Exception): class SPIError(Exception):
"""Raised when a SPI transaction fails.""" """Raised when a SPI transaction fails."""
pass artiq_builtin = True
class UnwrapNoneError(Exception):
"""Raised when unwrapping a none Option."""
artiq_builtin = True

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,7 +4,7 @@ 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
@ -23,7 +23,7 @@ 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)
@ -31,9 +31,8 @@ class _CancellableLineEdit(QtWidgets.QLineEdit):
class _MoninjWidget(QtWidgets.QFrame): class _MoninjWidget(QtWidgets.QFrame):
def __init__(self, title): def __init__(self, title):
QtWidgets.QFrame.__init__(self) QtWidgets.QFrame.__init__(self)
self.setFrameShape(QtWidgets.QFrame.Box) self.setFrameShape(QtWidgets.QFrame.Shape.Box)
self.setFrameShape(QtWidgets.QFrame.Box) self.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
self.setFrameShadow(QtWidgets.QFrame.Raised)
self.setFixedHeight(100) self.setFixedHeight(100)
self.setFixedWidth(150) self.setFixedWidth(150)
self.grid = QtWidgets.QGridLayout() self.grid = QtWidgets.QGridLayout()
@ -43,7 +42,9 @@ class _MoninjWidget(QtWidgets.QFrame):
self.setLayout(self.grid) self.setLayout(self.grid)
title = elide(title, 20) title = elide(title, 20)
label = QtWidgets.QLabel(title) label = QtWidgets.QLabel(title)
label.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop) label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
label.setSizePolicy(QtWidgets.QSizePolicy.Policy.Ignored,
QtWidgets.QSizePolicy.Policy.Preferred)
self.grid.addWidget(label, 1, 1) self.grid.addWidget(label, 1, 1)
@ -60,7 +61,7 @@ class _TTLWidget(_MoninjWidget):
self.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()
@ -80,7 +81,7 @@ class _TTLWidget(_MoninjWidget):
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)
self.grid.addWidget(self.value, 3, 1) self.grid.addWidget(self.value, 3, 1)
self.grid.setRowStretch(1, 1) self.grid.setRowStretch(1, 1)
@ -215,11 +216,11 @@ class _DDSWidget(_MoninjWidget):
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)
@ -231,10 +232,10 @@ class _DDSWidget(_MoninjWidget):
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)
@ -337,7 +338,7 @@ class _DACWidget(_MoninjWidget):
self.offset_dacs = offset_dacs self.offset_dacs = offset_dacs
self.value = QtWidgets.QLabel() self.value = QtWidgets.QLabel()
self.value.setAlignment(QtCore.Qt.AlignHCenter | QtCore.Qt.AlignTop) self.value.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignTop)
self.grid.addWidget(self.value, 2, 1, 6, 1) self.grid.addWidget(self.value, 2, 1, 6, 1)
self.grid.setRowStretch(1, 1) self.grid.setRowStretch(1, 1)
@ -760,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()
@ -770,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)
@ -799,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
@ -809,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)
@ -820,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)
@ -833,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):
@ -841,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))
@ -930,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()
@ -944,10 +947,10 @@ class MonInj:
dock.deleteLater() 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)
@ -967,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,24 +200,27 @@ 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.DropAction.MoveAction)
else: else:
super().mouseMoveEvent(e) super().mouseMoveEvent(e)
def wheelEvent(self, e): def wheelEvent(self, e):
if e.modifiers() & QtCore.Qt.ControlModifier: if e.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
super().wheelEvent(e) super().wheelEvent(e)
else:
e.ignore()
def mouseDoubleClickEvent(self, e): def mouseDoubleClickEvent(self, e):
pos = self.view_box.mapSceneToView(e.pos()) pos = self.view_box.mapSceneToView(e.position())
self.cursorMove.emit(pos.x()) self.cursorMove.emit(pos.x())
@ -393,6 +396,13 @@ class LogWaveform(_BaseWaveform):
self.plot_data_item.setData(x=[], y=[]) self.plot_data_item.setData(x=[], y=[])
# pg.GraphicsView ignores dragEnterEvent but not dragLeaveEvent
# https://github.com/pyqtgraph/pyqtgraph/blob/1e98704eac6b85de9c35371079f561042e88ad68/pyqtgraph/widgets/GraphicsView.py#L388
class _RefAxis(pg.PlotWidget):
def dragLeaveEvent(self, ev):
ev.ignore()
class _WaveformView(QtWidgets.QWidget): class _WaveformView(QtWidgets.QWidget):
cursorMove = QtCore.pyqtSignal(float) cursorMove = QtCore.pyqtSignal(float)
@ -408,7 +418,7 @@ class _WaveformView(QtWidgets.QWidget):
layout.setSpacing(0) layout.setSpacing(0)
self.setLayout(layout) self.setLayout(layout)
self._ref_axis = pg.PlotWidget() self._ref_axis = _RefAxis()
self._ref_axis.hideAxis("bottom") self._ref_axis.hideAxis("bottom")
self._ref_axis.hideAxis("left") self._ref_axis.hideAxis("left")
self._ref_axis.hideButtons() self._ref_axis.hideButtons()
@ -428,8 +438,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 +455,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 +531,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 +560,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 +668,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 +678,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 +708,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 +736,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 +755,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 +775,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 +785,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

@ -173,4 +173,12 @@ static mut API: &'static [(&'static str, *const ())] = &[
api!(spi_set_config = ::nrt_bus::spi::set_config), api!(spi_set_config = ::nrt_bus::spi::set_config),
api!(spi_write = ::nrt_bus::spi::write), api!(spi_write = ::nrt_bus::spi::write),
api!(spi_read = ::nrt_bus::spi::read), api!(spi_read = ::nrt_bus::spi::read),
/*
* syscall for unit tests
* Used in `artiq.tests.coredevice.test_exceptions.ExceptionTest.test_raise_exceptions_kernel`
* This syscall checks that the exception IDs used in the Python `EmbeddingMap` (in `artiq.compiler.embedding`)
* match the `EXCEPTION_ID_LOOKUP` defined in the firmware (`artiq::firmware::ksupport::eh_artiq`)
*/
api!(test_exception_id_sync = ::eh_artiq::test_exception_id_sync)
]; ];

View File

@ -328,19 +328,30 @@ extern fn stop_fn(_version: c_int,
} }
} }
static EXCEPTION_ID_LOOKUP: [(&str, u32); 12] = [ // Must be kept in sync with `artiq.compiler.embedding`
("RuntimeError", 0), static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
("RTIOUnderflow", 1), ("RTIOUnderflow", 0),
("RTIOOverflow", 2), ("RTIOOverflow", 1),
("RTIODestinationUnreachable", 3), ("RTIODestinationUnreachable", 2),
("DMAError", 4), ("DMAError", 3),
("I2CError", 5), ("I2CError", 4),
("CacheError", 6), ("CacheError", 5),
("SPIError", 7), ("SPIError", 6),
("ZeroDivisionError", 8), ("SubkernelError", 7),
("IndexError", 9), ("AssertionError", 8),
("UnwrapNoneError", 10), ("AttributeError", 9),
("SubkernelError", 11) ("IndexError", 10),
("IOError", 11),
("KeyError", 12),
("NotImplementedError", 13),
("OverflowError", 14),
("RuntimeError", 15),
("TimeoutError", 16),
("TypeError", 17),
("ValueError", 18),
("ZeroDivisionError", 19),
("LinAlgError", 20),
("UnwrapNoneError", 21),
]; ];
pub fn get_exception_id(name: &str) -> u32 { pub fn get_exception_id(name: &str) -> u32 {
@ -352,3 +363,29 @@ pub fn get_exception_id(name: &str) -> u32 {
unimplemented!("unallocated internal exception id") unimplemented!("unallocated internal exception id")
} }
/// Takes as input exception id from host
/// Generates a new exception with:
/// * `id` set to `exn_id`
/// * `message` set to corresponding exception name from `EXCEPTION_ID_LOOKUP`
///
/// The message is matched on host to ensure correct exception is being referred
/// This test checks the synchronization of exception ids for runtime errors
#[no_mangle]
pub extern "C-unwind" fn test_exception_id_sync(exn_id: u32) {
let message = EXCEPTION_ID_LOOKUP
.iter()
.find_map(|&(name, id)| if id == exn_id { Some(name) } else { None })
.unwrap_or("unallocated internal exception id");
let exn = Exception {
id: exn_id,
file: file!().as_c_slice(),
line: 0,
column: 0,
function: "test_exception_id_sync".as_c_slice(),
message: message.as_c_slice(),
param: [0, 0, 0]
};
unsafe { raise(&exn) };
}

View File

@ -484,17 +484,23 @@ extern "C-unwind" fn subkernel_load_run(id: u32, destination: u8, run: bool) {
extern "C-unwind" fn subkernel_await_finish(id: u32, timeout: i64) { extern "C-unwind" fn subkernel_await_finish(id: u32, timeout: i64) {
send(&SubkernelAwaitFinishRequest { id: id, timeout: timeout }); send(&SubkernelAwaitFinishRequest { id: id, timeout: timeout });
recv!(SubkernelAwaitFinishReply { status } => { recv(move |request| {
match status { if let SubkernelAwaitFinishReply = request { }
SubkernelStatus::NoError => (), else if let SubkernelError(status) = request {
SubkernelStatus::IncorrectState => raise!("SubkernelError", match status {
"Subkernel not running"), SubkernelStatus::IncorrectState => raise!("SubkernelError",
SubkernelStatus::Timeout => raise!("SubkernelError", "Subkernel not running"),
"Subkernel timed out"), SubkernelStatus::Timeout => raise!("SubkernelError",
SubkernelStatus::CommLost => raise!("SubkernelError", "Subkernel timed out"),
"Lost communication with satellite"), SubkernelStatus::CommLost => raise!("SubkernelError",
SubkernelStatus::OtherError => raise!("SubkernelError", "Lost communication with satellite"),
"An error occurred during subkernel operation") SubkernelStatus::OtherError => raise!("SubkernelError",
"An error occurred during subkernel operation"),
SubkernelStatus::Exception(e) => unsafe { crate::eh_artiq::raise(e) },
}
} else {
send(&Log(format_args!("unexpected reply: {:?}\n", request)));
loop {}
} }
}) })
} }
@ -512,23 +518,28 @@ extern fn subkernel_send_message(id: u32, is_return: bool, destination: u8,
extern "C-unwind" fn subkernel_await_message(id: i32, timeout: i64, tags: &CSlice<u8>, min: u8, max: u8) -> u8 { extern "C-unwind" fn subkernel_await_message(id: i32, timeout: i64, tags: &CSlice<u8>, min: u8, max: u8) -> u8 {
send(&SubkernelMsgRecvRequest { id: id, timeout: timeout, tags: tags.as_ref() }); send(&SubkernelMsgRecvRequest { id: id, timeout: timeout, tags: tags.as_ref() });
recv!(SubkernelMsgRecvReply { status, count } => { recv(move |request| {
match status { if let SubkernelMsgRecvReply { count } = request {
SubkernelStatus::NoError => { if count < &min || count > &max {
if count < &min || count > &max { raise!("SubkernelError",
raise!("SubkernelError", "Received less or more arguments than expected");
"Received less or more arguments than expected");
}
*count
} }
SubkernelStatus::IncorrectState => raise!("SubkernelError", *count
"Subkernel not running"), } else if let SubkernelError(status) = request {
SubkernelStatus::Timeout => raise!("SubkernelError", match status {
"Subkernel timed out"), SubkernelStatus::IncorrectState => raise!("SubkernelError",
SubkernelStatus::CommLost => raise!("SubkernelError", "Subkernel not running"),
"Lost communication with satellite"), SubkernelStatus::Timeout => raise!("SubkernelError",
SubkernelStatus::OtherError => raise!("SubkernelError", "Subkernel timed out"),
"An error occurred during subkernel operation") SubkernelStatus::CommLost => raise!("SubkernelError",
"Lost communication with satellite"),
SubkernelStatus::OtherError => raise!("SubkernelError",
"An error occurred during subkernel operation"),
SubkernelStatus::Exception(e) => unsafe { crate::eh_artiq::raise(e) },
}
} else {
send(&Log(format_args!("unexpected reply: {:?}\n", request)));
loop {}
} }
}) })
// RpcRecvRequest should be called `count` times after this to receive message data // RpcRecvRequest should be called `count` times after this to receive message data

View File

@ -123,8 +123,8 @@ pub enum Packet {
SubkernelLoadRunRequest { source: u8, destination: u8, id: u32, run: bool }, SubkernelLoadRunRequest { source: u8, destination: u8, id: u32, run: bool },
SubkernelLoadRunReply { destination: u8, succeeded: bool }, SubkernelLoadRunReply { destination: u8, succeeded: bool },
SubkernelFinished { destination: u8, id: u32, with_exception: bool, exception_src: u8 }, SubkernelFinished { destination: u8, id: u32, with_exception: bool, exception_src: u8 },
SubkernelExceptionRequest { destination: u8 }, SubkernelExceptionRequest { source: u8, destination: u8 },
SubkernelException { last: bool, length: u16, data: [u8; SAT_PAYLOAD_MAX_SIZE] }, SubkernelException { destination: u8, last: bool, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
SubkernelMessage { source: u8, destination: u8, id: u32, status: PayloadStatus, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] }, SubkernelMessage { source: u8, destination: u8, id: u32, status: PayloadStatus, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
SubkernelMessageAck { destination: u8 }, SubkernelMessageAck { destination: u8 },
} }
@ -367,14 +367,17 @@ impl Packet {
exception_src: reader.read_u8()? exception_src: reader.read_u8()?
}, },
0xc9 => Packet::SubkernelExceptionRequest { 0xc9 => Packet::SubkernelExceptionRequest {
source: reader.read_u8()?,
destination: reader.read_u8()? destination: reader.read_u8()?
}, },
0xca => { 0xca => {
let destination = reader.read_u8()?;
let last = reader.read_bool()?; let last = reader.read_bool()?;
let length = reader.read_u16()?; let length = reader.read_u16()?;
let mut data: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE]; let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut data[0..length as usize])?; reader.read_exact(&mut data[0..length as usize])?;
Packet::SubkernelException { Packet::SubkernelException {
destination: destination,
last: last, last: last,
length: length, length: length,
data: data data: data
@ -663,12 +666,14 @@ impl Packet {
writer.write_bool(with_exception)?; writer.write_bool(with_exception)?;
writer.write_u8(exception_src)?; writer.write_u8(exception_src)?;
}, },
Packet::SubkernelExceptionRequest { destination } => { Packet::SubkernelExceptionRequest { source, destination } => {
writer.write_u8(0xc9)?; writer.write_u8(0xc9)?;
writer.write_u8(source)?;
writer.write_u8(destination)?; writer.write_u8(destination)?;
}, },
Packet::SubkernelException { last, length, data } => { Packet::SubkernelException { destination, last, length, data } => {
writer.write_u8(0xca)?; writer.write_u8(0xca)?;
writer.write_u8(destination)?;
writer.write_bool(last)?; writer.write_bool(last)?;
writer.write_u16(length)?; writer.write_u16(length)?;
writer.write_all(&data[0..length as usize])?; writer.write_all(&data[0..length as usize])?;
@ -693,18 +698,20 @@ impl Packet {
pub fn routable_destination(&self) -> Option<u8> { pub fn routable_destination(&self) -> Option<u8> {
// only for packets that could be re-routed, not only forwarded // only for packets that could be re-routed, not only forwarded
match self { match self {
Packet::DmaAddTraceRequest { destination, .. } => Some(*destination), Packet::DmaAddTraceRequest { destination, .. } => Some(*destination),
Packet::DmaAddTraceReply { destination, .. } => Some(*destination), Packet::DmaAddTraceReply { destination, .. } => Some(*destination),
Packet::DmaRemoveTraceRequest { destination, .. } => Some(*destination), Packet::DmaRemoveTraceRequest { destination, .. } => Some(*destination),
Packet::DmaRemoveTraceReply { destination, .. } => Some(*destination), Packet::DmaRemoveTraceReply { destination, .. } => Some(*destination),
Packet::DmaPlaybackRequest { destination, .. } => Some(*destination), Packet::DmaPlaybackRequest { destination, .. } => Some(*destination),
Packet::DmaPlaybackReply { destination, .. } => Some(*destination), Packet::DmaPlaybackReply { destination, .. } => Some(*destination),
Packet::SubkernelLoadRunRequest { destination, .. } => Some(*destination), Packet::SubkernelLoadRunRequest { destination, .. } => Some(*destination),
Packet::SubkernelLoadRunReply { destination, .. } => Some(*destination), Packet::SubkernelLoadRunReply { destination, .. } => Some(*destination),
Packet::SubkernelMessage { destination, .. } => Some(*destination), Packet::SubkernelMessage { destination, .. } => Some(*destination),
Packet::SubkernelMessageAck { destination, .. } => Some(*destination), Packet::SubkernelMessageAck { destination, .. } => Some(*destination),
Packet::DmaPlaybackStatus { destination, .. } => Some(*destination), Packet::SubkernelExceptionRequest { destination, .. } => Some(*destination),
Packet::SubkernelFinished { destination, .. } => Some(*destination), Packet::SubkernelException { destination, .. } => Some(*destination),
Packet::DmaPlaybackStatus { destination, .. } => Some(*destination),
Packet::SubkernelFinished { destination, .. } => Some(*destination),
_ => None _ => None
} }
} }

View File

@ -11,12 +11,12 @@ pub const KERNELCPU_LAST_ADDRESS: usize = 0x4fffffff;
pub const KSUPPORT_HEADER_SIZE: usize = 0x74; pub const KSUPPORT_HEADER_SIZE: usize = 0x74;
#[derive(Debug)] #[derive(Debug)]
pub enum SubkernelStatus { pub enum SubkernelStatus<'a> {
NoError,
Timeout, Timeout,
IncorrectState, IncorrectState,
CommLost, CommLost,
OtherError Exception(eh::eh_artiq::Exception<'a>),
OtherError,
} }
#[derive(Debug)] #[derive(Debug)]
@ -106,10 +106,11 @@ pub enum Message<'a> {
SubkernelLoadRunRequest { id: u32, destination: u8, run: bool }, SubkernelLoadRunRequest { id: u32, destination: u8, run: bool },
SubkernelLoadRunReply { succeeded: bool }, SubkernelLoadRunReply { succeeded: bool },
SubkernelAwaitFinishRequest { id: u32, timeout: i64 }, SubkernelAwaitFinishRequest { id: u32, timeout: i64 },
SubkernelAwaitFinishReply { status: SubkernelStatus }, SubkernelAwaitFinishReply,
SubkernelMsgSend { id: u32, destination: Option<u8>, count: u8, tag: &'a [u8], data: *const *const () }, SubkernelMsgSend { id: u32, destination: Option<u8>, count: u8, tag: &'a [u8], data: *const *const () },
SubkernelMsgRecvRequest { id: i32, timeout: i64, tags: &'a [u8] }, SubkernelMsgRecvRequest { id: i32, timeout: i64, tags: &'a [u8] },
SubkernelMsgRecvReply { status: SubkernelStatus, count: u8 }, SubkernelMsgRecvReply { count: u8 },
SubkernelError(SubkernelStatus<'a>),
Log(fmt::Arguments<'a>), Log(fmt::Arguments<'a>),
LogSlice(&'a str) LogSlice(&'a str)

View File

@ -95,7 +95,9 @@ pub mod subkernel {
use board_artiq::drtio_routing::RoutingTable; use board_artiq::drtio_routing::RoutingTable;
use board_misoc::clock; use board_misoc::clock;
use proto_artiq::{drtioaux_proto::{PayloadStatus, MASTER_PAYLOAD_MAX_SIZE}, rpc_proto as rpc}; use proto_artiq::{drtioaux_proto::{PayloadStatus, MASTER_PAYLOAD_MAX_SIZE}, rpc_proto as rpc};
use io::Cursor; use io::{Cursor, ProtoRead};
use eh::eh_artiq::Exception;
use cslice::CSlice;
use rtio_mgt::drtio; use rtio_mgt::drtio;
use sched::{Io, Mutex, Error as SchedError}; use sched::{Io, Mutex, Error as SchedError};
@ -226,8 +228,8 @@ pub mod subkernel {
if subkernel.state == SubkernelState::Running { if subkernel.state == SubkernelState::Running {
subkernel.state = SubkernelState::Finished { subkernel.state = SubkernelState::Finished {
status: match with_exception { status: match with_exception {
true => FinishStatus::Exception(exception_src), true => FinishStatus::Exception(exception_src),
false => FinishStatus::Ok, false => FinishStatus::Ok,
} }
} }
} }
@ -256,6 +258,49 @@ pub mod subkernel {
} }
} }
fn read_exception_string<'a>(reader: &mut Cursor<&[u8]>) -> Result<CSlice<'a, u8>, Error> {
let len = reader.read_u32()? as usize;
if len == usize::MAX {
let data = reader.read_u32()?;
Ok(unsafe { CSlice::new(data as *const u8, len) })
} else {
let pos = reader.position();
let slice = unsafe {
let ptr = reader.get_ref().as_ptr().offset(pos as isize);
CSlice::new(ptr, len)
};
reader.set_position(pos + len);
Ok(slice)
}
}
pub fn read_exception(buffer: &[u8]) -> Result<Exception, Error>
{
let mut reader = Cursor::new(buffer);
let mut byte = reader.read_u8()?;
// to sync
while byte != 0x5a {
byte = reader.read_u8()?;
}
// skip sync bytes, 0x09 indicates exception
while byte != 0x09 {
byte = reader.read_u8()?;
}
let _len = reader.read_u32()?;
// ignore the remaining exceptions, stack traces etc. - unwinding from another device would be unwise anyway
Ok(Exception {
id: reader.read_u32()?,
message: read_exception_string(&mut reader)?,
param: [reader.read_u64()? as i64, reader.read_u64()? as i64, reader.read_u64()? as i64],
file: read_exception_string(&mut reader)?,
line: reader.read_u32()?,
column: reader.read_u32()?,
function: read_exception_string(&mut reader)?
})
}
pub fn retrieve_finish_status(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex, pub fn retrieve_finish_status(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, id: u32) -> Result<SubkernelFinished, Error> { routing_table: &RoutingTable, id: u32) -> Result<SubkernelFinished, Error> {
let _lock = subkernel_mutex.lock(io)?; let _lock = subkernel_mutex.lock(io)?;

View File

@ -119,17 +119,19 @@ pub mod drtio {
}, },
// (potentially) routable packets // (potentially) routable packets
drtioaux::Packet::DmaAddTraceRequest { destination, .. } | drtioaux::Packet::DmaAddTraceRequest { destination, .. } |
drtioaux::Packet::DmaAddTraceReply { destination, .. } | drtioaux::Packet::DmaAddTraceReply { destination, .. } |
drtioaux::Packet::DmaRemoveTraceRequest { destination, .. } | drtioaux::Packet::DmaRemoveTraceRequest { destination, .. } |
drtioaux::Packet::DmaRemoveTraceReply { destination, .. } | drtioaux::Packet::DmaRemoveTraceReply { destination, .. } |
drtioaux::Packet::DmaPlaybackRequest { destination, .. } | drtioaux::Packet::DmaPlaybackRequest { destination, .. } |
drtioaux::Packet::DmaPlaybackReply { destination, .. } | drtioaux::Packet::DmaPlaybackReply { destination, .. } |
drtioaux::Packet::SubkernelLoadRunRequest { destination, .. } | drtioaux::Packet::SubkernelLoadRunRequest { destination, .. } |
drtioaux::Packet::SubkernelLoadRunReply { destination, .. } | drtioaux::Packet::SubkernelLoadRunReply { destination, .. } |
drtioaux::Packet::SubkernelMessage { destination, .. } | drtioaux::Packet::SubkernelMessage { destination, .. } |
drtioaux::Packet::SubkernelMessageAck { destination, .. } | drtioaux::Packet::SubkernelMessageAck { destination, .. } |
drtioaux::Packet::DmaPlaybackStatus { destination, .. } | drtioaux::Packet::SubkernelExceptionRequest { destination, .. } |
drtioaux::Packet::SubkernelFinished { destination, .. } => { drtioaux::Packet::SubkernelException { destination, .. } |
drtioaux::Packet::DmaPlaybackStatus { destination, .. } |
drtioaux::Packet::SubkernelFinished { destination, .. } => {
if *destination == 0 { if *destination == 0 {
false false
} else { } else {
@ -612,9 +614,9 @@ pub mod drtio {
let mut remote_data: Vec<u8> = Vec::new(); let mut remote_data: Vec<u8> = Vec::new();
loop { loop {
let reply = aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, let reply = aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&drtioaux::Packet::SubkernelExceptionRequest { destination: destination })?; &drtioaux::Packet::SubkernelExceptionRequest { source: 0, destination: destination })?;
match reply { match reply {
drtioaux::Packet::SubkernelException { last, length, data } => { drtioaux::Packet::SubkernelException { destination: 0, last, length, data } => {
remote_data.extend(&data[0..length as usize]); remote_data.extend(&data[0..length as usize]);
if last { if last {
return Ok(remote_data); return Ok(remote_data);

View File

@ -126,19 +126,6 @@ macro_rules! unexpected {
($($arg:tt)*) => (return Err(Error::Unexpected(format!($($arg)*)))); ($($arg:tt)*) => (return Err(Error::Unexpected(format!($($arg)*))));
} }
#[cfg(has_drtio)]
macro_rules! propagate_subkernel_exception {
( $exception:ident, $stream:ident ) => {
error!("Exception in subkernel");
match $stream {
None => return Ok(true),
Some(ref mut $stream) => {
$stream.write_all($exception)?;
}
}
}
}
// Persistent state // Persistent state
#[derive(Debug)] #[derive(Debug)]
struct Congress { struct Congress {
@ -686,23 +673,26 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex,
&kern::SubkernelAwaitFinishRequest{ id, timeout } => { &kern::SubkernelAwaitFinishRequest{ id, timeout } => {
let res = subkernel::await_finish(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, let res = subkernel::await_finish(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table,
id, timeout); id, timeout);
let status = match res { let response = match res {
Ok(ref res) => { Ok(ref res) => {
if res.comm_lost { if res.comm_lost {
kern::SubkernelStatus::CommLost kern::SubkernelError(kern::SubkernelStatus::CommLost)
} else if let Some(exception) = &res.exception { } else if let Some(raw_exception) = &res.exception {
propagate_subkernel_exception!(exception, stream); let exception = subkernel::read_exception(raw_exception);
// will not be called after exception is served if let Ok(exception) = exception {
kern::SubkernelStatus::OtherError kern::SubkernelError(kern::SubkernelStatus::Exception(exception))
} else {
kern::SubkernelError(kern::SubkernelStatus::OtherError)
}
} else { } else {
kern::SubkernelStatus::NoError kern::SubkernelAwaitFinishReply
} }
}, },
Err(SubkernelError::Timeout) => kern::SubkernelStatus::Timeout, Err(SubkernelError::Timeout) => kern::SubkernelError(kern::SubkernelStatus::Timeout),
Err(SubkernelError::IncorrectState) => kern::SubkernelStatus::IncorrectState, Err(SubkernelError::IncorrectState) => kern::SubkernelError(kern::SubkernelStatus::IncorrectState),
Err(_) => kern::SubkernelStatus::OtherError Err(_) => kern::SubkernelError(kern::SubkernelStatus::OtherError)
}; };
kern_send(io, &kern::SubkernelAwaitFinishReply { status: status }) kern_send(io, &response)
} }
#[cfg(has_drtio)] #[cfg(has_drtio)]
&kern::SubkernelMsgSend { id, destination, count, tag, data } => { &kern::SubkernelMsgSend { id, destination, count, tag, data } => {
@ -712,72 +702,80 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex,
#[cfg(has_drtio)] #[cfg(has_drtio)]
&kern::SubkernelMsgRecvRequest { id, timeout, tags } => { &kern::SubkernelMsgRecvRequest { id, timeout, tags } => {
let message_received = subkernel::message_await(io, subkernel_mutex, id as u32, timeout); let message_received = subkernel::message_await(io, subkernel_mutex, id as u32, timeout);
let (status, count) = match message_received { if let Err(SubkernelError::SubkernelFinished) = message_received {
Ok(ref message) => (kern::SubkernelStatus::NoError, message.count), let res = subkernel::retrieve_finish_status(io, aux_mutex, ddma_mutex, subkernel_mutex,
Err(SubkernelError::Timeout) => (kern::SubkernelStatus::Timeout, 0), routing_table, id as u32)?;
Err(SubkernelError::IncorrectState) => (kern::SubkernelStatus::IncorrectState, 0), if res.comm_lost {
Err(SubkernelError::SubkernelFinished) => { kern_send(io,
let res = subkernel::retrieve_finish_status(io, aux_mutex, ddma_mutex, subkernel_mutex, &kern::SubkernelError(kern::SubkernelStatus::CommLost))?;
routing_table, id as u32)?; } else if let Some(raw_exception) = &res.exception {
if res.comm_lost { let exception = subkernel::read_exception(raw_exception);
(kern::SubkernelStatus::CommLost, 0) if let Ok(exception) = exception {
} else if let Some(exception) = &res.exception { kern_send(io,
propagate_subkernel_exception!(exception, stream); &kern::SubkernelError(kern::SubkernelStatus::Exception(exception)))?;
(kern::SubkernelStatus::OtherError, 0)
} else { } else {
(kern::SubkernelStatus::OtherError, 0) kern_send(io,
&kern::SubkernelError(kern::SubkernelStatus::OtherError))?;
} }
} else {
kern_send(io,
&kern::SubkernelError(kern::SubkernelStatus::OtherError))?;
} }
Err(_) => (kern::SubkernelStatus::OtherError, 0) } else {
}; let message = match message_received {
kern_send(io, &kern::SubkernelMsgRecvReply { status: status, count: count})?; Ok(ref message) => kern::SubkernelMsgRecvReply { count: message.count },
if let Ok(message) = message_received { Err(SubkernelError::Timeout) => kern::SubkernelError(kern::SubkernelStatus::Timeout),
// receive code almost identical to RPC recv, except we are not reading from a stream Err(SubkernelError::IncorrectState) => kern::SubkernelError(kern::SubkernelStatus::IncorrectState),
let mut reader = Cursor::new(message.data); Err(SubkernelError::SubkernelFinished) => unreachable!(), // taken care of above
let mut current_tags = tags; Err(_) => kern::SubkernelError(kern::SubkernelStatus::OtherError)
let mut i = 0; };
loop { kern_send(io, &message)?;
// kernel has to consume all arguments in the whole message if let Ok(message) = message_received {
let slot = kern_recv(io, |reply| { // receive code almost identical to RPC recv, except we are not reading from a stream
match reply { let mut reader = Cursor::new(message.data);
&kern::RpcRecvRequest(slot) => Ok(slot), let mut current_tags = tags;
other => unexpected!( let mut i = 0;
"expected root value slot from kernel CPU, not {:?}", other) loop {
} // kernel has to consume all arguments in the whole message
})?; let slot = kern_recv(io, |reply| {
let res = rpc::recv_return(&mut reader, current_tags, slot, &|size| -> Result<_, Error<SchedError>> {
if size == 0 {
return Ok(0 as *mut ())
}
kern_send(io, &kern::RpcRecvReply(Ok(size)))?;
Ok(kern_recv(io, |reply| {
match reply { match reply {
&kern::RpcRecvRequest(slot) => Ok(slot), &kern::RpcRecvRequest(slot) => Ok(slot),
other => unexpected!( other => unexpected!(
"expected nested value slot from kernel CPU, not {:?}", other) "expected root value slot from kernel CPU, not {:?}", other)
} }
})?) })?;
}); let res = rpc::recv_return(&mut reader, current_tags, slot, &|size| -> Result<_, Error<SchedError>> {
match res { if size == 0 {
Ok(new_tags) => { return Ok(0 as *mut ())
kern_send(io, &kern::RpcRecvReply(Ok(0)))?;
i += 1;
if i < message.count {
// update the tag for next read
current_tags = new_tags;
} else {
// should be done by then
break;
} }
}, kern_send(io, &kern::RpcRecvReply(Ok(size)))?;
Err(_) => unexpected!("expected valid subkernel message data") Ok(kern_recv(io, |reply| {
}; match reply {
&kern::RpcRecvRequest(slot) => Ok(slot),
other => unexpected!(
"expected nested value slot from kernel CPU, not {:?}", other)
}
})?)
});
match res {
Ok(new_tags) => {
kern_send(io, &kern::RpcRecvReply(Ok(0)))?;
i += 1;
if i < message.count {
// update the tag for next read
current_tags = new_tags;
} else {
// should be done by then
break;
}
},
Err(_) => unexpected!("expected valid subkernel message data")
};
}
} }
Ok(())
} else {
// if timed out, no data has been received, exception should be raised by kernel // if timed out, no data has been received, exception should be raised by kernel
Ok(())
} }
Ok(())
}, },
request => unexpected!("unexpected request {:?} from kernel CPU", request) request => unexpected!("unexpected request {:?} from kernel CPU", request)
@ -916,7 +914,7 @@ pub fn thread(io: Io, aux_mutex: &Mutex,
Ok(()) => Ok(()) =>
info!("startup kernel finished"), info!("startup kernel finished"),
Err(Error::KernelNotFound) => Err(Error::KernelNotFound) =>
info!("no startup kernel found"), debug!("no startup kernel found"),
Err(err) => { Err(err) => {
congress.finished_cleanly.set(false); congress.finished_cleanly.set(false);
error!("startup kernel aborted: {}", err); error!("startup kernel aborted: {}", err);
@ -1011,7 +1009,7 @@ pub fn thread(io: Io, aux_mutex: &Mutex,
drtio::clear_buffers(&io, &aux_mutex); drtio::clear_buffers(&io, &aux_mutex);
} }
Err(Error::KernelNotFound) => { Err(Error::KernelNotFound) => {
info!("no idle kernel found"); debug!("no idle kernel found");
while io.relinquish().is_ok() {} while io.relinquish().is_ok() {}
} }
Err(err) => { Err(err) => {

View File

@ -1,6 +1,6 @@
use core::mem; use core::mem;
use alloc::{string::String, format, vec::Vec, collections::btree_map::BTreeMap}; use alloc::{string::String, format, vec::Vec, collections::btree_map::BTreeMap};
use cslice::AsCSlice; use cslice::{CSlice, AsCSlice};
use board_artiq::{drtioaux, drtio_routing::RoutingTable, mailbox, spi}; use board_artiq::{drtioaux, drtio_routing::RoutingTable, mailbox, spi};
use board_misoc::{csr, clock, i2c}; use board_misoc::{csr, clock, i2c};
@ -10,14 +10,13 @@ use proto_artiq::{
session_proto::Reply::KernelException as HostKernelException, session_proto::Reply::KernelException as HostKernelException,
rpc_proto as rpc}; rpc_proto as rpc};
use eh::eh_artiq; use eh::eh_artiq;
use io::Cursor; use io::{Cursor, ProtoRead};
use kernel::eh_artiq::StackPointerBacktrace; use kernel::eh_artiq::StackPointerBacktrace;
use ::{cricon_select, RtioMaster}; use ::{cricon_select, RtioMaster};
use cache::Cache; use cache::Cache;
use dma::{Manager as DmaManager, Error as DmaError}; use dma::{Manager as DmaManager, Error as DmaError};
use routing::{Router, Sliceable, SliceMeta}; use routing::{Router, Sliceable, SliceMeta};
use SAT_PAYLOAD_MAX_SIZE;
use MASTER_PAYLOAD_MAX_SIZE; use MASTER_PAYLOAD_MAX_SIZE;
mod kernel_cpu { mod kernel_cpu {
@ -69,6 +68,7 @@ enum KernelState {
SubkernelAwaitFinish { max_time: i64, id: u32 }, SubkernelAwaitFinish { max_time: i64, id: u32 },
DmaUploading { max_time: u64 }, DmaUploading { max_time: u64 },
DmaAwait { max_time: u64 }, DmaAwait { max_time: u64 },
SubkernelRetrievingException { destination: u8 },
} }
#[derive(Debug)] #[derive(Debug)]
@ -134,10 +134,13 @@ struct MessageManager {
struct Session { struct Session {
kernel_state: KernelState, kernel_state: KernelState,
log_buffer: String, log_buffer: String,
last_exception: Option<Sliceable>, last_exception: Option<Sliceable>, // exceptions raised locally
source: u8, // which destination requested running the kernel external_exception: Vec<u8>, // exceptions from sub-subkernels
// which destination requested running the kernel
source: u8,
messages: MessageManager, messages: MessageManager,
subkernels_finished: Vec<u32> // ids of subkernels finished // ids of subkernels finished (with exception)
subkernels_finished: Vec<(u32, Option<u8>)>,
} }
#[derive(Debug)] #[derive(Debug)]
@ -277,6 +280,7 @@ impl Session {
kernel_state: KernelState::Absent, kernel_state: KernelState::Absent,
log_buffer: String::new(), log_buffer: String::new(),
last_exception: None, last_exception: None,
external_exception: Vec::new(),
source: 0, source: 0,
messages: MessageManager::new(), messages: MessageManager::new(),
subkernels_finished: Vec::new() subkernels_finished: Vec::new()
@ -428,9 +432,9 @@ impl Manager {
} }
} }
pub fn exception_get_slice(&mut self, data_slice: &mut [u8; SAT_PAYLOAD_MAX_SIZE]) -> SliceMeta { pub fn exception_get_slice(&mut self, data_slice: &mut [u8; MASTER_PAYLOAD_MAX_SIZE]) -> SliceMeta {
match self.session.last_exception.as_mut() { match self.session.last_exception.as_mut() {
Some(exception) => exception.get_slice_sat(data_slice), Some(exception) => exception.get_slice_master(data_slice),
None => SliceMeta { destination: 0, len: 0, status: PayloadStatus::FirstAndLast } None => SliceMeta { destination: 0, len: 0, status: PayloadStatus::FirstAndLast }
} }
} }
@ -517,7 +521,7 @@ impl Manager {
return; return;
} }
match self.process_external_messages() { match self.process_external_messages(router, routing_table, rank, destination) {
Ok(()) => (), Ok(()) => (),
Err(Error::AwaitingMessage) => return, // kernel still waiting, do not process kernel messages Err(Error::AwaitingMessage) => return, // kernel still waiting, do not process kernel messages
Err(Error::KernelException(exception)) => { Err(Error::KernelException(exception)) => {
@ -549,20 +553,42 @@ impl Manager {
} }
} }
fn process_external_messages(&mut self) -> Result<(), Error> { fn check_finished_kernels(&mut self, id: u32, router: &mut Router, routing_table: &RoutingTable, rank: u8, self_destination: u8) {
for (i, (status, exception_source)) in self.session.subkernels_finished.iter().enumerate() {
if *status == id {
if exception_source.is_none() {
kern_send(&kern::SubkernelAwaitFinishReply).unwrap();
self.session.kernel_state = KernelState::Running;
self.session.subkernels_finished.swap_remove(i);
} else {
let destination = exception_source.unwrap();
self.session.external_exception = Vec::new();
self.session.kernel_state = KernelState::SubkernelRetrievingException { destination: destination };
router.route(drtioaux::Packet::SubkernelExceptionRequest {
source: self_destination, destination: destination
}, &routing_table, rank, self_destination);
}
break;
}
}
}
fn process_external_messages(&mut self, router: &mut Router, routing_table: &RoutingTable, rank: u8, self_destination: u8) -> Result<(), Error> {
match &self.session.kernel_state { match &self.session.kernel_state {
KernelState::MsgAwait { id, max_time, tags } => { KernelState::MsgAwait { id, max_time, tags } => {
if *max_time > 0 && clock::get_ms() > *max_time as u64 { if *max_time > 0 && clock::get_ms() > *max_time as u64 {
kern_send(&kern::SubkernelMsgRecvReply { status: kern::SubkernelStatus::Timeout, count: 0 })?; kern_send(&kern::SubkernelError(kern::SubkernelStatus::Timeout))?;
self.session.kernel_state = KernelState::Running; self.session.kernel_state = KernelState::Running;
return Ok(()) return Ok(())
} }
if let Some(message) = self.session.messages.get_incoming(*id) { if let Some(message) = self.session.messages.get_incoming(*id) {
kern_send(&kern::SubkernelMsgRecvReply { status: kern::SubkernelStatus::NoError, count: message.count })?; kern_send(&kern::SubkernelMsgRecvReply { count: message.count })?;
let tags = tags.clone(); let tags = tags.clone();
self.session.kernel_state = KernelState::Running; self.session.kernel_state = KernelState::Running;
pass_message_to_kernel(&message, &tags) pass_message_to_kernel(&message, &tags)
} else { } else {
let id = *id;
self.check_finished_kernels(id, router, routing_table, rank, self_destination);
Err(Error::AwaitingMessage) Err(Error::AwaitingMessage)
} }
}, },
@ -576,19 +602,11 @@ impl Manager {
}, },
KernelState::SubkernelAwaitFinish { max_time, id } => { KernelState::SubkernelAwaitFinish { max_time, id } => {
if *max_time > 0 && clock::get_ms() > *max_time as u64 { if *max_time > 0 && clock::get_ms() > *max_time as u64 {
kern_send(&kern::SubkernelAwaitFinishReply { status: kern::SubkernelStatus::Timeout })?; kern_send(&kern::SubkernelError(kern::SubkernelStatus::Timeout))?;
self.session.kernel_state = KernelState::Running; self.session.kernel_state = KernelState::Running;
} else { } else {
let mut i = 0; let id = *id;
for status in &self.session.subkernels_finished { self.check_finished_kernels(id, router, routing_table, rank, self_destination);
if *status == *id {
kern_send(&kern::SubkernelAwaitFinishReply { status: kern::SubkernelStatus::NoError })?;
self.session.kernel_state = KernelState::Running;
self.session.subkernels_finished.swap_remove(i);
break;
}
i += 1;
}
} }
Ok(()) Ok(())
} }
@ -606,6 +624,9 @@ impl Manager {
} }
Ok(()) Ok(())
} }
KernelState::SubkernelRetrievingException { destination: _ } => {
Err(Error::AwaitingMessage)
}
_ => Ok(()) _ => Ok(())
} }
} }
@ -628,16 +649,30 @@ impl Manager {
} }
pub fn remote_subkernel_finished(&mut self, id: u32, with_exception: bool, exception_source: u8) { pub fn remote_subkernel_finished(&mut self, id: u32, with_exception: bool, exception_source: u8) {
if with_exception { let exception_src = if with_exception { Some(exception_source) } else { None };
unsafe { kernel_cpu::stop() } self.session.subkernels_finished.push((id, exception_src));
self.session.kernel_state = KernelState::Absent; }
unsafe { self.cache.unborrow() }
self.last_finished = Some(SubkernelFinished { pub fn received_exception(&mut self, exception_data: &[u8], last: bool, router: &mut Router, routing_table: &RoutingTable,
source: self.session.source, id: self.current_id, rank: u8, self_destination: u8) {
with_exception: true, exception_source: exception_source if let KernelState::SubkernelRetrievingException { destination } = self.session.kernel_state {
}) self.session.external_exception.extend_from_slice(exception_data);
if last {
if let Ok(exception) = read_exception(&self.session.external_exception) {
kern_send(&kern::SubkernelError(kern::SubkernelStatus::Exception(exception))).unwrap();
} else {
kern_send(
&kern::SubkernelError(kern::SubkernelStatus::OtherError)).unwrap();
}
self.session.kernel_state = KernelState::Running;
} else {
/* fetch another slice */
router.route(drtioaux::Packet::SubkernelExceptionRequest {
source: self_destination, destination: destination
}, routing_table, rank, self_destination);
}
} else { } else {
self.session.subkernels_finished.push(id); warn!("Received unsolicited exception data");
} }
} }
@ -655,6 +690,7 @@ impl Manager {
(_, KernelState::DmaAwait { .. }) | (_, KernelState::DmaAwait { .. }) |
(_, KernelState::MsgSending) | (_, KernelState::MsgSending) |
(_, KernelState::SubkernelAwaitLoad) | (_, KernelState::SubkernelAwaitLoad) |
(_, KernelState::SubkernelRetrievingException { .. }) |
(_, KernelState::SubkernelAwaitFinish { .. }) => { (_, KernelState::SubkernelAwaitFinish { .. }) => {
// We're standing by; ignore the message. // We're standing by; ignore the message.
return Ok(None) return Ok(None)
@ -822,6 +858,48 @@ impl Drop for Manager {
} }
} }
fn read_exception_string<'a>(reader: &mut Cursor<&[u8]>) -> Result<CSlice<'a, u8>, Error> {
let len = reader.read_u32()? as usize;
if len == usize::MAX {
let data = reader.read_u32()?;
Ok(unsafe { CSlice::new(data as *const u8, len) })
} else {
let pos = reader.position();
let slice = unsafe {
let ptr = reader.get_ref().as_ptr().offset(pos as isize);
CSlice::new(ptr, len)
};
reader.set_position(pos + len);
Ok(slice)
}
}
fn read_exception(buffer: &[u8]) -> Result<eh_artiq::Exception, Error>
{
let mut reader = Cursor::new(buffer);
let mut byte = reader.read_u8()?;
// to sync
while byte != 0x5a {
byte = reader.read_u8()?;
}
// skip sync bytes, 0x09 indicates exception
while byte != 0x09 {
byte = reader.read_u8()?;
}
let _len = reader.read_u32()?;
// ignore the remaining exceptions, stack traces etc. - unwinding from another device would be unwise anyway
Ok(eh_artiq::Exception {
id: reader.read_u32()?,
message: read_exception_string(&mut reader)?,
param: [reader.read_u64()? as i64, reader.read_u64()? as i64, reader.read_u64()? as i64],
file: read_exception_string(&mut reader)?,
line: reader.read_u32()?,
column: reader.read_u32()?,
function: read_exception_string(&mut reader)?
})
}
fn kern_recv<R, F>(f: F) -> Result<R, Error> fn kern_recv<R, F>(f: F) -> Result<R, Error>
where F: FnOnce(&kern::Message) -> Result<R, Error> { where F: FnOnce(&kern::Message) -> Result<R, Error> {
if mailbox::receive() == 0 { if mailbox::receive() == 0 {

View File

@ -455,15 +455,21 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
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 { source, destination: _destination } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _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; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_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 { router.send(drtioaux::Packet::SubkernelException {
destination: source,
last: meta.status.is_last(), last: meta.status.is_last(),
length: meta.len, length: meta.len,
data: data_slice, data: data_slice,
}) }, _routing_table, *rank, *self_destination)
}
drtioaux::Packet::SubkernelException { destination: _destination, last, length, data } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
kernelmgr.received_exception(&data[..length as usize], last, router, _routing_table, *rank, *self_destination);
Ok(())
} }
drtioaux::Packet::SubkernelMessage { source, destination: _destination, id, status, length, data } => { drtioaux::Packet::SubkernelMessage { source, destination: _destination, id, status, length, data } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet); forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);

View File

@ -4,7 +4,6 @@ use board_artiq::{drtioaux, drtio_routing};
use board_misoc::csr; use board_misoc::csr;
use core::cmp::min; use core::cmp::min;
use proto_artiq::drtioaux_proto::PayloadStatus; use proto_artiq::drtioaux_proto::PayloadStatus;
use SAT_PAYLOAD_MAX_SIZE;
use MASTER_PAYLOAD_MAX_SIZE; use MASTER_PAYLOAD_MAX_SIZE;
/* represents data that has to be sent with the aux protocol */ /* represents data that has to be sent with the aux protocol */
@ -57,7 +56,6 @@ impl Sliceable {
self.data.extend(data); self.data.extend(data);
} }
get_slice_fn!(get_slice_sat, SAT_PAYLOAD_MAX_SIZE);
get_slice_fn!(get_slice_master, MASTER_PAYLOAD_MAX_SIZE); get_slice_fn!(get_slice_master, MASTER_PAYLOAD_MAX_SIZE);
} }

View File

@ -164,8 +164,10 @@ class Client:
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 = (await self.reader.read(length)).decode("ascii") json_bytes = await self.reader.read(length)
return "OK", json_str if length != len(json_bytes):
raise ValueError(f"Received data length ({len(json_bytes)}) doesn't match expected length ({length})")
return "OK", json_bytes
def get_argparser(): def get_argparser():
@ -266,7 +268,7 @@ async def main_async():
variant = args.variant variant = args.variant
else: else:
variant = await 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 = await client.get_json(variant) result, json_bytes = 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.")
@ -275,10 +277,10 @@ async def main_async():
if not args.force and os.path.exists(args.out): if not args.force and os.path.exists(args.out):
print(f"File {args.out} already exists. You can use -f to overwrite the existing file.") print(f"File {args.out} already exists. You can use -f to overwrite the existing file.")
sys.exit(1) sys.exit(1)
with open(args.out, "w") as f: with open(args.out, "wb") as f:
f.write(json_str) f.write(json_bytes)
else: else:
print(json_str) sys.stdout.buffer.write(json_bytes)
else: else:
raise ValueError raise ValueError
finally: finally:

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)
@ -140,7 +140,12 @@ def main():
args.db_file = os.path.join(get_user_config_dir(), "artiq_browser.pyon") args.db_file = os.path.join(get_user_config_dir(), "artiq_browser.pyon")
widget_log_handler = log.init_log(args, "browser") widget_log_handler = log.init_log(args, "browser")
app = QtWidgets.QApplication(["ARTIQ Browser"]) forced_platform = []
if (QtGui.QGuiApplication.platformName() == "wayland" and
not os.getenv("QT_QPA_PLATFORM")):
# force XCB instead of Wayland due to applets not embedding
forced_platform = ["-platform", "xcb"]
app = QtWidgets.QApplication(["ARTIQ Browser"] + forced_platform)
loop = QEventLoop(app) loop = QEventLoop(app)
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
atexit.register(loop.close) atexit.register(loop.close)

View File

@ -1,10 +1,4 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
"""
Client to send commands to :mod:`artiq_master` and display results locally.
The client can perform actions such as accessing/setting datasets,
scanning devices, scheduling experiments, and looking for experiments/devices.
"""
import argparse import argparse
import logging import logging

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
@ -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())
@ -132,7 +133,12 @@ def main():
server=args.server.replace(":", "."), server=args.server.replace(":", "."),
port=args.port_notify)) port=args.port_notify))
app = QtWidgets.QApplication(["ARTIQ Dashboard"]) forced_platform = []
if (QtGui.QGuiApplication.platformName() == "wayland" and
not os.getenv("QT_QPA_PLATFORM")):
# force XCB instead of Wayland due to applets not embedding
forced_platform = ["-platform", "xcb"]
app = QtWidgets.QApplication(["ARTIQ Dashboard"] + forced_platform)
loop = QEventLoop(app) loop = QEventLoop(app)
asyncio.set_event_loop(loop) asyncio.set_event_loop(loop)
atexit.register(loop.close) atexit.register(loop.close)
@ -186,8 +192,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
@ -257,10 +263,10 @@ def main():
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

@ -11,7 +11,7 @@ def get_argparser():
description="ARTIQ session manager. " description="ARTIQ session manager. "
"Automatically runs the master, dashboard and " "Automatically runs the master, dashboard and "
"local controller manager on the current machine. " "local controller manager on the current machine. "
"The latter requires the artiq-comtools package to " "The latter requires the ``artiq-comtools`` package to "
"be installed.") "be installed.")
parser.add_argument("--version", action="version", parser.add_argument("--version", action="version",
version="ARTIQ v{}".format(artiq_version), version="ARTIQ v{}".format(artiq_version),

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):
@ -24,7 +24,7 @@ class VDragDropSplitter(QtWidgets.QSplitter):
e.accept() e.accept()
def dragMoveEvent(self, e): def dragMoveEvent(self, e):
pos = e.pos() pos = e.position()
src = e.source() src = e.source()
src_i = self.indexOf(src) src_i = self.indexOf(src)
self.setRubberBand(self.height()) self.setRubberBand(self.height())
@ -48,7 +48,7 @@ class VDragDropSplitter(QtWidgets.QSplitter):
def dropEvent(self, e): def dropEvent(self, e):
self.setRubberBand(-1) self.setRubberBand(-1)
pos = e.pos() pos = e.position()
src = e.source() src = e.source()
src_i = self.indexOf(src) src_i = self.indexOf(src)
for n in range(self.count()): for n in range(self.count()):
@ -78,10 +78,10 @@ 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.position().y()
self._direction = 0 self._direction = 0
if y < val + self._margin: if y < val + self._margin:
self._direction = -1 self._direction = -1
@ -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,9 +117,9 @@ 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.position())
if index == -1: if index == -1:
return return
drag = QtGui.QDrag(self) drag = QtGui.QDrag(self)
@ -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()
@ -136,7 +136,7 @@ class DragDropFlowLayoutWidget(QtWidgets.QWidget):
event.accept() event.accept()
def dropEvent(self, event): def dropEvent(self, event):
index = self._get_index(event.pos()) index = self._get_index(event.position())
source_layout = event.source() source_layout = event.source()
source_index = int(bytes(event.mimeData().data("index")).decode()) source_index = int(bytes(event.mimeData().data("index")).decode())
if source_layout == self: if source_layout == self:

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
@ -23,13 +23,13 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
set_resize_mode = self.header().setSectionResizeMode set_resize_mode = self.header().setSectionResizeMode
else: else:
set_resize_mode = self.header().setResizeMode set_resize_mode = self.header().setResizeMode
set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents) set_resize_mode(0, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
set_resize_mode(1, QtWidgets.QHeaderView.Stretch) set_resize_mode(1, QtWidgets.QHeaderView.ResizeMode.Stretch)
set_resize_mode(2, QtWidgets.QHeaderView.ResizeToContents) set_resize_mode(2, QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
self.header().setVisible(False) self.header().setVisible(False)
self.setSelectionMode(self.NoSelection) self.setSelectionMode(self.SelectionMode.NoSelection)
self.setHorizontalScrollMode(self.ScrollPerPixel) self.setHorizontalScrollMode(self.ScrollMode.ScrollPerPixel)
self.setVerticalScrollMode(self.ScrollPerPixel) self.setVerticalScrollMode(self.ScrollMode.ScrollPerPixel)
self.setStyleSheet("QTreeWidget {background: " + self.setStyleSheet("QTreeWidget {background: " +
self.palette().midlight().color().name() + " ;}") self.palette().midlight().color().name() + " ;}")
@ -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))
@ -109,7 +109,6 @@ class EntryTreeWidget(QtWidgets.QTreeWidget):
group = QtWidgets.QTreeWidgetItem([key]) group = QtWidgets.QTreeWidgetItem([key])
for col in range(3): for col in range(3):
group.setBackground(col, self.palette().mid()) group.setBackground(col, self.palette().mid())
group.setForeground(col, self.palette().brightText())
font = group.font(col) font = group.font(col)
font.setBold(True) font.setBold(True)
group.setFont(col, font) group.setFont(col, font)
@ -540,7 +539,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

@ -151,15 +151,8 @@
inkscape:connector-curvature="0" /></g><path inkscape:connector-curvature="0" /></g><path
d="M 28.084,368.98 0,429.872 v 1.124 h 14.16 l 4.202,-8.945 H 43.57 l 4.195,8.945 h 14.16 v -1.124 L 33.753,368.98 Z m -5.438,41.259 8.215,-19.134 8.424,19.134 z" d="M 28.084,368.98 0,429.872 v 1.124 h 14.16 l 4.202,-8.945 H 43.57 l 4.195,8.945 h 14.16 v -1.124 L 33.753,368.98 Z m -5.438,41.259 8.215,-19.134 8.424,19.134 z"
id="path493" id="path493"
style="fill:#ffffff;fill-opacity:1" style="fill:#ffffff;fill-opacity:1" /><path
inkscape:connector-curvature="0" /><g d="m 313.73062,383.49498 10.87999,-18.07999 c 1.49333,-2.18667 1.97333,-4.8 1.97333,-7.36 0,-8.79999 -6.18666,-12.79999 -14.34666,-12.79999 -8.21332,0 -14.18665,4 -14.18665,12.79999 0,7.09333 5.11999,12.53333 13.01332,11.94666 l -7.94666,13.49333 z m -1.49334,-30.07999 c 2.93334,0 4.69333,2.02667 4.69333,4.64 0,2.88 -1.59999,4.69333 -4.69333,4.69333 -2.82666,0 -4.69333,-1.97333 -4.69333,-4.69333 0,-2.61333 1.86667,-4.64 4.69333,-4.64 z"
id="text3371" id="text1"
style="font-style:normal;font-variant:normal;font-weight:600;font-stretch:expanded;font-size:45px;line-height:125%;font-family:'Novecento sans wide';-inkscape-font-specification:'Novecento sans wide, Semi-Bold Expanded';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"><g style="font-size:53.3333px;font-family:Intro;-inkscape-font-specification:Intro;fill:#ffffff"
transform="translate(-1.7346398,0.84745763)" aria-label="9" /></svg>
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:96px;line-height:25px;font-family:'Droid Sans Thai';-inkscape-font-specification:'Droid Sans Thai, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none"
id="text861"
aria-label="7"><path
style="font-size:53.3333px;line-height:13.8889px;font-family:Intro;-inkscape-font-specification:Intro;fill:#ffffff"
d="m 325.20597,358.80118 c 5.38667,-5.86667 2.18667,-16.74666 -9.43999,-16.74666 -11.62666,0 -14.87999,10.77333 -9.44,16.69332 -8.90666,6.4 -5.75999,21.91999 9.44,21.91999 15.09332,0 18.23999,-15.41332 9.43999,-21.86665 z m -9.43999,13.81332 c -6.4,0 -6.4,-9.27999 0,-9.27999 6.45333,0 6.45333,9.27999 0,9.27999 z m 0,-17.06665 c -4.64,0 -4.64,-5.97333 0,-5.97333 4.58666,0 4.58666,5.97333 0,5.97333 z"
id="text248"
aria-label="8" /></g>&#10;</g></svg>

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 14 KiB

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,6 +1,7 @@
import asyncio import asyncio
from collections import OrderedDict from collections import OrderedDict
import logging import logging
import shutil
from sipyco.asyncio_tools import TaskObject from sipyco.asyncio_tools import TaskObject
from sipyco import pyon from sipyco import pyon
@ -45,6 +46,8 @@ class StateManager(TaskObject):
logger.info("State database '%s' not found, using defaults", logger.info("State database '%s' not found, using defaults",
self.filename) self.filename)
return return
else:
shutil.copy2(self.filename, self.filename + ".backup")
# The state of one object may depend on the state of another, # The state of one object may depend on the state of another,
# e.g. the display state may create docks that are referenced in # e.g. the display state may create docks that are referenced in
# the area state. # the area state.

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

@ -206,10 +206,9 @@ class GitBackend:
a git hash a git hash
""" """
commit, _ = self.git.resolve_refish(rev) commit, _ = self.git.resolve_refish(rev)
commit_id = str(commit.id)
logger.debug('Resolved git ref "%s" into "%s"', rev, commit.hex) logger.debug('Resolved git ref "%s" into "%s"', rev, commit_id)
return commit_id
return commit.hex
def request_rev(self, rev): def request_rev(self, rev):
rev = self._get_pinned_rev(rev) rev = self._get_pinned_rev(rev)

View File

@ -0,0 +1,59 @@
import unittest
import artiq.coredevice.exceptions as exceptions
from artiq.experiment import *
from artiq.test.hardware_testbench import ExperimentCase
from artiq.compiler.embedding import EmbeddingMap
from artiq.coredevice.core import test_exception_id_sync
"""
Test sync in exceptions raised between host and kernel
Check `artiq.compiler.embedding` and `artiq::firmware::ksupport::eh_artiq`
Considers the following two cases:
1) Exception raised on kernel and passed to host
2) Exception raised in a host function called from kernel
Ensures same exception is raised on both kernel and host in either case
"""
exception_names = EmbeddingMap().str_reverse_map
class _TestExceptionSync(EnvExperiment):
def build(self):
self.setattr_device("core")
@rpc
def _raise_exception_host(self, id):
exn = exception_names[id].split('.')[-1].split(':')[-1]
exn = getattr(exceptions, exn)
raise exn
@kernel
def raise_exception_host(self, id):
self._raise_exception_host(id)
@kernel
def raise_exception_kernel(self, id):
test_exception_id_sync(id)
class ExceptionTest(ExperimentCase):
def test_raise_exceptions_kernel(self):
exp = self.create(_TestExceptionSync)
for id, name in list(exception_names.items())[::-1]:
name = name.split('.')[-1].split(':')[-1]
with self.assertRaises(getattr(exceptions, name)) as ctx:
exp.raise_exception_kernel(id)
self.assertEqual(str(ctx.exception).strip("'"), name)
def test_raise_exceptions_host(self):
exp = self.create(_TestExceptionSync)
for id, name in exception_names.items():
name = name.split('.')[-1].split(':')[-1]
with self.assertRaises(getattr(exceptions, name)) as ctx:
exp.raise_exception_host(id)

View File

@ -8,7 +8,7 @@ def catch(f):
except Exception as e: except Exception as e:
print(e) print(e)
# CHECK-L: 8(0, 0, 0) # CHECK-L: 19(0, 0, 0)
catch(lambda: 1/0) catch(lambda: 1/0)
# CHECK-L: 9(10, 1, 0) # CHECK-L: 10(10, 1, 0)
catch(lambda: [1.0][10]) catch(lambda: [1.0][10])

View File

@ -10,7 +10,7 @@ def catch(f):
except IndexError as ie: except IndexError as ie:
print(ie) print(ie)
# CHECK-L: 8(0, 0, 0) # CHECK-L: 19(0, 0, 0)
catch(lambda: 1/0) catch(lambda: 1/0)
# CHECK-L: 9(10, 1, 0) # CHECK-L: 10(10, 1, 0)
catch(lambda: [1.0][10]) catch(lambda: [1.0][10])

View File

@ -3,7 +3,7 @@
# REQUIRES: exceptions # REQUIRES: exceptions
def f(): def f():
# CHECK-L: Uncaught 8 # CHECK-L: Uncaught 19
# CHECK-L: at input.py:${LINE:+1}: # CHECK-L: at input.py:${LINE:+1}:
1/0 1/0

View File

@ -9,7 +9,7 @@ def g():
try: try:
f() f()
except Exception as e: except Exception as e:
# CHECK-L: Uncaught 8 # CHECK-L: Uncaught 19
# CHECK-L: at input.py:${LINE:+1}: # CHECK-L: at input.py:${LINE:+1}:
raise e raise e

View File

@ -2,6 +2,6 @@
# RUN: OutputCheck %s --file-to-check=%t # RUN: OutputCheck %s --file-to-check=%t
# REQUIRES: exceptions # REQUIRES: exceptions
# CHECK-L: Uncaught 8: cannot divide by zero (0, 0, 0) # CHECK-L: Uncaught 19: cannot divide by zero (0, 0, 0)
# CHECK-L: at input.py:${LINE:+1}: # CHECK-L: at input.py:${LINE:+1}:
1/0 1/0

View File

@ -44,7 +44,8 @@ class TestClient(unittest.TestCase):
self.device_db_path, *args], encoding="utf8", env=get_env(), self.device_db_path, *args], encoding="utf8", env=get_env(),
text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
while self.master.stdout.readline().strip() != "ARTIQ master is now ready.": while self.master.stdout.readline().strip() != "ARTIQ master is now ready.":
pass if self.master.poll() is not None:
raise IOError("master terminated unexpectedly")
def check_and_terminate_master(self): def check_and_terminate_master(self):
while not ("test content" in self.master.stdout.readline()): while not ("test content" in self.master.stdout.readline()):

View File

@ -3,7 +3,6 @@ import importlib.util
import importlib.machinery import importlib.machinery
import inspect import inspect
import logging import logging
import os
import pathlib import pathlib
import string import string
import sys import sys
@ -11,9 +10,9 @@ import sys
import numpy as np import numpy as np
from sipyco import pyon from sipyco import pyon
from platformdirs import user_config_dir
from artiq import __version__ as artiq_version from artiq import __version__ as artiq_version
from artiq.appdirs import user_config_dir
from artiq.language.environment import is_public_experiment from artiq.language.environment import is_public_experiment
from artiq.language import units from artiq.language import units
@ -195,6 +194,4 @@ def get_windows_drives():
def get_user_config_dir(): def get_user_config_dir():
major = artiq_version.split(".")[0] major = artiq_version.split(".")[0]
dir = user_config_dir("artiq", "m-labs", major) return user_config_dir("artiq", "m-labs", major, ensure_exists=True)
os.makedirs(dir, exist_ok=True)
return dir

View File

@ -0,0 +1,252 @@
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 (note that the edits must be made in the main ARTIQ flake, even if you are working with Zynq, see also tip below).
* 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 the corresponding older versions of the manual.
Once you have run ``nix develop`` you are in the ARTIQ development environment. All ARTIQ commands and utilities -- :mod:`~artiq.frontend.artiq_run`, :mod:`~artiq.frontend.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 source 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``, or make changes to the dependencies, 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 :mod:`~artiq.frontend.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 :mod:`~artiq.frontend.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,11 +1,11 @@
Compiler Compiler
======== ========
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. 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 :mod:`~artiq.frontend.artiq_compile`. Otherwise it is invoked automatically whenever a function with an applicable decorator is called.
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`). 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`).
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. 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 ARTIQ experiments.
.. note:: .. note::
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. 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.
@ -20,7 +20,7 @@ Functions and decorators
The ARTIQ compiler recognizes several specialized decorators, which determine the way the decorated function will be compiled and handled. The ARTIQ compiler recognizes several specialized decorators, which determine the way the decorated function will be compiled and handled.
``@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. ``@kernel`` (see :meth:`~artiq.language.core.kernel`) designates kernel functions, which will be compiled for and 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.
``@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). ``@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).
@ -132,10 +132,10 @@ ARTIQ makes various useful built-in and mathematical functions from Python, NumP
* Functions * Functions
+ * `Python built-ins <https://docs.python.org/3/library/functions.html>`_ + * `Python built-ins <https://docs.python.org/3/library/functions.html>`_
* - ``len()``, ``round()``, ``abs()``, ``min()``, ``max()`` * - ``len()``, ``round()``, ``abs()``, ``min()``, ``max()``
- ``print()`` (for specifics see warning below) - ``print()`` (with caveats; see below)
- all basic type conversions (``int()``, ``float()`` etc.) - all basic type conversions (``int()``, ``float()`` etc.)
+ * `NumPy mathematic utilities <https://numpy.org/doc/stable/reference/routines.math.html>`_ + * `NumPy mathematic utilities <https://numpy.org/doc/stable/reference/routines.math.html>`_
* - ``sqrt()``, ``cbrt``` * - ``sqrt()``, ``cbrt()``
- ``fabs()``, ``fmax()``, ``fmin()`` - ``fabs()``, ``fmax()``, ``fmin()``
- ``floor()``, ``ceil()``, ``trunc()``, ``rint()`` - ``floor()``, ``ceil()``, ``trunc()``, ``rint()``
+ * `NumPy exponents and logarithms <https://numpy.org/doc/stable/reference/routines.math.html#exponents-and-logarithms>`_ + * `NumPy exponents and logarithms <https://numpy.org/doc/stable/reference/routines.math.html#exponents-and-logarithms>`_
@ -154,10 +154,14 @@ ARTIQ makes various useful built-in and mathematical functions from Python, NumP
- ``gamma()``, ``gammaln()`` - ``gamma()``, ``gammaln()``
- ``j0()``, ``j1()``, ``y0()``, ``y1()`` - ``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. 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:: Print and logging functions
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). ^^^^^^^^^^^^^^^^^^^^^^^^^^^
ARTIQ offers two native built-in logging functions: ``rtio_log()``, which prints to the :ref:`RTIO log <rtio-analyzer>`, as retrieved by :mod:`~artiq.frontend.artiq_coreanalyzer`, and ``core_log()``, which prints directly to the core log, regardless of context or network connection status. Both exist for debugging purposes, especially in contexts where a ``print()`` RPC is not suitable, such as in idle/startup kernels or when debugging delicate RTIO slack issues which may be significantly affected by the overhead of ``print()``.
``print()`` itself is in practice an RPC to the regular host Python ``print()``, i.e. with output either in the terminal of :mod:`~artiq.frontend.artiq_run` or in the client logs when using :mod:`~artiq.frontend.artiq_dashboard` or :mod:`~artiq.frontend.artiq_compile`. This means on one hand that it should not be used in idle, startup, or subkernels, and on the other hand that it suffers of some of the timing limitations of any other RPC, especially if the RPC queue is full. Accordingly, it is important to be aware that the timing of ``print()`` outputs can't reliably be used to debug timing in kernels, and especially not the timing of other RPCs.
.. _compiler-pitfalls: .. _compiler-pitfalls:

View File

@ -29,21 +29,13 @@ builtins.__in_sphinx__ = True
# we cannot use autodoc_mock_imports (does not help with argparse) # we cannot use autodoc_mock_imports (does not help with argparse)
mock_modules = ["artiq.gui.waitingspinnerwidget", mock_modules = ["artiq.gui.waitingspinnerwidget",
"artiq.gui.flowlayout", "artiq.gui.flowlayout",
"artiq.gui.state",
"artiq.gui.log",
"artiq.gui.models", "artiq.gui.models",
"artiq.compiler.module", "artiq.compiler.module",
"artiq.compiler.embedding", "artiq.compiler.embedding",
"artiq.dashboard.waveform", "artiq.dashboard.waveform",
"artiq.dashboard.interactive_args", "artiq.coredevice.jsondesc",
"qasync", "pyqtgraph", "matplotlib", "lmdb", "qasync", "lmdb", "dateutil.parser", "prettytable", "PyQt6",
"numpy", "dateutil", "dateutil.parser", "prettytable", "PyQt5", "h5py", "llvmlite", "pythonparser", "tqdm", "jsonschema"]
"h5py", "serial", "scipy", "scipy.interpolate",
"llvmlite", "Levenshtein", "pythonparser",
"sipyco", "sipyco.pc_rpc", "sipyco.sync_struct",
"sipyco.asyncio_tools", "sipyco.logging_tools",
"sipyco.broadcast", "sipyco.packed_exceptions",
"sipyco.keepalive", "sipyco.pipe_ipc"]
for module in mock_modules: for module in mock_modules:
sys.modules[module] = Mock() sys.modules[module] = Mock()
@ -78,7 +70,8 @@ extensions = [
'sphinx.ext.napoleon', 'sphinx.ext.napoleon',
'sphinxarg.ext', 'sphinxarg.ext',
'sphinxcontrib.wavedrom', # see also below for config 'sphinxcontrib.wavedrom', # see also below for config
"sphinxcontrib.jquery", 'sphinxcontrib.jquery',
'sphinxcontrib.tikz' # see also below for config
] ]
mathjax_path = "https://m-labs.hk/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML.js" mathjax_path = "https://m-labs.hk/MathJax/MathJax.js?config=TeX-AMS-MML_HTMLorMML.js"
@ -150,15 +143,15 @@ pygments_style = 'sphinx'
nitpicky = True nitpicky = True
# (type, target) regex tuples to ignore when generating warnings in 'nitpicky' mode # (type, target) regex tuples to ignore when generating warnings in 'nitpicky' mode
# i.e. objects that are not documented in this manual and do not need to be
nitpick_ignore_regex = [ nitpick_ignore_regex = [
(r'py:.*', r'numpy..*'), (r'py:.*', r'numpy..*'),
(r'py:.*', r'sipyco..*'), (r'py:.*', r'sipyco..*'),
('py:const', r'.*'), # no constants are documented anyway ('py:const', r'.*'), # no constants are documented anyway
('py.attr', r'.*'), # nor attributes ('py.attr', r'.*'), # nor attributes
(r'py:.*', r'artiq.gateware.*'), (r'py:.*', r'artiq.gateware.*'),
('py:mod', r'artiq.frontend.*'),
('py:mod', r'artiq.test.*'), ('py:mod', r'artiq.test.*'),
('py:mod', 'artiq.experiment'), ('py:mod', r'artiq.applets.*'),
('py:class', 'dac34H84'), ('py:class', 'dac34H84'),
('py:class', 'trf372017'), ('py:class', 'trf372017'),
('py:class', r'list(.*)'), ('py:class', r'list(.*)'),
@ -334,3 +327,10 @@ texinfo_documents = [
offline_skin_js_path = '_static/default.js' offline_skin_js_path = '_static/default.js'
offline_wavedrom_js_path = '_static/WaveDrom.js' offline_wavedrom_js_path = '_static/WaveDrom.js'
render_using_wavedrompy = True render_using_wavedrompy = True
# -- Options for sphinxcontrib-tikz ---------------------------------------
# tikz_proc_suite = pdf2svg
# tikz_transparent = True
# these are the defaults
tikz_tikzlibraries = 'positioning, shapes, arrows.meta'

View File

@ -24,7 +24,7 @@ If ping fails, check that the Ethernet LED is ON; on Kasli, it is the LED next t
Core management tool 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: :: The tool used to configure the core device is the command-line utility :mod:`~artiq.frontend.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 $ artiq_coremgmt -D <IP_address> log
@ -33,7 +33,7 @@ The tool used to configure the core device is the command-line utility ``artiq_c
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. 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. 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 :mod:`~artiq.frontend.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: :: Once you can reach your core device, the IP can be changed at any time by running: ::
@ -63,34 +63,36 @@ For Kasli or KC705:
$ 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_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 $ 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. 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 :mod:`~artiq.frontend.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. 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.
If problems persist, see the :ref:`network troubleshooting <faq-networking>` section of the FAQ.
.. _core-device-config: .. _core-device-config:
Configuring the core device Configuring the core device
--------------------------- ---------------------------
.. note:: .. 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. 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 first tutorial 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 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. The *idle kernel* is the kernel (that is, a piece of code running on the core device; see :doc:`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: :: 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_compile idle.py
$ artiq_coremgmt config write -f idle_kernel idle.elf $ 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`). 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`` (see :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. To flash a startup kernel, proceed as with the idle kernel, but using the ``startup_kernel`` key in the :mod:`~artiq.frontend.artiq_coremgmt` command.
.. note:: .. 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. Subkernels (see :doc:`using_drtio_subkernels`) are allowed in idle (and startup) experiments without any additional ceremony. :mod:`~artiq.frontend.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 Select the RTIO clock source
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -104,10 +106,12 @@ The default is to use an internal 125MHz clock. To select a source, use a comman
See :ref:`core-device-clocking` for availability of specific options. See :ref:`core-device-clocking` for availability of specific options.
.. _config-rtiomap:
Set up resolving RTIO channels to their names 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: :: This feature allows you to print the channels' respective names alongside with their numbers in RTIO error messages. To enable it, run the :mod:`~artiq.frontend.artiq_rtiomap` tool and write its result into the device config at the ``device_map`` key: ::
$ artiq_rtiomap dev_map.bin $ artiq_rtiomap dev_map.bin
$ artiq_coremgmt config write -f device_map dev_map.bin $ artiq_coremgmt config write -f device_map dev_map.bin

View File

@ -1,16 +1,16 @@
Core device Core device
=========== ===========
The core device is a FPGA-based hardware component that contains a softcore or hardcore CPU tightly coupled with the so-called RTIO core, which runs in gateware and provides precision timing. The CPU executes Python code that is statically compiled by the ARTIQ compiler and communicates with peripherals (TTL, DDS, etc.) through the RTIO core, as described in :ref:`artiq-real-time-i-o-concepts`. This architecture provides high timing resolution, low latency, low jitter, high-level programming capabilities, and good integration with the rest of the Python experiment code. The core device is a FPGA-based hardware component that contains a softcore or hardcore CPU tightly coupled with the so-called RTIO core, which runs in gateware and provides precision timing. The CPU executes Python code that is statically compiled by the ARTIQ compiler and communicates with peripherals (TTL, DDS, etc.) through the RTIO core, as described in :doc:`rtio`. This architecture provides high timing resolution, low latency, low jitter, high-level programming capabilities, and good integration with the rest of the Python experiment code.
While it is possible to use the other parts of ARTIQ (controllers, master, GUI, dataset management, etc.) without a core device, many experiments require it. While it is possible to use the other parts of ARTIQ (controllers, master, GUI, dataset management, etc.) without a core device, most use cases will require it.
.. _core-device-flash-storage: .. _configuration-storage:
Flash storage Configuration 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 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: The core device reserves some storage space (either flash or directly on SD card, depending on target board) to store configuration data. The configuration data is organized as a list of key-value records, accessible either through :mod:`~artiq.frontend.artiq_mkfs` and :mod:`~artiq.frontend.artiq_flash` or, preferably in most cases, the ``config`` option of the :mod:`~artiq.frontend.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:
``idle_kernel`` ``idle_kernel``
Stores (compiled ``.tar`` or ``.elf`` binary of) idle kernel. See :ref:`core-device-config`. Stores (compiled ``.tar`` or ``.elf`` binary of) idle kernel. See :ref:`core-device-config`.
@ -37,7 +37,7 @@ The core device contains some flash storage space which is used to store configu
``routing_table`` ``routing_table``
Sets the routing table in DRTIO systems; see :ref:`drtio-routing`. If not set, a star topology is assumed. Sets the routing table in DRTIO systems; see :ref:`drtio-routing`. If not set, a star topology is assumed.
``device_map`` ``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>`. 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 :mod:`~artiq.frontend.artiq_rtiomap`, see :ref:`Utilities<rtiomap-tool>`.
``net_trace`` ``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). 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`` ``panic_reset``
@ -45,7 +45,7 @@ The core device contains some flash storage space which is used to store configu
``no_flash_boot`` ``no_flash_boot``
If set to ``1``, will disable flash boot. Network boot is attempted if possible. Not applicable for ARTIQ-Zynq. If set to ``1``, will disable flash boot. Network boot is attempted if possible. Not applicable for ARTIQ-Zynq.
``boot`` ``boot``
Allows full firmware/gateware (``boot.bin``) to be written with ``artiq_coremgmt``, on ARTIQ-Zynq systems only. Allows full firmware/gateware (``boot.bin``) to be written with :mod:`~artiq.frontend.artiq_coremgmt`, on ARTIQ-Zynq systems only.
Common configuration commands Common configuration commands
----------------------------- -----------------------------
@ -65,7 +65,7 @@ You do not need to remove a record in order to change its value. Just overwrite
You can write several records at once:: You can write several records at once::
$ artiq_coremgmt config write -s key1 value1 -f key2 filename -s key3 value3 $ 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:: 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::
@ -75,7 +75,36 @@ You can also write entire files in a record using the ``-f`` option. This is use
The same option is used to write ``boot.bin`` in ARTIQ-Zynq. Note that the ``boot`` key is write-only. 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>`. See also the full reference of :mod:`~artiq.frontend.artiq_coremgmt` in :ref:`Utilities <core-device-management-tool>`.
.. _core-device-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:
* ``int_100`` - internal crystal reference is used to synthesize a 100MHz RTIO clock,
* ``int_125`` - internal crystal reference is used to synthesize a 125MHz RTIO clock (default option),
* ``int_150`` - internal crystal reference is used to synthesize a 150MHz RTIO clock.
* ``ext0_synth0_10to125`` - external 10MHz reference clock used to synthesize a 125MHz RTIO clock,
* ``ext0_synth0_80to125`` - external 80MHz reference clock used to synthesize a 125MHz RTIO clock,
* ``ext0_synth0_100to125`` - external 100MHz reference clock used to synthesize a 125MHz RTIO clock,
* ``ext0_synth0_125to125`` - external 125MHz reference clock used to synthesize a 125MHz 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 :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 not using WRPLL, PLL can also be bypassed entirely with the options
* ``ext0_bypass`` (input clock used directly)
* ``ext0_bypass_125`` (explicit alias)
* ``ext0_bypass_100`` (explicit alias)
Bypassing the PLL ensures the skews between input clock, downstream clock outputs, and RTIO clock are deterministic across reboots of the system. This is useful when phase determinism is required in situations where the reference clock fans out to other devices before reaching the master.
Board details Board details
------------- -------------
@ -85,23 +114,23 @@ 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 is based on a Xilinx Artix-7 FPGA, and Kasli-SoC, which runs on a separate `Zynq port <https://git.m-labs.hk/M-Labs/artiq-zynq>`_ of the ARTIQ firmware, is based on a Zynq-7000 SoC, notably including an ARM CPU allowing for much heavier software computations at high speeds. They are architecturally very different but supply similar feature sets. Kasli itself exists in two versions, of which the improved Kasli v2.0 is now in more common use, but the original 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).
The DRTIO line rate depends upon the RTIO clock frequency running, e.g., at 125MHz the line rate is 2.5Gbps, at 150MHz 3.0Gbps, etc. See below for information on RTIO clocks. The DRTIO line rate depends upon the RTIO clock frequency running, e.g., at 125MHz the line rate is 2.5Gbps, at 150MHz 3.0Gbps, etc. See below for information on RTIO clocks.
KC705 KC705 and ZC706
^^^^^
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
^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^
Two high-end evaluation kits are also supported as alternative ARTIQ core device target boards, respectively the Kintex7 `KC705 <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and Zynq-SoC `ZC706 <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_, both from Xilinx. ZC706, like Kasli-SoC, runs on the ARTIQ-Zynq port. Both are supported in several set variants, namely NIST CLOCK and QC2 (FMC), either available in master, satellite, or standalone variants. See also :doc:`building_developing` for more on system variants.
Common KC705 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.
@ -111,11 +140,13 @@ 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.
Variant details
---------------
NIST CLOCK NIST CLOCK
^^^^^^^^^^ ^^^^^^^^^^
With the CLOCK hardware, the TTL lines are mapped as follows: With the KC705 CLOCK hardware, the TTL lines are mapped as follows:
+--------------------+-----------------------+--------------+ +--------------------+-----------------------+--------------+
| RTIO channel | TTL line | Capability | | RTIO channel | TTL line | Capability |
@ -155,11 +186,21 @@ The board has RTIO SPI buses mapped as follows:
The DDS bus is on channel 27. The DDS bus is on channel 27.
The ZC706 variant is identical except for the following differences:
- The SMA GPIO on channel 18 is replaced by an Input+Output capable PMOD1_0 line.
- Since there is no SDIO on the programmable logic side, channel 26 is instead occupied by an additional SPI:
+--------------+------------------+--------------+--------------+--------------+
| RTIO channel | CS_N | MOSI | MISO | CLK |
+==============+==================+==============+==============+==============+
| 26 | PMOD_SPI_CS_N | PMOD_SPI_MOSI| PMOD_SPI_MISO| PMOD_SPI_CLK |
+--------------+------------------+--------------+--------------+--------------+
NIST QC2 NIST QC2
^^^^^^^^ ^^^^^^^^
With the QC2 hardware, the TTL lines are mapped as follows: With the KC705 QC2 hardware, the TTL lines are mapped as follows:
+--------------------+-----------------------+--------------+ +--------------------+-----------------------+--------------+
| RTIO channel | TTL line | Capability | | RTIO channel | TTL line | Capability |
@ -193,38 +234,11 @@ The board has RTIO SPI buses mapped as follows:
There are two DDS buses on channels 50 (LPC, DDS0-DDS11) and 51 (HPC, DDS12-DDS23). There are two DDS buses on channels 50 (LPC, DDS0-DDS11) and 51 (HPC, DDS12-DDS23).
The QC2 hardware uses TCA6424A I2C I/O expanders to define the directions of its TTL buffers. There is one such expander per FMC card, and they are selected using the PCA9548 on the KC705. The QC2 hardware uses TCA6424A I2C I/O expanders to define the directions of its TTL buffers. There is one such expander per FMC card, and they are selected using the PCA9548 on the KC705.
To avoid I/O contention, the startup kernel should first program the TCA6424A expanders and then call ``output()`` on all ``TTLInOut`` channels that should be configured as outputs. To avoid I/O contention, the startup kernel should first program the TCA6424A expanders and then call ``output()`` on all ``TTLInOut`` channels that should be configured as outputs. See :mod:`artiq.coredevice.i2c` for more details.
See :mod:`artiq.coredevice.i2c` for more details. The ZC706 is identical except for the following differences:
.. _core-device-clocking: - The SMA GPIO is once again replaced with PMOD1_0.
- The first four TTLs also have edge counters, on channels 52, 53, 54, and 55.
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:
* ``int_100`` - internal crystal reference is used to synthesize a 100MHz RTIO clock,
* ``int_125`` - internal crystal reference is used to synthesize a 125MHz RTIO clock (default option),
* ``int_150`` - internal crystal reference is used to synthesize a 150MHz RTIO clock.
* ``ext0_synth0_10to125`` - external 10MHz reference clock used to synthesize a 125MHz RTIO clock,
* ``ext0_synth0_80to125`` - external 80MHz reference clock used to synthesize a 125MHz RTIO clock,
* ``ext0_synth0_100to125`` - external 100MHz reference clock used to synthesize a 125MHz RTIO clock,
* ``ext0_synth0_125to125`` - external 125MHz reference clock used to synthesize a 125MHz 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 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 not using WRPLL, PLL can also be bypassed entirely with the options
* ``ext0_bypass`` (input clock used directly)
* ``ext0_bypass_125`` (explicit alias)
* ``ext0_bypass_100`` (explicit alias)
Bypassing the PLL ensures the skews between input clock, downstream clock outputs, and RTIO clock are deterministic across reboots of the system. This is useful when phase determinism is required in situations where the reference clock fans out to other devices before reaching the master.

View File

@ -1,4 +1,4 @@
Core drivers reference Core real-time drivers
====================== ======================
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.

View File

@ -1,7 +1,7 @@
Core language reference Core language and environment
======================= =============================
The most commonly used features from the ARTIQ language modules and from the core device modules are bundled together in :mod:`artiq.experiment` and can be imported with ``from artiq.experiment import *``. The most commonly used features from the ARTIQ language modules and from the core device modules are bundled together in ``artiq.experiment`` and can be imported with ``from artiq.experiment import *``.
:mod:`artiq.language.core` module :mod:`artiq.language.core` module
--------------------------------- ---------------------------------

View File

@ -10,9 +10,9 @@ Default network ports
+---------------------------------+--------------+ +---------------------------------+--------------+
| Core device (analyzer) | 1382 | | Core device (analyzer) | 1382 |
+---------------------------------+--------------+ +---------------------------------+--------------+
| Moninj (core device or proxy) | 1383 | | MonInj (core device or proxy) | 1383 |
+---------------------------------+--------------+ +---------------------------------+--------------+
| Moninj (proxy control) | 1384 | | MonInj (proxy control) | 1384 |
+---------------------------------+--------------+ +---------------------------------+--------------+
| Core analyzer proxy (proxy) | 1385 | | Core analyzer proxy (proxy) | 1385 |
+---------------------------------+--------------+ +---------------------------------+--------------+

View File

@ -1,22 +0,0 @@
.. _developing-artiq:
Developing ARTIQ
^^^^^^^^^^^^^^^^
.. warning::
This section is only for software or FPGA developers who want to modify ARTIQ. If you want to modify the firmware for Kasli-SoC or other ARM boards, please refer to the `ARTIQ on Zynq repository <https://git.m-labs.hk/M-Labs/artiq-zynq>`_. 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).

View File

@ -19,7 +19,7 @@ Full support for a specific device, called a network device support package or N
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``. 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. 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, which would be unwieldy to pass as ``sipyco_rpctool`` command-line parameters, must be transferred over the network API.
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
@ -120,7 +120,7 @@ To access this driver in an experiment, we can retrieve the ``Client`` instance
Integration with ARTIQ experiments 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: :: 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 :mod:`~artiq_comtools.artiq_ctlmgr` utility from ``artiq-comtools``). To do so, add an entry to your device database in this format: ::
device_db.update({ device_db.update({
"hello": { "hello": {
@ -213,7 +213,7 @@ Command line and options
^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^
* 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. * 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. * 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 names.
* 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. * 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.
Style Style

View File

@ -30,12 +30,12 @@ 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 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. It is stored in a binary format that can be generated and manipulated with the utility :mod:`~artiq.frontend.artiq_route`, 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.
Internal details Internal details
---------------- ----------------
Bits 16-24 of the RTIO channel number (assigned to a respective device in the initial system description JSON, 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. 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
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -29,7 +29,7 @@ This is stored in a Python dictionary whose keys are the device names, which the
"arguments": {"channel": 19} "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 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 (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:: .. 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``. 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``.
@ -37,13 +37,13 @@ Note that the key (the name of the device) is ``led`` and the value is itself a
.. warning:: .. 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. 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. 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, real-time, e.g. your Sinara hardware) must be connected 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.) While controllers can be added and removed to your device database on an *ad hoc* basis, 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 be provided with new binaries and necessary assistance.) See :doc:`building_developing`.
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. 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 :ref:`system description file<system-description>`. 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>`. 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 controllers, aliases, and so on. In the relatively unlikely case that you are writing a device database from scratch, the :mod:`~artiq.frontend.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
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
@ -52,14 +52,16 @@ Local device entries are dictionaries which contain a ``type`` field set to ``lo
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. 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``. 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 :mod:`~artiq.frontend.artiq_ddb_template`.
.. _environment-ctlrs:
Controllers Controllers
^^^^^^^^^^^ ^^^^^^^^^^^
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>`. 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 ``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 (matching 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. 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.
@ -71,18 +73,20 @@ If an entry is a string, that string is used as a key for another lookup in the
Arguments Arguments
--------- ---------
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``. 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, see the reference for :mod:`artiq.language.environment`; for specific methods, see the reference for :class:`~artiq.language.environment.HasEnvironment`.
.. _environment-datasets:
Datasets Datasets
-------- --------
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>`. 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:`data interfaces tutorial <mgmt-datasets>`.
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. 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.
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 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.
By default, datasets are archived in the HDF5 output for that run, although this can be opted against (``archive=False``). By default, datasets are archived in the ``results`` HDF5 output for that run, although this can be opted against (``archive=False``). They can be viewed and analyzed with the ARTIQ browser, or with an HDF5 viewer of your choice.
Datasets and units Datasets and units
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^

View File

@ -0,0 +1,272 @@
Extending RTIO
==============
.. warning::
This page is for users who want to extend or modify ARTIQ RTIO. Broadly speaking, one of the core intentions of ARTIQ is to provide a high-level, easy-to-use interface for experimentation, while the infrastructure handles the technological challenges of the high-resolution, timing-critical operations required. Rather than worrying about the details of timing in hardware, users can outline tasks quickly and efficiently in ARTIQ Python, and trust the system to carry out those tasks in real time. It is not normally, or indeed ever, necessary to make modifications on a gateware level.
However, ARTIQ is an open-source project, and welcomes interest and agency from its users, as well as from experienced developers. This page is intended to serve firstly as a broad introduction to the internal structure of ARTIQ, and secondly as a tutorial for how RTIO extensions in ARTIQ can be made. Experience with FPGAs or hardware description languages is not strictly necessary, but additional research on the topic will likely be required to make serious modifications of your own.
For instructions on setting up the ARTIQ development environment and on building gateware and firmware binaries, first see :doc:`building_developing` in the main part of the manual.
Introduction to the ARTIQ internal stack
----------------------------------------
.. tikz::
:align: center
:libs: arrows.meta
:xscale: 70
\definecolor{primary}{HTML}{0d3547} % ARTIQ blue
\pgfdeclarelayer{bg} % declare background layer
\pgfsetlayers{bg,main} % set layer order
\node[draw, dotted, thick] (frontend) at (0, 6) {Host machine: Compiler, master, GUI...};
\node[draw=primary, fill=white] (software) at (0, 5) {Software: \it{Kernel code}};
\node[draw=blue, fill=white, thick] (firmware) at (0, 4) {Firmware: \it{ARTIQ runtime}};
\node[draw=blue, fill=white, thick] (gateware) at (0, 3) {Gateware: \it{Migen and Misoc}};
\node[draw=primary, fill=white] (hardware) at (0, 2) {Hardware: \it{Sinara ecosystem}};
\begin{pgfonlayer}{bg}
\draw[primary, -Stealth, dotted, thick] (frontend.south) to [out=180, in=180] (firmware.west);
\draw[primary, -Stealth, dotted, thick] (frontend) to (software);
\end{pgfonlayer}
\draw[primary, -Stealth] (firmware) to (software);
\draw[primary, -Stealth] (gateware) to (firmware);
\draw[primary, -Stealth] (hardware) to (gateware);
Like any other modern piece of software, kernel code running on an ARTIQ core device rests upon a layered infrastructure, starting with the hardware: the physical carrier board and its peripherals. Generally, though not exclusively, this is the `Sinara device family <https://m-labs.hk/experiment-control/sinara-core/>`_, which is designed to work with ARTIQ. Other carrier boards, such as the Xilinx KC705 and ZC706, are also supported.
All of the ARTIQ core device carrier boards necessarily center around a physical field-programmable gate array, or FPGA. If you have never worked with FPGAs before, it is easiest to understand them as 'rearrangeable' circuits. Ideally, they are capable of approaching the tremendous speed and timing precision advantages of custom-designed, application-specific hardware, while still being reprogrammable, allowing development and revision to continue after manufacturing.
The 'configuration' of an FPGA, the circuit design it is programmed with, is its *gateware*. Gateware is not software, and is not written in programming languages. Rather, it is written in a *hardware description language,* of which the most common are VHDL and Verilog. The ARTIQ codebase uses a set of tools called `Migen <https://m-labs.hk/gateware/migen/>`_ to write hardware description in a subset of Python, which is later translated to Verilog behind the scenes. This has the advantage of preserving much of the flexibility and convenience of Python as a programming language, but shouldn't be mistaken for it *being* Python, or functioning like Python. (MiSoC, built on Migen, is used to implement softcore -- i.e. 'programmed', on-FPGA, not hardwired -- CPUs on Kasli and KC705. Zynq devices contain 'hardcore' ARM CPUs already and correspondingly make relatively less intensive use of MiSoC.)
The low-level software that runs directly on the core device's CPU, softcore or hardcore, is its *firmware.* This is the 'operating system' of the core device. The firmware is tasked, among other things, with handling the low-level communication between the core device and the host machine, as well as between the core devices in a DRTIO setting. It is written in bare-metal `Rust <https://www.rust-lang.org/>`__. There are currently two active versions of the ARTIQ firmware (the version used for ARTIQ-Zynq, NAR3, is more modern than that used on Kasli and KC705, and will likely eventually replace it) but they are functionally equivalent except for internal details.
Experiment kernels themselves -- ARTIQ Python, processed by the ARTIQ compiler and loaded from the host machine -- rest on top of and are framed and supported by the firmware, in the same sense way that application software on your PC rests on top of an operating system. All together, software kernels communicate with the firmware to set parameters for the gateware, which passes signals directly to the hardware.
These frameworks are built to be self-contained and extensible. To make additions to the gateware and software, for example, we do not need to make changes to the firmware; we can interact purely with the interfaces provided on either side.
Extending gateware logic
------------------------
As briefly explained in :doc:`rtio`, when we talk about RTIO infrastructure, we are primarily speaking of structures implemented in gateware. The FIFO banks which hold scheduled output events or recorded input events, for example, are in gateware. Sequence errors, overflow exceptions, event spreading, and so on, happen in the gateware. In some cases, you may want to make relatively simple, parametric changes to existing RTIO, like changing the sizes of certain queues. In this case, it can be as simple as tracking down the part of the code where this parameter is set, changing it, and :doc:`rebuilding the binaries <building_developing>`.
.. warning::
Note that FPGA resources are finite, and buffer sizes, lane counts, etc., are generally chosen to maximize available resources already, with different values depending on the core device in use. Depending on the peripherals you include (some are more resource-intensive than others) blanket increases will likely quickly outstrip the capacity of your FPGA and fail to build. Increasing the depth of a particular channel you know to be heavily used is more likely to succeed; the easiest way to find out is to attempt the build and observe what results.
Gateware in ARTIQ is housed in ``artiq/gateware`` on the main ARTIQ repository and (for Zynq-specific additions) in ``artiq-zynq/src/gateware`` on ARTIQ-Zynq. The starting point for figuring out your changes will often be the *target file*, which is core device-specific and which you may recognize as the primary module called when building gateware. Depending on your core device, simply track down the file named after it, as in ``kasli.py``, ``kasli_soc.py``, and so on. Note that the Kasli and Kasli-SoC targets are designed to take JSON description files as input, whereas their KC705 and ZC706 equivalents work with hardcoded variants instead.
To change parameters related to particular peripherals, see also the files ``eem.py`` and ``eem_7series.py``, which describe the core device's interface with other EEM cards in Migen terms, and contain ``add_std`` methods that in turn reference specific gateware modules and assign RTIO channels.
Adding a module to gateware
^^^^^^^^^^^^^^^^^^^^^^^^^^^
To demonstrate how RTIO can be *extended,* on the other hand, we will develop a new interface entirely for the control of certain hardware -- in our case, for a simple example, the core device LEDs. If you haven't already, follow the instructions in :doc:`building_developing` to clone the ARTIQ repository and set up a development environment. The first part of our addition will be a module added to ``gateware/rtio/phy`` (PHY, for interaction with the physical layer), written in the Migen Fragmented Hardware Description Language (FHDL).
.. seealso::
To find reference material for FHDL and the Migen constructs we will use, see the Migen manual, in particular the page `The FHDL domain-specific language <https://m-labs.hk/migen/manual/fhdl.html>`_.
.. warning::
If you have never worked with a hardware description language before, it is important to understand that hardware description is fundamentally different to programming in a language like Python or Rust. At its most basic, a program is a set of instructions: a step-by-step guide to a task you want to see performed, where each step is written, and executed, principally in sequence. In contrast, hardware description is *a description*. It specifies the static state of a piece of hardware. There are no 'steps', and no chronological execution, only stated facts about how the system should be built.
The examples we will handle in this tutorial are simple, and you will likely find Migen much more readable than traditional languages like VHDL and Verilog, but keep in mind that we are describing how a system connects and interlocks its signals, *not* operations it should perform.
Normally, the PHY module used for LEDs is the ``Output`` of ``ttl_simple.py``. Take a look at its source code. Note that values like ``override`` and ``probes`` exist to support RTIO MonInj -- ``probes`` for monitoring, ``override`` for injection -- and are not involved with normal control of the output. Note also that ``pad``, among FPGA engineers, refers to an input/output pad, i.e. a physical connection through which signals are sent. ``pad_n`` is its negative pair, necessary only for certain kinds of TTLs and not applicable to LEDs.
Interface and signals
"""""""""""""""""""""
To get started, create a new file in ``gateware/rtio/phy``. Call it ``linked_leds.py``. In it, create a class ``Output``, which will inherit from Migen's ``Module``, and give it an ``init`` method, which takes two pads as input: ::
from migen import *
class Output(Module):
def __init__(self, pad0, pad1):
``pad0`` and ``pad1`` will represent output pads, in our case ultimately connecting to the board's user LEDs. On the other side, to receive output events from a RTIO FIFO queue, we will use an ``Interface`` provided by the ``rtlink`` module, also found in ``artiq/gateware``. Both output and input interfaces are available, and both can be combined into one link, but we are only handling output events. We use the ``data_width`` parameter to request an interface that is 2 bits wide: ::
from migen import *
from artiq.gateware.rtio import rtlink
class Output(Module):
def __init__(self, pad0, pad1):
self.rtlink = rtlink.Interface(rtlink.OInterface(2))
In our example, rather than controlling both LEDs manually using ``on`` and ``off``, which is the functionality ``ttl_simple.py`` provides, we will control one LED manually and have the gateware determine the value of the other based on the first. This same logic would be easy (in fact, much easier) to implement in ARTIQ Python; the advantage of placing it in gateware is that logic in gateware is *extremely fast,* in effect 'instant', i.e., completed within a single clock cycle. Rather than waiting for a CPU to process and respond to instructions, a response can happen at the speed of a dedicated logic circuit.
.. note::
Naturally, the truth is more complicated, and depends heavily on how complex the logic in question is. An overlong chain of gateware logic will fail to settle within a single RTIO clock cycle, causing a wide array of potential problems that are difficult to diagnose and difficult to fix; the only solutions are to simplify the logic, deliberately split it across multiple clock cycles (correspondingly increasing latency for the operation), or to decrease the speed of the clock (increasing latency for *everything* the device does).
For now, it's enough to say that you are unlikely to encounter timing failures with the kind of simple logic demonstrated in this tutorial. Indeed, designing gateware logic to run in as few cycles as possible without 'failing timing' is an engineering discipline in itself, and much of what FPGA developers spend their time on.
In practice, of course, since ARTIQ explicitly allows scheduling simultaneous output events to different channels, there's still no reason to make gateware modifications to accomplish this. After all, leveraging the real-time capabilities of customized gateware without making it necessary to *write* it is much of the point of ARTIQ as a system. Only in more complex cases, such as directly binding inputs to outputs without feeding back through the CPU, might gateware-level additions become necessary.
For now, add two intermediate signals for our logic, instances of the Migen ``Signal`` construct: ::
def __init__(self, pad0, pad1):
self.rtlink = rtlink.Interface(rtlink.OInterface(2))
reg = Signal()
pad0_o = Signal()
.. note::
A gateware 'signal' is not a signal in the sense of being a piece of transmitted information. Rather, it represents a channel, which bits of information can be held in. To conceptualize a Migen ``Signal``, take it as a kind of register: a box that holds a certain number of bits, and can update those bits from an input, or broadcast them to an output connection. The number of bits is arbitrary, e.g., a ``Signal(2)`` will be two bits wide, but in our example we handle only single-bit registers.
These are our inputs, outputs, and intermediate signals. By convention, in Migen, these definitions are all made at the beginning of a module, and separated from the logic that interconnects them with a line containing the three symbols ``###``. See also ``ttl_simple.py`` and other modules.
Since hardware description is not linear or chronological, nothing conceptually prevents us from making these statements in any other order -- in fact, except for the practicalities of code execution, nothing particularly prevents us from defining the connections between the signals before we define the signals themselves -- but for readable and maintainable code, this format is vastly preferable.
Combinatorial and synchronous statements
""""""""""""""""""""""""""""""""""""""""
After the ``###`` separator, we will set the connecting logic. A Migen ``Module`` has several special attributes, to which different logical statements can be assigned. We will be using ``self.sync``, for synchronous statements, and ``self.comb``, for combinatorial statements. If a statement is *synchronous*, it is only updated once per clock cycle, i.e. when the clock ticks. If a statement is *combinatorial*, it is updated whenever one of its inputs change, i.e. 'instantly'.
Add a synchronous block as follows: ::
self.sync.rio_phy += [
If(self.rtlink.o.stb,
pad0_o.eq(self.rtlink.o.data[0] ^ pad0_o),
reg.eq(self.rtlink.o.data[1])
)
]
In other words, at every tick of the ``rtio_phy`` clock, if the ``rtlink`` strobe signal (which is set to high when the data is valid, i.e., when an output event has just reached the PHY) is high, the ``pad0_o`` and ``reg`` registers are updated according to the input data on ``rtlink``.
.. note::
Notice that, in a standard synchronous block, it makes no difference how or how many times the inputs to an ``.eq()`` statement change or fluctuate. The output is updated *exactly once* per cycle, at the tick, according to the instantaneous state of the inputs in that moment. In between ticks and during the clock cycle, it remains stable at the last updated level, no matter the state of the inputs. This stability is vital for the broader functioning of synchronous circuits, even though 'waiting for the tick' adds latency to the update.
``reg`` is simply set equal to the incoming bit. ``pad0_o``, on the other hand, flips its old value if the input is ``1``, and keeps it if the input is ``0``. Note that ``^``, which you may know as the Python notation for a bitwise XOR operation, here simply represents a XOR gate. In summary, we can flip the value of ``pad0`` with the first bit of the interface, and set the value of ``reg`` with the other.
Add the combinatorial block as follows: ::
self.comb += [
pad0.eq(pad0_o),
If(reg,
pad1.eq(pad0_k)
)
]
The output ``pad0`` is continuously connected to the value of the ``pad0_o`` register. The output of ``pad1`` is set equal to that of ``pad0``, but only if the ``reg`` register is high, or ``1``.
The module is now capable of accepting RTIO output events and applying them to the hardware outputs. What we can't yet do is generate these output events in an ARTIQ kernel. To do that, we need to add a core device driver.
Adding a core device driver
^^^^^^^^^^^^^^^^^^^^^^^^^^^
If you have been writing ARTIQ experiments for any length of time, you will already be familiar with the core device drivers. Their reference is kept in this manual on the page :doc:`core_drivers_reference`; their methods are commonly used to manipulate the core device and its close peripherals. Source code for these drivers is kept in the directory ``artiq/coredevice``. Create a new file, again called ``linked_led.py``, in this directory.
The drivers are software, not gateware, and they are written in regular ARTIQ Python. They use methods given in ``coredevice/rtio.py`` to queue input and output events to RTIO channels. We will start with its ``__init__``, the method ``get_rtio_channels`` (which is formulaic, and exists only to be used by :meth:`~artiq.frontend.artiq_rtiomap`), and a output set method ``set_o``: ::
from artiq.language.core import *
from artiq.language.types import *
from artiq.coredevice.rtio import rtio_output
class LinkedLED:
def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device)
self.channel = channel
self.target_o = channel << 8
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel
def set_o(self, o):
rtio_output(self.target_o, o)
.. note::
``rtio_output()`` is one of four methods given in ``coredevice/rtio.py``, which provides an interface with lower layers of the system. You can think of it ultimately as representing the other side of the ``Interface`` we requested in our Migen module. Notably, in between the two, events pass through the SED and its FIFO lanes, where they are held until the exact real-time moment the events were scheduled for, as originally described in :doc:`rtio`.
Now we can write the kernel API. In the gateware, bit 0 flips the value of the first pad: ::
@kernel
def flip_led(self):
self.set_o(0b01)
and bit 1 connects the second pad to the first: ::
@kernel
def link_up(self):
self.set_o(0b10)
There's no reason we can't do both at the same time: ::
@kernel
def flip_together(self):
self.set_o(0b11)
Target and device database
^^^^^^^^^^^^^^^^^^^^^^^^^^
Our ``linked_led`` PHY module exists, but in order for it to be generated as part of a set of ARTIQ binaries, we need to add it to one of the target files. Find the target file for your core device, as described above. Each target file is structured differently; track down the part of the file where channels and PHY modules are assigned to the user LEDs. Depending on your core device, there may be two or more LEDs that are available. Look for lines similar to: ::
for i in (0, 1):
user_led = self.platform.request("user_led", i)
phy = ttl_simple.Output(user_led)
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
Edit the code so that, rather than assigning a separate PHY and channel to each LED, two of the LEDs are grouped together in ``linked_led``. You might use something like: ::
print("Linked LEDs at:", len(rtio_channels))
phy = linked_led.Output(self.platform.request("user_led", 0), self.platform.request("user_led", 1))
self.submodules += phy
self.rtio_channels.append(rtio.Channel.from_phy(phy))
Save the target file, under a different name if you prefer. Follow the instructions in :doc:`building_developing` to build a set of binaries, being sure to use your edited target file for the gateware, and flash your core device, for simplicity preferably in a standalone configuration without peripherals.
Now, before you can access your new core device driver from a kernel, it must be added to your device database. Find your ``device_db.py``. Delete the entries dedicated to the user LEDs that you have repurposed; if you tried to control those LEDs using the standard TTL interfaces now, the corresponding gateware would be missing anyway. Add an entry with your new driver, as in: ::
device_db["leds"] = {
"type": "local",
"module": "artiq.coredevice.linked_led",
"class": "LinkedLED",
"arguments": {"channel": 0x000008}
}
.. warning::
Channel numbers are assigned sequentially each time ``rtio_channels.append()`` is called. Since we assigned the channel for our linked LEDs in the same location as the old user LEDs, the correct channel number is likely simply the one previously used in your device database for the first LED. In any other case, however, the ``print()`` statement we added to the target file should tell us the exact canonical channel. Search through the console logs produced when generating the gateware to find the line starting with ``Linked LEDs at:``.
Depending on how your device database was written, note that the channel numbers for other peripherals, if they are present, *will have changed*, and :meth:`~artiq.frontend.artiq_ddb_template` will not generate their numbers correctly unless it is edited to match the new assignments of the user LEDs. For a longer-term gateware change, especially the addition of a new EEM card, ``artiq/frontend/artiq_ddb_template.py`` and ``artiq/coredevice/coredevice_generic.schema`` should be edited accordingly, so that system descriptions and device databases can continue to be parsed and generated correctly.
Test experiments
^^^^^^^^^^^^^^^^
Now the device ``leds`` can be called from your device database, and its corresponding driver accessed, just as with any other device. Try writing some miniature experiments, for instance ``flip.py``: ::
from artiq.experiment import *
class flip(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("leds")
@kernel
def run(self):
self.core.reset()
self.leds.flip_led()
and ``linkup.py``: ::
from artiq.experiment import *
class sync(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("leds")
@kernel
def run(self):
self.core.reset()
self.leds.link_up()
Run these and observe the results. Congratulations! You have successfully constructed an extension to the ARTIQ RTIO.
.. + 'Adding custom EEMs' and 'Merging support'

View File

@ -1,32 +1,179 @@
.. Copyright (C) 2014, 2015 Robert Jordens <jordens@gmail.com> FAQ (How do I...)
=================
FAQ use this documentation?
=== -----------------------
How do I ... The content of this manual is arranged in rough reading order. If you start at the beginning and make your way through section by section, you should form a pretty good idea of how ARTIQ works and how to use it. Otherwise:
------------
**If you are just starting out,** and would like to get ARTIQ set up on your computer and your core device, start with :doc:`installing`, :doc:`flashing`, and :doc:`configuring`, in that order.
**If you have a working ARTIQ setup** (or someone else has set it up for you), start with the tutorials: read :doc:`rtio`, then progress to :doc:`getting_started_core`, :doc:`getting_started_mgmt`, and :doc:`using_data_interfaces`. If your system is in a DRTIO configuration, :doc:`DRTIO and subkernels <using_drtio_subkernels>` will also be helpful.
Pages like :doc:`management_system` and :doc:`core_device` describe **specific components of the ARTIQ ecosystem** in more detail. If you want to understand more about device and dataset databases, for example, read the :doc:`environment` page; if you want to understand the ARTIQ Python dialect and everything it does or does not support, read the :doc:`compiler` page.
Reference pages, like :doc:`main_frontend_tools` and :doc:`core_drivers_reference`, contain the detailed documentation of the individual methods and command-line tools ARTIQ provides. They are heavily interlinked throughout the rest of the documentation: whenever a method, tool, or exception is mentioned by name, like :class:`~artiq.frontend.artiq_run`, :meth:`~artiq.language.core.now_mu`, or :exc:`~artiq.coredevice.exceptions.RTIOUnderflow`, it can normally be clicked on to directly access the reference material. Notice also that the online version of this manual is searchable; see the 'Search docs' bar at left.
.. _build-documentation:
build this documentation?
-------------------------
To generate this manual from source, you can use ``nix build`` directives, for example: ::
$ nix build git+https://github.com/m-labs/artiq.git\?ref=release-[number]#artiq-manual-html
Substitute ``artiq-manual-pdf`` to get the LaTeX PDF version. The results will be in ``result``.
The manual is written in `reStructured Text <https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html>`_; you can find the source files in the ARTIQ repository under ``doc/manual``. If you spot a mistake, a typo, or something that's out of date or missing -- in particular, if you want to add something to this FAQ -- feel free to clone the repository, edit the source RST files, and make a pull request with your version of an improvement. (If you're not a fan of or not familiar with command-line Git, both GitHub and Gitea support making edits and pull requests directly in the web interface; tutorial materials are easy to find online.) The second best thing is to open an issue to make M-Labs aware of the problem.
.. _faq-networking:
troubleshoot networking problems?
---------------------------------
Diagnosis aids:
- Can you ``ping`` the device?
- Is the Ethernet LED on?
- Is the ERROR LED on?
- Is there anything unusual recorded in :ref:`the UART log <connecting-UART>`?
Some things to consider:
- Is the ``core_addr`` field of your ``device_db.py`` set correctly?
- Did your device flash and boot successfully? Were the binaries generated for the correct board hardware version?
- Are your core device's IP address and networking configurations definitely set correctly? Check the UART log to confirm, and talk to your network administrator about what the correct choices are.
- Is your core device configured for an external reference clock? If so, it cannot function correctly without one. Is the external reference clock plugged in?
- Are Ethernet and (on Kasli only) SFP0 plugged in all the way? Are they working? Try different cables and SFP adapters; M-Labs tests with CAT6 cables, but lower categories should be supported too.
- Are your PC and your crate in the same subnet?
- Is some other device in your network already using the configured IP address? Turn off the core device and try pinging the configured IP address; if it responds, you have a culprit. One of the two will need a different networking configuration.
- Are there restrictions or issues in your router or subnet that are preventing the core device from connecting? It may help to try connecting the core device to your PC directly.
fix 'Mismatch between gateware and software versions'?
------------------------------------------------------
Either reflash your core device with a newer version of ARTIQ (see :doc:`flashing`) or update your software (see :ref:`installing-upgrading`), depending on which is out of date.
.. note::
You can check the specific versions you are using at any time by comparing the gateware version given in the core startup log and the output given by adding ``--version`` to any of the standard ARTIQ front-end commands. This is especially useful when e.g. seeking help in the forum or at the helpdesk, where your running ARTIQ version is often crucial information to diagnose a problem.
Minor version mismatches are common, even in stable ARTIQ versions, but should not cause any issues. The ARTIQ release system ensures breaking changes are strictly limited to new release versions, or to the beta branch (which explicitly makes no promises of stability.) Updates that *are* applied to the stable version are usually bug fixes, documentation improvements, or other quality-of-life changes. As long as gateware and software are using the same stable release version of ARTIQ, even if there is a minor mismatch, no warning will be displayed.
change configuration settings of satellite devices?
---------------------------------------------------
Currently, it is not possible to reach satellites through ``artiq_coremgmt config``, although this is being worked on. On Kasli, use :class:`~artiq.frontend.artiq_mkfs` and :class:`~artiq.frontend.artiq_flash`; on Kasli-SoC, preload the SD card with a ``config.txt``, formatted as a list of ``key=value`` pairs, one per line.
Don't worry about individually flashing idle or startup kernels. If your idle or startup kernel contains subkernels, it will automatically compile as a ``.tar``, which you only need to flash to the master.
fix unreliable DRTIO master-satellite links?
--------------------------------------------
Inconsistent DRTIO connections, especially with odd or absent errors in the core logs, are often a symptom of overheating either in the master or satellite boards. Check the core device fans for failure or defects. Improve air circulation around the crate or attach additional fans to see if that improves or resolves the issue. In the long term, fan trays to be rack-mounted together with the crate are a clean solution to these kinds of problems.
add or remove EEM peripherals or DRTIO satellites?
--------------------------------------------------
Adding new real-time hardware to an ARTIQ system almost always means reflashing the core device; if you are adding new satellite core devices, they will have to be flashed as well. If you have obtained your upgrades from M-Labs or QUARTIQ, updated binaries and reflashing support will normally be offered to you directly. In any other case, track down your JSON system description file(s), bring them up to date with the updated state of your system, and see :doc:`building_developing`.
Once you have an updated set of binaries, reflash the core device, following the instructions in :doc:`flashing`. Be sure to update your device database before starting experimentation; run :mod:`~artiq.frontend.artiq_ddb_template` on your system description(s) to update the local devices, and copy over any aliases or entries for NDSP controllers you may have been using. Note that the device database is a Python file, and the generated file of local devices can also simply be imported into the final version, allowing for dynamic modifications, especially in complex systems that may have multiple device databases in use.
see command-line help?
----------------------
Like most if not almost all terminal utilities, ARTIQ commands, tools and applets print their help messages directly into the terminal and exit when run with the flag ``--help`` or ``-h``: ::
$ artiq_run -h
This is the simplest and most direct way of accessing the same usage and reference material that is replicated in this manual on the pages :doc:`main_frontend_tools` and :doc:`utilities`.
.. _faq-find-examples:
find ARTIQ examples? find ARTIQ examples?
-------------------- --------------------
The examples are installed in the ``examples`` folder of the ARTIQ package. You can find where the ARTIQ package is installed on your machine with: :: The official examples are stored in the ``examples`` folder of the ARTIQ package. You can find the location of the ARTIQ package on your machine with: ::
python3 -c "import artiq; print(artiq.__path__[0])" python3 -c "import artiq; print(artiq.__path__[0])"
Copy the ``examples`` folder from that path into your home/user directory, and start experimenting! Copy the ``examples`` folder from that path into your home or user directory, and start experimenting! (Note that some examples have dependencies not included with a standard ARTIQ install, like matplotlib and numba. To run those examples properly, make sure those modules are accessible.)
prevent my first RTIO command from causing an underflow? If you have progressed past this level and would like to see more in-depth code or real-life examples of how other groups have handled running experiments with ARTIQ, see the "Community code" directory on the M-labs `resources page <https://m-labs.hk/experiment-control/resources/>`_.
--------------------------------------------------------
The first RTIO event is programmed with a small timestamp above the value of the timecounter when the core device is reset. If the kernel needs more time than this timestamp to produce the event, an underflow will occur. You can prevent it by calling ``break_realtime`` just before programming the first event, or by adding a sufficient delay. fix ``failed to connect to moninj`` in the dashboard?
-----------------------------------------------------
If you are not resetting the core device, the time cursor stays where the previous experiment left it. This and other similar messages almost always indicate that your device database lists controllers (for example, ``aqctl_moninj_proxy``) that either haven't been started or aren't reachable at the given host and port. See :ref:`mgmt-ctlmgr`, or simply run: ::
$ artiq_ctlgmr
to let the controller manager start the necessary controllers automatically.
diagnose and fix sequence errors?
---------------------------------
Go through your code, keeping manual track of SED lanes. See the following example: ::
@kernel
def run(self):
self.core.reset()
with parallel:
self.ttl0.on() # lane0
self.ttl_sma.pulse(800*us) # lane1(rising) lane1(falling)
with sequential:
self.ttl1.on() # lane2
self.ttl2.on() # lane3
self.ttl3.on() # lane4
self.ttl4.on() # lane5
delay(800*us)
self.ttl1.off() # lane5
self.ttl2.off() # lane6
self.ttl3.off() # lane7
self.ttl4.off() # lane0
self.ttl0.off() # lane1 -> clashes with the falling edge of ttl_sma,
# which is already at +800us
In most cases, as in this one, it's relatively easy to rearrange the generation of events so that they will be better spread out across SED lanes without sacrificing actual functionality. One possible solution for the above sequence looks like: ::
@kernel
def run(self):
self.core.reset()
self.ttl0.on() # lane0
self.ttl_sma.on() # lane1
self.ttl1.on() # lane2
self.ttl2.on() # lane3
self.ttl3.on() # lane4
self.ttl4.on() # lane5
delay(800*us)
self.ttl1.off() # lane5
self.ttl2.off() # lane6
self.ttl3.off() # lane7
self.ttl4.off() # lane0 (no clash: new timestamp is higher than last)
self.ttl_sma.off() # lane1
self.ttl0.off() # lane2
In this case, the :meth:`~artiq.coredevice.ttl.TTLInOut.pulse` is split up into its component :meth:`~artiq.coredevice.ttl.TTLInOut.on` and :meth:`~artiq.coredevice.ttl.TTLInOut.off` so that events can be generated more linearly. It can also be worth keeping in mind that delaying by even a single coarse RTIO cycle between events avoids switching SED lanes at all; in contexts where perfect simultaneity is not a priority, this is an easy way to avoid sequencing issues. See again :ref:`sequence-errors`.
understand applet commands?
---------------------------
The 'Command' field contains the exact terminal command used to open and operate the applet. The default ``${artiq_applet}`` prefix simply translates to something to the effect of ``python -m artiq.applets.``, intended to be immediately followed by the applet module name. The options suffixed after the module name are the same used in the command line, and a list of them can be shown by using the standard command line ``-h`` help flag: ::
$ python -m artiq.applets.plot_xy -h
in any terminal.
organize datasets in folders? organize datasets in folders?
----------------------------- -----------------------------
Use the dot (".") in dataset names to separate folders. The GUI will automatically create and delete folders in the dataset tree display. Use the dot (".") in dataset names to separate folders. The GUI will automatically create and delete folders in the dataset tree display.
organize applets in groups?
---------------------------
Create groups by left-clicking within the applet list and selecting 'New Group'. Move applets in and out of groups by dragging them with the mouse. To unselect an applet or a group, use CTRL+click.
organize experiment windows in the dashboard? organize experiment windows in the dashboard?
--------------------------------------------- ---------------------------------------------
@ -37,74 +184,38 @@ Experiment windows can be organized by using the following hotkeys:
The windows will be organized in the order they were last interacted with. The windows will be organized in the order they were last interacted with.
write a generator feeding a kernel feeding an analyze function? fix errors when restarting the management system after a crash?
--------------------------------------------------------------- ---------------------------------------------------------------
Like this:: On Windows in particular, abnormal shutdowns such as power outages or bluescreens can sometimes corrupt the organizational files used by the management system, resulting in errors to the tune of ``ValueError: source code string cannot contain null bytes`` when restarting. The easiest way to handle these problems is to delete the corrupted files and start from scratch.
def run(self): If the master itself fails to start, it may be necessary to delete the dataset database or even ``last_rid.pyon`` to restart properly, but if the dashboard or browser fail, the problem is probably in the GUI configuration files, where the state of the GUI (arrangement of docks, applets, etc.) is kept. These files are backed up once whenever they are successfully loaded. Navigate to the user configuration directory (see :ref:`gui-config-files`) and look for a file suffixed ``.backup``. To restore the GUI, simply delete the corrupted configuration file and rename the backup to replace it.
self.parse(self.pipe(iter(range(10))))
def pipe(self, gen): create and use variable-length arrays in kernels?
for i in gen: -------------------------------------------------
r = self.do(i)
yield r
def parse(self, gen): You can't, in general; see the corresponding notes under :ref:`compiler-types`. ARTIQ kernels do not support heap allocation, meaning in particular that lists, arrays, and strings must be of constant size. One option is to preallocate everything, as mentioned on the Compiler page; another option is to chunk it and e.g. read 100 events per function call, push them upstream and retry until the gate time closes.
for i in gen:
pass
@kernel
def do(self, i):
return i
create and use variable lengths arrays in kernels?
--------------------------------------------------
Don't. Preallocate everything. Or chunk it and e.g. read 100 events per
function call, push them upstream and retry until the gate time closes.
execute multiple slow controller RPCs in parallel without losing time?
----------------------------------------------------------------------
Use ``threading.Thread``: portable, fast, simple for one-shot calls.
write part of my experiment as a coroutine/asyncio task/generator? write part of my experiment as a coroutine/asyncio task/generator?
------------------------------------------------------------------ ------------------------------------------------------------------
You can not change the API that your experiment exposes: ``build()``, You cannot change the API that your experiment exposes: :meth:`~artiq.language.environment.HasEnvironment.build`, :meth:`~artiq.language.environment.Experiment.prepare`, :meth:`~artiq.language.environment.Experiment.run` and :meth:`~artiq.language.environment.Experiment.analyze` need to be regular functions, not generators or asyncio coroutines. That would make reusing your own code in sub-experiments difficult and fragile. You can however wrap your own generators/coroutines/tasks in regular functions that you then expose as part of the API.
``prepare()``, ``run()`` and ``analyze()`` need to be regular functions, not
generators or asyncio coroutines. That would make reusing your own code in
sub-experiments difficult and fragile. You can however wrap your own
generators/coroutines/tasks in regular functions that you then expose as part
of the API.
determine the pyserial URL to attach to a device by its serial number? determine the pyserial URL to connect to a device by its serial number?
---------------------------------------------------------------------- -----------------------------------------------------------------------
You can list your system's serial devices and print their vendor/product You can list your system's serial devices and print their vendor/product id and serial number by running::
id and serial number by running::
$ python3 -m serial.tools.list_ports -v $ python3 -m serial.tools.list_ports -v
It will give you the ``/dev/ttyUSBxx`` (or the ``COMxx`` for Windows) device This will give you the ``/dev/ttyUSBxx`` (or ``COMxx`` for Windows) device names. The ``hwid:`` field gives you the string you can pass via the ``hwgrep://`` feature of pyserial `serial_for_url() <https://pythonhosted.org/pyserial/pyserial_api.html#serial.serial_for_url>`_ in order to open a serial device.
names.
The ``hwid:`` field gives you the string you can pass via the ``hwgrep://``
feature of pyserial
`serial_for_url() <https://pythonhosted.org/pyserial/pyserial_api.html#serial.serial_for_url>`_
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://`` URL: it allows for selecting the serial device by its USB vendor ID, product
URL: it allows to select the serial device by its USB vendor ID, product ID and/or serial number. These never change, unlike the device file name.
ID and/or serial number. Those never change, unlike the device file name.
For instance, if you want to specify the Vendor/Product ID and the USB Serial Number, you can do: For instance, if you want to specify the Vendor/Product ID and the USB Serial Number, you can do: ::
``-d "hwgrep://<VID>:<PID> SNR=<serial_number>"``.
for example:
``-d "hwgrep://0403:faf0 SNR=83852734"``
$ -d "hwgrep://<VID>:<PID> SNR=<serial_number>"``.
run unit tests? run unit tests?
--------------- ---------------
@ -124,9 +235,44 @@ The core device tests require the following TTL devices and connections:
If TTL devices are missing, the corresponding tests are skipped. If TTL devices are missing, the corresponding tests are skipped.
find the dashboard and browser configuration files are stored? .. _gui-config-files:
--------------------------------------------------------------
find the dashboard and browser configuration files?
---------------------------------------------------
:: ::
python -c "from artiq.tools import get_user_config_dir; print(get_user_config_dir())" python -c "from artiq.tools import get_user_config_dir; print(get_user_config_dir())"
Additional Resources
====================
Other related documentation
---------------------------
- the `Sinara wiki <https://github.com/sinara-hw/meta/wiki>`_
- the `SiPyCo manual <https://m-labs.hk/artiq/sipyco-manual/>`_
- the `Migen manual <https://m-labs.hk/migen/manual/>`_
- in a pinch, the `M-labs internal docs <https://git.m-labs.hk/sinara-hw/assembly>`_
For more advanced questions, sometimes the `list of publications <https://m-labs.hk/experiment-control/publications/>`_ about experiments performed using ARTIQ may be interesting. See also the official M-Labs `resources <https://m-labs.hk/experiment-control/resources/>`_ page, especially the section on community code.
"Help, I've done my best and I can't get any further!"
------------------------------------------------------
- If you have an active M-Labs AFWS/support subscription, you can email helpdesk@ at any time for personalized assistance. Please include the following information:
- Your installed ARTIQ version (add ``--version`` to any of the standard ARTIQ commands)
- The variant name of your system (refer to the sticker on the crate if you aren't sure)
- The recent output of your core log, either through ``artiq_coremgmt`` (if you're able to contact your device by network), or over UART following :ref:`the guide here <connecting-UART>`
- How your problem happened, and what you've already tried to fix it
- Compare your materials with the examples; see also :ref:`finding ARTIQ examples <faq-find-examples>` above.
- Check the list of `active issues <https://github.com/m-labs/artiq/issues>`_ on the ARTIQ GitHub repository for possible known problems with ARTIQ. Search through the closed issues to see if your question or concern has been addressed before.
- Search the `M-Labs forum <https://forum.m-labs.hk/>`_ for similar problems, or make a post asking for help yourself.
- Look into the `Mattermost live chat <https://chat.m-labs.hk>`_ or the bridged IRC channel.
- Read the open source code and its docstrings and figure it out.
- If you're reasonably certain you've identified a bug, or if you'd like to suggest a feature that should be included in future ARTIQ releases, `file a GitHub issue <https://github.com/m-labs/artiq/issues/new/choose>`_ yourself, following one of the provided templates.
- In some odd cases, you may want to see the `mailing list archive <https://www.mail-archive.com/artiq@lists.m-labs.hk/>`_; the ARTIQ mailing list was shut down at the end of 2020 and was last regularly used during the time of ARTIQ-2 and 3, but for some older ARTIQ features, or to understand a development thought process, you may still find relevant information there.
In any situation, if you found the manual unclear or unhelpful, you might consider following the :ref:`directions for contribution <build-documentation>` and editing it to be more helpful for future readers.

View File

@ -9,13 +9,13 @@
Obtaining board 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. 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 :mod:`~artiq.frontend.afws_client` is included in all ARTIQ installations.
Run the command:: Run the command::
$ afws_client <username> build <afws_director> <variant> $ 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. 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 :mod:`~artiq.frontend.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). 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).
@ -25,9 +25,9 @@ Installing and configuring OpenOCD
---------------------------------- ----------------------------------
.. warning:: .. 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`. These instructions are not applicable to Zynq devices (Kasli-SoC or ZC706), which do not use the utility :mod:`~artiq.frontend.artiq_flash`. 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. ARTIQ supplies the utility :mod:`~artiq.frontend.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:: .. note::
@ -78,12 +78,12 @@ On Windows
Writing the 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. 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 :mod:`~artiq.frontend.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: For Kasli-SoC or ZC706:
:: ::
$ artiq_coremgmt [-D 192.168.1.75] config write -f boot [afws_directory]/boot.bin $ artiq_coremgmt [-D IP_address] config write -f boot <afws_directory>/boot.bin
$ artiq_coremgmt reboot $ 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. If the device is not reachable due to corrupted firmware or networking problems, extract the SD card and copy ``boot.bin`` onto it manually.
@ -91,16 +91,16 @@ For Kasli-SoC or ZC706:
For Kasli: For Kasli:
:: ::
$ artiq_flash -d [afws_directory] $ artiq_flash -d <afws_directory>
For KC705: For KC705:
:: ::
$ artiq_flash -t kc705 -d [afws_directory] $ artiq_flash -t kc705 -d <afws_directory>
The SW13 switches need to be set to 00001. 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. 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 :mod:`~artiq.frontend.artiq_flash` in the :ref:`Utilities <flashing-loading-tool>` reference.
.. _connecting-uart: .. _connecting-uart:

View File

@ -1,10 +1,5 @@
Getting started with the core language Getting started with the core device
====================================== ====================================
.. _connecting-to-the-core-device:
Connecting to the core device
-----------------------------
As a very first step, we will turn on a LED on the core device. Create a file ``led.py`` containing the following: :: As a very first step, we will turn on a LED on the core device. Create a file ``led.py`` containing the following: ::
@ -14,12 +9,12 @@ As a very first step, we will turn on a LED on the core device. Create a file ``
class LED(EnvExperiment): class LED(EnvExperiment):
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("led") self.setattr_device("led0")
@kernel @kernel
def run(self): def run(self):
self.core.reset() self.core.reset()
self.led.on() self.led0.on()
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). 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).
@ -32,7 +27,7 @@ If you don't have a ``device_db.py`` for your system, consult :ref:`device-db` t
python3 -c "import artiq; print(artiq.__path__[0])" python3 -c "import artiq; print(artiq.__path__[0])"
Run your code using ``artiq_run``, which is one of the ARTIQ front-end tools: :: Run your code using :mod:`~artiq.frontend.artiq_run`, which is one of the ARTIQ front-end tools: ::
$ artiq_run led.py $ artiq_run led.py
@ -51,7 +46,7 @@ Modify ``led.py`` as follows: ::
class LED(EnvExperiment): class LED(EnvExperiment):
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("led") self.setattr_device("led0")
@kernel @kernel
def run(self): def run(self):
@ -59,9 +54,9 @@ Modify ``led.py`` as follows: ::
s = input_led_state() s = input_led_state()
self.core.break_realtime() self.core.break_realtime()
if s: if s:
self.led.on() self.led0.on()
else: else:
self.led.off() self.led0.off()
You can then turn the LED off and on by entering 0 or 1 at the prompt that appears: :: You can then turn the LED off and on by entering 0 or 1 at the prompt that appears: ::
@ -75,7 +70,7 @@ What happens is that the ARTIQ compiler notices that the ``input_led_state`` fun
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`. 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 :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. Without the :meth:`~artiq.coredevice.core.Core.break_realtime` call, the RTIO events emitted by :meth:`self.led0.on() <artiq.coredevice.ttl.TTLInOut.on>` or :meth:`self.led0.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.led0.on() <artiq.coredevice.ttl.TTLInOut.on>` or :meth:`self.led0.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 :doc:`rtio` 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,19 +95,11 @@ 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.HasEnvironment.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. Bidirectional TTL channels (i.e. :class:`~artiq.coredevice.ttl.TTLInOut`) are in input (high impedance) mode by default, output-only TTL channels (:class:`~artiq.coredevice.ttl.TTLOut`) are always in output mode. There are no input-only TTL channels.
Bidirectional TTL channels (i.e. :class:`~artiq.coredevice.ttl.TTLInOut`) are in input (high impedance) mode by default, output-only TTL channels (:class:`~artiq.coredevice.ttl.TTLOut`) are always in output mode.
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 rtio.py``. 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. 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. Any asymmetry in the overhead would manifest itself in a distorted and variable duty cycle.
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%.
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.
Any asymmetry in the overhead would manifest itself in a distorted and variable duty cycle.
Instead, inside the core device, output timing is generated by the gateware and the CPU only programs switching commands with certain timestamps that the CPU computes. Instead, inside the core device, output timing is generated by the gateware and the CPU only programs switching commands with certain timestamps that the CPU computes.
@ -166,11 +153,7 @@ Try the following code and observe the generated pulses on a 2-channel oscillosc
self.ttl1.pulse(4*us) self.ttl1.pulse(4*us)
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 its position on the timeline (``now_mu``) when entering the ``parallel`` block and resets to that position after each individual statement. At the end of the block, the cursor is advanced to the furthest position it reached during the block. 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.
It simply remembers its position on the timeline (``now_mu``) when entering the ``parallel`` block and resets to that position after each individual statement.
At the end of the block, the cursor is advanced to the furthest position it reached during the block.
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. 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.
@ -186,32 +169,34 @@ Within a parallel block, some statements can be scheduled sequentially again usi
delay(4*us) delay(4*us)
.. warning:: .. warning::
``with parallel`` specifically 'parallelizes' the *top-level* statements inside a block. Consider as an example: :: ``with parallel`` specifically 'parallelizes' the *top-level* statements inside a block. Consider as an example:
.. code-block::
:linenos:
for i in range(1000000): for i in range(1000000):
with parallel: with parallel:
self.ttl0.pulse(2*us) # 1 self.ttl0.pulse(2*us)
if True: # 2 if True:
self.ttl1.pulse(2*us) # 3 self.ttl1.pulse(2*us)
self.ttl2.pulse(2*us) # 4 self.ttl2.pulse(2*us)
delay(4*us) delay(4*us)
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. 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 line #4; it will not repeat the reset at the deeper indentation level for #5 or #6.
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. 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, lines #5 and #6, contained within the top-level if statement, are considered an atomic sequence and executed within an implicit ``with sequential``. To schedule #5 and #6 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`. 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:
RTIO analyzer RTIO analyzer
------------- -------------
The core device records the real-time I/O waveforms into a circular buffer. It is possible to dump any Python object so that it appears alongside the waveforms using the ``rtio_log`` function, which accepts a channel name (i.e. a log target) as the first argument: :: The core device records all real-time I/O waveforms, as well as the variation of RTIO slack, into a circular buffer, the contents of which can be extracted using :mod:`~artiq.frontend.artiq_coreanalyzer`. Try for example: ::
from artiq.experiment import * from artiq.experiment import *
class Tutorial(EnvExperiment): class Tutorial(EnvExperiment):
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
@ -220,19 +205,37 @@ The core device records the real-time I/O waveforms into a circular buffer. It i
@kernel @kernel
def run(self): def run(self):
self.core.reset() self.core.reset()
for i in range(100): for i in range(5):
self.ttl0.pulse(...) self.ttl0.pulse(0.1 * ms)
rtio_log("ttl0", "i", i) delay(0.1 * ms)
delay(...)
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`. When using :mod:`~artiq.frontend.artiq_run`, the recorded buffer data can be extracted directly into the terminal, using a command in the form of: ::
$ artiq_coreanalyzer -p
.. note::
The first time this command is run, it will retrieve the entire contents of the analyzer buffer, which may include every experiment you have run so far. For a more manageable introduction, run the analyzer once to clear the buffer, run the experiment, and then run the analyzer a second time, so that only the data from this single experiment is displayed.
This will produce a list of the exact output events submitted to RTIO, printed in chronological order, along with the state of both ``now_mu`` and ``rtio_counter_mu``. While useful in diagnosing some specific gateware errors (in particular, :ref:`sequencing issues <sequence-errors>`), it isn't the most readable of formats. An alternate is to export to VCD, which can be viewed using third-party tools such as GTKWave. Run the experiment again, and use a command in the form of: ::
$ artiq_coreanalyzer -w <file_name>.vcd
The ``<file_name>.vcd`` file should be immediately created and written. Check the directory the command was run in to find it.
.. tip::
Tutorials on GTKWave options (or other third-party tools) and how best to view VCD files can be found online. By default, the data in a trace like ``rtio_slack`` will probably be presented in a raw form. To see a stepped wave as in the ARTIQ dashboard, look for options to interpret the data as a real number, then as an analog signal.
Pay attention to the timescale of the waveform dock in your chosen viewer; if you have set your signals to display but nothing is visible, it is likely zoomed in or out much too far.
The easiest way to view recorded analyzer data, however, is directly in the ARTIQ dashboard, a feature which will be presented later in :ref:`interactivity-waveform`.
.. _getting-started-dma: .. _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 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. 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' to ``now_mu``. 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: ::

View File

@ -1,27 +1,31 @@
Getting started with the management system Using the management system
========================================== ===========================
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. In practice, rather than managing experiments by executing :mod:`~artiq.frontend.artiq_run` over and over, most use cases are better served by using the ARTIQ *management system*. This is the high-level application part of ARTIQ, which can be used to schedule experiments, manage devices and parameters, and distribute and store results. It also allows for distributed use of ARTIQ, with a single master coordinating demands on the system issued over the network by multiple clients. Using this system, multiple users on different machines can schedule experiments or analyze results on the same ARTIQ system, potentially simultaneously, without interfering with each other.
The management system consists of at least two parts: 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, 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. 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, as well as, optionally,
c. one or more **controller managers**, which help coordinate the operation of certain (generally, non-realtime) classes of device. c. one or more **controller managers**, which coordinate the operation of certain (generally, non-realtime) classes of device and provide certain services to the clients,
d. and one or more instances of the **ARTIQ browser**, a GUI application designed to facilitate the analysis of experiment results and datasets.
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. 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 specialized hardware set up to use it. The examples in this tutorial can all be carried out using only your host computer.
Starting your first experiment with the master Running your first experiment with the master
---------------------------------------------- ---------------------------------------------
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. Until now, we have executed experiments using :mod:`~artiq.frontend.artiq_run`, which is a simple standalone tool that bypasses the management system. We will now see how to run an experiment using a master and a client. In this arrangement, the master is responsible for communicating with the core device, scheduling and keeping track of experiments, and carrying out RPCs the core device may call for. Clients submit experiments to the master to be scheduled and, if necessary, query the master about the state of experiments and their results.
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``. First, create a folder called ``~/artiq-master``. Copy into it the ``device_db.py`` for your system (your device database, exactly as in :doc:`getting_started_core`); the master uses the device database in the same way as :mod:`~artiq.frontend.artiq_run` when communicating with the core device.
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. .. tip::
Since no devices are actually used in these examples, you can also use a device database in the model of the ``device_db.py`` from ``examples/no_hardware``, which uses resources from ``artiq/sim`` instead of referencing or requiring any real local hardware.
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. Experiments don't have to be in the repository to be submitted to the master, but the repository contains those experiments the master is automatically aware of.
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``: ::
@ -35,63 +39,117 @@ Create a very simple experiment in ``~/artiq-master/repository`` and save it as
def run(self): def run(self):
print("Hello World") print("Hello World")
Start the master with: :: Start the master with: ::
$ cd ~/artiq-master $ cd ~/artiq-master
$ artiq_master $ artiq_master
This last command should not return, as the master keeps running. This command should display ``ARTIQ master is now ready`` and not return, as the master keeps running. In another terminal, use the client to request this experiment: ::
Now, start the dashboard with the following commands in another terminal: :: $ artiq_client submit repository/mgmt_tutorial.py
$ cd ~ This command should print a message in the format ``RID: 0``, telling you the scheduling ID assigned to the experiment by the master, and exit. Note that it doesn't matter *where* the client is run; the client does not require direct access to ``device_db.py`` or the repository folder, and only directly communicates with the master. Relatedly, the path to an experiment a client submits is given relative to the location of the *master*, not the client.
$ artiq_dashboard
.. note:: Return to the terminal where the master is running. You should see an output similar to: ::
In order to connect to a master over the network, start it with the command ::
$ artiq_master --bind [hostname or IP] INFO:worker(0,mgmt_tutorial.py):print:Hello World
In other words, a worker created by the master has executed the experiment and carried out the print instruction. Congratulations!
.. tip::
In order to run the master and the clients on different PCs, start the master with a ``--bind`` flag: ::
$ artiq_master --bind [hostname or IP to bind to]
and then use the option ``--server`` or ``-s`` for clients, as in: :: 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] $ artiq_client -s [hostname or IP of the master]
$ artiq_dashboard -s [hostname or IP of the master]
Both IPv4 and IPv6 are supported. Both IPv4 and IPv6 are supported. See also the individual references :mod:`~artiq.frontend.artiq_master`, :mod:`~artiq.frontend.artiq_dashboard`, and :mod:`~artiq.frontend.artiq_client` for more details.
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. You may also notice that the master has created some other organizational files in its home directory, notably a folder ``results``, where a HDF5 record is preserved of every experiment that is submitted and run. The files in ``results`` will be discussed in greater detail in :doc:`using_data_interfaces`.
.. seealso:: Running the dashboard and controller manager
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`. --------------------------------------------
Submitting experiments with :mod:`~artiq.frontend.artiq_client` has some interesting qualities: for instance, experiments can be requested simultaneously by different clients and be relied upon to execute cleanly in sequence, which is useful in a distributed context. On the other hand, on an local level, it doesn't necessarily carry many practical advantages over using :mod:`~artiq.frontend.artiq_run`. The real convenience of the management system lies in its GUI, the dashboard. We will now try submitting an experiment using the dashboard.
First, start the controller manager: ::
$ artiq_ctlmgr
Like the master, this command should not return, as the controller manager keeps running. Note that the controller manager requires access to the device database, but not in the local directory -- it gets that access automatically by connecting to the master.
.. note::
We will not be using controllers in this part of the tutorial. Nonetheless, the dashboard will expect to be able to contact certain controllers given in the device database, and print error messages if this isn't the case (e.g. ``Is aqctl_moninj_proxy running?``). It is equally possible to check your device database and start the requisite controllers manually, or to temporarily delete their entries from ``device_db.py``, but it's normally quite convenient to let the controller manager handle things. The role and use of controller managers will be covered in more detail in :doc:`using_data_interfaces`.
In a third terminal, start the dashboard: ::
$ artiq_dashboard
Like :mod:`~artiq.frontend.artiq_client`, the dashboard requires no direct access to the device database or the repository. It communicates with the master to receive information about available experiments and the state of the system.
You should see the list of experiments from the ``repository`` in the dock called 'Explorer'. In our case, this will only be the single experiment we created, listed by the name we gave it in the docstring inside the triple quotes, "Management tutorial". Select it, and in the window that opens, click 'Submit'.
This time you will find the output displayed directly in the dock called 'Log'. The dashboard log combines the master's console output, the dashboard's own logs, and the device logs of the core device itself (if there is one in use); normally, this is the only log it's necessary to check.
Adding a new experiment
-----------------------
Create a new file in your ``repository`` folder, called ``timed_tutorial.py``: ::
from artiq.experiment import *
import time
class TimedTutorial(EnvExperiment):
"""Timed tutorial"""
def build(self):
pass # no devices used
def run(self):
print("Hello World")
time.sleep(10)
print("Goodnight World")
Save it. You will notice that it does not immediately appear in the 'Explorer' dock. For stability reasons, the master operates with a cached idea of the repository, and changes in the file system will often not be reflected until a *repository rescan* is triggered.
You can ask it to do this through the command-line client: ::
$ artiq_client scan-repository
or you can right-click in the Explorer and select 'Scan repository HEAD'. Now you should be able to select and submit the new experiment.
If you switch the 'Log' dock to its 'Schedule' tab while the experiment is still running, you will see the experiment appear, displaying its RID, status, priority, and other information. Click 'Submit' again while the first experiment is in progress, and a second iteration of the experiment will appear in the Schedule, queued up to execute next in line.
.. note::
You may have noted that experiments can 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, see :ref:`experiment-scheduling`.
In the meantime, you can try out submitting either of the two experiments with different priority levels and take a look at the queues that ensue. If you are interested, you can try submitting experiments through the command line client at the same time, or even open a second dashboard in a different terminal. Observe that no matter the source, all submitted experiments will be accounted for and handled by the scheduler in an orderly way.
.. _mgmt-arguments: .. _mgmt-arguments:
Adding an argument Adding arguments
------------------ ----------------
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, values which can be set in the dashboard on submission and used in the experiment's code. Create a new experiment called ``argument_tutorial.py``, and give it the following :meth:`~artiq.language.environment.HasEnvironment.build` and :meth:`~artiq.language.environment.Experiment.run` functions: ::
def build(self): def build(self):
self.setattr_argument("count", NumberValue(precision=0, step=1)) self.setattr_argument("count", NumberValue(precision=0, step=1))
def run(self): def run(self):
for i in range(self.count): for i in range(self.count):
print("Hello World", i) print("Hello World", i)
The method :meth:`~artiq.language.environment.HasEnvironment.setattr_argument` acts to set the argument and make its value accessible, similar to the effect of :meth:`~artiq.language.environment.HasEnvironment.setattr_device`. The second input sets the type of the argument; here, :class:`~artiq.language.environment.NumberValue` represents a floating point numerical value. To learn what other types are supported, 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`. Rescan the repository as before. Open the new experiment in the dashboard. Above the submission options, you should now see a spin box that allows you to set the value of ``count``. Try setting it and submitting it.
Use the command-line client to trigger a repository rescan: ::
artiq_client scan-repository
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 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: :: With standard arguments, it is only possible to use :meth:`~artiq.language.environment.HasEnvironment.setattr_argument` in :meth:`~artiq.language.environment.HasEnvironment.build`; these arguments are always requested at submission time. However, it is also possible to use *interactive* arguments, which can be requested and supplied inside :meth:`~artiq.language.environment.Experiment.run`, while the experiment is being executed. Modify the experiment as follows (and push the result): ::
def build(self): def build(self):
pass pass
@ -104,72 +162,81 @@ It is also possible to use interactive arguments, which may be requested and sup
interactive.setattr_argument("repeat", BooleanValue(True)) interactive.setattr_argument("repeat", BooleanValue(True))
repeat = interactive.repeat repeat = interactive.repeat
Close and reopen the submission window, or click on the button labeled 'Recompute all arguments', in order to update the submission parameters. Submit again. It should print once, then wait; you may notice in 'Schedule' that the experiment does not exit, but hangs at status 'running'.
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.) Now, in the same dock as 'Explorer', navigate to the tab 'Interactive Args'. You can now choose and submit a value for 'repeat'. Every time an interactive argument is requested, the experiment pauses until an input is supplied.
.. note::
If you choose to 'Cancel' instead, an :exc:`~artiq.language.environment.CancelledArgsError` will be raised (which an experiment can catch, instead of halting).
In order to request and supply multiple interactive arguments at once, simply place them in the same ``with`` block; see also the example ``interactive.py`` in ``examples/no_hardware``.
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: .. _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 tracking modifications to experiments and with the traceability to a particular version of an experiment.
.. note:: .. 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. The workflow we will describe in this tutorial corresponds to a situation where the computer running the ARTIQ master is also used as a Git server to which multiple users may contribute 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. See the :ref:`Management system <mgmt-git-integration>` page for notes on alternate workflows.
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 our current ``repository`` folder as the working directory for making local modifications to the experiments, move it away from the master's data directory, and replace it with a new ``repository`` folder, which will hold only the Git data used by the master. Stop the master with Ctrl+C and enter the following commands: ::
$ cd ~/artiq-master $ cd ~/artiq-master
$ mv repository ~/artiq-work $ mv repository ~/artiq-work
$ mkdir repository $ mkdir repository
$ cd repository $ cd repository
$ git init --bare $ git init bare
Now, push data to into the bare repository. Initialize a regular (non-bare) Git repository into our working directory: :: Now initialize a regular (non-bare) Git repository in our working directory: ::
$ cd ~/artiq-work $ cd ~/artiq-work
$ git init $ git init
Then commit our experiment: :: Then add and commit our experiments: ::
$ git add mgmt_tutorial.py $ git add mgmt_tutorial.py
$ git commit -m "First version of the tutorial experiment" $ git add timed_tutorial.py
$ git commit -m "First version of the tutorial experiments"
and finally, push the commit into the master's bare repository: :: and finally, connect the two repositories and push the commit upstream to the master's repository: ::
$ git remote add origin ~/artiq-master/repository $ git remote add origin ~/artiq-master/repository
$ git push -u origin master $ git push -u origin master
Start the master again with the ``-g`` flag, telling it to treat the contents of the ``repository`` folder (not ``artiq-work``) as a bare Git repository: :: .. tip::
If you are not familiar with command-line Git and would like to understand these commands in more detail, search for some tutorials in basic use of Git; there are many available online.
Start the master again with the ``-g`` flag, which tells it to treat its ``repository`` folder as a bare Git repository: ::
$ cd ~/artiq-master $ cd ~/artiq-master
$ artiq_master -g $ artiq_master -g
.. note:: .. note::
You need at least one commit in the repository before you can start the master. Note that you need at least one commit in the repository before the master can be started.
There should be no errors displayed, and if you start the GUI again, you will find the experiment there. Now you should be able to restart the dashboard and see your experiments there.
To complete the master configuration, we must tell Git to make the master rescan the repository when new data is added to it. Create a file ``~/artiq-master/repository/hooks/post-receive`` with the following contents: :: To make things more convenient, we will make Git tell the master to rescan the repository whenever new data is pushed from downstream. Create a file ``~/artiq-master/repository/hooks/post-receive`` with the following contents: ::
#!/bin/sh #!/bin/sh
artiq_client scan-repository --async artiq_client scan-repository --async
Then set the execution permission on it: :: Then set its execution permissions: ::
$ chmod 755 ~/artiq-master/repository/hooks/post-receive $ chmod 755 repository/hooks/post-receive
.. note:: .. note::
Remote machines may also push and pull into the master's bare repository using e.g. Git over SSH. Remote client machines may also push and pull into the master 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 experiments. In the working directory ``artiq-work``, open ``mgmt_tutorial.py`` again and add an exclamation mark to the end of "Hello World". Before committing it, check that the experiment can still be executed correctly by submitting it directly from the working directory, using the command-line client: ::
$ artiq_client submit ~/artiq-work/mgmt_tutorial.py $ artiq_client submit ~/artiq-work/mgmt_tutorial.py
.. note:: .. note::
You may also use the "Open file outside repository" feature of the GUI, by right-clicking on the explorer. Alternatively, right-click in the Explorer dock and select the 'Open file outside repository' option for the same effect.
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: ::
@ -177,86 +244,17 @@ 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 commit`` and ``git push`` are no longer necessary. The Git hook should cause a repository rescan automatically, and submitting the experiment in the dashboard should run the new version, with enthusiasm included.
Notice that commands other than ``git push`` are no longer necessary.
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.
.. _getting-started-datasets:
Datasets
--------
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):
self.set_dataset("parabola", np.full(self.count, np.nan), broadcast=True)
for i in range(self.count):
self.mutate_dataset("parabola", i, i*i)
time.sleep(0.5)
.. 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. 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>``.
.. 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`.
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/``.
Applets
-------
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.
.. 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 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: :: Often, you will want to run an instance of the controller manager and dashboard along with the ARTIQ master, whether or not you also intend to allow other clients to connect remotely. For convenience, all three can be started simultaneously with a single command: ::
$ artiq_session $ artiq_session
Arguments to the individuals (including ``-s`` and ``--bind``) can still be specified using the ``-m``, ``-d`` and ``-c`` options respectively. Arguments to the individual tools (including ``-s`` and ``--bind``) can still be specified using the ``-m``, ``-d`` and ``-c`` options for master, dashboard and manager respectively. Use an equals sign to avoid confusion in parsing, for example: ::
$ artiq_session -m=-g
to start the session with the master in Git mode. See also :mod:`~artiq.frontend.artiq_session`.

View File

@ -15,7 +15,7 @@ ARTIQ manual
installing installing
flashing flashing
configuring configuring
developing building_developing
.. toctree:: .. toctree::
:caption: Tutorials :caption: Tutorials
@ -24,12 +24,14 @@ ARTIQ manual
rtio rtio
getting_started_core getting_started_core
getting_started_mgmt getting_started_mgmt
using_data_interfaces
using_drtio_subkernels using_drtio_subkernels
.. toctree:: .. toctree::
:caption: ARTIQ components :caption: ARTIQ components
:maxdepth: 2 :maxdepth: 2
overview
environment environment
compiler compiler
management_system management_system
@ -48,13 +50,15 @@ ARTIQ manual
:caption: References :caption: References
:maxdepth: 2 :maxdepth: 2
main_frontend_reference main_frontend_tools
core_language_reference core_language_reference
core_drivers_reference core_drivers_reference
mgmt_system_reference
utilities utilities
.. toctree:: .. toctree::
:caption: Addenda :caption: Addenda
:maxdepth: 2 :maxdepth: 2
extending_rtio
faq faq

View File

@ -19,7 +19,7 @@ The easiest way to obtain ARTIQ is to install it into the user environment with
$ nix profile install git+https://github.com/m-labs/artiq.git $ nix profile install git+https://github.com/m-labs/artiq.git
Answer "Yes" to the questions about setting Nix configuration options (for more details see 'Troubleshooting' below.) You should now have a minimal installation of ARTIQ, where the usual front-end commands (``artiq_run``, ``artiq_master``, ``artiq_dashboard``, etc.) are all available to you. Answer "Yes" to the questions about setting Nix configuration options (for more details see 'Troubleshooting' below.) You should now have a minimal installation of ARTIQ, where the usual front-end commands (:mod:`~artiq.frontend.artiq_run`, :mod:`~artiq.frontend.artiq_master`, :mod:`~artiq.frontend.artiq_dashboard`, etc.) are all available to you.
This installation is however quite limited, as Nix creates a dedicated Python environment for the ARTIQ commands alone. This means that other useful Python packages, which ARTIQ is not dependent on but which you may want to use in your experiments (pandas, matplotlib...), are not available. This installation is however quite limited, as Nix creates a dedicated Python environment for the ARTIQ commands alone. This means that other useful Python packages, which ARTIQ is not dependent on but which you may want to use in your experiments (pandas, matplotlib...), are not available.
@ -82,15 +82,23 @@ Installing multiple packages and making them visible to the ARTIQ commands requi
}; };
} }
.. note::
You might consider adding matplotlib and numba in particular, as these are required by certain ARTIQ example experiments.
You can now spawn a shell containing these packages by running ``$ nix shell`` in the directory containing the ``flake.nix``. This should make both the ARTIQ commands and all the additional packages available to you. You can exit the shell with Control+D or with the command ``exit``. A first execution of ``$ nix shell`` may take some time, but for any future repetitions Nix will use cached packages and startup should be much faster. You can now spawn a shell containing these packages by running ``$ nix shell`` in the directory containing the ``flake.nix``. This should make both the ARTIQ commands and all the additional packages available to you. You can exit the shell with Control+D or with the command ``exit``. A first execution of ``$ nix shell`` may take some time, but for any future repetitions Nix will use cached packages and startup should be much faster.
You might be interested in creating multiple directories containing different ``flake.nix`` files which represent different sets of packages for different purposes. If you are familiar with Conda, using Nix in this way is similar to having multiple Conda environments. You might be interested in creating multiple directories containing different ``flake.nix`` files which represent different sets of packages for different purposes. If you are familiar with Conda, using Nix in this way is similar to having multiple Conda environments.
To find more packages you can browse the `Nix package search <https://search.nixos.org/packages>`_ website. If your favorite package is not available with Nix, contact M-Labs using the helpdesk@ email. To find more packages you can browse the `Nix package search <https://search.nixos.org/packages>`_ website. If your favorite package is not available with Nix, contact M-Labs using the helpdesk@ email.
.. note::
If you find you prefer using flakes to your original ``nix profile`` installation, you can remove it from your system by running: ::
$ nix profile list
finding the entry with its ``Original flake URL`` listed as the GitHub ARTIQ repository, noting its index number (in a fresh Nix system it will normally be the only entry, at index 0), and running: ::
$ nix profile remove [index]
While using flakes, ARTIQ is not 'installed' as such in any permanent way. However, Nix will preserve independent cached packages in ``/nix/store`` for each flake, which over time or with many different flakes and versions can take up large amounts of storage space. To clear this cache, run ``$ nix-garbage-collect``.
.. _installing-troubleshooting: .. _installing-troubleshooting:
Troubleshooting Troubleshooting
@ -197,6 +205,8 @@ After the installation, activate the newly created environment by name. ::
This activation has to be performed in every new shell you open to make the ARTIQ tools from that environment available. This activation has to be performed in every new shell you open to make the ARTIQ tools from that environment available.
.. _installing-upgrading:
Upgrading ARTIQ Upgrading ARTIQ
--------------- ---------------

View File

@ -16,7 +16,7 @@ ARTIQ and its dependencies are available in the form of Nix packages (for Linux)
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. 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.
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/experiment-control/artiq | Website: https://m-labs.hk/experiment-control/artiq
| (US-hosted mirror: https://m-labs-intl.com/experiment-control/artiq) | (US-hosted mirror: https://m-labs-intl.com/experiment-control/artiq)

View File

@ -3,30 +3,19 @@ List of available NDSPs
The following network device support packages are available for ARTIQ. If you would like to add yours to this list, just send us an email or a pull request. The following network device support packages are available for ARTIQ. If you would like to add yours to this list, just send us an email or a pull request.
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+ .. csv-table::
| Equipment | Nix package | MSYS2 package | Documentation | URL | :header: Equipment, Nix package, MSYS2 package, Documentation, URL
+=================================+===================================+==================================+===================================================================+========================================================+
| PDQ2 | Not available | Not available | `HTML <https://pdq.readthedocs.io>`_ | https://github.com/m-labs/pdq | PDQ2, Not available, Not available, `HTML <https://pdq.readthedocs.io>`_, `GitHub <https://github.com/m-labs/pdq>`__
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+ Lab Brick Digital Attenuator, ``lda``, ``lda``, Not available, `GitHub <https://github.com/m-labs/lda>`__
| Lab Brick Digital Attenuator | ``lda`` | ``lda`` | Not available | https://github.com/m-labs/lda | Novatech 4098B, ``novatech409b``, ``novatech409b``, Not available, `GitHub <https://github.com/m-labs/novatech409b>`__
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+ Thorlabs T-Cubes, ``thorlabs_tcube``, ``thorlabs_tcube``, Not available, `GitHub <https://github.com/m-labs/thorlabs_tcube>`__
| Novatech 409B | ``novatech409b`` | ``novatech409b`` | Not available | https://github.com/m-labs/novatech409b | Korad KA3005P, ``korad_ka3005p``, ``korad_ka3005p``, Not available, `GitHub <https://github.com/m-labs/korad_ka3005p>`__
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+ Newfocus 8742, ``newfocus8742``, ``newfocus8742``, Not available, `GitHub <https://github.com/quartiq/newfocus8742>`__
| Thorlabs T-Cubes | ``thorlabs_tcube`` | ``thorlabs_tcube`` | Not available | https://github.com/m-labs/thorlabs_tcube | Princeton Instruments PICam, Not available, Not available, Not available, `GitHub <https://github.com/quartiq/picam>`__
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+ Anel HUT2 power distribution, ``hut2``, Not available, Not available, `GitHub <https://github.com/quartiq/hut2>`__
| Korad KA3005P | ``korad_ka3005p`` | ``korad_ka3005p`` | Not available | https://github.com/m-labs/korad_ka3005p | TOPTICA lasers, ``toptica-lasersdk-artiq``, Not available, Not available, `GitHub <https://github.com/quartiq/lasersdk-artiq>`__
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+ HighFinesse wavemeters, ``highfinesse-net``, Not available, Not available, `GitHub <https://github.com/quartiq/highfinesse-net>`__
| Newfocus 8742 | ``newfocus8742`` | ``newfocus8742`` | Not available | https://github.com/quartiq/newfocus8742 | InfluxDB database, Not available, Not available, `HTML <https://gitlab.com/charlesbaynham/artiq_influx_generic>`__, `GitHub <https://gitlab.com/charlesbaynham/artiq_influx_generic>`__
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+
| Princeton Instruments PICam | Not available | Not available | Not available | https://github.com/quartiq/picam |
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+
| Anel HUT2 power distribution | ``hut2`` | Not available | Not available | https://github.com/quartiq/hut2 |
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+
| TOPTICA lasers | ``toptica-lasersdk-artiq`` | Not available | Not available | https://github.com/quartiq/lasersdk-artiq |
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+
| HighFinesse wavemeters | ``highfinesse-net`` | Not available | Not available | https://github.com/quartiq/highfinesse-net |
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+
| InfluxDB database | Not available | Not available | `HTML <https://gitlab.com/charlesbaynham/artiq_influx_generic>`__ | https://gitlab.com/charlesbaynham/artiq_influx_generic |
+---------------------------------+-----------------------------------+----------------------------------+-------------------------------------------------------------------+--------------------------------------------------------+
MSYS2 packages all start with the ``mingw-w64-clang-x86_64-`` prefix. MSYS2 packages all start with the ``mingw-w64-clang-x86_64-`` prefix.

View File

@ -1,51 +0,0 @@
Main front-end tool reference
=============================
These are the top-level commands used to run and manage ARTIQ experiments. Not all of the ARTIQ front-end is described here (many additional useful commands are presented in this manual in :doc:`utilities`) but these together comprise the main points of access for using ARTIQ as a system.
:mod:`artiq.frontend.artiq_run`
-------------------------------
.. argparse::
:ref: artiq.frontend.artiq_run.get_argparser
:prog: artiq_run
:nodefault:
.. _frontend-artiq-master:
:mod:`artiq.frontend.artiq_master`
----------------------------------
.. argparse::
:ref: artiq.frontend.artiq_master.get_argparser
:prog: artiq_master
:nodefault:
.. _frontend-artiq-client:
:mod:`artiq.frontend.artiq_client`
----------------------------------
.. argparse::
:ref: artiq.frontend.artiq_client.get_argparser
:prog: artiq_client
:nodefault:
.. _frontend-artiq-dashboard:
:mod:`artiq.frontend.artiq_dashboard`
-------------------------------------
.. argparse::
:ref: artiq.frontend.artiq_dashboard.get_argparser
:prog: artiq_dashboard
:nodefault:
:mod:`artiq.frontend.artiq_session`
-----------------------------------
.. argparse::
:ref: artiq.frontend.artiq_session.get_argparser
:prog: artiq_session
:nodefault:

View File

@ -0,0 +1,90 @@
Main front-end tools
====================
These are the top-level commands used to run and manage ARTIQ experiments. Not all of the ARTIQ front-end is described here (many additional useful commands are presented in this manual in :doc:`utilities`) but these together comprise the main points of access for using ARTIQ as a system.
.. Note that ARTIQ frontend has no docstrings and the ..automodule directives display nothing; they are there to make :mod: references function correctly, since sphinx-argparse does not support links to ..argparse directives in the same way.
:mod:`artiq.frontend.artiq_run`
-------------------------------
.. automodule:: artiq.frontend.artiq_run
.. argparse::
:ref: artiq.frontend.artiq_run.get_argparser
:prog: artiq_run
:nodefault:
.. _frontend-artiq-master:
:mod:`artiq.frontend.artiq_master`
----------------------------------
.. automodule:: artiq.frontend.artiq_master
.. argparse::
:ref: artiq.frontend.artiq_master.get_argparser
:prog: artiq_master
:nodefault:
.. _frontend-artiq-client:
:mod:`artiq.frontend.artiq_client`
----------------------------------
.. automodule:: artiq.frontend.artiq_client
.. argparse::
:ref: artiq.frontend.artiq_client.get_argparser
:prog: artiq_client
:nodefault:
.. _frontend-artiq-dashboard:
:mod:`artiq.frontend.artiq_dashboard`
-------------------------------------
.. automodule:: artiq.frontend.artiq_dashboard
.. argparse::
:ref: artiq.frontend.artiq_dashboard.get_argparser
:prog: artiq_dashboard
:nodefault:
.. _frontend-artiq-browser:
:mod:`artiq.frontend.artiq_browser`
-----------------------------------
.. automodule:: artiq.frontend.artiq_browser
.. argparse::
:ref: artiq.frontend.artiq_browser.get_argparser
:prog: artiq_browser
:nodefault:
:mod:`artiq.frontend.artiq_session`
-----------------------------------
.. automodule:: artiq.frontend.artiq_session
ARTIQ session manager. Automatically runs the master, dashboard and local controller manager on the current machine. The latter requires the ``artiq-comtools`` package to be installed.
When supplying arguments to individual front-end tools, use ``=`` to avoid ambiguity in argument parsing, e.g.: ::
artiq_session -m=-g -m=--device-db=alternate_device_db.py -c=-v
and so on.
.. argparse::
:ref: artiq.frontend.artiq_session.get_argparser
:prog: artiq_session
:nodescription:
:nodefault:
:mod:`artiq_comtools.artiq_ctlmgr`
----------------------------------
.. automodule:: artiq_comtools.artiq_ctlmgr
ARTIQ controller manager. Supplied in the separate package ``artiq-comtools``, which is included with a standard ARTIQ installation but can also be `installed standalone <https://github.com/m-labs/artiq-comtools>`_, with the intention of making it easier to run controllers and controller managers on machines where a full ARTIQ installation may not be necessary or convenient.
.. argparse::
:ref: artiq_comtools.artiq_ctlmgr.get_argparser
:prog: artiq_ctlmgr
:nodescription:
:nodefault:

View File

@ -10,43 +10,53 @@ Components
Master Master
^^^^^^ ^^^^^^
The :ref:`ARTIQ master <frontend-artiq-master>` is responsible for managing the parameter and device databases, the experiment repository, scheduling and running experiments, archiving results, and distributing real-time results. It is a headless component, and one or several clients (command-line or GUI) use the network to interact with it. The :ref:`ARTIQ master <frontend-artiq-master>` is responsible for managing the dataset and device databases, the experiment repository, scheduling and running experiments, archiving results, and distributing real-time results. It is a headless component, and one or several clients (command-line or GUI) use the network to interact with it.
It should not be confused with the 'master' device in a DRTIO system, which is only a designation for the particular core device acting as central node in a distributed configuration of ARTIQ. The two concepts are otherwise unrelated. The master expects to be given a directory on startup, the experiment repository, containing these experiments which are automatically tracked and communicated to clients. By default, it simply looks for a directory called ``repository``. The ``-r`` flag can be used to substitute an alternate location.
Command-line client It also expects access to a ``device_db.py``, with a corresponding flag ``--device-db`` to substitute a different file name. Additionally, it will reference or create certain files in the directory it is run in, among them ``dataset_db.mdb``, the LMDB database containing persistent datasets, ``last_rid.pyon``, which simply contains the last used RID, and the ``results`` directory.
^^^^^^^^^^^^^^^^^^^
The :ref:`command-line client <frontend-artiq-client>` connects to the master and permits modification and monitoring of the databases, monitoring the experiment schedule and log, and submitting experiments. .. note::
Because the other parts of the management system all seem to be able to access the information stored in these files, confusion can sometimes result about where it is really stored and how it is distributed. Device databases, datasets, results, and experiments are all solely kept and administered by the master, which communicates information to dashboards, browsers, and clients over the network whenever necessary.
Dashboard Notably, clients and dashboards do not send in experiments to the master; they request them from the array of experiments the master knows about, primarily those in ``repository``, but also in the master's local file system, if 'Open file outside repository' is selected. This is true even if ``repository`` is configured as a Git repository and cloned on other machines.
^^^^^^^^^
The :ref:`dashboard <frontend-artiq-dashboard>` connects to the master and is the main method of interacting with it. The main features of the dashboard are scheduling of experiments, setting of their arguments, examining the schedule, displaying real-time results, and debugging TTL and DDS channels in real time. The ARTIQ master should not be confused with the 'master' device in a DRTIO system, which is only a designation for the particular core device acting as central node in a distributed configuration of ARTIQ. The two concepts are otherwise unrelated.
Clients
^^^^^^^
The :ref:`command-line client <frontend-artiq-client>` connects to the master and permits modification and monitoring of the databases, reading the experiment schedule and log, and submitting experiments.
The :ref:`dashboard <frontend-artiq-dashboard>` connects to the master and is the main method of interacting with it. The main roles of the dashboard are scheduling of experiments, setting of their arguments, examining the schedule, displaying real-time results, and debugging TTL and DDS channels in real time.
The dashboard remembers and restores GUI state (window/dock positions, last values entered by the user, etc.) in between instances. This information is stored in a file called ``artiq_dashboard_{server}_{port}.pyon`` in the configuration directory (e.g. generally ``~/.config/artiq`` for Unix, same as data directory for Windows), distinguished in subfolders by ARTIQ version. The dashboard remembers and restores GUI state (window/dock positions, last values entered by the user, etc.) in between instances. This information is stored in a file called ``artiq_dashboard_{server}_{port}.pyon`` in the configuration directory (e.g. generally ``~/.config/artiq`` for Unix, same as data directory for Windows), distinguished in subfolders by ARTIQ version.
Browser
^^^^^^^
The :ref:`browser <frontend-artiq-browser>` is used to read ARTIQ ``results`` HDF5 files and run experiment :meth:`~artiq.language.environment.Experiment.analyze` functions, in particular to retrieve previous result databases, process them, and display them in ARTIQ applets. The browser also remembers and restores its GUI state; this is stored in a file called simply ``artiq_browser``, kept in the same configuration directory as the dashboard.
Controller manager Controller manager
^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^
The controller manager is provided in the ``artiq-comtools`` package (which is also made available separately from mainline ARTIQ, to allow independent use with minimal dependencies) and started with the ``artiq_ctlmgr`` command. It is responsible for running and stopping controllers on a machine. One controller manager must be run by each network node that runs controllers. The controller manager is provided in the ``artiq-comtools`` package (which is also made available separately from mainline ARTIQ, to allow independent use with minimal dependencies) and started with the :mod:`~artiq_comtools.artiq_ctlmgr` command. It is responsible for running and stopping controllers on a machine. One controller manager must be run by each network node that runs controllers.
A controller manager connects to the master and accesses the device database through it to determine what controllers need to be run. The local network address of the connection is used to filter for only those controllers allocated to the current node. Hostname resolution is supported. Changes to the device database are tracked and controllers will be stopped and started accordingly. A controller manager connects to the master and accesses the device database through it to determine what controllers need to be run. The local network address of the connection is used to filter for only those controllers allocated to the current node. Hostname resolution is supported. Changes to the device database are tracked and controllers will be stopped and started accordingly.
.. _mgmt-git-integration:
Git integration Git integration
--------------- ---------------
The master may use a Git repository to store experiment source code. Using Git has many advantages. For example, each result file (HDF5) contains the commit ID corresponding to the exact source code it was produced by, which helps reproducibility. The master may use a Git repository to store experiment source code. Using Git has many advantages. For example, each result file (HDF5) contains the commit ID corresponding to the exact source code it was produced by, which helps reproducibility. Although the master also supports non-bare repositories, it is recommended to use a bare repository (e.g. ``git init --bare``) to easily support push transactions from clients.
Although the master also supports non-bare repositories, it is recommended to use a bare repository (e.g. ``git init --bare``) to easily support push transactions from clients.
You will want Git to notify the master every time the repository is pushed to (e.g. updated), so that the master knows to rescan the repository for new or changed experiments. This is easiest done with the ``post-receive`` hook, as described in :ref:`master-setting-up-git`. You will want Git to notify the master every time the repository is pushed to (e.g. updated), so that the master knows to rescan the repository for new or changed experiments. This is easiest done with the ``post-receive`` hook, as described in :ref:`master-setting-up-git`.
.. note:: .. note::
If you plan to run the ARTIQ system entirely on a single machine, you may also consider using a non-bare repository and the ``post-commit`` hook to trigger repository scans every time you commit changes (locally). In this case, note that the ARTIQ master never uses the repository's working directory, but only what is committed. More precisely, when scanning the repository, it fetches the last (atomically) completed commit at that time of repository scan and checks it out in a temporary folder. This commit ID is used by default when subsequently submitting experiments. There is one temporary folder by commit ID currently referenced in the system, so concurrently running experiments from different repository revisions is fully supported by the master. If you plan to run the ARTIQ system entirely on a single machine, you may also consider using a non-bare repository and the ``post-commit`` hook to trigger repository scans every time you commit changes (locally). In this case, note that the ARTIQ master never uses the repository's working directory, but only what is committed. More precisely, when scanning the repository, it fetches the last (atomically) completed commit at that time of repository scan and checks it out in a temporary folder. This commit ID is used by default when subsequently submitting experiments. There is one temporary folder by commit ID currently referenced in the system, so concurrently running experiments from different repository revisions is fully supported by the master.
By default, the dashboard runs experiments from the repository, whereas the command-line client (``artiq_client submit``) runs experiments from the raw filesystem (which is useful for iterating rapidly without creating many disorganized commits). In order to run from the raw filesystem when using the dashboard, right-click in the Explorer window and select the option "Open file outside repository"; in order to run from the repository when using the command-line client, simply pass the ``-R`` flag. By default, the dashboard runs experiments from the repository, whereas the command-line client (``artiq_client submit``) runs experiments from the raw filesystem (which is useful for iterating rapidly without creating many disorganized commits). In order to run from the raw filesystem when using the dashboard, right-click in the Explorer window and select the option 'Open file outside repository'. In order to run from the repository when using the command-line client, simply pass the ``-R`` flag.
.. _experiment-scheduling: .. _experiment-scheduling:
@ -59,7 +69,7 @@ Basics
To make more efficient use of hardware resources, experiments are generally split into three phases and pipelined, such that potentially compute-intensive pre-computation or analysis phases may be executed in parallel with the bodies of other experiments, which access hardware. To make more efficient use of hardware resources, experiments are generally split into three phases and pipelined, such that potentially compute-intensive pre-computation or analysis phases may be executed in parallel with the bodies of other experiments, which access hardware.
.. seealso:: .. seealso::
These steps are implemented in :class:`~artiq.language.environment.Experiment`. However, user-written experiments should usually derive from (sub-class) :class:`artiq.language.environment.EnvExperiment`. These steps are implemented in :class:`~artiq.language.environment.Experiment`. However, user-written experiments should usually derive from (sub-class) :class:`artiq.language.environment.EnvExperiment`, which additionally provides access to the methods of :class:`artiq.language.environment.HasEnvironment`.
There are three stages of a standard experiment users may write code in: There are three stages of a standard experiment users may write code in:
@ -104,92 +114,3 @@ The experiment must place the hardware in a safe state and disconnect from the c
Accessing the :meth:`pause` and :meth:`~artiq.master.scheduler.Scheduler.check_pause` methods is done through a virtual device called ``scheduler`` that is accessible to all experiments. The scheduler virtual device is requested like regular devices using :meth:`~artiq.language.environment.HasEnvironment.get_device` (``self.get_device()``) or :meth:`~artiq.language.environment.HasEnvironment.setattr_device` (``self.setattr_device()``). Accessing the :meth:`pause` and :meth:`~artiq.master.scheduler.Scheduler.check_pause` methods is done through a virtual device called ``scheduler`` that is accessible to all experiments. The scheduler virtual device is requested like regular devices using :meth:`~artiq.language.environment.HasEnvironment.get_device` (``self.get_device()``) or :meth:`~artiq.language.environment.HasEnvironment.setattr_device` (``self.setattr_device()``).
:meth:`~artiq.master.scheduler.Scheduler.check_pause` can be called (via RPC) from a kernel, but :meth:`pause` must not be. :meth:`~artiq.master.scheduler.Scheduler.check_pause` can be called (via RPC) from a kernel, but :meth:`pause` must not be.
Scheduler API reference
-----------------------
The scheduler is exposed to the experiments via a virtual device called ``scheduler``. It can be requested like any regular device, and the methods below, as well as :meth:`pause`, can be called on the returned object.
The scheduler virtual device also contains the attributes ``rid``, ``pipeline_name``, ``priority`` and ``expid``, which contain the corresponding information about the current run.
.. autoclass:: artiq.master.scheduler.Scheduler
:members:
Client control broadcasts (CCBs)
--------------------------------
Client control broadcasts are requests made by experiments for clients to perform some action. Experiments do so by requesting the ``ccb`` virtual device and calling its ``issue`` method. The first argument of the issue method is the name of the broadcast, and any further positional and keyword arguments are passed to the broadcast.
CCBs are used by experiments to configure applets in the dashboard, for example for plotting purposes.
.. autoclass:: artiq.dashboard.applets_ccb.AppletsCCBDock
:members:
.. _applet-references:
Applet request interfaces
-------------------------
Applet request interfaces allow applets to perform actions on the master database and set arguments in the dashboard. Applets may inherit from ``artiq.applets.simple.SimpleApplet`` and call the methods defined below through the ``req`` attribute.
Embedded applets should use ``AppletRequestIPC`` while standalone applets use ``AppletRequestRPC``. ``SimpleApplet`` automatically chooses the correct interface on initialization.
.. autoclass:: artiq.applets.simple._AppletRequestInterface
:members:
Applet entry area
-----------------
Argument widgets can be used in applets through the :class:`~artiq.gui.applets.EntryArea` class. Below is a simple example code snippet: ::
entry_area = EntryArea()
# Create a new widget
entry_area.setattr_argument("bl", BooleanValue(True))
# Get the value of the widget (output: True)
print(entry_area.bl)
# Set the value
entry_area.set_value("bl", False)
# False
print(entry_area.bl)
The :class:`~artiq.gui.applets.EntryArea` object can then be added to a layout and integrated with the applet GUI. Multiple :class:`~artiq.gui.applets.EntryArea` objects can be used in a single applet.
.. class:: artiq.gui.applets.EntryArea
.. method:: setattr_argument(name, proc, group=None, tooltip=None)
Sets an argument as attribute. The names of the argument and of the
attribute are the same.
:param name: Argument name
:param proc: Argument processor, for example :class:`~artiq.language.environment.NumberValue`
:param group: Used to group together arguments in the GUI under a common category
:param tooltip: Tooltip displayed when hovering over the entry widget
.. method:: get_value(name)
Get the value of an entry widget.
:param name: Argument name
.. method:: get_values()
Get all values in the :class:`~artiq.gui.applets.EntryArea` as a dictionary. Names are stored as keys, and argument values as values.
.. method:: set_value(name, value)
Set the value of an entry widget. The change is temporary and will reset to default when the reset button is clicked.
:param name: Argument name
:param value: Object representing the new value of the argument. For :class:`~artiq.language.scan.Scannable` arguments, 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.
.. method:: set_values(values)
Set multiple values from a dictionary input. Calls :meth:`set_value` for each key-value pair.
:param values: Dictionary with names as keys and new argument values as values.

View File

@ -0,0 +1,99 @@
Management system interface
===========================
ARTIQ makes certain provisions to allow interactions between different components when using the :doc:`management system <management_system>`. An experiment may make requests of the master or clients using virtual devices to represent the necessary line of communication; applets may interact with databases, the dashboard, and directly with the user (through argument widgets). This page collects the references for these features.
In experiments
--------------
``scheduler`` virtual device
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The scheduler is exposed to the experiments via a virtual device called ``scheduler``. It can be requested like any other device, and the methods below, as well as :meth:`pause`, can be called on the returned object.
The scheduler virtual device also contains the attributes ``rid``, ``pipeline_name``, ``priority`` and ``expid``, which contain the corresponding information about the current run.
.. autoclass:: artiq.master.scheduler.Scheduler
:members:
``ccb`` virtual device
^^^^^^^^^^^^^^^^^^^^^^
Client control broadcasts (CCBs) are requests made by experiments for clients to perform some action. Experiments do so by requesting the ``ccb`` virtual device and calling its ``issue`` method. The first argument of the issue method is the name of the broadcast, and any further positional and keyword arguments are passed to the broadcast.
CCBs are especially used by experiments to configure applets in the dashboard, for example for plotting purposes.
.. autoclass:: artiq.dashboard.applets_ccb.AppletsCCBDock
:members:
In applets
----------
.. _applet-references:
Applet request interfaces
^^^^^^^^^^^^^^^^^^^^^^^^^
Applet request interfaces allow applets to perform actions on the master database and set arguments in the dashboard. Applets may inherit from ``artiq.applets.simple.SimpleApplet`` and call the methods defined below through the ``req`` attribute.
Embedded applets should use ``AppletRequestIPC``, while standalone applets use ``AppletRequestRPC``. ``SimpleApplet`` automatically chooses the correct interface on initialization.
.. autoclass:: artiq.applets.simple._AppletRequestInterface
:members:
Applet entry area
^^^^^^^^^^^^^^^^^
Argument widgets can be used in applets through the :class:`~artiq.gui.applets.EntryArea` class. Below is a simple example code snippet: ::
entry_area = EntryArea()
# Create a new widget
entry_area.setattr_argument("bl", BooleanValue(True))
# Get the value of the widget (output: True)
print(entry_area.bl)
# Set the value
entry_area.set_value("bl", False)
# False
print(entry_area.bl)
The :class:`~artiq.gui.applets.EntryArea` object can then be added to a layout and integrated with the applet GUI. Multiple :class:`~artiq.gui.applets.EntryArea` objects can be used in a single applet.
.. class:: artiq.gui.applets.EntryArea
.. method:: setattr_argument(name, proc, group=None, tooltip=None)
Sets an argument as attribute. The names of the argument and of the
attribute are the same.
:param name: Argument name
:param proc: Argument processor, for example :class:`~artiq.language.environment.NumberValue`
:param group: Used to group together arguments in the GUI under a common category
:param tooltip: Tooltip displayed when hovering over the entry widget
.. method:: get_value(name)
Get the value of an entry widget.
:param name: Argument name
.. method:: get_values()
Get all values in the :class:`~artiq.gui.applets.EntryArea` as a dictionary. Names are stored as keys, and argument values as values.
.. method:: set_value(name, value)
Set the value of an entry widget. The change is temporary and will reset to default when the reset button is clicked.
:param name: Argument name
:param value: Object representing the new value of the argument. For :class:`~artiq.language.scan.Scannable` arguments, 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.
.. method:: set_values(values)
Set multiple values from a dictionary input. Calls :meth:`set_value` for each key-value pair.
:param values: Dictionary with names as keys and new argument values as values.

20
doc/manual/overview.rst Normal file
View File

@ -0,0 +1,20 @@
Overview diagram
================
.. Kept in a separate file for the luxury of good syntax highlighting
.. tikz:: Structure of an ARTIQ system. Note that most components can also be used independently of each other. Network-connected components can be distributed across multiple machines.
:align: left
:libs: positioning, shapes, arrows
:xscale: 100
:include: overview.tex
Footnotes:
1. As described in the :doc:`management tutorial <getting_started_mgmt>`, the clients can simply be run on the same machine as the master. It is common however to set up several other 'client machines' to allow distributed access to the system, each with an installation of ARTIQ and (potentially) a Git clone of the experiment repository. The communication between components is the same. Other ARTIQ commands and utilities can also be used from these machines.
2. It is common to have controllers for one or several 'slow' devices running on other machines. In this case, it's simply necessary to have one controller manager per machine. Note that running controllers and controller managers does not require a full ARTIQ install: the minimal package ``artiq-comtools`` can be installed instead. Otherwise, if these controllers are run side-by-side to the built-in controllers, they can be handled by the same controller manager, as depicted by the non-dashed lines.
3. The various other ARTIQ utilities may also connect to the master, read the device database, communicate with the core device, etc., depending on their specific functionalities. See their :doc:`individual references <utilities>`.
4. These are the :ref:`built-in controllers <built-in-ctlrs>` used by the ARTIQ dashboard to provide certain functionalities which require access to the core device. They can be found listed in :ref:`Utilities <utilities-ctrls>` (the commands prefixed with ``aqctl_`` rather than ``artiq_``). It is possible to use the ARTIQ management system without them, and the other abilities of the dashboard will still function, but important features will be unavailable.

212
doc/manual/overview.tex Normal file
View File

@ -0,0 +1,212 @@
[
thin/.style={line width=1.2pt}, % default line thickness
thick/.style={line width=1.8pt},
% ampersand replacement apparently necessary for sphinx latexpdf output (but not for html!)
ampersand replacement=\&
]
% artiq colors
\definecolor{brand}{HTML}{715ec7} % 'brand colour' violet
\definecolor{brand-light}{HTML}{a88cfd} % lighter brand colour
\definecolor{primary}{HTML}{0d3547}
\definecolor{secondary}{HTML}{1a6d93}
\definecolor{dingle}{HTML}{76c5d2}
\definecolor{rtm}{HTML}{3084bc} % RTM blue
% other colors used are tikz standard issue
% tikzstyle settings
\tikzstyle{every node} = [anchor = center, thin, minimum size = 1cm]
% the every matrix style is odd and can't be overriden (??) so its usefulness is limited
\tikzstyle{every matrix} = [anchor = north west, rounded corners]
\tikzstyle{matrices} = [thick, row sep = 0.3cm, column sep=0.3cm]
\tikzstyle{legend} = [draw = primary, thin, row sep=0.1cm, minimum width = 5cm, column sep = 0.2cm]
% particular node styles
\tikzstyle{label} = [minimum size = 0.5cm]
\tikzstyle{single} = [draw = brand, minimum width = 2 cm]
\tikzstyle{splits} = [draw = brand, rectangle split, solid]
\tikzstyle{linelabel} = [anchor = west, font=\sc\footnotesize]
\tikzstyle{colorbox} = [rectangle, very thin, draw=gray, sharp corners, minimum height = 0.4 cm, minimum width = 1.8 cm]
% color coding
\tikzstyle{line} = [thick]
\tikzstyle{network} = [line, draw = magenta]
\tikzstyle{git} = [line, draw = violet]
\tikzstyle{rtio} = [line, draw = brand-light]
\tikzstyle{drtio} = [line, draw = blue]
\tikzstyle{slow} = [line, draw = dingle]
\tikzstyle{jtag} = [line, draw = gray]
\tikzstyle{direct} = [line, draw = primary, thin]
\tikzstyle{master} = [draw = purple]
\tikzstyle{host} = [row sep = 0.5 cm, column sep = 0.7 cm]
\tikzstyle{pc} = [matrices, thin, inner sep = 0.2cm, draw = gray, dashed]
\tikzstyle{peripheral} = [matrices, draw = secondary]
\tikzstyle{core} = [matrices, draw = purple]
% PERIPHERALS (SLOW)
\matrix[peripheral](peri-slow) at (0, 15.75)
{
\node[label] (label){Peripherals (slow)};\\
\node [splits, rectangle split parts = 4](slow-list){
\nodepart{one} Optical translation stages
\nodepart{two} Optical rotation stages
\nodepart{three} Temperature controllers
\nodepart{four} etc...
};\\
};
% OTHER MACHINES
\matrix[pc](comtools) at (6.38, 15.75)
{
\node [splits, rectangle split parts = 3](remote-ctlrs){
\nodepart{one} Controller
\nodepart{two} Controller
\nodepart{three} etc...
}; \\
\node[single, minimum height = 0.5cm, font=\tt](remote-ctlmgr){
artiq\_ctlmgr\textsuperscript{2}
}; \\
};
\node[anchor = north west] (label) at (11, 16) {Client machines, NB: see footnote 1.};
\matrix[pc](clients) at (10.75, 15)
{
\node[single, draw=rtm, solid](remote-repo) {Cloned repository};
\&
\node [splits, master, rectangle split parts = 2, minimum width = 3 cm](remote-client){
\nodepart[font=\tt]{one} artiq\_client
\nodepart[font=\tt]{two} artiq\_dashboard
};
\\
};
% HOST MACHINE
\matrix[host](host-machine) at (0, 11)
{
\node[font=\tt, single](browser) {artiq\_browser};
\&
\node[single, master](results){Results};
\&
\node[splits, rectangle split parts = 2, master](master) {
\nodepart[font=\bf]{one} ARTIQ master
\nodepart{two} Datasets
};
\&
\node [single, master](repo){Repository};
\&
\node[font=\tt, single, master](ctlmgr) {artiq\_ctlmgr};
\\
\node[font=\tt, single](flash) {artiq\_flash};
\&
\node[font=\tt, single](run) {artiq\_run};
\&
\node[single, master](ddb) {Device database};
\&
\node[font=\tt, single](coremgmt) {artiq\_coremgmt\textsuperscript{3}};
\&
\node[single, master](builtin) {Built-in controllers\textsuperscript{4}};
\\
};
% BORDER LINE
\node[linelabel] (label1) at (-0.25, 6.3) {PCs AND REMOTE DEVICES};
\draw[primary, thick, dotted] (-0.5, 6) -- (19, 6);
\node[linelabel] (label2) at (0, 5.7) {REAL TIME HARDWARE};
% PERIPHERALS (FAST)
\matrix[peripheral](peri-fast) at (0, 5)
{
\node[label] (label){Peripherals (fast)};\\
\node [splits, rectangle split parts = 5](fast-list){
\nodepart{one} TTL in/out
\nodepart{two} DDS/Urukul
\nodepart{three} AWG/Phaser
\nodepart{four} DAC/Zotino
\nodepart{five} etc...
};\\
};
% CORE DEVICES
\matrix[core](core) at (6.75, 5)
{
\node[label] (label){Core device};\\
\node [splits, rectangle split parts = 2](core-list){
\nodepart{one} CPU
\nodepart{two} RTIO gateware
};\\
};
% SATELLITE
\matrix[core](satellite) at (6.25, 2)
{
\node[label] (label){Satellite core devices};\\
};
% legend
\matrix[legend] (legend) at (12, 5.5) {
\node [colorbox, fill=magenta] (network) {}; \& \node[label] (network-label) {Network}; \\
\node [colorbox, fill=brand-light] (rtio) {}; \& \node[label] (rtio-label) {RTIO}; \\
\node [colorbox, fill=dingle] (slow) {}; \& \node[label] (slow-label) {Slow IO}; \\
\node [colorbox, fill=blue] (drtio) {}; \& \node[label] (drtio-label) {DRTIO}; \\
\node [colorbox, fill=gray] (jtag) {}; \& \node[label] (jtag-label) {JTAG}; \\
\node [colorbox, fill=primary] (direct) {}; \& \node[label] (direct-label) {Local}; \\
\node [colorbox, fill=violet] (git) {}; \& \node[label] (git-label) {Git}; \\
};
%controllers
\draw[direct, dashed] (remote-ctlmgr.north) -- (remote-ctlrs);
\draw[slow] (slow-list.one east) -| +(0.5,0) |- (remote-ctlrs.one west);
\draw[slow] (slow-list.two east) -| +(0.8,0) |- (remote-ctlrs.two west);
\draw[slow] (slow-list.three east) -| +(1.1,0) |- (remote-ctlrs.three west);
\draw[direct] (remote-ctlrs.east) -| + (1, -2.25) -|(ctlmgr.70);
%ctlmgr
\draw[network, dashed] (remote-ctlmgr.west) -| +(-0.5, -1) -| (master.120);
% client
\draw[git] (remote-repo.south) -- +(0, -1) -| (repo.110);
\draw[network] (remote-client.south) -- +(0, -1.5) -| (master.90);
\draw[network] (remote-client.two east) -- +(0.7, 0) |- (builtin.east);
%host
\draw[direct] (browser) -- (results);
%master
\draw[direct] (results) -- (master);
\draw[direct] (master) -- (ddb);
\draw[direct] (master) -- (repo);
\draw[direct] (run) -- (ddb);
\draw[direct] (coremgmt) -- (ddb);
% ctlmgr
\draw[network] (master.60) -- +(0, 0.25) -| (ctlmgr.110);
\draw[network] (ctlmgr) -- (builtin);
% core connections
\draw[jtag] (flash.south) |- +(1, -1) -| (core.125);
\draw[network] (run.south) |- +(1, -0.75) -| (core.110);
\draw[network] (master.230) |- +(-1.25, -0.25) |- +(0, -2) -| (core.north);
\draw[network] (coremgmt.south) |- +(-0.75, -0.75) -| (core.70);
\draw[network] (builtin.south) |- +(-2, -1.1) -| (core.55);
%rtio
\node (branch) at (5, 2.5){};
\draw[rtio] (core-list.two west) |- +(-1, 0) |- (branch.center);
\draw[rtio] (branch.center) |- (fast-list.one east);
\draw[rtio] (branch.center) |- (fast-list.two east);
\draw[rtio] (branch.center) |- (fast-list.three east);
\draw[rtio] (branch.center) |- (fast-list.four east);
\draw[rtio] (branch.center) |- (fast-list.five east);
%drtio
\draw[drtio] (core-list.one east) |- +(1, 0) |- (satellite.east);
\draw[rtio] (satellite.west) -| +(-0.5, 0) |- (5, 2);

View File

@ -1,32 +1,19 @@
.. _artiq-real-time-i-o-concepts: ARTIQ Real-Time I/O concepts
ARTIQ Real-Time I/O Concepts
============================ ============================
The ARTIQ Real-Time I/O design employs several concepts to achieve its goals of high timing resolution on the nanosecond scale and low latency on the microsecond scale while still not sacrificing a readable and extensible language. The ARTIQ Real-Time I/O design employs several concepts to achieve its goals of high timing resolution on the nanosecond scale and low latency on the microsecond scale while still not sacrificing a readable and extensible language.
In a typical environment two very different classes of hardware need to be controlled. In a typical environment two very different classes of hardware need to be controlled. One class is the vast arsenal of diverse laboratory hardware that interfaces with and is controlled from a typical PC. The other is specialized real-time hardware that requires tight coupling and a low-latency interface to a CPU. The ARTIQ code that describes a given experiment is composed of two types of "programs": regular Python code that is executed on the host and ARTIQ *kernels* that are executed on a *core device*. The CPU that executes the ARTIQ kernels has direct access to specialized programmable I/O timing logic (part of the *gateware*). The two types of code can invoke each other and transitions between them are seamless.
One class is the vast arsenal of diverse laboratory hardware that interfaces with and is controlled from a typical PC.
The other is specialized real-time hardware that requires tight coupling and a low-latency interface to a CPU.
The ARTIQ code that describes a given experiment is composed of two types of "programs":
regular Python code that is executed on the host and ARTIQ *kernels* that are executed on a *core device*.
The CPU that executes the ARTIQ kernels has direct access to specialized programmable I/O timing logic (part of the *gateware*).
The two types of code can invoke each other and transitions between them are seamless.
The ARTIQ kernels do not interface with the real-time gateware directly. The ARTIQ kernels do not interface with the real-time gateware directly. That would lead to imprecise, indeterminate, and generally unpredictable timing. Instead the CPU operates at one end of a bank of FIFO (first-in-first-out) buffers while the real-time gateware at the other end guarantees the *all or nothing* level of excellent timing precision.
That would lead to imprecise, indeterminate, and generally unpredictable timing.
Instead the CPU operates at one end of a bank of FIFO (first-in-first-out) buffers while the real-time gateware at the other end guarantees the *all or nothing* level of excellent timing precision.
A FIFO for an output channel holds timestamps and event data describing when and what is to be executed. A FIFO for an output channel holds timestamps and event data describing when and what is to be executed. The CPU feeds events into this FIFO. A FIFO for an input channel contains timestamps and event data for events that have been recorded by the real-time gateware and are waiting to be read out by the CPU on the other end.
The CPU feeds events into this FIFO.
A FIFO for an input channel contains timestamps and event data for events that have been recorded by the real-time gateware and are waiting to be read out by the CPU on the other end.
Timeline and terminology Timeline and terminology
------------------------ ------------------------
The set of all input and output events on all channels constitutes the *timeline*. The set of all input and output events on all channels constitutes the *timeline*. A high-resolution wall clock (``rtio_counter_mu``) counts clock cycles and manages the precise timing of the events. Output events are executed when their timestamp matches the current clock value. Input events are recorded when they reach the gateware and stamped with the current clock value accordingly.
A high-resolution wall clock (``rtio_counter_mu``) counts clock cycles and manages the precise timing of the events. Output events are executed when their timestamp matches the current clock value. Input events are recorded when they reach the gateware and stamped with the current clock value accordingly.
The kernel runtime environment maintains a timeline cursor (called ``now_mu``) used as the timestamp when output events are submitted to the FIFOs. Both ``now_mu`` and ``rtio_counter_mu`` are counted in integer *machine units,* or mu, rather than SI units. The machine unit represents the maximum resolution of RTIO timing in an ARTIQ system. The duration of a machine unit is the *reference period* of the system, and may be changed by the user, but normally corresponds to a duration of one nanosecond. The kernel runtime environment maintains a timeline cursor (called ``now_mu``) used as the timestamp when output events are submitted to the FIFOs. Both ``now_mu`` and ``rtio_counter_mu`` are counted in integer *machine units,* or mu, rather than SI units. The machine unit represents the maximum resolution of RTIO timing in an ARTIQ system. The duration of a machine unit is the *reference period* of the system, and may be changed by the user, but normally corresponds to a duration of one nanosecond.
@ -34,34 +21,22 @@ The timeline cursor ``now_mu`` can be moved forward or backward on the timeline
RTIO timestamps, the timeline cursor, and the ``rtio_counter_mu`` wall clock are all counted relative to the core device startup/boot time. The wall clock keeps running across experiments. RTIO timestamps, the timeline cursor, and the ``rtio_counter_mu`` wall clock are all counted relative to the core device startup/boot time. The wall clock keeps running across experiments.
Absolute timestamps can be large numbers. Absolute timestamps can be large numbers. They are represented internally as 64-bit integers. With a typical one-nanosecond machine unit, this covers a range of hundreds of years. Conversions between such a large integer number and a floating point representation can cause loss of precision through cancellation. When computing the difference of absolute timestamps, use ``self.core.mu_to_seconds(t2-t1)``, not ``self.core.mu_to_seconds(t2)-self.core.mu_to_seconds(t1)`` (see :meth:`~artiq.coredevice.core.Core.mu_to_seconds`). When accumulating time, do it in machine units and not in SI units, so that rounding errors do not accumulate.
They are represented internally as 64-bit integers.
With a typical one-nanosecond machine unit, this covers a range of hundreds of years.
Conversions between such a large integer number and a floating point representation can cause loss of precision through cancellation.
When computing the difference of absolute timestamps, use ``self.core.mu_to_seconds(t2-t1)``, not ``self.core.mu_to_seconds(t2)-self.core.mu_to_seconds(t1)`` (see :meth:`~artiq.coredevice.core.Core.mu_to_seconds`).
When accumulating time, do it in machine units and not in SI units, so that rounding errors do not accumulate.
.. note:: .. note::
Absolute timestamps are also referred to as *RTIO fine timestamps,* because they run on a significantly finer resolution than the timestamps provided by the so-called *coarse RTIO clock,* the actual clocking signal provided to or generated by the core device. Absolute timestamps are also referred to as *RTIO fine timestamps,* because they run on a significantly finer resolution than the timestamps of the so-called *coarse RTIO clock,* the actual clocking signal provided to or generated by the core device. The frequency of the coarse RTIO clock is set by the core device :ref:`clocking settings <core-device-clocking>` but is most commonly 125MHz, which corresponds to eight one-nanosecond machine units per coarse RTIO cycle.
The frequency of the coarse RTIO clock is set by the core device :ref:`clocking settings <core-device-clocking>` but is most commonly 125MHz, which corresponds to eight one-nanosecond machine units per coarse RTIO cycle.
The *coarse timestamp* of an event is its timestamp as according to the lower resolution of the coarse clock. The *coarse timestamp* of an event is its timestamp as according to the lower resolution of the coarse clock. It is in practice a truncated version of the fine timestamp. In general, ARTIQ offers *precision* on the fine level, but *operates* at the coarse level; this is rarely relevant to the user, but understanding it may clarify the behavior of some RTIO issues (e.g. sequence errors).
It is in practice a truncated version of the fine timestamp.
In general, ARTIQ offers *precision* on the fine level, but *operates* at the coarse level; this is rarely relevant to the user, but understanding it may clarify the behavior of some RTIO issues (e.g. sequence errors).
.. Related: https://github.com/m-labs/artiq/issues/1237 .. Related: https://github.com/m-labs/artiq/issues/1237
The following basic example shows how to place output events on the timeline. The following basic example shows how to place output events on the timeline. It emits a precisely timed 2 µs pulse::
It emits a precisely timed 2 µs pulse::
ttl.on() ttl.on()
delay(2*us) delay(2*us)
ttl.off() ttl.off()
The device ``ttl`` represents a single digital output channel (:class:`artiq.coredevice.ttl.TTLOut`). The device ``ttl`` represents a single digital output channel (:class:`artiq.coredevice.ttl.TTLOut`). The :meth:`artiq.coredevice.ttl.TTLOut.on` method places an rising edge on the timeline at the current cursor position (``now_mu``). Then the cursor is moved forward 2 µs and a falling edge is placed at the new cursor position. Later, when the wall clock reaches the respective timestamps, the RTIO gateware executes the two events.
The :meth:`artiq.coredevice.ttl.TTLOut.on` method places an rising edge on the timeline at the current cursor position (``now_mu``).
Then the cursor is moved forward 2 µs and a falling edge is placed at the new cursor position.
Later, when the wall clock reaches the respective timestamps, the RTIO gateware executes the two events.
The following diagram shows what is going on at the different levels of the software and gateware stack (assuming one machine unit of time is 1 ns): The following diagram shows what is going on at the different levels of the software and gateware stack (assuming one machine unit of time is 1 ns):
@ -88,7 +63,7 @@ This sequence is exactly equivalent to::
ttl.pulse(2*us) ttl.pulse(2*us)
This method :meth:`artiq.coredevice.ttl.TTLOut.pulse` advances the timeline cursor (using :func:`~artiq.language.core.delay` internally) by exactly the amount given. Other methods such as :meth:`~artiq.coredevice.ttl.TTLOut.on`, :meth:`~artiq.coredevice.ttl.TTLOut.off`, :meth:`~artiq.coredevice.ad9914.AD9914.set` do not modify the timeline cursor. The latter are called *zero-duration* methods. This method :meth:`artiq.coredevice.ttl.TTLOut.pulse` advances the timeline cursor (using :func:`~artiq.language.core.delay` internally) by exactly the amount given. ther methods such as :meth:`~artiq.coredevice.ttl.TTLOut.on`, :meth:`~artiq.coredevice.ttl.TTLOut.off`, :meth:`~artiq.coredevice.ad9914.AD9914.set` do not modify the timeline cursor. The latter are called *zero-duration* methods.
Output errors and exceptions Output errors and exceptions
---------------------------- ----------------------------
@ -96,11 +71,7 @@ Output errors and exceptions
Underflows Underflows
^^^^^^^^^^ ^^^^^^^^^^
A RTIO ouput event must always be programmed with a timestamp in the future. A RTIO ouput event must always be programmed with a timestamp in the future. In other words, the timeline cursor ``now_mu`` must be in advance of the current wall clock ``rtio_counter_mu``: the past cannot be altered. The following example tries to place a rising edge event on the timeline. If the current cursor is in the past, an :class:`artiq.coredevice.exceptions.RTIOUnderflow` exception is thrown. The experiment attempts to handle the exception by moving the cursor forward and repeating the programming of the rising edge::
In other words, the timeline cursor ``now_mu`` must be in advance of the current wall clock ``rtio_counter_mu``: the past cannot be altered.
The following example tries to place a rising edge event on the timeline.
If the current cursor is in the past, an :class:`artiq.coredevice.exceptions.RTIOUnderflow` exception is thrown.
The experiment attempts to handle the exception by moving the cursor forward and repeating the programming of the rising edge::
try: try:
ttl.on() ttl.on()
@ -109,8 +80,7 @@ The experiment attempts to handle the exception by moving the cursor forward and
delay(16.6667*ms) delay(16.6667*ms)
ttl.on() ttl.on()
Once the timeline cursor has overtaken the wall clock, the exception does not reoccur and the event can be scheduled successfully. Once the timeline cursor has overtaken the wall clock, the exception does not reoccur and the event can be scheduled successfully. This can also be thought of as adding positive slack to the system.
This can also be thought of as adding positive slack to the system.
.. wavedrom:: .. wavedrom::
@ -133,7 +103,7 @@ This can also be thought of as adding positive slack to the system.
To track down :class:`~artiq.coredevice.exceptions.RTIOUnderflow` exceptions in an experiment there are a few approaches: To track down :class:`~artiq.coredevice.exceptions.RTIOUnderflow` exceptions in an experiment there are a few approaches:
* Exception backtraces show where underflow has occurred while executing the code. * Exception backtraces show where underflow has occurred while executing the code.
* The :ref:`integrated logic analyzer <core-device-rtio-analyzer-tool>` shows the timeline context that lead to the exception. The analyzer is always active and supports plotting of RTIO slack. This may be useful to spot where and how an experiment has 'run out' of positive slack. * The :ref:`integrated logic analyzer <rtio-analyzer>` shows the timeline context that lead to the exception. The analyzer is always active and supports plotting of RTIO slack. This makes it possible to visually find where and how an experiment has 'run out' of positive slack.
.. _sequence-errors: .. _sequence-errors:
@ -149,7 +119,7 @@ If an event with a timestamp coarsely equal to or lesser than the previous times
By default, the ARTIQ SED has eight lanes, which normally suffices to avoid sequence errors, but problems may still occur if many (>8) events are issued to the gateware with interleaving timestamps. Due to the strict timing limitations imposed on RTIO gateware, it is not possible for the SED to rearrange events in a lane once submitted, nor to anticipate future events when making lane choices. This makes sequence errors fairly 'unintelligent', but also generally fairly easy to eliminate by manually rearranging the generation of events (*not* rearranging the timing of the events themselves, which is rarely necessary.) By default, the ARTIQ SED has eight lanes, which normally suffices to avoid sequence errors, but problems may still occur if many (>8) events are issued to the gateware with interleaving timestamps. Due to the strict timing limitations imposed on RTIO gateware, it is not possible for the SED to rearrange events in a lane once submitted, nor to anticipate future events when making lane choices. This makes sequence errors fairly 'unintelligent', but also generally fairly easy to eliminate by manually rearranging the generation of events (*not* rearranging the timing of the events themselves, which is rarely necessary.)
It is also possible to increase the number of SED lanes in the gateware, which will reduce the frequency of sequencing issues, but will also correspondingly put more stress on FPGA resources and timing. It is also possible to increase the number of SED lanes in the gateware, which will reduce the frequency of sequencing issues, but will correspondingly put more stress on FPGA resources and timing.
Other notes: Other notes:
@ -160,7 +130,7 @@ Other notes:
* Whether a particular sequence of timestamps causes a sequence error or not is fully deterministic (starting from a known RTIO state, e.g. after a reset). Adding a constant offset to the sequence will not affect the result. * Whether a particular sequence of timestamps causes a sequence error or not is fully deterministic (starting from a known RTIO state, e.g. after a reset). Adding a constant offset to the sequence will not affect the result.
.. note:: .. note::
To change the number of SED lanes, it is necessary to recompile the gateware and reflash your core device. Use the ``sed_lanes`` field in your system description file to set the value, then follow the instructions in :doc:`developing`. Alternatively, if you have an active firmware subscription with M-Labs, contact helpdesk@ for edited binaries. To change the number of SED lanes, it is necessary to recompile the gateware and reflash your core device. Use the ``sed_lanes`` field in your system description file to set the value, then follow the instructions in :doc:`building_developing`. Alternatively, if you have an active firmware subscription with M-Labs, contact helpdesk@ for edited binaries.
.. _collisions-busy-errors: .. _collisions-busy-errors:
@ -173,24 +143,20 @@ Like sequence errors, collisions originate in gateware and do not stop the execu
Busy errors Busy errors
^^^^^^^^^^^ ^^^^^^^^^^^
A busy error occurs when at least one output event could not be executed because the output channel was already busy executing an event. This differs from a collision error in that a collision is triggered when a sequence of events overwhelms *communication* with a channel, and a busy error is triggered when *execution* is overwhelmed. Busy errors are only possible in the context of single events with execution times longer than a coarse RTIO clock cycle; the exact parameters will depend on the nature of the output channel (e.g. specific peripheral device). A busy error occurs when at least one output event could not be executed because the output channel was already busy executing an event. This differs from a collision error in that a collision is triggered when a sequence of events overwhelms *communication* with a channel, and a busy error is triggered when *execution* is overwhelmed. Busy errors are only possible in the context of single events with execution times longer than a coarse RTIO clock cycle; the exact parameters will depend on the nature of the output channel (e.g. the specific peripheral device).
Offending event(s) are discarded and the problem is reported asynchronously via the core log. Offending event(s) are discarded and the problem is reported asynchronously via the core log.
Input channels and events Input channels and events
------------------------- -------------------------
Input channels detect events, timestamp them, and place them in a buffer for the experiment to read out. Input channels detect events, timestamp them, and place them in a buffer for the experiment to read out. The following example counts the rising edges occurring during a precisely timed 500 ns interval. If more than 20 rising edges are received, it outputs a pulse::
The following example counts the rising edges occurring during a precisely timed 500 ns interval.
If more than 20 rising edges are received, it outputs a pulse::
if input.count(input.gate_rising(500*ns)) > 20: if input.count(input.gate_rising(500*ns)) > 20:
delay(2*us) delay(2*us)
output.pulse(500*ns) output.pulse(500*ns)
Note that many input methods will necessarily involve the wall clock catching up to the timeline cursor or advancing before it. Note that many input methods will necessarily involve the wall clock catching up to the timeline cursor or advancing before it. This is to be expected: managing output events means working to plan the future, but managing input events means working to react to the past. For input channels, it is the past that is under discussion.
This is to be expected: managing output events means working to plan the future, but managing input events means working to react to the past.
For input channels, it is the past that is under discussion.
In this case, the :meth:`~artiq.coredevice.ttl.TTLInOut.gate_rising` waits for the duration of the 500ns interval (or *gate window*) and records an event for each rising edge. At the end of the interval it exits, leaving the timeline cursor at the end of the interval (``now_mu = rtio_counter_mu``). :meth:`~artiq.coredevice.ttl.TTLInOut.count` unloads these events from the input buffers and counts the number of events recorded, during which the wall clock necessarily advances (``rtio_counter_mu > now_mu``). Accordingly, before we place any further output events, a :func:`~artiq.language.core.delay` is necessary to re-establish positive slack. In this case, the :meth:`~artiq.coredevice.ttl.TTLInOut.gate_rising` waits for the duration of the 500ns interval (or *gate window*) and records an event for each rising edge. At the end of the interval it exits, leaving the timeline cursor at the end of the interval (``now_mu = rtio_counter_mu``). :meth:`~artiq.coredevice.ttl.TTLInOut.count` unloads these events from the input buffers and counts the number of events recorded, during which the wall clock necessarily advances (``rtio_counter_mu > now_mu``). Accordingly, before we place any further output events, a :func:`~artiq.language.core.delay` is necessary to re-establish positive slack.
@ -220,13 +186,7 @@ Similar situations arise with methods such as :meth:`TTLInOut.sample_get <artiq.
Overflow exceptions Overflow exceptions
^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^
The RTIO input channels buffer input events received while an input gate is open, or when using the sampling API (:meth:`TTLInOut.sample_input <artiq.coredevice.ttl.TTLInOut.sample_input>`) at certain points in time. The RTIO input channels buffer input events received while an input gate is open, or when using the sampling API (:meth:`TTLInOut.sample_input <artiq.coredevice.ttl.TTLInOut.sample_input>`) at certain points in time. The events are kept in a FIFO until the CPU reads them out via e.g. :meth:`~artiq.coredevice.ttl.TTLInOut.count`, :meth:`~artiq.coredevice.ttl.TTLInOut.timestamp_mu` or :meth:`~artiq.coredevice.ttl.TTLInOut.sample_get`. The size of these FIFOs is finite and specified in gateware; in practice, it is limited by the resources available to the FPGA, and therefore differs depending on the specific core device being used. If a FIFO is full and another event comes in, this causes an overflow condition. The condition is converted into an :class:`~artiq.coredevice.exceptions.RTIOOverflow` exception that is raised on a subsequent invocation of one of the readout methods. Overflow exceptions are generally best dealt with simply by reading out from the input buffers more frequently. In odd or particular cases, users may consider modifying the length of individual buffers in gateware.
The events are kept in a FIFO until the CPU reads them out via e.g. :meth:`~artiq.coredevice.ttl.TTLInOut.count`, :meth:`~artiq.coredevice.ttl.TTLInOut.timestamp_mu` or :meth:`~artiq.coredevice.ttl.TTLInOut.sample_get`.
The size of these FIFOs is finite and specified in gateware; in practice, it is limited by the resources available to the FPGA, and therefore differs depending on the specific core device being used.
If a FIFO is full and another event comes in, this causes an overflow condition.
The condition is converted into an :class:`~artiq.coredevice.exceptions.RTIOOverflow` exception that is raised on a subsequent invocation of one of the readout methods.
Overflow exceptions are generally best dealt with simply by reading out from the input buffers more frequently. In odd or particular cases, users may consider modifying the length of individual buffers in gateware.
.. note:: .. note::
It is not possible to provoke an :class:`~artiq.coredevice.exceptions.RTIOOverflow` on a RTIO output channel. While output buffers are also of finite size, and can be filled up, the CPU will simply stall the submission of further events until it is once again possible to buffer them. Among other things, this means that padding the timeline cursor with large amounts of positive slack is not always a valid strategy to avoid :class:`~artiq.coredevice.exceptions.RTIOOverflow` exceptions when generating fast event sequences. In practice only a fixed number of events can be generated in advance, and the rest of the processing will be carried out when the wall clock is much closer to ``now_mu``. It is not possible to provoke an :class:`~artiq.coredevice.exceptions.RTIOOverflow` on a RTIO output channel. While output buffers are also of finite size, and can be filled up, the CPU will simply stall the submission of further events until it is once again possible to buffer them. Among other things, this means that padding the timeline cursor with large amounts of positive slack is not always a valid strategy to avoid :class:`~artiq.coredevice.exceptions.RTIOOverflow` exceptions when generating fast event sequences. In practice only a fixed number of events can be generated in advance, and the rest of the processing will be carried out when the wall clock is much closer to ``now_mu``.
@ -251,8 +211,7 @@ Note that event spreading can be particularly helpful in DRTIO satellites, as it
Seamless handover Seamless handover
----------------- -----------------
The timeline cursor persists across kernel invocations. The timeline cursor persists across kernel invocations. This is demonstrated in the following example where a pulse is split across two kernels::
This is demonstrated in the following example where a pulse is split across two kernels::
def run(): def run():
k1() k1()

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