forked from M-Labs/artiq
Compare commits
300 Commits
Author | SHA1 | Date | |
---|---|---|---|
63db4af1fc | |||
626c709b4e | |||
8ff433596b | |||
dc21f0b6dd | |||
087eb514c1 | |||
|
7534a8fe04 | ||
7669cfce3d | |||
99fe642cab | |||
d8184cfb56 | |||
5b52f187d0 | |||
9c99d116bb | |||
366bb0fc59 | |||
c65520ab01 | |||
f5bbc688f0 | |||
598046b2e6 | |||
|
9a8338d71b | ||
|
db293d5ecd | ||
|
67dde30625 | ||
|
2508ff4812 | ||
3db8d2310c | |||
|
6097a32f4a | ||
592f0a7708 | |||
de8f8af3dd | |||
145a973213 | |||
fd664e82d1 | |||
07ba7a075f | |||
d5020f205e | |||
f4e3b3a0a5 | |||
9b5b4c07ea | |||
61c311fc54 | |||
270a417a28 | |||
e1aa8a5a8c | |||
8b335c4459 | |||
df1a389007 | |||
1ed04ebc91 | |||
cb836cd4a0 | |||
90764b04f9 | |||
6251e73459 | |||
e36916b931 | |||
de349e4c39 | |||
f2c13a5041 | |||
1583debfe7 | |||
a643da2c5e | |||
f5ff908098 | |||
e8bd99048e | |||
21944ff865 | |||
9882e16216 | |||
ffcf79b74e | |||
ea61d9bd39 | |||
cc40313501 | |||
2e9622b9d6 | |||
9d3204d019 | |||
6b6bcdb6d6 | |||
28654501af | |||
a2d341f4d6 | |||
5c21649d10 | |||
2b73d5a4c6 | |||
4a54241e1b | |||
f2f27e2d30 | |||
a56294f9de | |||
e85b640a83 | |||
1874438890 | |||
d935c22aad | |||
2c0a4c0bae | |||
bb8148e554 | |||
c5988ab48b | |||
045ebd53c4 | |||
5502aefa39 | |||
7af8511de3 | |||
f6cf66966d | |||
644e24be34 | |||
455e2a8f9d | |||
cbcf2bd84a | |||
05578b282e | |||
e57d18a41f | |||
b60a616e78 | |||
12682a277e | |||
|
1836ab5196 | ||
673fe29a12 | |||
52c07a2b14 | |||
292a07d830 | |||
bd7b07b0fd | |||
40ab4fee5b | |||
|
27d54cb8f3 | ||
9aae89be69 | |||
82505a2203 | |||
|
bca30becbf | ||
83254d13da | |||
e336e33a46 | |||
b4085312b0 | |||
65d20b7857 | |||
56bd975a34 | |||
4bf2331d6a | |||
|
02235b2d80 | ||
58ea3b5bcc | |||
fd2df7ce68 | |||
4178fed3f7 | |||
33b81d0e2e | |||
|
d602cdbc1f | ||
|
30b9479437 | ||
|
700812471c | ||
fcac2ea20e | |||
1fc6ab8c57 | |||
|
a570e6fd87 | ||
e06534913c | |||
65843696cd | |||
|
a761b9cf9c | ||
2fa60cc084 | |||
cdcaee80d1 | |||
6afbd90c59 | |||
049ef90220 | |||
b751e5455b | |||
333623e24b | |||
21cc0f7273 | |||
5b63cbe986 | |||
5b72a1faa5 | |||
1d093d0bce | |||
40227421a8 | |||
41203df735 | |||
38e0d6b953 | |||
|
d8a3da449e | ||
|
6f70d629cf | ||
|
03606f4d7e | ||
|
ed3b60b270 | ||
|
6a7926ddeb | ||
|
4e654011c3 | ||
|
5912142836 | ||
0c1ffa9f4f | |||
|
11bab7dadb | ||
|
a1fb6e1b70 | ||
00f2e3ae93 | |||
9aaec5db67 | |||
583a4ceadd | |||
dba877471f | |||
457b3edd44 | |||
fbb1a2c25d | |||
352cf907ee | |||
7a2b11cc54 | |||
792f3d1ca8 | |||
f6bc4d559a | |||
6b570c0484 | |||
70dce7c1dd | |||
e38dc59656 | |||
70d0f930c6 | |||
9bfac74c3f | |||
3d125e76b3 | |||
8e6fe41dc4 | |||
61e96b37f9 | |||
788e0cb3da | |||
62afcdaaf6 | |||
83bf984216 | |||
020fe6caf0 | |||
9e557cdbf9 | |||
b5787ac8f4 | |||
e4b4657a6d | |||
25b3553469 | |||
7e32f00121 | |||
76ead047bf | |||
cd4a0bb39e | |||
09128f87e6 | |||
33d5002f39 | |||
0eddd2bbaa | |||
d28355541a | |||
00b429b468 | |||
1b75bd1448 | |||
83922cce8b | |||
775aff730e | |||
c0805b9cb9 | |||
322f9f6e55 | |||
9f9acb3528 | |||
2241a32c9a | |||
e627aaeda0 | |||
cec24feac8 | |||
c8b797c5ac | |||
a547fac41b | |||
55a89b1dbb | |||
477320d72c | |||
1b28e38d51 | |||
468ace0e6d | |||
4fcb7cc408 | |||
0623480c82 | |||
e63ac3435f | |||
02479e4fb3 | |||
c5c5708f49 | |||
fb8dd01e8d | |||
e12bc586a5 | |||
d69c2b6aa2 | |||
994a936f26 | |||
75ffbeba4d | |||
fbf11ca002 | |||
61ac6da547 | |||
2ec01a3c45 | |||
bac22b7163 | |||
937f3811d1 | |||
ab090f9caf | |||
|
6698a6f80c | ||
191494e430 | |||
b588295063 | |||
e7f906e47a | |||
d6bcc64518 | |||
c4c932020a | |||
|
f7edb7b706 | ||
|
4bd328afe7 | ||
|
5dd2f7c4e8 | ||
|
9fbd6de30c | ||
378d962edb | |||
11c5f537bb | |||
ee3d93ce6a | |||
bd3bcbce64 | |||
|
0952c47934 | ||
e67bcfb36e | |||
52b0f30216 | |||
b0d2705c38 | |||
7e6a94c6b0 | |||
|
5aee0df9f0 | ||
8a6a6042fd | |||
7b13ffd9f3 | |||
8a775bc61b | |||
|
499eb42c3e | ||
c05b109d5f | |||
5dbf870e68 | |||
4cd9b8a8cf | |||
c9a4f3b9ee | |||
bff46fcf1e | |||
a5327e383c | |||
088ea36d53 | |||
a4bfb0d5dd | |||
0e1b29c5d9 | |||
81106f3567 | |||
9087c8698d | |||
c2e323662b | |||
26483d5daf | |||
d5f11387e8 | |||
158789b2e5 | |||
e0e96fb08b | |||
96e4949c37 | |||
8cfae86634 | |||
b2955f2bbe | |||
1032b9accf | |||
708262615d | |||
|
530f67f4cd | ||
4a88693c32 | |||
e9f1b9d4ff | |||
5ab2602802 | |||
86c6d11ed4 | |||
7a50afd9a9 | |||
af99a06919 | |||
8a178a628a | |||
810ab20425 | |||
a5437dd4f5 | |||
de5cd99787 | |||
c785c763fe | |||
8f0147f029 | |||
9c1482aa69 | |||
6044d810ca | |||
dcc5307771 | |||
d8cf91bfb2 | |||
24133ca04d | |||
868e4defda | |||
e7faca81fc | |||
b8c12976db | |||
91a4315386 | |||
688f3d9225 | |||
|
6f3322ea35 | ||
fdb0668c8a | |||
f1e8b8772a | |||
9386a7a16f | |||
bcc760a3fb | |||
a804be1a45 | |||
3cd6d50ad9 | |||
bd7daa5247 | |||
48cdf42016 | |||
c568109f3f | |||
81cda2380d | |||
ea22f67c4c | |||
aa21b78681 | |||
de1df88dcd | |||
0dc727c1fd | |||
45ef4d18d7 | |||
edfa5aa957 | |||
|
a9a74398ab | ||
e09fde6f51 | |||
02c1d2514f | |||
1ca7a3c6a3 | |||
|
102506d603 | ||
19132ae0e3 | |||
85545a8447 | |||
77580b5bf6 | |||
84b97976c0 | |||
ff79854c46 | |||
a167cc6043 | |||
1ee3988188 | |||
|
2c945f260e | ||
|
f432529014 | ||
bfeac30c44 | |||
a901ab74b5 | |||
|
8b64315ecf | ||
|
4509ad86f8 | ||
|
59302da71c | ||
ebc1e3fb76 |
18
README.rst
18
README.rst
@ -7,22 +7,18 @@
|
|||||||
ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is a leading-edge control and data acquisition system for quantum information experiments.
|
ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is a leading-edge control and data acquisition system for quantum information experiments.
|
||||||
It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Many laboratories around the world have adopted ARTIQ as their control system and some have `contributed <https://m-labs.hk/experiment-control/funding/>`_ to it.
|
It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Many laboratories around the world have adopted ARTIQ as their control system and some have `contributed <https://m-labs.hk/experiment-control/funding/>`_ to it.
|
||||||
|
|
||||||
The system features a high-level programming language that helps describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results.
|
The system features a high-level programming language, capable of describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results.
|
||||||
|
|
||||||
ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware <https://github.com/sinara-hw>`_, and in particular the Kasli FPGA carrier, is designed to work with ARTIQ.
|
ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware <https://github.com/sinara-hw>`_, and in particular the Kasli FPGA carrier, are designed to work with ARTIQ. ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers. Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort.
|
||||||
ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers.
|
|
||||||
Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and of a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort.
|
|
||||||
|
|
||||||
ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions.
|
ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions. Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration. Like any open-source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_.
|
||||||
Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration.
|
|
||||||
Like any open source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_.
|
|
||||||
|
|
||||||
ARTIQ is supported by M-Labs and developed openly.
|
ARTIQ is supported by M-Labs and developed openly. Components, features, fixes, improvements, and extensions are often `funded <https://m-labs.hk/experiment-control/funding/>`_ by and developed for the partnering research groups.
|
||||||
Components, features, fixes, improvements, and extensions are often `funded <https://m-labs.hk/experiment-control/funding/>`_ by and developed for the partnering research groups.
|
|
||||||
|
|
||||||
Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt5 <https://www.qt.io/>`_.
|
Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt6 <https://www.qt.io/>`_.
|
||||||
|
|
||||||
Website: https://m-labs.hk/artiq
|
| Website: https://m-labs.hk/experiment-control/artiq
|
||||||
|
| (US-hosted mirror: https://m-labs-intl.com/experiment-control/artiq)
|
||||||
|
|
||||||
`Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``.
|
`Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``.
|
||||||
|
|
||||||
|
@ -3,6 +3,29 @@
|
|||||||
Release notes
|
Release notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
|
ARTIQ-9 (Unreleased)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
* Dashboard:
|
||||||
|
- Experiment windows can have different colors, selected by the user.
|
||||||
|
- Zotino monitoring now displays the values in volts.
|
||||||
|
- Schedule display columns can now be reordered and shown/hidden using the table
|
||||||
|
header context menu.
|
||||||
|
- State files are now automatically backed up upon successful loading.
|
||||||
|
* afws_client now uses the "happy eyeballs" algorithm (RFC 6555) for a faster and more
|
||||||
|
reliable connection to the server.
|
||||||
|
* The Zadig driver installer was added to the MSYS2 offline installer.
|
||||||
|
* Fastino monitoring with Moninj is now supported.
|
||||||
|
* Qt6 support.
|
||||||
|
* Python 3.12 support.
|
||||||
|
* Compiler can now give automatic suggestions for ``kernel_invariants``.
|
||||||
|
* Idle kernels now restart when written with ``artiq_coremgmt`` and stop when erased/removed from config.
|
||||||
|
* New support for the EBAZ4205 Zynq-SoC control card.
|
||||||
|
* New core device driver for the AD9834 DDS, tested with the ZonRi Technology Co., Ltd. AD9834-Module.
|
||||||
|
* Support for coredevice reflashing through the new ``flash`` tool in ``artiq_coremgmt``.
|
||||||
|
* ``artiq_coremgmt`` now supports configuring satellites.
|
||||||
|
* ``artiq.coredevice.fmcdio_vhdci_eem`` has been removed.
|
||||||
|
|
||||||
ARTIQ-8
|
ARTIQ-8
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
@ -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")
|
||||||
|
557
artiq/appdirs.py
557
artiq/appdirs.py
@ -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)))
|
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
@ -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)
|
||||||
|
@ -92,7 +92,6 @@ class EmbeddingMap:
|
|||||||
# The exceptions declared here must be defined in `artiq.coredevice.exceptions`
|
# The exceptions declared here must be defined in `artiq.coredevice.exceptions`
|
||||||
# Verify synchronization by running the test cases in `artiq.test.coredevice.test_exceptions`
|
# Verify synchronization by running the test cases in `artiq.test.coredevice.test_exceptions`
|
||||||
self.preallocate_runtime_exception_names([
|
self.preallocate_runtime_exception_names([
|
||||||
"0:RuntimeError",
|
|
||||||
"RTIOUnderflow",
|
"RTIOUnderflow",
|
||||||
"RTIOOverflow",
|
"RTIOOverflow",
|
||||||
"RTIODestinationUnreachable",
|
"RTIODestinationUnreachable",
|
||||||
@ -100,10 +99,22 @@ class EmbeddingMap:
|
|||||||
"I2CError",
|
"I2CError",
|
||||||
"CacheError",
|
"CacheError",
|
||||||
"SPIError",
|
"SPIError",
|
||||||
"0:ZeroDivisionError",
|
|
||||||
"0:IndexError",
|
|
||||||
"UnwrapNoneError",
|
|
||||||
"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):
|
||||||
|
437
artiq/coredevice/ad9834.py
Normal file
437
artiq/coredevice/ad9834.py
Normal file
@ -0,0 +1,437 @@
|
|||||||
|
"""
|
||||||
|
RTIO Driver for the Analog Devices AD9834 DDS via 3-wire SPI interface.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# https://www.analog.com/media/en/technical-documentation/data-sheets/AD9834.pdf
|
||||||
|
# https://www.analog.com/media/en/technical-documentation/app-notes/an-1070.pdf
|
||||||
|
|
||||||
|
from numpy import int32
|
||||||
|
|
||||||
|
from artiq.coredevice import spi2 as spi
|
||||||
|
from artiq.experiment import *
|
||||||
|
from artiq.language.core import *
|
||||||
|
from artiq.language.types import *
|
||||||
|
from artiq.language.units import *
|
||||||
|
|
||||||
|
AD9834_B28 = 1 << 13
|
||||||
|
AD9834_HLB = 1 << 12
|
||||||
|
AD9834_FSEL = 1 << 11
|
||||||
|
AD9834_PSEL = 1 << 10
|
||||||
|
AD9834_PIN_SW = 1 << 9
|
||||||
|
AD9834_RESET = 1 << 8
|
||||||
|
AD9834_SLEEP1 = 1 << 7
|
||||||
|
AD9834_SLEEP12 = 1 << 6
|
||||||
|
AD9834_OPBITEN = 1 << 5
|
||||||
|
AD9834_SIGN_PIB = 1 << 4
|
||||||
|
AD9834_DIV2 = 1 << 3
|
||||||
|
AD9834_MODE = 1 << 1
|
||||||
|
|
||||||
|
AD9834_FREQ_REG_0 = 0b01 << 14
|
||||||
|
AD9834_FREQ_REG_1 = 0b10 << 14
|
||||||
|
FREQ_REGS = [AD9834_FREQ_REG_0, AD9834_FREQ_REG_1]
|
||||||
|
|
||||||
|
AD9834_PHASE_REG = 0b11 << 14
|
||||||
|
AD9834_PHASE_REG_0 = AD9834_PHASE_REG | (0 << 13)
|
||||||
|
AD9834_PHASE_REG_1 = AD9834_PHASE_REG | (1 << 13)
|
||||||
|
PHASE_REGS = [AD9834_PHASE_REG_0, AD9834_PHASE_REG_1]
|
||||||
|
|
||||||
|
|
||||||
|
class AD9834:
|
||||||
|
"""
|
||||||
|
AD9834 DDS driver.
|
||||||
|
|
||||||
|
This class provides control for the DDS AD9834.
|
||||||
|
|
||||||
|
The driver utilizes bit-controlled :const:`AD9834_FSEL`, :const:`AD9834_PSEL`, and
|
||||||
|
:const:`AD9834_RESET`. To pin control ``FSELECT``, ``PSELECT``, and ``RESET`` set
|
||||||
|
:const:`AD9834_PIN_SW`. The ``ctrl_reg`` attribute is used to maintain the state of
|
||||||
|
the control register, enabling persistent management of various configurations.
|
||||||
|
|
||||||
|
:param spi_device: SPI bus device name.
|
||||||
|
:param spi_freq: SPI bus clock frequency (default: 10 MHz, max: 40 MHz).
|
||||||
|
:param clk_freq: DDS clock frequency (default: 75 MHz).
|
||||||
|
:param core_device: Core device name (default: "core").
|
||||||
|
"""
|
||||||
|
|
||||||
|
kernel_invariants = {"core", "bus", "spi_freq", "clk_freq"}
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self, dmgr, spi_device, spi_freq=10 * MHz, clk_freq=75 * MHz, core_device="core"
|
||||||
|
):
|
||||||
|
self.core = dmgr.get(core_device)
|
||||||
|
self.bus = dmgr.get(spi_device)
|
||||||
|
assert spi_freq <= 40 * MHz, "SPI frequency exceeds maximum value of 40 MHz"
|
||||||
|
self.spi_freq = spi_freq
|
||||||
|
self.clk_freq = clk_freq
|
||||||
|
self.ctrl_reg = 0x0000 # Reset control register
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def write(self, data: TInt32):
|
||||||
|
"""
|
||||||
|
Write a 16-bit word to the AD9834.
|
||||||
|
|
||||||
|
This method sends a 16-bit data word to the AD9834 via the SPI bus. The input
|
||||||
|
data is left-shifted by 16 bits to ensure proper alignment for the SPI controller,
|
||||||
|
allowing for accurate processing of the command by the AD9834.
|
||||||
|
|
||||||
|
This method is used internally by other methods to update the control registers
|
||||||
|
and frequency settings of the AD9834. It should not be called directly unless
|
||||||
|
low-level register manipulation is required.
|
||||||
|
|
||||||
|
:param data: The 16-bit word to be sent to the AD9834.
|
||||||
|
"""
|
||||||
|
self.bus.write(data << 16)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def enable_reset(self):
|
||||||
|
"""
|
||||||
|
Enable the DDS reset.
|
||||||
|
|
||||||
|
This method sets :const:`AD9834_RESET`, putting the AD9834 into a reset state.
|
||||||
|
While in this state, the digital-to-analog converter (DAC) is not operational.
|
||||||
|
|
||||||
|
This method should be called during initialization or when a reset is required
|
||||||
|
to reinitialize the device and ensure proper operation.
|
||||||
|
"""
|
||||||
|
self.ctrl_reg |= AD9834_RESET
|
||||||
|
self.write(self.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def output_enable(self):
|
||||||
|
"""
|
||||||
|
Disable the DDS reset and start signal generation.
|
||||||
|
|
||||||
|
This method clears :const:`AD9834_RESET`, allowing the AD9834 to begin generating
|
||||||
|
signals. Once this method is called, the device will resume normal operation and
|
||||||
|
output the generated waveform.
|
||||||
|
|
||||||
|
This method should be called after configuration of the frequency and phase
|
||||||
|
settings to activate the output.
|
||||||
|
"""
|
||||||
|
self.ctrl_reg &= ~AD9834_RESET
|
||||||
|
self.write(self.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def init(self):
|
||||||
|
"""
|
||||||
|
Initialize the AD9834: configure the SPI bus and reset the DDS.
|
||||||
|
|
||||||
|
This method performs the necessary setup for the AD9834 device, including:
|
||||||
|
- Configuring the SPI bus parameters (clock polarity, data width, and frequency).
|
||||||
|
- Putting the AD9834 into a reset state to ensure proper initialization.
|
||||||
|
|
||||||
|
The SPI bus is configured to use 16 bits of data width with the clock frequency
|
||||||
|
provided as a parameter when creating the AD9834 instance. After configuring
|
||||||
|
the SPI bus, the method invokes :meth:`enable_reset()` to reset the AD9834.
|
||||||
|
This is an essential step to prepare the device for subsequent configuration
|
||||||
|
of frequency and phase.
|
||||||
|
|
||||||
|
This method should be called before any other operations are performed
|
||||||
|
on the AD9834 to ensure that the device is in a known state.
|
||||||
|
"""
|
||||||
|
self.bus.set_config(spi.SPI_CLK_POLARITY | spi.SPI_END, 16, self.spi_freq, 1)
|
||||||
|
self.enable_reset()
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_frequency_reg_msb(self, freq_reg: TInt32, word: TInt32):
|
||||||
|
"""
|
||||||
|
Set the fourteen most significant bits MSBs of the specified frequency register.
|
||||||
|
|
||||||
|
This method updates the specified frequency register with the provided MSB value.
|
||||||
|
It configures the control register to indicate that the MSB is being set.
|
||||||
|
|
||||||
|
:param freq_reg: The frequency register to write to (0-1).
|
||||||
|
:param word: The value to be written to the fourteen MSBs of the frequency register.
|
||||||
|
|
||||||
|
The method first clears the appropriate control bits, sets :const:`AD9834_HLB` to
|
||||||
|
indicate that the MSB is being sent, and then writes the updated control register
|
||||||
|
followed by the MSB value to the specified frequency register.
|
||||||
|
"""
|
||||||
|
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
|
||||||
|
self.ctrl_reg &= ~AD9834_B28
|
||||||
|
self.ctrl_reg |= AD9834_HLB
|
||||||
|
self.write(self.ctrl_reg)
|
||||||
|
self.write(FREQ_REGS[freq_reg] | (word & 0x3FFF))
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_frequency_reg_lsb(self, freq_reg: TInt32, word: TInt32):
|
||||||
|
"""
|
||||||
|
Set the fourteen least significant bits LSBs of the specified frequency register.
|
||||||
|
|
||||||
|
This method updates the specified frequency register with the provided LSB value.
|
||||||
|
It configures the control register to indicate that the LSB is being set.
|
||||||
|
|
||||||
|
:param freq_reg: The frequency register to write to (0-1).
|
||||||
|
:param word: The value to be written to the fourteen LSBs of the frequency register.
|
||||||
|
|
||||||
|
The method first clears the appropriate control bits and writes the updated control
|
||||||
|
register followed by the LSB value to the specified frequency register.
|
||||||
|
"""
|
||||||
|
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
|
||||||
|
self.ctrl_reg &= ~AD9834_B28
|
||||||
|
self.ctrl_reg &= ~AD9834_HLB
|
||||||
|
self.write(self.ctrl_reg)
|
||||||
|
self.write(FREQ_REGS[freq_reg] | (word & 0x3FFF))
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_frequency_reg(self, freq_reg: TInt32, freq_word: TInt32):
|
||||||
|
"""
|
||||||
|
Set the frequency for the specified frequency register using a precomputed frequency word.
|
||||||
|
|
||||||
|
This writes to the 28-bit frequency register in one transfer.
|
||||||
|
|
||||||
|
:param freq_reg: The frequency register to write to (0-1).
|
||||||
|
:param freq_word: The precomputed frequency word.
|
||||||
|
"""
|
||||||
|
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
|
||||||
|
self.ctrl_reg |= AD9834_B28
|
||||||
|
self.write(self.ctrl_reg)
|
||||||
|
lsb = freq_word & 0x3FFF
|
||||||
|
msb = (freq_word >> 14) & 0x3FFF
|
||||||
|
self.write(FREQ_REGS[freq_reg] | lsb)
|
||||||
|
self.write(FREQ_REGS[freq_reg] | msb)
|
||||||
|
|
||||||
|
@portable(flags={"fast-math"})
|
||||||
|
def frequency_to_ftw(self, frequency: TFloat) -> TInt32:
|
||||||
|
"""Return the 28-bit frequency tuning word corresponding to the given
|
||||||
|
frequency.
|
||||||
|
"""
|
||||||
|
assert frequency <= 37.5 * MHz, "Frequency exceeds maximum value of 37.5 MHz"
|
||||||
|
return int((frequency * (1 << 28)) / self.clk_freq) & 0x0FFFFFFF
|
||||||
|
|
||||||
|
@portable(flags={"fast-math"})
|
||||||
|
def turns_to_pow(self, turns: TFloat) -> TInt32:
|
||||||
|
"""Return the 12-bit phase offset word corresponding to the given phase
|
||||||
|
in turns."""
|
||||||
|
assert 0.0 <= turns <= 1.0, "Turns exceeds range 0.0 - 1.0"
|
||||||
|
return int32(round(turns * 0x1000)) & int32(0x0FFF)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def select_frequency_reg(self, freq_reg: TInt32):
|
||||||
|
"""
|
||||||
|
Select the active frequency register for the phase accumulator.
|
||||||
|
|
||||||
|
This method chooses between the two available frequency registers in the AD9834 to
|
||||||
|
control the frequency of the output waveform. The control register is updated
|
||||||
|
to reflect the selected frequency register.
|
||||||
|
|
||||||
|
:param freq_reg: The frequency register to write to (0-1).
|
||||||
|
"""
|
||||||
|
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
|
||||||
|
if freq_reg:
|
||||||
|
self.ctrl_reg |= AD9834_FSEL
|
||||||
|
else:
|
||||||
|
self.ctrl_reg &= ~AD9834_FSEL
|
||||||
|
|
||||||
|
self.ctrl_reg &= ~AD9834_PIN_SW
|
||||||
|
self.write(self.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_phase_reg(self, phase_reg: TInt32, phase: TInt32):
|
||||||
|
"""
|
||||||
|
Set the phase for the specified phase register.
|
||||||
|
|
||||||
|
This method updates the specified phase register with the provided phase value.
|
||||||
|
|
||||||
|
:param phase_reg: The phase register to write to (0-1).
|
||||||
|
:param phase: The value to be written to the phase register.
|
||||||
|
|
||||||
|
The method masks the phase value to ensure it fits within the 12-bit limit
|
||||||
|
and writes it to the specified phase register.
|
||||||
|
"""
|
||||||
|
assert 0 <= phase_reg <= 1, "Invalid phase register index"
|
||||||
|
phase_word = phase & 0x0FFF
|
||||||
|
self.write(PHASE_REGS[phase_reg] | phase_word)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def select_phase_reg(self, phase_reg: TInt32):
|
||||||
|
"""
|
||||||
|
Select the active phase register for the phase accumulator.
|
||||||
|
|
||||||
|
This method chooses between the two available phase registers in the AD9834 to
|
||||||
|
control the phase of the output waveform. The control register is updated
|
||||||
|
to reflect the selected phase register.
|
||||||
|
|
||||||
|
:param phase_reg: The phase register to write to (0-1).
|
||||||
|
"""
|
||||||
|
assert 0 <= phase_reg <= 1, "Invalid phase register index"
|
||||||
|
if phase_reg:
|
||||||
|
self.ctrl_reg |= AD9834_PSEL
|
||||||
|
else:
|
||||||
|
self.ctrl_reg &= ~AD9834_PSEL
|
||||||
|
|
||||||
|
self.ctrl_reg &= ~AD9834_PIN_SW
|
||||||
|
self.write(self.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def sleep(self, dac_pd: bool = False, clk_dis: bool = False):
|
||||||
|
"""
|
||||||
|
Put the AD9834 into sleep mode by selectively powering down the DAC and/or disabling
|
||||||
|
the internal clock.
|
||||||
|
|
||||||
|
This method controls the sleep mode behavior of the AD9834 by setting or clearing the
|
||||||
|
corresponding bits in the control register. Two independent options can be specified:
|
||||||
|
|
||||||
|
:param dac_pd: Set to ``True`` to power down the DAC (:const:`AD9834_SLEEP12` is set).
|
||||||
|
``False`` will leave the DAC active.
|
||||||
|
:param clk_dis: Set to ``True`` to disable the internal clock (:const:`AD9834_SLEEP1` is set).
|
||||||
|
``False`` will keep the clock running.
|
||||||
|
|
||||||
|
Both options can be enabled independently, allowing the DAC and/or clock to be powered down as needed.
|
||||||
|
|
||||||
|
The method updates the control register and writes the changes to the AD9834 device.
|
||||||
|
"""
|
||||||
|
if dac_pd:
|
||||||
|
self.ctrl_reg |= AD9834_SLEEP12
|
||||||
|
else:
|
||||||
|
self.ctrl_reg &= ~AD9834_SLEEP12
|
||||||
|
|
||||||
|
if clk_dis:
|
||||||
|
self.ctrl_reg |= AD9834_SLEEP1
|
||||||
|
else:
|
||||||
|
self.ctrl_reg &= ~AD9834_SLEEP1
|
||||||
|
|
||||||
|
self.write(self.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def awake(self):
|
||||||
|
"""
|
||||||
|
Exit sleep mode and restore normal operation.
|
||||||
|
|
||||||
|
This method brings the AD9834 out of sleep mode by clearing any DAC power-down or
|
||||||
|
internal clock disable settings. It calls :meth:`sleep()` with no arguments,
|
||||||
|
effectively setting both ``dac_powerdown`` and ``internal_clk_disable`` to ``False``.
|
||||||
|
|
||||||
|
The device will resume generating output based on the current frequency and phase
|
||||||
|
settings.
|
||||||
|
"""
|
||||||
|
self.sleep()
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def config_sign_bit_out(
|
||||||
|
self,
|
||||||
|
high_z: bool = False,
|
||||||
|
msb_2: bool = False,
|
||||||
|
msb: bool = False,
|
||||||
|
comp_out: bool = False,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Configure the ``SIGN BIT OUT`` pin for various output modes.
|
||||||
|
|
||||||
|
This method sets the output mode for the ``SIGN BIT OUT`` pin of the AD9834 based on the provided flags.
|
||||||
|
The user can enable one of several modes, including high impedance, MSB/2 output, MSB output,
|
||||||
|
or comparator output. These modes are mutually exclusive, and passing ``True`` to one flag will
|
||||||
|
configure the corresponding mode, while other flags should be left as ``False``.
|
||||||
|
|
||||||
|
:param high_z: Set to ``True`` to place the ``SIGN BIT OUT`` pin in high impedance (disabled) mode.
|
||||||
|
:param msb_2: Set to ``True`` to output DAC Data MSB divided by 2 on the ``SIGN BIT OUT`` pin.
|
||||||
|
:param msb: Set to ``True`` to output DAC Data MSB on the ``SIGN BIT OUT`` pin.
|
||||||
|
:param comp_out: Set to ``True`` to output the comparator signal on the ``SIGN BIT OUT`` pin.
|
||||||
|
|
||||||
|
Only one flag should be set to ``True`` at a time. If no valid mode is selected, the ``SIGN BIT OUT``
|
||||||
|
pin will default to high impedance mode.
|
||||||
|
|
||||||
|
The method updates the control register with the appropriate configuration and writes it to the AD9834.
|
||||||
|
"""
|
||||||
|
if high_z:
|
||||||
|
self.ctrl_reg &= ~AD9834_OPBITEN
|
||||||
|
elif msb_2:
|
||||||
|
self.ctrl_reg |= AD9834_OPBITEN
|
||||||
|
self.ctrl_reg &= ~(AD9834_MODE | AD9834_SIGN_PIB | AD9834_DIV2)
|
||||||
|
elif msb:
|
||||||
|
self.ctrl_reg |= AD9834_OPBITEN | AD9834_DIV2
|
||||||
|
self.ctrl_reg &= ~(AD9834_MODE | AD9834_SIGN_PIB)
|
||||||
|
elif comp_out:
|
||||||
|
self.ctrl_reg |= AD9834_OPBITEN | AD9834_SIGN_PIB | AD9834_DIV2
|
||||||
|
self.ctrl_reg &= ~AD9834_MODE
|
||||||
|
else:
|
||||||
|
self.ctrl_reg &= ~AD9834_OPBITEN
|
||||||
|
|
||||||
|
self.write(self.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def enable_triangular_waveform(self):
|
||||||
|
"""
|
||||||
|
Enable triangular waveform generation.
|
||||||
|
|
||||||
|
This method configures the AD9834 to output a triangular waveform. It does so
|
||||||
|
by clearing :const:`AD9834_OPBITEN` in the control register and setting :const:`AD9834_MODE`.
|
||||||
|
Once this method is called, the AD9834 will begin generating a triangular waveform
|
||||||
|
at the frequency set for the selected frequency register.
|
||||||
|
|
||||||
|
This method should be called when a triangular waveform is desired for signal
|
||||||
|
generation. Ensure that the frequency is set appropriately before invoking this method.
|
||||||
|
"""
|
||||||
|
self.ctrl_reg &= ~AD9834_OPBITEN
|
||||||
|
self.ctrl_reg |= AD9834_MODE
|
||||||
|
self.write(self.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def disable_triangular_waveform(self):
|
||||||
|
"""
|
||||||
|
Disable triangular waveform generation.
|
||||||
|
|
||||||
|
This method disables the triangular waveform output by clearing :const:`AD9834_MODE`.
|
||||||
|
After invoking this method, the AD9834 will cease generating a triangular waveform.
|
||||||
|
The device can then be configured to output other waveform types if needed.
|
||||||
|
|
||||||
|
This method should be called when switching to a different waveform type or
|
||||||
|
when the triangular waveform is no longer required.
|
||||||
|
"""
|
||||||
|
self.ctrl_reg &= ~AD9834_MODE
|
||||||
|
self.write(self.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_mu(
|
||||||
|
self,
|
||||||
|
freq_word: TInt32 = 0,
|
||||||
|
phase_word: TInt32 = 0,
|
||||||
|
freq_reg: TInt32 = 0,
|
||||||
|
phase_reg: TInt32 = 0,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Set DDS frequency and phase in machine units.
|
||||||
|
|
||||||
|
This method updates the specified frequency and phase registers with the provided
|
||||||
|
machine units, selects the corresponding registers, and enables the output.
|
||||||
|
|
||||||
|
:param freq_word: Frequency tuning word (28-bit).
|
||||||
|
:param phase_word: Phase tuning word (12-bit).
|
||||||
|
:param freq_reg: Frequency register to write to (0 or 1).
|
||||||
|
:param phase_reg: Phase register to write to (0 or 1).
|
||||||
|
"""
|
||||||
|
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
|
||||||
|
assert 0 <= phase_reg <= 1, "Invalid phase register index"
|
||||||
|
|
||||||
|
self.set_frequency_reg_lsb(freq_reg, freq_word & 0x3FFF)
|
||||||
|
self.set_frequency_reg_msb(freq_reg, (freq_word >> 14) & 0x3FFF)
|
||||||
|
self.set_phase_reg(phase_reg, phase_word)
|
||||||
|
self.select_frequency_reg(freq_reg)
|
||||||
|
self.select_phase_reg(phase_reg)
|
||||||
|
self.output_enable()
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set(
|
||||||
|
self,
|
||||||
|
frequency: TFloat = 0.0,
|
||||||
|
phase: TFloat = 0.0,
|
||||||
|
freq_reg: TInt32 = 0,
|
||||||
|
phase_reg: TInt32 = 0,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Set DDS frequency in Hz and phase using fractional turns.
|
||||||
|
|
||||||
|
This method converts the specified frequency and phase to their corresponding
|
||||||
|
machine units, updates the selected registers, and enables the output.
|
||||||
|
|
||||||
|
:param frequency: Frequency in Hz.
|
||||||
|
:param phase: Phase in fractional turns (e.g., 0.5 for 180 degrees).
|
||||||
|
:param freq_reg: Frequency register to write to (0 or 1).
|
||||||
|
:param phase_reg: Phase register to write to (0 or 1).
|
||||||
|
"""
|
||||||
|
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
|
||||||
|
assert 0 <= phase_reg <= 1, "Invalid phase register index"
|
||||||
|
|
||||||
|
freq_word = self.frequency_to_ftw(frequency)
|
||||||
|
phase_word = self.turns_to_pow(phase)
|
||||||
|
self.set_mu(freq_word, phase_word, freq_reg, phase_reg)
|
@ -181,25 +181,25 @@ class AnalyzerProxyReceiver:
|
|||||||
async def _receive_cr(self):
|
async def _receive_cr(self):
|
||||||
try:
|
try:
|
||||||
while True:
|
while True:
|
||||||
endian_byte = await self.reader.read(1)
|
data = bytearray()
|
||||||
if endian_byte == b"E":
|
data.extend(await self.reader.read(1))
|
||||||
endian = '>'
|
if len(data) == 0:
|
||||||
elif endian_byte == b"e":
|
|
||||||
endian = '<'
|
|
||||||
elif endian_byte == b"":
|
|
||||||
# EOF reached, connection lost
|
# EOF reached, connection lost
|
||||||
return
|
return
|
||||||
|
if data[0] == ord("E"):
|
||||||
|
endian = '>'
|
||||||
|
elif data[0] == ord("e"):
|
||||||
|
endian = '<'
|
||||||
else:
|
else:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
payload_length_word = await self.reader.readexactly(4)
|
data.extend(await self.reader.readexactly(4))
|
||||||
payload_length = struct.unpack(endian + "I", payload_length_word)[0]
|
payload_length = struct.unpack(endian + "I", data[1:5])[0]
|
||||||
if payload_length > 10 * 512 * 1024:
|
if payload_length > 10 * 512 * 1024:
|
||||||
# 10x buffer size of firmware
|
# 10x buffer size of firmware
|
||||||
raise ValueError
|
raise ValueError
|
||||||
|
|
||||||
# The remaining header length is 11 bytes.
|
# The remaining header length is 11 bytes.
|
||||||
remaining_data = await self.reader.readexactly(payload_length + 11)
|
data.extend(await self.reader.readexactly(payload_length + 11))
|
||||||
data = endian_byte + payload_length_word + remaining_data
|
|
||||||
self.receive_cb(data)
|
self.receive_cb(data)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("analyzer receiver connection terminating with exception", exc_info=True)
|
logger.error("analyzer receiver connection terminating with exception", exc_info=True)
|
||||||
|
@ -705,8 +705,13 @@ class CommKernel:
|
|||||||
python_exn_type = embedding_map.retrieve_object(core_exn.id)
|
python_exn_type = embedding_map.retrieve_object(core_exn.id)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
python_exn = python_exn_type(
|
message = nested_exceptions[0][1].format(*nested_exceptions[0][2])
|
||||||
nested_exceptions[-1][1].format(*nested_exceptions[0][2]))
|
except:
|
||||||
|
message = nested_exceptions[0][1]
|
||||||
|
logger.error("Couldn't format exception message", exc_info=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
python_exn = python_exn_type(message)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
python_exn = RuntimeError(
|
python_exn = RuntimeError(
|
||||||
f"Exception type={python_exn_type}, which couldn't be "
|
f"Exception type={python_exn_type}, which couldn't be "
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
import binascii
|
||||||
import logging
|
import logging
|
||||||
|
import io
|
||||||
import struct
|
import struct
|
||||||
|
|
||||||
from sipyco.keepalive import create_connection
|
from sipyco.keepalive import create_connection
|
||||||
@ -23,6 +25,8 @@ class Request(Enum):
|
|||||||
|
|
||||||
DebugAllocator = 8
|
DebugAllocator = 8
|
||||||
|
|
||||||
|
Flash = 9
|
||||||
|
|
||||||
|
|
||||||
class Reply(Enum):
|
class Reply(Enum):
|
||||||
Success = 1
|
Success = 1
|
||||||
@ -46,15 +50,17 @@ class LogLevel(Enum):
|
|||||||
|
|
||||||
|
|
||||||
class CommMgmt:
|
class CommMgmt:
|
||||||
def __init__(self, host, port=1380):
|
def __init__(self, host, port=1380, drtio_dest=0):
|
||||||
self.host = host
|
self.host = host
|
||||||
self.port = port
|
self.port = port
|
||||||
|
self.drtio_dest = drtio_dest
|
||||||
|
|
||||||
def open(self):
|
def open(self):
|
||||||
if hasattr(self, "socket"):
|
if hasattr(self, "socket"):
|
||||||
return
|
return
|
||||||
self.socket = create_connection(self.host, self.port)
|
self.socket = create_connection(self.host, self.port)
|
||||||
self.socket.sendall(b"ARTIQ management\n")
|
self.socket.sendall(b"ARTIQ management\n")
|
||||||
|
self._write_int8(self.drtio_dest)
|
||||||
endian = self._read(1)
|
endian = self._read(1)
|
||||||
if endian == b"e":
|
if endian == b"e":
|
||||||
self.endian = "<"
|
self.endian = "<"
|
||||||
@ -194,3 +200,22 @@ class CommMgmt:
|
|||||||
|
|
||||||
def debug_allocator(self):
|
def debug_allocator(self):
|
||||||
self._write_header(Request.DebugAllocator)
|
self._write_header(Request.DebugAllocator)
|
||||||
|
|
||||||
|
def flash(self, bin_paths):
|
||||||
|
self._write_header(Request.Flash)
|
||||||
|
|
||||||
|
with io.BytesIO() as image_buf:
|
||||||
|
for filename in bin_paths:
|
||||||
|
with open(filename, "rb") as fi:
|
||||||
|
bin_ = fi.read()
|
||||||
|
if (len(bin_paths) > 1):
|
||||||
|
image_buf.write(
|
||||||
|
struct.pack(self.endian + "I", len(bin_)))
|
||||||
|
image_buf.write(bin_)
|
||||||
|
|
||||||
|
crc = binascii.crc32(image_buf.getvalue())
|
||||||
|
image_buf.write(struct.pack(self.endian + "I", crc))
|
||||||
|
|
||||||
|
self._write_bytes(image_buf.getvalue())
|
||||||
|
|
||||||
|
self._read_expect(Reply.RebootImminent)
|
||||||
|
@ -85,6 +85,8 @@ class Core:
|
|||||||
(optional).
|
(optional).
|
||||||
:param analyze_at_run_end: automatically trigger the core device analyzer
|
:param analyze_at_run_end: automatically trigger the core device analyzer
|
||||||
proxy after the Experiment's run stage finishes.
|
proxy after the Experiment's run stage finishes.
|
||||||
|
:param report_invariants: report variables which are not changed inside
|
||||||
|
kernels and are thus candidates for inclusion in kernel_invariants
|
||||||
"""
|
"""
|
||||||
|
|
||||||
kernel_invariants = {
|
kernel_invariants = {
|
||||||
@ -95,7 +97,8 @@ class Core:
|
|||||||
host, ref_period,
|
host, ref_period,
|
||||||
analyzer_proxy=None, analyze_at_run_end=False,
|
analyzer_proxy=None, analyze_at_run_end=False,
|
||||||
ref_multiplier=8,
|
ref_multiplier=8,
|
||||||
target="rv32g", satellite_cpu_targets={}):
|
target="rv32g", satellite_cpu_targets={},
|
||||||
|
report_invariants=False):
|
||||||
self.ref_period = ref_period
|
self.ref_period = ref_period
|
||||||
self.ref_multiplier = ref_multiplier
|
self.ref_multiplier = ref_multiplier
|
||||||
self.satellite_cpu_targets = satellite_cpu_targets
|
self.satellite_cpu_targets = satellite_cpu_targets
|
||||||
@ -107,6 +110,7 @@ class Core:
|
|||||||
self.comm = CommKernel(host)
|
self.comm = CommKernel(host)
|
||||||
self.analyzer_proxy_name = analyzer_proxy
|
self.analyzer_proxy_name = analyzer_proxy
|
||||||
self.analyze_at_run_end = analyze_at_run_end
|
self.analyze_at_run_end = analyze_at_run_end
|
||||||
|
self.report_invariants = report_invariants
|
||||||
|
|
||||||
self.first_run = True
|
self.first_run = True
|
||||||
self.dmgr = dmgr
|
self.dmgr = dmgr
|
||||||
@ -139,7 +143,8 @@ class Core:
|
|||||||
|
|
||||||
module = Module(stitcher,
|
module = Module(stitcher,
|
||||||
ref_period=self.ref_period,
|
ref_period=self.ref_period,
|
||||||
attribute_writeback=attribute_writeback)
|
attribute_writeback=attribute_writeback,
|
||||||
|
remarks=self.report_invariants)
|
||||||
target = target if target is not None else self.target_cls()
|
target = target if target is not None else self.target_cls()
|
||||||
|
|
||||||
library = target.compile_and_link([module])
|
library = target.compile_and_link([module])
|
||||||
|
@ -2,6 +2,7 @@ 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
|
||||||
@ -29,7 +30,12 @@ OSError = builtins.OSError
|
|||||||
|
|
||||||
|
|
||||||
class CoreException:
|
class CoreException:
|
||||||
"""Information about an exception raised or passed through the core device."""
|
"""Information about an exception raised or passed through the core device.
|
||||||
|
|
||||||
|
If the exception message contains positional format arguments, it
|
||||||
|
will attempt to substitute them with the provided parameters.
|
||||||
|
If the substitution fails, the original message will remain unchanged.
|
||||||
|
"""
|
||||||
def __init__(self, exceptions, exception_info, traceback, stack_pointers):
|
def __init__(self, exceptions, exception_info, traceback, stack_pointers):
|
||||||
self.exceptions = exceptions
|
self.exceptions = exceptions
|
||||||
self.exception_info = exception_info
|
self.exception_info = exception_info
|
||||||
@ -91,7 +97,10 @@ class CoreException:
|
|||||||
exn_id = int(exn_id)
|
exn_id = int(exn_id)
|
||||||
else:
|
else:
|
||||||
exn_id = 0
|
exn_id = 0
|
||||||
|
try:
|
||||||
lines.append("{}({}): {}".format(name, exn_id, message.format(*params)))
|
lines.append("{}({}): {}".format(name, exn_id, message.format(*params)))
|
||||||
|
except:
|
||||||
|
lines.append("{}({}): {}".format(name, exn_id, message))
|
||||||
zipped.append(((exception[3], exception[4], exception[5], exception[6],
|
zipped.append(((exception[3], exception[4], exception[5], exception[6],
|
||||||
None, []), None))
|
None, []), None))
|
||||||
|
|
||||||
@ -182,6 +191,7 @@ class SPIError(Exception):
|
|||||||
"""Raised when a SPI transaction fails."""
|
"""Raised when a SPI transaction fails."""
|
||||||
artiq_builtin = True
|
artiq_builtin = True
|
||||||
|
|
||||||
|
|
||||||
class UnwrapNoneError(Exception):
|
class UnwrapNoneError(Exception):
|
||||||
"""Raised when unwrapping a none Option."""
|
"""Raised when unwrapping a none Option."""
|
||||||
artiq_builtin = True
|
artiq_builtin = True
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
# Definitions for using the "FMC DIO 32ch LVDS a" card with the VHDCI-EEM breakout v1.1
|
|
||||||
|
|
||||||
eem_fmc_connections = {
|
|
||||||
0: [0, 8, 2, 3, 4, 5, 6, 7],
|
|
||||||
1: [1, 9, 10, 11, 12, 13, 14, 15],
|
|
||||||
2: [17, 16, 24, 19, 20, 21, 22, 23],
|
|
||||||
3: [18, 25, 26, 27, 28, 29, 30, 31],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def shiftreg_bits(eem, out_pins):
|
|
||||||
"""
|
|
||||||
Returns the bits that have to be set in the FMC card direction
|
|
||||||
shift register for the given EEM.
|
|
||||||
|
|
||||||
Takes a set of pin numbers (0-7) at the EEM. Return values
|
|
||||||
of this function for different EEMs should be ORed together.
|
|
||||||
"""
|
|
||||||
r = 0
|
|
||||||
for i in range(8):
|
|
||||||
if i not in out_pins:
|
|
||||||
lvds_line = eem_fmc_connections[eem][i]
|
|
||||||
# lines are swapped in pairs to ease PCB routing
|
|
||||||
# at the shift register
|
|
||||||
shift = lvds_line ^ 1
|
|
||||||
r |= 1 << shift
|
|
||||||
return r
|
|
||||||
|
|
||||||
|
|
||||||
dio_bank0_out_pins = set(range(4))
|
|
||||||
dio_bank1_out_pins = set(range(4, 8))
|
|
||||||
urukul_out_pins = {
|
|
||||||
0, # clk
|
|
||||||
1, # mosi
|
|
||||||
3, 4, 5, # cs_n
|
|
||||||
6, # io_update
|
|
||||||
7, # dds_reset
|
|
||||||
}
|
|
||||||
urukul_aux_out_pins = {
|
|
||||||
4, # sw0
|
|
||||||
5, # sw1
|
|
||||||
6, # sw2
|
|
||||||
7, # sw3
|
|
||||||
}
|
|
||||||
zotino_out_pins = {
|
|
||||||
0, # clk
|
|
||||||
1, # mosi
|
|
||||||
3, 4, # cs_n
|
|
||||||
5, # ldac_n
|
|
||||||
7, # clr_n
|
|
||||||
}
|
|
@ -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:
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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
|
||||||
@ -28,6 +28,7 @@ class _ArgumentEditor(EntryTreeWidget):
|
|||||||
def __init__(self, manager, dock, expurl):
|
def __init__(self, manager, dock, expurl):
|
||||||
self.manager = manager
|
self.manager = manager
|
||||||
self.expurl = expurl
|
self.expurl = expurl
|
||||||
|
self.dock = dock
|
||||||
|
|
||||||
EntryTreeWidget.__init__(self)
|
EntryTreeWidget.__init__(self)
|
||||||
|
|
||||||
@ -44,12 +45,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()
|
||||||
@ -78,6 +79,18 @@ class _ArgumentEditor(EntryTreeWidget):
|
|||||||
argument["desc"] = procdesc
|
argument["desc"] = procdesc
|
||||||
argument["state"] = state
|
argument["state"] = state
|
||||||
self.update_argument(name, argument)
|
self.update_argument(name, argument)
|
||||||
|
self.dock.apply_colors()
|
||||||
|
|
||||||
|
def apply_color(self, palette, color):
|
||||||
|
self.setPalette(palette)
|
||||||
|
for child in self.findChildren(QtWidgets.QWidget):
|
||||||
|
child.setPalette(palette)
|
||||||
|
child.setAutoFillBackground(True)
|
||||||
|
items = self.findItems("*",
|
||||||
|
QtCore.Qt.MatchFlag.MatchWildcard | QtCore.Qt.MatchFlag.MatchRecursive)
|
||||||
|
for item in items:
|
||||||
|
for column in range(item.columnCount()):
|
||||||
|
item.setBackground(column, QtGui.QColor(color))
|
||||||
|
|
||||||
# Hooks that allow user-supplied argument editors to react to imminent user
|
# Hooks that allow user-supplied argument editors to react to imminent user
|
||||||
# actions. Here, we always keep the manager-stored submission arguments
|
# actions. Here, we always keep the manager-stored submission arguments
|
||||||
@ -92,6 +105,19 @@ class _ArgumentEditor(EntryTreeWidget):
|
|||||||
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||||
|
|
||||||
|
|
||||||
|
class _ColoredTitleBar(QtWidgets.QProxyStyle):
|
||||||
|
def __init__(self, color, style=None):
|
||||||
|
super().__init__(style)
|
||||||
|
self.color = color
|
||||||
|
|
||||||
|
def drawComplexControl(self, control, option, painter, widget=None):
|
||||||
|
if control == QtWidgets.QStyle.ComplexControl.CC_TitleBar:
|
||||||
|
option = QtWidgets.QStyleOptionTitleBar(option)
|
||||||
|
option.palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColor(self.color))
|
||||||
|
option.palette.setColor(QtGui.QPalette.ColorRole.Highlight, QtGui.QColor(self.color))
|
||||||
|
self.baseStyle().drawComplexControl(control, option, painter, widget)
|
||||||
|
|
||||||
|
|
||||||
class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
sigClosed = QtCore.pyqtSignal()
|
sigClosed = QtCore.pyqtSignal()
|
||||||
|
|
||||||
@ -101,7 +127,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()
|
||||||
@ -187,6 +213,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||||||
devarg_override.lineEdit().setPlaceholderText("Override device arguments")
|
devarg_override.lineEdit().setPlaceholderText("Override device arguments")
|
||||||
devarg_override.lineEdit().setClearButtonEnabled(True)
|
devarg_override.lineEdit().setClearButtonEnabled(True)
|
||||||
devarg_override.insertItem(0, "core:analyze_at_run_end=True")
|
devarg_override.insertItem(0, "core:analyze_at_run_end=True")
|
||||||
|
devarg_override.insertItem(1, "core:report_invariants=True")
|
||||||
self.layout.addWidget(devarg_override, 2, 3)
|
self.layout.addWidget(devarg_override, 2, 3)
|
||||||
|
|
||||||
devarg_override.setCurrentText(options["devarg_override"])
|
devarg_override.setCurrentText(options["devarg_override"])
|
||||||
@ -237,21 +264,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)
|
||||||
|
|
||||||
@ -302,14 +329,61 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||||||
self.argeditor = editor_class(self.manager, self, self.expurl)
|
self.argeditor = editor_class(self.manager, self, self.expurl)
|
||||||
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
||||||
self.argeditor.restore_state(argeditor_state)
|
self.argeditor.restore_state(argeditor_state)
|
||||||
|
self.apply_colors()
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
def contextMenuEvent(self, event):
|
||||||
menu = QtWidgets.QMenu(self)
|
menu = QtWidgets.QMenu(self)
|
||||||
|
select_title_bar_color = menu.addAction("Select title bar color")
|
||||||
|
select_window_color = menu.addAction("Select window color")
|
||||||
|
reset_colors = menu.addAction("Reset to default colors")
|
||||||
|
menu.addSeparator()
|
||||||
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 == select_title_bar_color:
|
||||||
|
self.select_color("title_bar")
|
||||||
|
elif action == select_window_color:
|
||||||
|
self.select_color("window")
|
||||||
|
elif action == reset_colors:
|
||||||
|
self.reset_colors()
|
||||||
|
elif action == reset_sched:
|
||||||
asyncio.ensure_future(self._recompute_sched_options_task())
|
asyncio.ensure_future(self._recompute_sched_options_task())
|
||||||
|
|
||||||
|
def select_color(self, key):
|
||||||
|
color = QtWidgets.QColorDialog.getColor(
|
||||||
|
title=f"Select {key.replace('_', ' ').title()} color")
|
||||||
|
if color.isValid():
|
||||||
|
self.manager.set_color(self.expurl, key, color.name())
|
||||||
|
self.apply_colors()
|
||||||
|
|
||||||
|
def apply_colors(self):
|
||||||
|
colors = self.manager.get_colors(self.expurl)
|
||||||
|
if colors is None:
|
||||||
|
palette = QtWidgets.QApplication.palette()
|
||||||
|
colors = {
|
||||||
|
"window": palette.color(QtGui.QPalette.ColorRole.Window).name(),
|
||||||
|
"title_bar": palette.color(QtGui.QPalette.ColorRole.Highlight).name(),
|
||||||
|
}
|
||||||
|
self.manager.colors[self.expurl] = colors
|
||||||
|
colors["window_text"] = "#000000" if QtGui.QColor(
|
||||||
|
colors["window"]).lightness() > 128 else "#FFFFFF"
|
||||||
|
self.modify_palette(colors)
|
||||||
|
self.setStyle(_ColoredTitleBar(colors["title_bar"]))
|
||||||
|
self.argeditor.apply_color(self.palette(), (colors["window"]))
|
||||||
|
|
||||||
|
def modify_palette(self, colors):
|
||||||
|
palette = self.palette()
|
||||||
|
palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColor(colors["window"]))
|
||||||
|
palette.setColor(QtGui.QPalette.ColorRole.Base, QtGui.QColor(colors["window"]))
|
||||||
|
palette.setColor(QtGui.QPalette.ColorRole.Button, QtGui.QColor(colors["window"]))
|
||||||
|
palette.setColor(QtGui.QPalette.ColorRole.Text, QtGui.QColor(colors["window_text"]))
|
||||||
|
palette.setColor(QtGui.QPalette.ColorRole.ButtonText, QtGui.QColor(colors["window_text"]))
|
||||||
|
palette.setColor(QtGui.QPalette.ColorRole.WindowText, QtGui.QColor(colors["window_text"]))
|
||||||
|
self.setPalette(palette)
|
||||||
|
|
||||||
|
def reset_colors(self):
|
||||||
|
self.manager.reset_colors(self.expurl)
|
||||||
|
self.apply_colors()
|
||||||
|
|
||||||
async def _recompute_sched_options_task(self):
|
async def _recompute_sched_options_task(self):
|
||||||
try:
|
try:
|
||||||
expdesc, _ = await self.manager.compute_expdesc(self.expurl)
|
expdesc, _ = await self.manager.compute_expdesc(self.expurl)
|
||||||
@ -423,7 +497,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:
|
||||||
@ -456,6 +530,7 @@ class ExperimentManager:
|
|||||||
self.submission_options = dict()
|
self.submission_options = dict()
|
||||||
self.submission_arguments = dict()
|
self.submission_arguments = dict()
|
||||||
self.argument_ui_names = dict()
|
self.argument_ui_names = dict()
|
||||||
|
self.colors = dict()
|
||||||
|
|
||||||
self.datasets = dict()
|
self.datasets = dict()
|
||||||
dataset_sub.add_setmodel_callback(self.set_dataset_model)
|
dataset_sub.add_setmodel_callback(self.set_dataset_model)
|
||||||
@ -467,10 +542,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):
|
||||||
@ -482,6 +557,18 @@ class ExperimentManager:
|
|||||||
def set_schedule_model(self, model):
|
def set_schedule_model(self, model):
|
||||||
self.schedule = model.backing_store
|
self.schedule = model.backing_store
|
||||||
|
|
||||||
|
def set_color(self, expurl, key, value):
|
||||||
|
if expurl not in self.colors:
|
||||||
|
self.colors[expurl] = {}
|
||||||
|
self.colors[expurl][key] = value
|
||||||
|
|
||||||
|
def get_colors(self, expurl):
|
||||||
|
return self.colors.get(expurl)
|
||||||
|
|
||||||
|
def reset_colors(self, expurl):
|
||||||
|
if expurl in self.colors:
|
||||||
|
del self.colors[expurl]
|
||||||
|
|
||||||
def resolve_expurl(self, expurl):
|
def resolve_expurl(self, expurl):
|
||||||
if expurl[:5] == "repo:":
|
if expurl[:5] == "repo:":
|
||||||
expinfo = self.explist[expurl[5:]]
|
expinfo = self.explist[expurl[5:]]
|
||||||
@ -589,8 +676,9 @@ 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.apply_colors()
|
||||||
dock.show()
|
dock.show()
|
||||||
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
|
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
|
||||||
if expurl in self.dock_states:
|
if expurl in self.dock_states:
|
||||||
@ -707,7 +795,8 @@ class ExperimentManager:
|
|||||||
"arguments": self.submission_arguments,
|
"arguments": self.submission_arguments,
|
||||||
"docks": self.dock_states,
|
"docks": self.dock_states,
|
||||||
"argument_uis": self.argument_ui_names,
|
"argument_uis": self.argument_ui_names,
|
||||||
"open_docks": set(self.open_experiments.keys())
|
"open_docks": set(self.open_experiments.keys()),
|
||||||
|
"colors": self.colors
|
||||||
}
|
}
|
||||||
|
|
||||||
def restore_state(self, state):
|
def restore_state(self, state):
|
||||||
@ -718,6 +807,7 @@ class ExperimentManager:
|
|||||||
self.submission_options = state["options"]
|
self.submission_options = state["options"]
|
||||||
self.submission_arguments = state["arguments"]
|
self.submission_arguments = state["arguments"]
|
||||||
self.argument_ui_names = state.get("argument_uis", {})
|
self.argument_ui_names = state.get("argument_uis", {})
|
||||||
|
self.colors = state.get("colors", {})
|
||||||
for expurl in state["open_docks"]:
|
for expurl in state["open_docks"]:
|
||||||
self.open_experiment(expurl)
|
self.open_experiment(expurl)
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
@ -4,13 +4,14 @@ import textwrap
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt6 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe
|
from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe
|
||||||
from artiq.coredevice.ad9912_reg import AD9912_SER_CONF
|
from artiq.coredevice.ad9912_reg import AD9912_SER_CONF
|
||||||
from artiq.gui.tools import LayoutWidget, QDockWidgetCloseDetect, DoubleClickLineEdit
|
from artiq.gui.tools import LayoutWidget, QDockWidgetCloseDetect, DoubleClickLineEdit
|
||||||
from artiq.gui.dndwidgets import VDragScrollArea, DragDropFlowLayoutWidget
|
from artiq.gui.dndwidgets import VDragScrollArea, DragDropFlowLayoutWidget
|
||||||
from artiq.gui.models import DictSyncTreeSepModel
|
from artiq.gui.models import DictSyncTreeSepModel
|
||||||
|
from artiq.tools import elide
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -22,39 +23,45 @@ class _CancellableLineEdit(QtWidgets.QLineEdit):
|
|||||||
|
|
||||||
def keyPressEvent(self, event):
|
def keyPressEvent(self, event):
|
||||||
key = event.key()
|
key = event.key()
|
||||||
if key == QtCore.Qt.Key_Escape:
|
if key == QtCore.Qt.Key.Key_Escape:
|
||||||
self.esc_cb(event)
|
self.esc_cb(event)
|
||||||
QtWidgets.QLineEdit.keyPressEvent(self, event)
|
QtWidgets.QLineEdit.keyPressEvent(self, event)
|
||||||
|
|
||||||
|
|
||||||
class _TTLWidget(QtWidgets.QFrame):
|
class _MoninjWidget(QtWidgets.QFrame):
|
||||||
def __init__(self, dm, channel, force_out, title):
|
def __init__(self, title):
|
||||||
QtWidgets.QFrame.__init__(self)
|
QtWidgets.QFrame.__init__(self)
|
||||||
|
self.setFrameShape(QtWidgets.QFrame.Shape.Box)
|
||||||
|
self.setFrameShadow(QtWidgets.QFrame.Shadow.Raised)
|
||||||
|
self.setFixedHeight(100)
|
||||||
|
self.setFixedWidth(150)
|
||||||
|
self.grid = QtWidgets.QGridLayout()
|
||||||
|
self.grid.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.grid.setHorizontalSpacing(0)
|
||||||
|
self.grid.setVerticalSpacing(0)
|
||||||
|
self.setLayout(self.grid)
|
||||||
|
title = elide(title, 20)
|
||||||
|
label = QtWidgets.QLabel(title)
|
||||||
|
label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||||
|
label.setSizePolicy(QtWidgets.QSizePolicy.Policy.Ignored,
|
||||||
|
QtWidgets.QSizePolicy.Policy.Preferred)
|
||||||
|
self.grid.addWidget(label, 1, 1)
|
||||||
|
|
||||||
|
|
||||||
|
class _TTLWidget(_MoninjWidget):
|
||||||
|
def __init__(self, dm, channel, force_out, title):
|
||||||
|
_MoninjWidget.__init__(self, title)
|
||||||
|
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
self.set_mode = dm.ttl_set_mode
|
self.set_mode = dm.ttl_set_mode
|
||||||
self.force_out = force_out
|
self.force_out = force_out
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
||||||
self.setFrameShape(QtWidgets.QFrame.Box)
|
|
||||||
self.setFrameShadow(QtWidgets.QFrame.Raised)
|
|
||||||
|
|
||||||
grid = QtWidgets.QGridLayout()
|
|
||||||
grid.setContentsMargins(0, 0, 0, 0)
|
|
||||||
grid.setHorizontalSpacing(0)
|
|
||||||
grid.setVerticalSpacing(0)
|
|
||||||
self.setLayout(grid)
|
|
||||||
label = QtWidgets.QLabel(title)
|
|
||||||
label.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
label.setSizePolicy(QtWidgets.QSizePolicy.Ignored,
|
|
||||||
QtWidgets.QSizePolicy.Preferred)
|
|
||||||
grid.addWidget(label, 1, 1)
|
|
||||||
|
|
||||||
self.stack = QtWidgets.QStackedWidget()
|
self.stack = QtWidgets.QStackedWidget()
|
||||||
grid.addWidget(self.stack, 2, 1)
|
self.grid.addWidget(self.stack, 2, 1)
|
||||||
|
|
||||||
self.direction = QtWidgets.QLabel()
|
self.direction = QtWidgets.QLabel()
|
||||||
self.direction.setAlignment(QtCore.Qt.AlignCenter)
|
self.direction.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||||
self.stack.addWidget(self.direction)
|
self.stack.addWidget(self.direction)
|
||||||
|
|
||||||
grid_cb = LayoutWidget()
|
grid_cb = LayoutWidget()
|
||||||
@ -74,13 +81,13 @@ class _TTLWidget(QtWidgets.QFrame):
|
|||||||
self.stack.addWidget(grid_cb)
|
self.stack.addWidget(grid_cb)
|
||||||
|
|
||||||
self.value = QtWidgets.QLabel()
|
self.value = QtWidgets.QLabel()
|
||||||
self.value.setAlignment(QtCore.Qt.AlignCenter)
|
self.value.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||||
grid.addWidget(self.value, 3, 1)
|
self.grid.addWidget(self.value, 3, 1)
|
||||||
|
|
||||||
grid.setRowStretch(1, 1)
|
self.grid.setRowStretch(1, 1)
|
||||||
grid.setRowStretch(2, 0)
|
self.grid.setRowStretch(2, 0)
|
||||||
grid.setRowStretch(3, 0)
|
self.grid.setRowStretch(3, 0)
|
||||||
grid.setRowStretch(4, 1)
|
self.grid.setRowStretch(4, 1)
|
||||||
|
|
||||||
self.programmatic_change = False
|
self.programmatic_change = False
|
||||||
self.override.clicked.connect(self.override_toggled)
|
self.override.clicked.connect(self.override_toggled)
|
||||||
@ -186,7 +193,7 @@ class _DDSModel:
|
|||||||
return ftw / self.ftw_per_hz
|
return ftw / self.ftw_per_hz
|
||||||
|
|
||||||
|
|
||||||
class _DDSWidget(QtWidgets.QFrame):
|
class _DDSWidget(_MoninjWidget):
|
||||||
def __init__(self, dm, title, bus_channel, channel,
|
def __init__(self, dm, title, bus_channel, channel,
|
||||||
dds_type, ref_clk, cpld=None, pll=1, clk_div=0):
|
dds_type, ref_clk, cpld=None, pll=1, clk_div=0):
|
||||||
self.dm = dm
|
self.dm = dm
|
||||||
@ -197,19 +204,7 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||||||
self.dds_model = _DDSModel(dds_type, ref_clk, cpld, pll, clk_div)
|
self.dds_model = _DDSModel(dds_type, ref_clk, cpld, pll, clk_div)
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
||||||
QtWidgets.QFrame.__init__(self)
|
_MoninjWidget.__init__(self, title)
|
||||||
|
|
||||||
self.setFrameShape(QtWidgets.QFrame.Box)
|
|
||||||
self.setFrameShadow(QtWidgets.QFrame.Raised)
|
|
||||||
|
|
||||||
grid = QtWidgets.QGridLayout()
|
|
||||||
grid.setContentsMargins(0, 0, 0, 0)
|
|
||||||
grid.setHorizontalSpacing(0)
|
|
||||||
grid.setVerticalSpacing(0)
|
|
||||||
self.setLayout(grid)
|
|
||||||
label = QtWidgets.QLabel(title)
|
|
||||||
label.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
grid.addWidget(label, 1, 1)
|
|
||||||
|
|
||||||
# FREQ DATA/EDIT FIELD
|
# FREQ DATA/EDIT FIELD
|
||||||
self.data_stack = QtWidgets.QStackedWidget()
|
self.data_stack = QtWidgets.QStackedWidget()
|
||||||
@ -221,11 +216,11 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||||||
grid_disp.layout.setVerticalSpacing(0)
|
grid_disp.layout.setVerticalSpacing(0)
|
||||||
|
|
||||||
self.value_label = QtWidgets.QLabel()
|
self.value_label = QtWidgets.QLabel()
|
||||||
self.value_label.setAlignment(QtCore.Qt.AlignCenter)
|
self.value_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||||
grid_disp.addWidget(self.value_label, 0, 1, 1, 2)
|
grid_disp.addWidget(self.value_label, 0, 1, 1, 2)
|
||||||
|
|
||||||
unit = QtWidgets.QLabel("MHz")
|
unit = QtWidgets.QLabel("MHz")
|
||||||
unit.setAlignment(QtCore.Qt.AlignCenter)
|
unit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||||
grid_disp.addWidget(unit, 0, 3, 1, 1)
|
grid_disp.addWidget(unit, 0, 3, 1, 1)
|
||||||
|
|
||||||
self.data_stack.addWidget(grid_disp)
|
self.data_stack.addWidget(grid_disp)
|
||||||
@ -237,14 +232,14 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||||||
grid_edit.layout.setVerticalSpacing(0)
|
grid_edit.layout.setVerticalSpacing(0)
|
||||||
|
|
||||||
self.value_edit = _CancellableLineEdit(self)
|
self.value_edit = _CancellableLineEdit(self)
|
||||||
self.value_edit.setAlignment(QtCore.Qt.AlignRight)
|
self.value_edit.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight)
|
||||||
grid_edit.addWidget(self.value_edit, 0, 1, 1, 2)
|
grid_edit.addWidget(self.value_edit, 0, 1, 1, 2)
|
||||||
unit = QtWidgets.QLabel("MHz")
|
unit = QtWidgets.QLabel("MHz")
|
||||||
unit.setAlignment(QtCore.Qt.AlignCenter)
|
unit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||||
grid_edit.addWidget(unit, 0, 3, 1, 1)
|
grid_edit.addWidget(unit, 0, 3, 1, 1)
|
||||||
self.data_stack.addWidget(grid_edit)
|
self.data_stack.addWidget(grid_edit)
|
||||||
|
|
||||||
grid.addWidget(self.data_stack, 2, 1)
|
self.grid.addWidget(self.data_stack, 2, 1)
|
||||||
|
|
||||||
# BUTTONS
|
# BUTTONS
|
||||||
self.button_stack = QtWidgets.QStackedWidget()
|
self.button_stack = QtWidgets.QStackedWidget()
|
||||||
@ -277,11 +272,11 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||||||
cancel.setToolTip("Cancel changes")
|
cancel.setToolTip("Cancel changes")
|
||||||
apply_grid.addWidget(cancel, 0, 2, 1, 1)
|
apply_grid.addWidget(cancel, 0, 2, 1, 1)
|
||||||
self.button_stack.addWidget(apply_grid)
|
self.button_stack.addWidget(apply_grid)
|
||||||
grid.addWidget(self.button_stack, 3, 1)
|
self.grid.addWidget(self.button_stack, 3, 1)
|
||||||
|
|
||||||
grid.setRowStretch(1, 1)
|
self.grid.setRowStretch(1, 1)
|
||||||
grid.setRowStretch(2, 1)
|
self.grid.setRowStretch(2, 1)
|
||||||
grid.setRowStretch(3, 1)
|
self.grid.setRowStretch(3, 1)
|
||||||
|
|
||||||
set_btn.clicked.connect(self.set_clicked)
|
set_btn.clicked.connect(self.set_clicked)
|
||||||
apply.clicked.connect(self.apply_changes)
|
apply.clicked.connect(self.apply_changes)
|
||||||
@ -332,39 +327,31 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||||||
return "dds/{}".format(self.title)
|
return "dds/{}".format(self.title)
|
||||||
|
|
||||||
|
|
||||||
class _DACWidget(QtWidgets.QFrame):
|
class _DACWidget(_MoninjWidget):
|
||||||
def __init__(self, dm, spi_channel, channel, title):
|
def __init__(self, dm, spi_channel, channel, title, vref, offset_dacs):
|
||||||
QtWidgets.QFrame.__init__(self)
|
_MoninjWidget.__init__(self, "{}_ch{}".format(title, channel))
|
||||||
self.spi_channel = spi_channel
|
self.spi_channel = spi_channel
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
self.cur_value = 0
|
self.cur_value = 0x8000
|
||||||
self.title = title
|
self.title = title
|
||||||
|
self.vref = vref
|
||||||
self.setFrameShape(QtWidgets.QFrame.Box)
|
self.offset_dacs = offset_dacs
|
||||||
self.setFrameShadow(QtWidgets.QFrame.Raised)
|
|
||||||
|
|
||||||
grid = QtWidgets.QGridLayout()
|
|
||||||
grid.setContentsMargins(0, 0, 0, 0)
|
|
||||||
grid.setHorizontalSpacing(0)
|
|
||||||
grid.setVerticalSpacing(0)
|
|
||||||
self.setLayout(grid)
|
|
||||||
label = QtWidgets.QLabel("{}_ch{}".format(title, channel))
|
|
||||||
label.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
grid.addWidget(label, 1, 1)
|
|
||||||
|
|
||||||
self.value = QtWidgets.QLabel()
|
self.value = QtWidgets.QLabel()
|
||||||
self.value.setAlignment(QtCore.Qt.AlignCenter)
|
self.value.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignTop)
|
||||||
grid.addWidget(self.value, 2, 1, 6, 1)
|
self.grid.addWidget(self.value, 2, 1, 6, 1)
|
||||||
|
|
||||||
grid.setRowStretch(1, 1)
|
self.grid.setRowStretch(1, 1)
|
||||||
grid.setRowStretch(2, 0)
|
self.grid.setRowStretch(2, 1)
|
||||||
grid.setRowStretch(3, 1)
|
|
||||||
|
|
||||||
self.refresh_display()
|
self.refresh_display()
|
||||||
|
|
||||||
|
def mu_to_voltage(self, code):
|
||||||
|
return ((code - self.offset_dacs * 0x4) / (1 << 16)) * (4. * self.vref)
|
||||||
|
|
||||||
def refresh_display(self):
|
def refresh_display(self):
|
||||||
self.value.setText("<font size=\"4\">{:.3f}</font><font size=\"2\"> %</font>"
|
self.value.setText("<font size=\"4\">{:+.3f} V</font>"
|
||||||
.format(self.cur_value * 100 / 2**16))
|
.format(self.mu_to_voltage(self.cur_value)))
|
||||||
|
|
||||||
def sort_key(self):
|
def sort_key(self):
|
||||||
return (2, self.spi_channel, self.channel)
|
return (2, self.spi_channel, self.channel)
|
||||||
@ -424,9 +411,17 @@ def setup_from_ddb(ddb):
|
|||||||
while isinstance(spi_device, str):
|
while isinstance(spi_device, str):
|
||||||
spi_device = ddb[spi_device]
|
spi_device = ddb[spi_device]
|
||||||
spi_channel = spi_device["arguments"]["channel"]
|
spi_channel = spi_device["arguments"]["channel"]
|
||||||
|
vref = v["arguments"].get("vref", 5.)
|
||||||
|
offset_dacs = v["arguments"].get("offset_dacs", 8192)
|
||||||
for channel in range(32):
|
for channel in range(32):
|
||||||
widget = _WidgetDesc((k, channel), comment, _DACWidget,
|
widget = _WidgetDesc((k, channel), comment, _DACWidget,
|
||||||
(spi_channel, channel, k))
|
(spi_channel, channel, k, vref, offset_dacs))
|
||||||
|
description.add(widget)
|
||||||
|
elif (v["module"] == "artiq.coredevice.fastino" and v["class"] == "Fastino"):
|
||||||
|
bus_channel = v["arguments"]["channel"]
|
||||||
|
for channel in range(0, 32):
|
||||||
|
widget = _WidgetDesc((k, channel), comment, _DACWidget,
|
||||||
|
(bus_channel, channel, k, 5, 8192))
|
||||||
description.add(widget)
|
description.add(widget)
|
||||||
elif v["type"] == "controller" and k == "core_moninj":
|
elif v["type"] == "controller" and k == "core_moninj":
|
||||||
mi_addr = v["host"]
|
mi_addr = v["host"]
|
||||||
@ -766,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()
|
||||||
@ -776,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)
|
||||||
@ -805,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
|
||||||
@ -815,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)
|
||||||
|
|
||||||
@ -826,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)
|
||||||
|
|
||||||
@ -839,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):
|
||||||
@ -847,10 +844,10 @@ 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))
|
||||||
|
|
||||||
def delete_all_widgets(self):
|
def delete_all_widgets(self):
|
||||||
for index in reversed(range(self.flow.count())):
|
for index in reversed(range(self.flow.count())):
|
||||||
@ -936,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()
|
||||||
@ -950,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)
|
||||||
|
|
||||||
@ -973,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()
|
||||||
|
|
||||||
|
@ -3,9 +3,10 @@ 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.gui.tools import SelectableColumnTableView
|
||||||
from artiq.tools import elide
|
from artiq.tools import elide
|
||||||
|
|
||||||
|
|
||||||
@ -61,31 +62,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 = SelectableColumnTableView()
|
||||||
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)
|
||||||
@ -104,6 +105,9 @@ class ScheduleDock(QtWidgets.QDockWidget):
|
|||||||
h.resizeSection(6, 20 * cw)
|
h.resizeSection(6, 20 * cw)
|
||||||
h.resizeSection(7, 20 * cw)
|
h.resizeSection(7, 20 * cw)
|
||||||
|
|
||||||
|
# Allow user to reorder or disable columns.
|
||||||
|
h.setSectionsMovable(True)
|
||||||
|
|
||||||
def set_model(self, model):
|
def set_model(self, model):
|
||||||
self.table_model = model
|
self.table_model = model
|
||||||
self.table.setModel(self.table_model)
|
self.table.setModel(self.table_model)
|
||||||
@ -154,4 +158,9 @@ class ScheduleDock(QtWidgets.QDockWidget):
|
|||||||
return bytes(self.table.horizontalHeader().saveState())
|
return bytes(self.table.horizontalHeader().saveState())
|
||||||
|
|
||||||
def restore_state(self, state):
|
def restore_state(self, state):
|
||||||
self.table.horizontalHeader().restoreState(QtCore.QByteArray(state))
|
h = self.table.horizontalHeader()
|
||||||
|
h.restoreState(QtCore.QByteArray(state))
|
||||||
|
|
||||||
|
# The state includes the sectionsMovable property, so set it again to be able to
|
||||||
|
# deal with pre-existing save files from when we used not to enable it.
|
||||||
|
h.setSectionsMovable(True)
|
||||||
|
@ -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):
|
||||||
|
@ -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)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from PyQt5 import QtWidgets
|
from PyQt6 import QtWidgets
|
||||||
|
|
||||||
from artiq.applets.simple import SimpleApplet
|
from artiq.applets.simple import SimpleApplet
|
||||||
|
|
||||||
|
4
artiq/firmware/Cargo.lock
generated
4
artiq/firmware/Cargo.lock
generated
@ -513,6 +513,7 @@ dependencies = [
|
|||||||
"board_misoc",
|
"board_misoc",
|
||||||
"build_misoc",
|
"build_misoc",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"crc",
|
||||||
"cslice",
|
"cslice",
|
||||||
"dyld",
|
"dyld",
|
||||||
"eh",
|
"eh",
|
||||||
@ -553,10 +554,13 @@ dependencies = [
|
|||||||
"board_artiq",
|
"board_artiq",
|
||||||
"board_misoc",
|
"board_misoc",
|
||||||
"build_misoc",
|
"build_misoc",
|
||||||
|
"byteorder",
|
||||||
|
"crc",
|
||||||
"cslice",
|
"cslice",
|
||||||
"eh",
|
"eh",
|
||||||
"io",
|
"io",
|
||||||
"log",
|
"log",
|
||||||
|
"logger_artiq",
|
||||||
"proto_artiq",
|
"proto_artiq",
|
||||||
"riscv",
|
"riscv",
|
||||||
]
|
]
|
||||||
|
@ -329,19 +329,29 @@ extern fn stop_fn(_version: c_int,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Must be kept in sync with `artiq.compiler.embedding`
|
// Must be kept in sync with `artiq.compiler.embedding`
|
||||||
static EXCEPTION_ID_LOOKUP: [(&str, u32); 12] = [
|
static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [
|
||||||
("RuntimeError", 0),
|
("RTIOUnderflow", 0),
|
||||||
("RTIOUnderflow", 1),
|
("RTIOOverflow", 1),
|
||||||
("RTIOOverflow", 2),
|
("RTIODestinationUnreachable", 2),
|
||||||
("RTIODestinationUnreachable", 3),
|
("DMAError", 3),
|
||||||
("DMAError", 4),
|
("I2CError", 4),
|
||||||
("I2CError", 5),
|
("CacheError", 5),
|
||||||
("CacheError", 6),
|
("SPIError", 6),
|
||||||
("SPIError", 7),
|
("SubkernelError", 7),
|
||||||
("ZeroDivisionError", 8),
|
("AssertionError", 8),
|
||||||
("IndexError", 9),
|
("AttributeError", 9),
|
||||||
("UnwrapNoneError", 10),
|
("IndexError", 10),
|
||||||
("SubkernelError", 11),
|
("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 {
|
||||||
|
@ -187,6 +187,24 @@ unsafe fn align_comma() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub unsafe fn align_wordslip(trx_no: u8) -> bool {
|
||||||
|
csr::eem_transceiver::transceiver_sel_write(trx_no);
|
||||||
|
|
||||||
|
for slip in 0..=1 {
|
||||||
|
csr::eem_transceiver::wordslip_write(slip as u8);
|
||||||
|
clock::spin_us(1);
|
||||||
|
csr::eem_transceiver::comma_align_reset_write(1);
|
||||||
|
clock::spin_us(100);
|
||||||
|
|
||||||
|
if csr::eem_transceiver::comma_read() == 1 {
|
||||||
|
debug!("comma alignment completed with {} wordslip", slip);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
pub fn init() {
|
pub fn init() {
|
||||||
for trx_no in 0..csr::CONFIG_EEM_DRTIO_COUNT {
|
for trx_no in 0..csr::CONFIG_EEM_DRTIO_COUNT {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -211,9 +229,6 @@ pub fn init() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
unsafe {
|
unsafe { align_comma(); }
|
||||||
align_comma();
|
|
||||||
csr::eem_transceiver::rx_ready_write(1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,17 @@ pub unsafe fn write(mut addr: usize, mut data: &[u8]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(any(soc_platform = "kasli", soc_platform = "kc705"))]
|
pub unsafe fn flash_binary(origin: usize, payload: &[u8]) {
|
||||||
|
assert!((origin & (SECTOR_SIZE - 1)) == 0);
|
||||||
|
let mut offset = 0;
|
||||||
|
while offset < payload.len() {
|
||||||
|
erase_sector(origin + offset);
|
||||||
|
offset += SECTOR_SIZE;
|
||||||
|
}
|
||||||
|
write(origin, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(soc_platform = "kasli", soc_platform = "kc705", soc_platform = "efc"))]
|
||||||
pub unsafe fn reload () -> ! {
|
pub unsafe fn reload () -> ! {
|
||||||
csr::icap::iprog_write(1);
|
csr::icap::iprog_write(1);
|
||||||
loop {}
|
loop {}
|
||||||
|
@ -127,6 +127,25 @@ pub enum Packet {
|
|||||||
SubkernelException { destination: u8, last: bool, length: u16, data: [u8; MASTER_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 },
|
||||||
|
|
||||||
|
CoreMgmtGetLogRequest { destination: u8, clear: bool },
|
||||||
|
CoreMgmtClearLogRequest { destination: u8 },
|
||||||
|
CoreMgmtSetLogLevelRequest { destination: u8, log_level: u8 },
|
||||||
|
CoreMgmtSetUartLogLevelRequest { destination: u8, log_level: u8 },
|
||||||
|
CoreMgmtConfigReadRequest { destination: u8, length: u16, key: [u8; MASTER_PAYLOAD_MAX_SIZE] },
|
||||||
|
CoreMgmtConfigReadContinue { destination: u8 },
|
||||||
|
CoreMgmtConfigWriteRequest { destination: u8, last: bool, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
|
||||||
|
CoreMgmtConfigRemoveRequest { destination: u8, length: u16, key: [u8; MASTER_PAYLOAD_MAX_SIZE] },
|
||||||
|
CoreMgmtConfigEraseRequest { destination: u8 },
|
||||||
|
CoreMgmtRebootRequest { destination: u8 },
|
||||||
|
CoreMgmtAllocatorDebugRequest { destination: u8 },
|
||||||
|
CoreMgmtFlashRequest { destination: u8, payload_length: u32 },
|
||||||
|
CoreMgmtFlashAddDataRequest { destination: u8, last: bool, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
|
||||||
|
CoreMgmtDropLinkAck { destination: u8 },
|
||||||
|
CoreMgmtDropLink,
|
||||||
|
CoreMgmtGetLogReply { last: bool, length: u16, data: [u8; SAT_PAYLOAD_MAX_SIZE] },
|
||||||
|
CoreMgmtConfigReadReply { last: bool, length: u16, value: [u8; SAT_PAYLOAD_MAX_SIZE] },
|
||||||
|
CoreMgmtReply { succeeded: bool },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Packet {
|
impl Packet {
|
||||||
@ -405,6 +424,115 @@ impl Packet {
|
|||||||
destination: reader.read_u8()?
|
destination: reader.read_u8()?
|
||||||
},
|
},
|
||||||
|
|
||||||
|
0xd0 => Packet::CoreMgmtGetLogRequest {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
|
clear: reader.read_bool()?,
|
||||||
|
},
|
||||||
|
0xd1 => Packet::CoreMgmtClearLogRequest {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
|
},
|
||||||
|
0xd2 => Packet::CoreMgmtSetLogLevelRequest {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
|
log_level: reader.read_u8()?,
|
||||||
|
},
|
||||||
|
0xd3 => Packet::CoreMgmtSetUartLogLevelRequest {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
|
log_level: reader.read_u8()?,
|
||||||
|
},
|
||||||
|
0xd4 => {
|
||||||
|
let destination = reader.read_u8()?;
|
||||||
|
let length = reader.read_u16()?;
|
||||||
|
let mut key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
||||||
|
reader.read_exact(&mut key[0..length as usize])?;
|
||||||
|
Packet::CoreMgmtConfigReadRequest {
|
||||||
|
destination: destination,
|
||||||
|
length: length,
|
||||||
|
key: key,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
0xd5 => Packet::CoreMgmtConfigReadContinue {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
|
},
|
||||||
|
0xd6 => {
|
||||||
|
let destination = reader.read_u8()?;
|
||||||
|
let last = reader.read_bool()?;
|
||||||
|
let length = reader.read_u16()?;
|
||||||
|
let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
||||||
|
reader.read_exact(&mut data[0..length as usize])?;
|
||||||
|
Packet::CoreMgmtConfigWriteRequest {
|
||||||
|
destination: destination,
|
||||||
|
last: last,
|
||||||
|
length: length,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
0xd7 => {
|
||||||
|
let destination = reader.read_u8()?;
|
||||||
|
let length = reader.read_u16()?;
|
||||||
|
let mut key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
||||||
|
reader.read_exact(&mut key[0..length as usize])?;
|
||||||
|
Packet::CoreMgmtConfigRemoveRequest {
|
||||||
|
destination: destination,
|
||||||
|
length: length,
|
||||||
|
key: key,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
0xd8 => Packet::CoreMgmtConfigEraseRequest {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
|
},
|
||||||
|
0xd9 => Packet::CoreMgmtRebootRequest {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
|
},
|
||||||
|
0xda => Packet::CoreMgmtAllocatorDebugRequest {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
|
},
|
||||||
|
0xdb => Packet::CoreMgmtFlashRequest {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
|
payload_length: reader.read_u32()?,
|
||||||
|
},
|
||||||
|
0xdc => {
|
||||||
|
let destination = reader.read_u8()?;
|
||||||
|
let last = reader.read_bool()?;
|
||||||
|
let length = reader.read_u16()?;
|
||||||
|
let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
||||||
|
reader.read_exact(&mut data[0..length as usize])?;
|
||||||
|
Packet::CoreMgmtFlashAddDataRequest {
|
||||||
|
destination: destination,
|
||||||
|
last: last,
|
||||||
|
length: length,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
0xdd => Packet::CoreMgmtDropLinkAck {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
|
},
|
||||||
|
0xde => Packet::CoreMgmtDropLink,
|
||||||
|
0xdf => {
|
||||||
|
let last = reader.read_bool()?;
|
||||||
|
let length = reader.read_u16()?;
|
||||||
|
let mut data: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
|
||||||
|
reader.read_exact(&mut data[0..length as usize])?;
|
||||||
|
Packet::CoreMgmtGetLogReply {
|
||||||
|
last: last,
|
||||||
|
length: length,
|
||||||
|
data: data,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
0xe0 => {
|
||||||
|
let last = reader.read_bool()?;
|
||||||
|
let length = reader.read_u16()?;
|
||||||
|
let mut value: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
|
||||||
|
reader.read_exact(&mut value[0..length as usize])?;
|
||||||
|
Packet::CoreMgmtConfigReadReply {
|
||||||
|
last: last,
|
||||||
|
length: length,
|
||||||
|
value: value,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
0xe1 => Packet::CoreMgmtReply {
|
||||||
|
succeeded: reader.read_bool()?,
|
||||||
|
},
|
||||||
|
|
||||||
ty => return Err(Error::UnknownPacket(ty))
|
ty => return Err(Error::UnknownPacket(ty))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -693,6 +821,108 @@ impl Packet {
|
|||||||
writer.write_u8(0xcc)?;
|
writer.write_u8(0xcc)?;
|
||||||
writer.write_u8(destination)?;
|
writer.write_u8(destination)?;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
Packet::CoreMgmtGetLogRequest { destination, clear } => {
|
||||||
|
writer.write_u8(0xd0)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
writer.write_bool(clear)?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtClearLogRequest { destination } => {
|
||||||
|
writer.write_u8(0xd1)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtSetLogLevelRequest { destination, log_level } => {
|
||||||
|
writer.write_u8(0xd2)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
writer.write_u8(log_level)?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtSetUartLogLevelRequest { destination, log_level } => {
|
||||||
|
writer.write_u8(0xd3)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
writer.write_u8(log_level)?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtConfigReadRequest {
|
||||||
|
destination,
|
||||||
|
length,
|
||||||
|
key,
|
||||||
|
} => {
|
||||||
|
writer.write_u8(0xd4)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
writer.write_u16(length)?;
|
||||||
|
writer.write_all(&key[0..length as usize])?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtConfigReadContinue { destination } => {
|
||||||
|
writer.write_u8(0xd5)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtConfigWriteRequest {
|
||||||
|
destination,
|
||||||
|
last,
|
||||||
|
length,
|
||||||
|
data,
|
||||||
|
} => {
|
||||||
|
writer.write_u8(0xd6)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
writer.write_bool(last)?;
|
||||||
|
writer.write_u16(length)?;
|
||||||
|
writer.write_all(&data[0..length as usize])?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtConfigRemoveRequest {
|
||||||
|
destination,
|
||||||
|
length,
|
||||||
|
key,
|
||||||
|
} => {
|
||||||
|
writer.write_u8(0xd7)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
writer.write_u16(length)?;
|
||||||
|
writer.write_all(&key[0..length as usize])?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtConfigEraseRequest { destination } => {
|
||||||
|
writer.write_u8(0xd8)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtRebootRequest { destination } => {
|
||||||
|
writer.write_u8(0xd9)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtAllocatorDebugRequest { destination } => {
|
||||||
|
writer.write_u8(0xda)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtFlashRequest { destination, payload_length } => {
|
||||||
|
writer.write_u8(0xdb)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
writer.write_u32(payload_length)?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtFlashAddDataRequest { destination, last, length, data } => {
|
||||||
|
writer.write_u8(0xdc)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
writer.write_bool(last)?;
|
||||||
|
writer.write_u16(length)?;
|
||||||
|
writer.write_all(&data[..length as usize])?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtDropLinkAck { destination } => {
|
||||||
|
writer.write_u8(0xdd)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtDropLink =>
|
||||||
|
writer.write_u8(0xde)?,
|
||||||
|
Packet::CoreMgmtGetLogReply { last, length, data } => {
|
||||||
|
writer.write_u8(0xdf)?;
|
||||||
|
writer.write_bool(last)?;
|
||||||
|
writer.write_u16(length)?;
|
||||||
|
writer.write_all(&data[0..length as usize])?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtConfigReadReply { last, length, value } => {
|
||||||
|
writer.write_u8(0xe0)?;
|
||||||
|
writer.write_bool(last)?;
|
||||||
|
writer.write_u16(length)?;
|
||||||
|
writer.write_all(&value[0..length as usize])?;
|
||||||
|
},
|
||||||
|
Packet::CoreMgmtReply { succeeded } => {
|
||||||
|
writer.write_u8(0xe1)?;
|
||||||
|
writer.write_bool(succeeded)?;
|
||||||
|
},
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -726,7 +956,8 @@ impl Packet {
|
|||||||
Packet::DmaAddTraceReply { .. } | Packet::DmaRemoveTraceReply { .. } |
|
Packet::DmaAddTraceReply { .. } | Packet::DmaRemoveTraceReply { .. } |
|
||||||
Packet::DmaPlaybackReply { .. } | Packet::SubkernelLoadRunReply { .. } |
|
Packet::DmaPlaybackReply { .. } | Packet::SubkernelLoadRunReply { .. } |
|
||||||
Packet::SubkernelMessageAck { .. } | Packet::DmaPlaybackStatus { .. } |
|
Packet::SubkernelMessageAck { .. } | Packet::DmaPlaybackStatus { .. } |
|
||||||
Packet::SubkernelFinished { .. } | Packet::InjectionRequest { .. } => false,
|
Packet::SubkernelFinished { .. } | Packet::CoreMgmtDropLinkAck { .. } |
|
||||||
|
Packet::InjectionRequest { .. } => false,
|
||||||
_ => true
|
_ => true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,9 @@ pub enum Error<T> {
|
|||||||
#[fail(display = "invalid UTF-8: {}", _0)]
|
#[fail(display = "invalid UTF-8: {}", _0)]
|
||||||
Utf8(Utf8Error),
|
Utf8(Utf8Error),
|
||||||
#[fail(display = "{}", _0)]
|
#[fail(display = "{}", _0)]
|
||||||
Io(#[cause] IoError<T>)
|
Io(#[cause] IoError<T>),
|
||||||
|
#[fail(display = "drtio error")]
|
||||||
|
DrtioError,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> From<IoError<T>> for Error<T> {
|
impl<T> From<IoError<T>> for Error<T> {
|
||||||
@ -65,6 +67,8 @@ pub enum Request {
|
|||||||
|
|
||||||
Reboot,
|
Reboot,
|
||||||
|
|
||||||
|
Flash { image: Vec<u8> },
|
||||||
|
|
||||||
DebugAllocator,
|
DebugAllocator,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -123,6 +127,10 @@ impl Request {
|
|||||||
|
|
||||||
8 => Request::DebugAllocator,
|
8 => Request::DebugAllocator,
|
||||||
|
|
||||||
|
9 => Request::Flash {
|
||||||
|
image: reader.read_bytes()?,
|
||||||
|
},
|
||||||
|
|
||||||
ty => return Err(Error::UnknownPacket(ty))
|
ty => return Err(Error::UnknownPacket(ty))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ build_misoc = { path = "../libbuild_misoc" }
|
|||||||
failure = { version = "0.1", default-features = false }
|
failure = { version = "0.1", default-features = false }
|
||||||
failure_derive = { version = "0.1", default-features = false }
|
failure_derive = { version = "0.1", default-features = false }
|
||||||
byteorder = { version = "1.0", default-features = false }
|
byteorder = { version = "1.0", default-features = false }
|
||||||
|
crc = { version = "1.7", default-features = false }
|
||||||
cslice = { version = "0.3" }
|
cslice = { version = "0.3" }
|
||||||
log = { version = "=0.4.14", default-features = false }
|
log = { version = "=0.4.14", default-features = false }
|
||||||
managed = { version = "^0.7.1", default-features = false, features = ["alloc", "map"] }
|
managed = { version = "^0.7.1", default-features = false, features = ["alloc", "map"] }
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
#![feature(lang_items, panic_info_message, const_btree_new, iter_advance_by, never_type)]
|
#![feature(lang_items, panic_info_message, const_btree_new, iter_advance_by, never_type)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
|
extern crate crc;
|
||||||
extern crate dyld;
|
extern crate dyld;
|
||||||
extern crate eh;
|
extern crate eh;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
@ -29,9 +30,10 @@ extern crate riscv;
|
|||||||
extern crate tar_no_std;
|
extern crate tar_no_std;
|
||||||
|
|
||||||
use alloc::collections::BTreeMap;
|
use alloc::collections::BTreeMap;
|
||||||
use core::cell::RefCell;
|
use core::cell::{RefCell, Cell};
|
||||||
use core::convert::TryFrom;
|
use core::convert::TryFrom;
|
||||||
use smoltcp::wire::HardwareAddress;
|
use smoltcp::wire::HardwareAddress;
|
||||||
|
use urc::Urc;
|
||||||
|
|
||||||
use board_misoc::{csr, ident, clock, spiflash, config, net_settings, pmp, boot};
|
use board_misoc::{csr, ident, clock, spiflash, config, net_settings, pmp, boot};
|
||||||
#[cfg(has_ethmac)]
|
#[cfg(has_ethmac)]
|
||||||
@ -196,6 +198,7 @@ fn startup() {
|
|||||||
|
|
||||||
let ddma_mutex = sched::Mutex::new();
|
let ddma_mutex = sched::Mutex::new();
|
||||||
let subkernel_mutex = sched::Mutex::new();
|
let subkernel_mutex = sched::Mutex::new();
|
||||||
|
let restart_idle = Urc::new(Cell::new(false));
|
||||||
|
|
||||||
let mut scheduler = sched::Scheduler::new(interface);
|
let mut scheduler = sched::Scheduler::new(interface);
|
||||||
let io = scheduler.io();
|
let io = scheduler.io();
|
||||||
@ -205,15 +208,22 @@ fn startup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rtio_mgt::startup(&io, &aux_mutex, &drtio_routing_table, &up_destinations, &ddma_mutex, &subkernel_mutex);
|
rtio_mgt::startup(&io, &aux_mutex, &drtio_routing_table, &up_destinations, &ddma_mutex, &subkernel_mutex);
|
||||||
|
{
|
||||||
io.spawn(4096, mgmt::thread);
|
let restart_idle = restart_idle.clone();
|
||||||
|
let aux_mutex = aux_mutex.clone();
|
||||||
|
let ddma_mutex = ddma_mutex.clone();
|
||||||
|
let subkernel_mutex = subkernel_mutex.clone();
|
||||||
|
let drtio_routing_table = drtio_routing_table.clone();
|
||||||
|
io.spawn(4096, move |io| { mgmt::thread(io, &restart_idle, &aux_mutex, &ddma_mutex, &subkernel_mutex, &drtio_routing_table) });
|
||||||
|
}
|
||||||
{
|
{
|
||||||
let aux_mutex = aux_mutex.clone();
|
let aux_mutex = aux_mutex.clone();
|
||||||
let drtio_routing_table = drtio_routing_table.clone();
|
let drtio_routing_table = drtio_routing_table.clone();
|
||||||
let up_destinations = up_destinations.clone();
|
let up_destinations = up_destinations.clone();
|
||||||
let ddma_mutex = ddma_mutex.clone();
|
let ddma_mutex = ddma_mutex.clone();
|
||||||
let subkernel_mutex = subkernel_mutex.clone();
|
let subkernel_mutex = subkernel_mutex.clone();
|
||||||
io.spawn(32768, move |io| { session::thread(io, &aux_mutex, &drtio_routing_table, &up_destinations, &ddma_mutex, &subkernel_mutex) });
|
let restart_idle = restart_idle.clone();
|
||||||
|
io.spawn(32768, move |io| { session::thread(io, &aux_mutex, &drtio_routing_table, &up_destinations, &ddma_mutex, &subkernel_mutex, &restart_idle) });
|
||||||
}
|
}
|
||||||
#[cfg(any(has_rtio_moninj, has_drtio))]
|
#[cfg(any(has_rtio_moninj, has_drtio))]
|
||||||
{
|
{
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
use log::{self, LevelFilter};
|
use core::cell::{Cell, RefCell};
|
||||||
|
|
||||||
use io::{Write, ProtoWrite, Error as IoError};
|
use board_artiq::drtio_routing::RoutingTable;
|
||||||
use board_misoc::{config, spiflash};
|
use io::{ProtoRead, Write, Error as IoError};
|
||||||
use logger_artiq::BufferLogger;
|
|
||||||
use mgmt_proto::*;
|
use mgmt_proto::*;
|
||||||
use sched::{Io, TcpListener, TcpStream, Error as SchedError};
|
use sched::{Io, Mutex, TcpListener, TcpStream, Error as SchedError};
|
||||||
|
use urc::Urc;
|
||||||
|
|
||||||
impl From<SchedError> for Error<SchedError> {
|
impl From<SchedError> for Error<SchedError> {
|
||||||
fn from(value: SchedError) -> Error<SchedError> {
|
fn from(value: SchedError) -> Error<SchedError> {
|
||||||
@ -12,29 +12,39 @@ impl From<SchedError> for Error<SchedError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn worker(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
mod local_coremgmt {
|
||||||
read_magic(stream)?;
|
use alloc::{string::String, vec::Vec};
|
||||||
Write::write_all(stream, "e".as_bytes())?;
|
use byteorder::{ByteOrder, NativeEndian};
|
||||||
info!("new connection from {}", stream.remote_endpoint());
|
use crc::crc32;
|
||||||
|
use log::LevelFilter;
|
||||||
|
|
||||||
loop {
|
use board_misoc::{config, mem, spiflash};
|
||||||
match Request::read_from(stream)? {
|
use io::ProtoWrite;
|
||||||
Request::GetLog => {
|
use logger_artiq::BufferLogger;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn get_log(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
BufferLogger::with(|logger| {
|
BufferLogger::with(|logger| {
|
||||||
let mut buffer = io.until_ok(|| logger.buffer())?;
|
let mut buffer = io.until_ok(|| logger.buffer())?;
|
||||||
Reply::LogContent(buffer.extract()).write_to(stream)
|
Reply::LogContent(buffer.extract()).write_to(stream)
|
||||||
})?;
|
})?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Request::ClearLog => {
|
|
||||||
BufferLogger::with(|logger| -> Result<(), Error<SchedError>> {
|
pub fn clear_log(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
|
BufferLogger::with(|logger| -> Result<(), IoError<SchedError>> {
|
||||||
let mut buffer = io.until_ok(|| logger.buffer())?;
|
let mut buffer = io.until_ok(|| logger.buffer())?;
|
||||||
Ok(buffer.clear())
|
Ok(buffer.clear())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Reply::Success.write_to(stream)?;
|
Reply::Success.write_to(stream)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Request::PullLog => {
|
|
||||||
BufferLogger::with(|logger| -> Result<(), Error<SchedError>> {
|
pub fn pull_log(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
|
BufferLogger::with(|logger| -> Result<(), IoError<SchedError>> {
|
||||||
loop {
|
loop {
|
||||||
// Do this *before* acquiring the buffer, since that sets the log level
|
// Do this *before* acquiring the buffer, since that sets the log level
|
||||||
// to OFF.
|
// to OFF.
|
||||||
@ -61,48 +71,75 @@ fn worker(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
|||||||
buffer.clear();
|
buffer.clear();
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Request::SetLogFilter(level) => {
|
|
||||||
|
pub fn set_log_filter(_io: &Io, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error<SchedError>> {
|
||||||
info!("changing log level to {}", level);
|
info!("changing log level to {}", level);
|
||||||
log::set_max_level(level);
|
log::set_max_level(level);
|
||||||
Reply::Success.write_to(stream)?;
|
Reply::Success.write_to(stream)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Request::SetUartLogFilter(level) => {
|
|
||||||
|
pub fn set_uart_log_filter(_io: &Io, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error<SchedError>> {
|
||||||
info!("changing UART log level to {}", level);
|
info!("changing UART log level to {}", level);
|
||||||
BufferLogger::with(|logger|
|
BufferLogger::with(|logger|
|
||||||
logger.set_uart_log_level(level));
|
logger.set_uart_log_level(level));
|
||||||
Reply::Success.write_to(stream)?;
|
Reply::Success.write_to(stream)?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
Request::ConfigRead { ref key } => {
|
pub fn config_read(_io: &Io, stream: &mut TcpStream, key: &String) -> Result<(), Error<SchedError>>{
|
||||||
config::read(key, |result| {
|
config::read(key, |result| {
|
||||||
match result {
|
match result {
|
||||||
Ok(value) => Reply::ConfigData(&value).write_to(stream),
|
Ok(value) => Reply::ConfigData(&value).write_to(stream),
|
||||||
Err(_) => Reply::Error.write_to(stream)
|
Err(_) => Reply::Error.write_to(stream)
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Request::ConfigWrite { ref key, ref value } => {
|
|
||||||
|
pub fn config_write(io: &Io, stream: &mut TcpStream, key: &String, value: &Vec<u8>, restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
|
||||||
match config::write(key, value) {
|
match config::write(key, value) {
|
||||||
Ok(_) => Reply::Success.write_to(stream),
|
Ok(_) => {
|
||||||
|
if key == "idle_kernel" {
|
||||||
|
io.until(|| !restart_idle.get())?;
|
||||||
|
restart_idle.set(true);
|
||||||
|
}
|
||||||
|
Reply::Success.write_to(stream)
|
||||||
|
},
|
||||||
Err(_) => Reply::Error.write_to(stream)
|
Err(_) => Reply::Error.write_to(stream)
|
||||||
}?;
|
}?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Request::ConfigRemove { ref key } => {
|
|
||||||
|
pub fn config_remove(io: &Io, stream: &mut TcpStream, key: &String, restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
|
||||||
match config::remove(key) {
|
match config::remove(key) {
|
||||||
Ok(()) => Reply::Success.write_to(stream),
|
Ok(()) => {
|
||||||
|
if key == "idle_kernel" {
|
||||||
|
io.until(|| !restart_idle.get())?;
|
||||||
|
restart_idle.set(true);
|
||||||
|
}
|
||||||
|
Reply::Success.write_to(stream)
|
||||||
|
},
|
||||||
Err(_) => Reply::Error.write_to(stream)
|
Err(_) => Reply::Error.write_to(stream)
|
||||||
}?;
|
}?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Request::ConfigErase => {
|
|
||||||
|
pub fn config_erase(io: &Io, stream: &mut TcpStream, restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
|
||||||
match config::erase() {
|
match config::erase() {
|
||||||
Ok(()) => Reply::Success.write_to(stream),
|
Ok(()) => {
|
||||||
|
io.until(|| !restart_idle.get())?;
|
||||||
|
restart_idle.set(true);
|
||||||
|
Reply::Success.write_to(stream)
|
||||||
|
},
|
||||||
Err(_) => Reply::Error.write_to(stream)
|
Err(_) => Reply::Error.write_to(stream)
|
||||||
}?;
|
}?;
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
Request::Reboot => {
|
pub fn reboot(_io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
Reply::RebootImminent.write_to(stream)?;
|
Reply::RebootImminent.write_to(stream)?;
|
||||||
stream.close()?;
|
stream.close()?;
|
||||||
stream.flush()?;
|
stream.flush()?;
|
||||||
@ -111,22 +148,524 @@ fn worker(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
|||||||
unsafe { spiflash::reload(); }
|
unsafe { spiflash::reload(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
Request::DebugAllocator =>
|
pub fn debug_allocator(_io: &Io, _stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
unsafe { println!("{}", ::ALLOC) },
|
unsafe { println!("{}", ::ALLOC) }
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flash(_io: &Io, stream: &mut TcpStream, image: &[u8]) -> Result<(), Error<SchedError>> {
|
||||||
|
let (expected_crc, mut image) = {
|
||||||
|
let (image, crc_slice) = image.split_at(image.len() - 4);
|
||||||
|
(NativeEndian::read_u32(crc_slice), image)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let actual_crc = crc32::checksum_ieee(image);
|
||||||
|
|
||||||
|
if actual_crc == expected_crc {
|
||||||
|
let bin_origins = [
|
||||||
|
("gateware" , 0 ),
|
||||||
|
("bootloader", mem::ROM_BASE ),
|
||||||
|
("firmware" , mem::FLASH_BOOT_ADDRESS),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (name, origin) in bin_origins {
|
||||||
|
info!("Flashing {} binary...", name);
|
||||||
|
let size = NativeEndian::read_u32(&image[..4]) as usize;
|
||||||
|
image = &image[4..];
|
||||||
|
|
||||||
|
let (bin, remaining) = image.split_at(size);
|
||||||
|
image = remaining;
|
||||||
|
|
||||||
|
unsafe { spiflash::flash_binary(origin, bin) };
|
||||||
|
}
|
||||||
|
|
||||||
|
reboot(_io, stream)?;
|
||||||
|
} else {
|
||||||
|
error!("CRC failed, images have not been written to flash.\n(actual {:08x}, expected {:08x})", actual_crc, expected_crc);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn thread(io: Io) {
|
#[cfg(has_drtio)]
|
||||||
|
mod remote_coremgmt {
|
||||||
|
use alloc::{string::String, vec::Vec};
|
||||||
|
use log::LevelFilter;
|
||||||
|
|
||||||
|
use board_artiq::{drtioaux, drtioaux::Packet};
|
||||||
|
use io::ProtoWrite;
|
||||||
|
use rtio_mgt::drtio;
|
||||||
|
use proto_artiq::drtioaux_proto::MASTER_PAYLOAD_MAX_SIZE;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
impl From<drtio::Error> for Error<SchedError> {
|
||||||
|
fn from(_value: drtio::Error) -> Error<SchedError> {
|
||||||
|
Error::DrtioError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_log(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
|
let mut buffer = String::new();
|
||||||
|
loop {
|
||||||
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtGetLogRequest { destination, clear: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtGetLogReply { last, length, data }) => {
|
||||||
|
buffer.push_str(
|
||||||
|
core::str::from_utf8(&data[..length as usize]).map_err(|_| Error::DrtioError)?);
|
||||||
|
if last {
|
||||||
|
Reply::LogContent(&buffer).write_to(stream)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
return Err(drtio::Error::UnexpectedReply.into());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_log(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtClearLogRequest { destination }
|
||||||
|
);
|
||||||
|
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
|
||||||
|
Reply::Success.write_to(stream)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(drtio::Error::UnexpectedReply.into())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pull_log(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
loop {
|
||||||
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtGetLogRequest { destination, clear: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtGetLogReply { last, length, data }) => {
|
||||||
|
buffer.extend(&data[..length as usize]);
|
||||||
|
|
||||||
|
if last {
|
||||||
|
stream.write_bytes(&buffer)?;
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
return Err(drtio::Error::UnexpectedReply.into());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_log_filter(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error<SchedError>> {
|
||||||
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtSetLogLevelRequest { destination, log_level: level as u8 }
|
||||||
|
);
|
||||||
|
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
|
||||||
|
Reply::Success.write_to(stream)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(drtio::Error::UnexpectedReply.into())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_uart_log_filter(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error<SchedError>> {
|
||||||
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtSetUartLogLevelRequest { destination, log_level: level as u8 }
|
||||||
|
);
|
||||||
|
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
|
||||||
|
Reply::Success.write_to(stream)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(drtio::Error::UnexpectedReply.into())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_read(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, stream: &mut TcpStream, key: &String) -> Result<(), Error<SchedError>> {
|
||||||
|
let mut config_key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
||||||
|
let len = key.len();
|
||||||
|
config_key[..len].clone_from_slice(key.as_bytes());
|
||||||
|
|
||||||
|
let mut reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtConfigReadRequest {
|
||||||
|
destination: destination,
|
||||||
|
length: len as u16,
|
||||||
|
key: config_key,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut buffer = Vec::<u8>::new();
|
||||||
|
loop {
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtConfigReadReply { length, last, value }) => {
|
||||||
|
buffer.extend(&value[..length as usize]);
|
||||||
|
|
||||||
|
if last {
|
||||||
|
Reply::ConfigData(&buffer).write_to(stream)?;
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtConfigReadContinue {
|
||||||
|
destination: destination,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
return Err(drtio::Error::UnexpectedReply.into());
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
return Err(e.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_write(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, stream: &mut TcpStream, key: &String, value: &Vec<u8>,
|
||||||
|
_restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
|
||||||
|
let mut message = Vec::with_capacity(key.len() + value.len() + 4 * 2);
|
||||||
|
message.write_string(key).unwrap();
|
||||||
|
message.write_bytes(value).unwrap();
|
||||||
|
|
||||||
|
match drtio::partition_data(&message, |slice, status, len: usize| {
|
||||||
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtConfigWriteRequest {
|
||||||
|
destination: destination, length: len as u16, last: status.is_last(), data: *slice});
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()),
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Err(drtio::Error::UnexpectedReply)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Ok(()) => {
|
||||||
|
Reply::Success.write_to(stream)?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(e.into())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_remove(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, stream: &mut TcpStream, key: &String,
|
||||||
|
_restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
|
||||||
|
let mut config_key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
||||||
|
let len = key.len();
|
||||||
|
config_key[..len].clone_from_slice(key.as_bytes());
|
||||||
|
|
||||||
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtConfigRemoveRequest {
|
||||||
|
destination: destination,
|
||||||
|
length: key.len() as u16,
|
||||||
|
key: config_key,
|
||||||
|
});
|
||||||
|
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
|
||||||
|
Reply::Success.write_to(stream)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(drtio::Error::UnexpectedReply.into())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn config_erase(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, stream: &mut TcpStream, _restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
|
||||||
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtConfigEraseRequest {
|
||||||
|
destination: destination,
|
||||||
|
});
|
||||||
|
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
|
||||||
|
Reply::Success.write_to(stream)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(drtio::Error::UnexpectedReply.into())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reboot(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtRebootRequest {
|
||||||
|
destination: destination,
|
||||||
|
});
|
||||||
|
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
|
||||||
|
Reply::RebootImminent.write_to(stream)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(drtio::Error::UnexpectedReply.into())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn debug_allocator(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, _stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtAllocatorDebugRequest {
|
||||||
|
destination: destination,
|
||||||
|
});
|
||||||
|
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()),
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Err(drtio::Error::UnexpectedReply.into())
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Err(e.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flash(io: &Io, aux_mutex: &Mutex,
|
||||||
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &RoutingTable, linkno: u8,
|
||||||
|
destination: u8, stream: &mut TcpStream, image: &[u8]) -> Result<(), Error<SchedError>> {
|
||||||
|
|
||||||
|
let alloc_reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtFlashRequest {
|
||||||
|
destination: destination,
|
||||||
|
payload_length: image.len() as u32,
|
||||||
|
});
|
||||||
|
|
||||||
|
match alloc_reply {
|
||||||
|
Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()),
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(drtio::Error::UnexpectedReply)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}?;
|
||||||
|
|
||||||
|
match drtio::partition_data(&image, |slice, status, len: usize| {
|
||||||
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
|
&Packet::CoreMgmtFlashAddDataRequest {
|
||||||
|
destination: destination, length: len as u16, last: status.is_last(), data: *slice});
|
||||||
|
match reply {
|
||||||
|
Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()),
|
||||||
|
Ok(Packet::CoreMgmtDropLink) => {
|
||||||
|
if status.is_last() {
|
||||||
|
drtioaux::send(
|
||||||
|
linkno, &Packet::CoreMgmtDropLinkAck { destination: destination }
|
||||||
|
).map_err(|_| drtio::Error::AuxError)
|
||||||
|
} else {
|
||||||
|
error!("received unexpected drop link packet");
|
||||||
|
Err(drtio::Error::UnexpectedReply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(packet) => {
|
||||||
|
error!("received unexpected aux packet: {:?}", packet);
|
||||||
|
Err(drtio::Error::UnexpectedReply)
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
error!("aux packet error ({})", e);
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
Ok(()) => {
|
||||||
|
Reply::RebootImminent.write_to(stream)?;
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
Reply::Error.write_to(stream)?;
|
||||||
|
Err(e.into())
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(has_drtio)]
|
||||||
|
macro_rules! process {
|
||||||
|
($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $subkernel_mutex:ident, $routing_table:ident, $tcp_stream:ident, $destination: ident, $func:ident $(, $param:expr)*) => {{
|
||||||
|
let hop = $routing_table.0[$destination as usize][0];
|
||||||
|
if hop == 0 {
|
||||||
|
local_coremgmt::$func($io, $tcp_stream, $($param, )*)
|
||||||
|
} else {
|
||||||
|
let linkno = hop - 1;
|
||||||
|
remote_coremgmt::$func($io, $aux_mutex, $ddma_mutex, $subkernel_mutex, $routing_table, linkno, $destination, $tcp_stream, $($param, )*)
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(has_drtio))]
|
||||||
|
macro_rules! process {
|
||||||
|
($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $subkernel_mutex:ident, $routing_table:ident, $tcp_stream:ident, $_destination: ident, $func:ident $(, $param:expr)*) => {{
|
||||||
|
local_coremgmt::$func($io, $tcp_stream, $($param, )*)
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn worker(io: &Io, stream: &mut TcpStream, restart_idle: &Urc<Cell<bool>>,
|
||||||
|
_aux_mutex: &Mutex, _ddma_mutex: &Mutex, _subkernel_mutex: &Mutex,
|
||||||
|
_routing_table: &RoutingTable) -> Result<(), Error<SchedError>> {
|
||||||
|
read_magic(stream)?;
|
||||||
|
let _destination = stream.read_u8()?;
|
||||||
|
Write::write_all(stream, "e".as_bytes())?;
|
||||||
|
info!("new connection from {}", stream.remote_endpoint());
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match Request::read_from(stream)? {
|
||||||
|
Request::GetLog => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, get_log),
|
||||||
|
Request::ClearLog => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, clear_log),
|
||||||
|
Request::PullLog => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, pull_log),
|
||||||
|
Request::SetLogFilter(level) => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, set_log_filter, level),
|
||||||
|
Request::SetUartLogFilter(level) => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, set_uart_log_filter, level),
|
||||||
|
Request::ConfigRead { ref key } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_read, key),
|
||||||
|
Request::ConfigWrite { ref key, ref value } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_write, key, value, restart_idle),
|
||||||
|
Request::ConfigRemove { ref key } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_remove, key, restart_idle),
|
||||||
|
Request::ConfigErase => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_erase, restart_idle),
|
||||||
|
Request::Reboot => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, reboot),
|
||||||
|
Request::DebugAllocator => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, debug_allocator),
|
||||||
|
Request::Flash { ref image } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, flash, &image[..]),
|
||||||
|
}?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn thread(io: Io, restart_idle: &Urc<Cell<bool>>, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex, routing_table: &Urc<RefCell<RoutingTable>>) {
|
||||||
let listener = TcpListener::new(&io, 8192);
|
let listener = TcpListener::new(&io, 8192);
|
||||||
listener.listen(1380).expect("mgmt: cannot listen");
|
listener.listen(1380).expect("mgmt: cannot listen");
|
||||||
info!("management interface active");
|
info!("management interface active");
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
let restart_idle = restart_idle.clone();
|
||||||
|
let aux_mutex = aux_mutex.clone();
|
||||||
|
let ddma_mutex = ddma_mutex.clone();
|
||||||
|
let subkernel_mutex = subkernel_mutex.clone();
|
||||||
|
let routing_table = routing_table.clone();
|
||||||
let stream = listener.accept().expect("mgmt: cannot accept").into_handle();
|
let stream = listener.accept().expect("mgmt: cannot accept").into_handle();
|
||||||
io.spawn(4096, move |io| {
|
io.spawn(16384, move |io| {
|
||||||
|
let routing_table = routing_table.borrow();
|
||||||
let mut stream = TcpStream::from_handle(&io, stream);
|
let mut stream = TcpStream::from_handle(&io, stream);
|
||||||
match worker(&io, &mut stream) {
|
match worker(&io, &mut stream, &restart_idle, &aux_mutex, &ddma_mutex, &subkernel_mutex, &routing_table) {
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
Err(Error::Io(IoError::UnexpectedEnd)) => (),
|
Err(Error::Io(IoError::UnexpectedEnd)) => (),
|
||||||
Err(err) => error!("aborted: {}", err)
|
Err(err) => error!("aborted: {}", err)
|
||||||
|
@ -16,6 +16,8 @@ const ASYNC_ERROR_SEQUENCE_ERROR: u8 = 1 << 2;
|
|||||||
pub mod drtio {
|
pub mod drtio {
|
||||||
use super::*;
|
use super::*;
|
||||||
use alloc::vec::Vec;
|
use alloc::vec::Vec;
|
||||||
|
#[cfg(has_drtio_eem)]
|
||||||
|
use board_artiq::drtio_eem;
|
||||||
use drtioaux;
|
use drtioaux;
|
||||||
use proto_artiq::drtioaux_proto::{MASTER_PAYLOAD_MAX_SIZE, PayloadStatus};
|
use proto_artiq::drtioaux_proto::{MASTER_PAYLOAD_MAX_SIZE, PayloadStatus};
|
||||||
use rtio_dma::remote_dma;
|
use rtio_dma::remote_dma;
|
||||||
@ -24,6 +26,9 @@ pub mod drtio {
|
|||||||
use kernel::subkernel;
|
use kernel::subkernel;
|
||||||
use sched::Error as SchedError;
|
use sched::Error as SchedError;
|
||||||
|
|
||||||
|
#[cfg(has_drtio_eem)]
|
||||||
|
const DRTIO_EEM_LINKNOS: core::ops::Range<usize> = (csr::DRTIO.len()-csr::CONFIG_EEM_DRTIO_COUNT as usize)..csr::DRTIO.len();
|
||||||
|
|
||||||
#[derive(Fail, Debug)]
|
#[derive(Fail, Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
#[fail(display = "timed out")]
|
#[fail(display = "timed out")]
|
||||||
@ -73,6 +78,18 @@ pub mod drtio {
|
|||||||
|
|
||||||
fn link_rx_up(linkno: u8) -> bool {
|
fn link_rx_up(linkno: u8) -> bool {
|
||||||
let linkno = linkno as usize;
|
let linkno = linkno as usize;
|
||||||
|
#[cfg(has_drtio_eem)]
|
||||||
|
if DRTIO_EEM_LINKNOS.contains(&linkno) {
|
||||||
|
let eem_trx_no = linkno - DRTIO_EEM_LINKNOS.start;
|
||||||
|
unsafe {
|
||||||
|
csr::eem_transceiver::transceiver_sel_write(eem_trx_no as u8);
|
||||||
|
csr::eem_transceiver::comma_align_reset_write(1);
|
||||||
|
}
|
||||||
|
clock::spin_us(100);
|
||||||
|
return unsafe {
|
||||||
|
csr::eem_transceiver::comma_read() == 1
|
||||||
|
};
|
||||||
|
}
|
||||||
unsafe {
|
unsafe {
|
||||||
(csr::DRTIO[linkno].rx_up_read)() == 1
|
(csr::DRTIO[linkno].rx_up_read)() == 1
|
||||||
}
|
}
|
||||||
@ -414,9 +431,27 @@ pub mod drtio {
|
|||||||
} else {
|
} else {
|
||||||
info!("[LINK#{}] link is down", linkno);
|
info!("[LINK#{}] link is down", linkno);
|
||||||
up_links[linkno as usize] = false;
|
up_links[linkno as usize] = false;
|
||||||
|
|
||||||
|
#[cfg(has_drtio_eem)]
|
||||||
|
if DRTIO_EEM_LINKNOS.contains(&(linkno as usize)) {
|
||||||
|
unsafe { csr::eem_transceiver::rx_ready_write(0); }
|
||||||
|
// Clear DRTIOAUX buffer
|
||||||
|
while !matches!(drtioaux::recv(linkno), Ok(None)) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
/* link was previously down */
|
/* link was previously down */
|
||||||
|
#[cfg(has_drtio_eem)]
|
||||||
|
if DRTIO_EEM_LINKNOS.contains(&(linkno as usize)) {
|
||||||
|
let eem_trx_no = linkno - DRTIO_EEM_LINKNOS.start as u8;
|
||||||
|
if !unsafe { drtio_eem::align_wordslip(eem_trx_no) } {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
csr::eem_transceiver::rx_ready_write(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if link_rx_up(linkno) {
|
if link_rx_up(linkno) {
|
||||||
info!("[LINK#{}] link RX became up, pinging", linkno);
|
info!("[LINK#{}] link RX became up, pinging", linkno);
|
||||||
let ping_count = ping_remote(&io, aux_mutex, linkno);
|
let ping_count = ping_remote(&io, aux_mutex, linkno);
|
||||||
@ -471,7 +506,7 @@ pub mod drtio {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn partition_data<F>(data: &[u8], send_f: F) -> Result<(), Error>
|
pub fn partition_data<F>(data: &[u8], send_f: F) -> Result<(), Error>
|
||||||
where F: Fn(&[u8; MASTER_PAYLOAD_MAX_SIZE], PayloadStatus, usize) -> Result<(), Error> {
|
where F: Fn(&[u8; MASTER_PAYLOAD_MAX_SIZE], PayloadStatus, usize) -> Result<(), Error> {
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
while i < data.len() {
|
while i < data.len() {
|
||||||
|
@ -839,7 +839,7 @@ fn flash_kernel_worker(io: &Io, aux_mutex: &Mutex,
|
|||||||
routing_table: &drtio_routing::RoutingTable,
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
up_destinations: &Urc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
|
up_destinations: &Urc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
|
||||||
ddma_mutex: &Mutex, subkernel_mutex: &Mutex, congress: &mut Congress,
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex, congress: &mut Congress,
|
||||||
config_key: &str) -> Result<(), Error<SchedError>> {
|
config_key: &str, restart_idle: Option<&Urc<Cell<bool>>>) -> Result<(), Error<SchedError>> {
|
||||||
let mut session = Session::new(congress);
|
let mut session = Session::new(congress);
|
||||||
|
|
||||||
config::read(config_key, |result| {
|
config::read(config_key, |result| {
|
||||||
@ -859,12 +859,17 @@ fn flash_kernel_worker(io: &Io, aux_mutex: &Mutex,
|
|||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
kern_run(&mut session)?;
|
kern_run(&mut session)?;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
if !rpc_queue::empty() {
|
if !rpc_queue::empty() {
|
||||||
unexpected!("unexpected background RPC in flash kernel")
|
unexpected!("unexpected background RPC in flash kernel")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let Some(r_idle) = restart_idle {
|
||||||
|
if r_idle.get() {
|
||||||
|
return Err(Error::KernelNotFound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if mailbox::receive() != 0 {
|
if mailbox::receive() != 0 {
|
||||||
if process_kern_message(io, aux_mutex, routing_table, up_destinations, ddma_mutex, subkernel_mutex, None, &mut session)? {
|
if process_kern_message(io, aux_mutex, routing_table, up_destinations, ddma_mutex, subkernel_mutex, None, &mut session)? {
|
||||||
return Ok(())
|
return Ok(())
|
||||||
@ -897,7 +902,7 @@ fn respawn<F>(io: &Io, handle: &mut Option<ThreadHandle>, f: F)
|
|||||||
pub fn thread(io: Io, aux_mutex: &Mutex,
|
pub fn thread(io: Io, aux_mutex: &Mutex,
|
||||||
routing_table: &Urc<RefCell<drtio_routing::RoutingTable>>,
|
routing_table: &Urc<RefCell<drtio_routing::RoutingTable>>,
|
||||||
up_destinations: &Urc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
|
up_destinations: &Urc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
|
||||||
ddma_mutex: &Mutex, subkernel_mutex: &Mutex) {
|
ddma_mutex: &Mutex, subkernel_mutex: &Mutex, restart_idle: &Urc<Cell<bool>>) {
|
||||||
let listener = TcpListener::new(&io, 65535);
|
let listener = TcpListener::new(&io, 65535);
|
||||||
listener.listen(1381).expect("session: cannot listen");
|
listener.listen(1381).expect("session: cannot listen");
|
||||||
info!("accepting network sessions");
|
info!("accepting network sessions");
|
||||||
@ -910,11 +915,11 @@ pub fn thread(io: Io, aux_mutex: &Mutex,
|
|||||||
let mut congress = congress.borrow_mut();
|
let mut congress = congress.borrow_mut();
|
||||||
info!("running startup kernel");
|
info!("running startup kernel");
|
||||||
match flash_kernel_worker(&io, &aux_mutex, &routing_table, &up_destinations,
|
match flash_kernel_worker(&io, &aux_mutex, &routing_table, &up_destinations,
|
||||||
ddma_mutex, subkernel_mutex, &mut congress, "startup_kernel") {
|
ddma_mutex, subkernel_mutex, &mut congress, "startup_kernel", None) {
|
||||||
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);
|
||||||
@ -994,11 +999,12 @@ pub fn thread(io: Io, aux_mutex: &Mutex,
|
|||||||
let congress = congress.clone();
|
let congress = congress.clone();
|
||||||
let ddma_mutex = ddma_mutex.clone();
|
let ddma_mutex = ddma_mutex.clone();
|
||||||
let subkernel_mutex = subkernel_mutex.clone();
|
let subkernel_mutex = subkernel_mutex.clone();
|
||||||
|
let restart_idle = restart_idle.clone();
|
||||||
respawn(&io, &mut kernel_thread, move |io| {
|
respawn(&io, &mut kernel_thread, move |io| {
|
||||||
let routing_table = routing_table.borrow();
|
let routing_table = routing_table.borrow();
|
||||||
let mut congress = congress.borrow_mut();
|
let mut congress = congress.borrow_mut();
|
||||||
match flash_kernel_worker(&io, &aux_mutex, &routing_table, &up_destinations,
|
match flash_kernel_worker(&io, &aux_mutex, &routing_table, &up_destinations,
|
||||||
&ddma_mutex, &subkernel_mutex, &mut *congress, "idle_kernel") {
|
&ddma_mutex, &subkernel_mutex, &mut *congress, "idle_kernel", Some(&restart_idle)) {
|
||||||
Ok(()) =>
|
Ok(()) =>
|
||||||
info!("idle kernel finished, standing by"),
|
info!("idle kernel finished, standing by"),
|
||||||
Err(Error::Protocol(host::Error::Io(
|
Err(Error::Protocol(host::Error::Io(
|
||||||
@ -1009,8 +1015,9 @@ 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 !restart_idle.get() && io.relinquish().is_ok() {}
|
||||||
|
restart_idle.set(false);
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
error!("idle kernel aborted: {}", err);
|
error!("idle kernel aborted: {}", err);
|
||||||
|
@ -15,9 +15,12 @@ build_misoc = { path = "../libbuild_misoc" }
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
log = { version = "0.4", default-features = false }
|
log = { version = "0.4", default-features = false }
|
||||||
io = { path = "../libio", features = ["byteorder", "alloc"] }
|
io = { path = "../libio", features = ["byteorder", "alloc"] }
|
||||||
|
byteorder = { version = "1.0", default-features = false }
|
||||||
|
crc = { version = "1.7", default-features = false }
|
||||||
cslice = { version = "0.3" }
|
cslice = { version = "0.3" }
|
||||||
board_misoc = { path = "../libboard_misoc", features = ["uart_console", "log"] }
|
board_misoc = { path = "../libboard_misoc", features = ["uart_console", "log"] }
|
||||||
board_artiq = { path = "../libboard_artiq", features = ["alloc"] }
|
board_artiq = { path = "../libboard_artiq", features = ["alloc"] }
|
||||||
|
logger_artiq = { path = "../liblogger_artiq" }
|
||||||
alloc_list = { path = "../liballoc_list" }
|
alloc_list = { path = "../liballoc_list" }
|
||||||
riscv = { version = "0.6.0", features = ["inline-asm"] }
|
riscv = { version = "0.6.0", features = ["inline-asm"] }
|
||||||
proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] }
|
proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] }
|
||||||
|
@ -6,21 +6,25 @@ extern crate log;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate board_misoc;
|
extern crate board_misoc;
|
||||||
extern crate board_artiq;
|
extern crate board_artiq;
|
||||||
|
extern crate logger_artiq;
|
||||||
extern crate riscv;
|
extern crate riscv;
|
||||||
extern crate alloc;
|
extern crate alloc;
|
||||||
extern crate proto_artiq;
|
extern crate proto_artiq;
|
||||||
|
extern crate byteorder;
|
||||||
|
extern crate crc;
|
||||||
extern crate cslice;
|
extern crate cslice;
|
||||||
extern crate io;
|
extern crate io;
|
||||||
extern crate eh;
|
extern crate eh;
|
||||||
|
|
||||||
use core::convert::TryFrom;
|
use core::convert::TryFrom;
|
||||||
use board_misoc::{csr, ident, clock, config, uart_logger, i2c, pmp};
|
use board_misoc::{csr, ident, clock, config, i2c, pmp};
|
||||||
#[cfg(has_si5324)]
|
#[cfg(has_si5324)]
|
||||||
use board_artiq::si5324;
|
use board_artiq::si5324;
|
||||||
#[cfg(has_si549)]
|
#[cfg(has_si549)]
|
||||||
use board_artiq::si549;
|
use board_artiq::si549;
|
||||||
#[cfg(soc_platform = "kasli")]
|
#[cfg(soc_platform = "kasli")]
|
||||||
use board_misoc::irq;
|
use board_misoc::irq;
|
||||||
|
use board_misoc::{boot, spiflash};
|
||||||
use board_artiq::{spi, drtioaux, drtio_routing};
|
use board_artiq::{spi, drtioaux, drtio_routing};
|
||||||
#[cfg(soc_platform = "efc")]
|
#[cfg(soc_platform = "efc")]
|
||||||
use board_artiq::ad9117;
|
use board_artiq::ad9117;
|
||||||
@ -30,6 +34,7 @@ use board_artiq::drtio_eem;
|
|||||||
use riscv::register::{mcause, mepc, mtval};
|
use riscv::register::{mcause, mepc, mtval};
|
||||||
use dma::Manager as DmaManager;
|
use dma::Manager as DmaManager;
|
||||||
use kernel::Manager as KernelManager;
|
use kernel::Manager as KernelManager;
|
||||||
|
use mgmt::Manager as CoreManager;
|
||||||
use analyzer::Analyzer;
|
use analyzer::Analyzer;
|
||||||
|
|
||||||
#[global_allocator]
|
#[global_allocator]
|
||||||
@ -41,6 +46,7 @@ mod dma;
|
|||||||
mod analyzer;
|
mod analyzer;
|
||||||
mod kernel;
|
mod kernel;
|
||||||
mod cache;
|
mod cache;
|
||||||
|
mod mgmt;
|
||||||
|
|
||||||
fn drtiosat_reset(reset: bool) {
|
fn drtiosat_reset(reset: bool) {
|
||||||
unsafe {
|
unsafe {
|
||||||
@ -129,7 +135,7 @@ macro_rules! forward {
|
|||||||
($router:expr, $routing_table:expr, $destination:expr, $rank:expr, $self_destination:expr, $repeaters:expr, $packet:expr) => {}
|
($router:expr, $routing_table:expr, $destination:expr, $rank:expr, $self_destination:expr, $repeaters:expr, $packet:expr) => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmgr: &mut KernelManager,
|
fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmgr: &mut KernelManager, coremgr: &mut CoreManager,
|
||||||
_repeaters: &mut [repeater::Repeater], _routing_table: &mut drtio_routing::RoutingTable, rank: &mut u8,
|
_repeaters: &mut [repeater::Repeater], _routing_table: &mut drtio_routing::RoutingTable, rank: &mut u8,
|
||||||
router: &mut routing::Router, self_destination: &mut u8, packet: drtioaux::Packet
|
router: &mut routing::Router, self_destination: &mut u8, packet: drtioaux::Packet
|
||||||
) -> Result<(), drtioaux::Error<!>> {
|
) -> Result<(), drtioaux::Error<!>> {
|
||||||
@ -495,6 +501,167 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
drtioaux::Packet::CoreMgmtGetLogRequest { destination: _destination, clear } => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
let mut data_slice = [0; SAT_PAYLOAD_MAX_SIZE];
|
||||||
|
if let Ok(meta) = coremgr.log_get_slice(&mut data_slice, clear) {
|
||||||
|
drtioaux::send(
|
||||||
|
0,
|
||||||
|
&drtioaux::Packet::CoreMgmtGetLogReply {
|
||||||
|
last: meta.status.is_last(),
|
||||||
|
length: meta.len as u16,
|
||||||
|
data: data_slice,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtClearLogRequest { destination: _destination } => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: mgmt::clear_log().is_ok() })
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtSetLogLevelRequest {destination: _destination, log_level } => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
if let Ok(level_filter) = mgmt::byte_to_level_filter(log_level) {
|
||||||
|
info!("changing log level to {}", level_filter);
|
||||||
|
log::set_max_level(level_filter);
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
|
||||||
|
} else {
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtSetUartLogLevelRequest { destination: _destination, log_level } => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
if let Ok(level_filter) = mgmt::byte_to_level_filter(log_level) {
|
||||||
|
info!("changing UART log level to {}", level_filter);
|
||||||
|
logger_artiq::BufferLogger::with(|logger|
|
||||||
|
logger.set_uart_log_level(level_filter));
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
|
||||||
|
} else {
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtConfigReadRequest {
|
||||||
|
destination: _destination,
|
||||||
|
length,
|
||||||
|
key,
|
||||||
|
} => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
let mut value_slice = [0; SAT_PAYLOAD_MAX_SIZE];
|
||||||
|
|
||||||
|
let key_slice = &key[..length as usize];
|
||||||
|
if !key_slice.is_ascii() {
|
||||||
|
error!("invalid key");
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
|
||||||
|
} else {
|
||||||
|
let key = core::str::from_utf8(key_slice).unwrap();
|
||||||
|
if coremgr.fetch_config_value(key).is_ok() {
|
||||||
|
let meta = coremgr.get_config_value_slice(&mut value_slice);
|
||||||
|
drtioaux::send(
|
||||||
|
0,
|
||||||
|
&drtioaux::Packet::CoreMgmtConfigReadReply {
|
||||||
|
length: meta.len as u16,
|
||||||
|
last: meta.status.is_last(),
|
||||||
|
value: value_slice,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtConfigReadContinue {
|
||||||
|
destination: _destination,
|
||||||
|
} => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
let mut value_slice = [0; SAT_PAYLOAD_MAX_SIZE];
|
||||||
|
let meta = coremgr.get_config_value_slice(&mut value_slice);
|
||||||
|
drtioaux::send(
|
||||||
|
0,
|
||||||
|
&drtioaux::Packet::CoreMgmtConfigReadReply {
|
||||||
|
length: meta.len as u16,
|
||||||
|
last: meta.status.is_last(),
|
||||||
|
value: value_slice,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtConfigWriteRequest { destination: _destination, last, length, data } => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
coremgr.add_config_data(&data, length as usize);
|
||||||
|
if last {
|
||||||
|
coremgr.write_config()
|
||||||
|
} else {
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtConfigRemoveRequest { destination: _destination, length, key } => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
let key = core::str::from_utf8(&key[..length as usize]).unwrap();
|
||||||
|
let succeeded = config::remove(key)
|
||||||
|
.map_err(|err| warn!("error on removing config: {:?}", err))
|
||||||
|
.is_ok();
|
||||||
|
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded })
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtConfigEraseRequest { destination: _destination } => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
let succeeded = config::erase()
|
||||||
|
.map_err(|err| warn!("error on erasing config: {:?}", err))
|
||||||
|
.is_ok();
|
||||||
|
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded })
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtRebootRequest { destination: _destination } => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })?;
|
||||||
|
warn!("restarting");
|
||||||
|
unsafe { spiflash::reload(); }
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtFlashRequest { destination: _destination, payload_length } => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
coremgr.allocate_image_buffer(payload_length as usize);
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtFlashAddDataRequest { destination: _destination, last, length, data } => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
coremgr.add_image_data(&data, length as usize);
|
||||||
|
if last {
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtDropLink)
|
||||||
|
} else {
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drtioaux::Packet::CoreMgmtDropLinkAck { destination: _destination } => {
|
||||||
|
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
|
||||||
|
|
||||||
|
#[cfg(not(has_drtio_eem))]
|
||||||
|
unsafe {
|
||||||
|
csr::gt_drtio::txenable_write(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(has_drtio_eem)]
|
||||||
|
unsafe {
|
||||||
|
csr::eem_transceiver::txenable_write(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
coremgr.flash_image();
|
||||||
|
warn!("restarting");
|
||||||
|
unsafe { spiflash::reload(); }
|
||||||
|
}
|
||||||
|
|
||||||
_ => {
|
_ => {
|
||||||
warn!("received unexpected aux packet");
|
warn!("received unexpected aux packet");
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -503,13 +670,13 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn process_aux_packets(dma_manager: &mut DmaManager, analyzer: &mut Analyzer,
|
fn process_aux_packets(dma_manager: &mut DmaManager, analyzer: &mut Analyzer,
|
||||||
kernelmgr: &mut KernelManager, repeaters: &mut [repeater::Repeater],
|
kernelmgr: &mut KernelManager, coremgr: &mut CoreManager, repeaters: &mut [repeater::Repeater],
|
||||||
routing_table: &mut drtio_routing::RoutingTable, rank: &mut u8, router: &mut routing::Router,
|
routing_table: &mut drtio_routing::RoutingTable, rank: &mut u8, router: &mut routing::Router,
|
||||||
destination: &mut u8) {
|
destination: &mut u8) {
|
||||||
let result =
|
let result =
|
||||||
drtioaux::recv(0).and_then(|packet| {
|
drtioaux::recv(0).and_then(|packet| {
|
||||||
if let Some(packet) = packet.or_else(|| router.get_local_packet()) {
|
if let Some(packet) = packet.or_else(|| router.get_local_packet()) {
|
||||||
process_aux_packet(dma_manager, analyzer, kernelmgr,
|
process_aux_packet(dma_manager, analyzer, kernelmgr, coremgr,
|
||||||
repeaters, routing_table, rank, router, destination, packet)
|
repeaters, routing_table, rank, router, destination, packet)
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -664,6 +831,27 @@ fn sysclk_setup() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn setup_log_levels() {
|
||||||
|
match config::read_str("log_level", |r| r.map(|s| s.parse())) {
|
||||||
|
Ok(Ok(log_level_filter)) => {
|
||||||
|
info!("log level set to {} by `log_level` config key",
|
||||||
|
log_level_filter);
|
||||||
|
log::set_max_level(log_level_filter);
|
||||||
|
}
|
||||||
|
_ => info!("log level set to INFO by default")
|
||||||
|
}
|
||||||
|
match config::read_str("uart_log_level", |r| r.map(|s| s.parse())) {
|
||||||
|
Ok(Ok(uart_log_level_filter)) => {
|
||||||
|
info!("UART log level set to {} by `uart_log_level` config key",
|
||||||
|
uart_log_level_filter);
|
||||||
|
logger_artiq::BufferLogger::with(|logger|
|
||||||
|
logger.set_uart_log_level(uart_log_level_filter));
|
||||||
|
}
|
||||||
|
_ => info!("UART log level set to INFO by default")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut LOG_BUFFER: [u8; 1<<17] = [0; 1<<17];
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern fn main() -> i32 {
|
pub extern fn main() -> i32 {
|
||||||
@ -683,12 +871,21 @@ pub extern fn main() -> i32 {
|
|||||||
irq::enable(csr::WRPLL_INTERRUPT);
|
irq::enable(csr::WRPLL_INTERRUPT);
|
||||||
|
|
||||||
clock::init();
|
clock::init();
|
||||||
uart_logger::ConsoleLogger::register();
|
unsafe {
|
||||||
|
logger_artiq::BufferLogger::new(&mut LOG_BUFFER[..]).register(||
|
||||||
|
boot::start_user(startup as usize));
|
||||||
|
}
|
||||||
|
|
||||||
|
0
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup() {
|
||||||
info!("ARTIQ satellite manager starting...");
|
info!("ARTIQ satellite manager starting...");
|
||||||
info!("software ident {}", csr::CONFIG_IDENTIFIER_STR);
|
info!("software ident {}", csr::CONFIG_IDENTIFIER_STR);
|
||||||
info!("gateware ident {}", ident::read(&mut [0; 64]));
|
info!("gateware ident {}", ident::read(&mut [0; 64]));
|
||||||
|
|
||||||
|
setup_log_levels();
|
||||||
|
|
||||||
#[cfg(has_i2c)]
|
#[cfg(has_i2c)]
|
||||||
i2c::init().expect("I2C initialization failed");
|
i2c::init().expect("I2C initialization failed");
|
||||||
#[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))]
|
#[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))]
|
||||||
@ -777,7 +974,12 @@ pub extern fn main() -> i32 {
|
|||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(has_drtio_eem)]
|
#[cfg(has_drtio_eem)]
|
||||||
|
{
|
||||||
drtio_eem::init();
|
drtio_eem::init();
|
||||||
|
unsafe {
|
||||||
|
csr::eem_transceiver::rx_ready_write(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(has_drtio_routing)]
|
#[cfg(has_drtio_routing)]
|
||||||
let mut repeaters = [repeater::Repeater::default(); csr::DRTIOREP.len()];
|
let mut repeaters = [repeater::Repeater::default(); csr::DRTIOREP.len()];
|
||||||
@ -829,6 +1031,7 @@ pub extern fn main() -> i32 {
|
|||||||
let mut dma_manager = DmaManager::new();
|
let mut dma_manager = DmaManager::new();
|
||||||
let mut analyzer = Analyzer::new();
|
let mut analyzer = Analyzer::new();
|
||||||
let mut kernelmgr = KernelManager::new();
|
let mut kernelmgr = KernelManager::new();
|
||||||
|
let mut coremgr = CoreManager::new();
|
||||||
|
|
||||||
cricon_select(RtioMaster::Drtio);
|
cricon_select(RtioMaster::Drtio);
|
||||||
drtioaux::reset(0);
|
drtioaux::reset(0);
|
||||||
@ -838,7 +1041,7 @@ pub extern fn main() -> i32 {
|
|||||||
while drtiosat_link_rx_up() {
|
while drtiosat_link_rx_up() {
|
||||||
drtiosat_process_errors();
|
drtiosat_process_errors();
|
||||||
process_aux_packets(&mut dma_manager, &mut analyzer,
|
process_aux_packets(&mut dma_manager, &mut analyzer,
|
||||||
&mut kernelmgr, &mut repeaters, &mut routing_table,
|
&mut kernelmgr, &mut coremgr, &mut repeaters, &mut routing_table,
|
||||||
&mut rank, &mut router, &mut destination);
|
&mut rank, &mut router, &mut destination);
|
||||||
for rep in repeaters.iter_mut() {
|
for rep in repeaters.iter_mut() {
|
||||||
rep.service(&routing_table, rank, destination, &mut router);
|
rep.service(&routing_table, rank, destination, &mut router);
|
||||||
|
149
artiq/firmware/satman/mgmt.rs
Normal file
149
artiq/firmware/satman/mgmt.rs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
use alloc::vec::Vec;
|
||||||
|
use byteorder::{ByteOrder, NativeEndian};
|
||||||
|
use crc::crc32;
|
||||||
|
|
||||||
|
use routing::{Sliceable, SliceMeta};
|
||||||
|
use board_artiq::drtioaux;
|
||||||
|
use board_misoc::{mem, config, spiflash};
|
||||||
|
use log::LevelFilter;
|
||||||
|
use logger_artiq::BufferLogger;
|
||||||
|
use io::{Cursor, ProtoRead, ProtoWrite};
|
||||||
|
use proto_artiq::drtioaux_proto::SAT_PAYLOAD_MAX_SIZE;
|
||||||
|
|
||||||
|
|
||||||
|
pub fn clear_log() -> Result<(), ()> {
|
||||||
|
BufferLogger::with(|logger| {
|
||||||
|
let mut buffer = logger.buffer()?;
|
||||||
|
Ok(buffer.clear())
|
||||||
|
}).map_err(|()| error!("error on clearing log buffer"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn byte_to_level_filter(level_byte: u8) -> Result<LevelFilter, ()> {
|
||||||
|
Ok(match level_byte {
|
||||||
|
0 => LevelFilter::Off,
|
||||||
|
1 => LevelFilter::Error,
|
||||||
|
2 => LevelFilter::Warn,
|
||||||
|
3 => LevelFilter::Info,
|
||||||
|
4 => LevelFilter::Debug,
|
||||||
|
5 => LevelFilter::Trace,
|
||||||
|
lv => {
|
||||||
|
error!("unknown log level: {}", lv);
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Manager {
|
||||||
|
config_payload: Cursor<Vec<u8>>,
|
||||||
|
image_payload: Cursor<Vec<u8>>,
|
||||||
|
last_value: Sliceable,
|
||||||
|
last_log: Sliceable,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Manager {
|
||||||
|
pub fn new() -> Manager {
|
||||||
|
Manager {
|
||||||
|
config_payload: Cursor::new(Vec::new()),
|
||||||
|
image_payload: Cursor::new(Vec::new()),
|
||||||
|
last_value: Sliceable::new(0, Vec::new()),
|
||||||
|
last_log: Sliceable::new(0, Vec::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fetch_config_value(&mut self, key: &str) -> Result<(), ()> {
|
||||||
|
config::read(key, |result| result.map(
|
||||||
|
|value| self.last_value = Sliceable::new(0, value.to_vec())
|
||||||
|
)).map_err(|_err| warn!("read error: no such key"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_get_slice(&mut self, data_slice: &mut [u8; SAT_PAYLOAD_MAX_SIZE], consume: bool) -> Result<SliceMeta, ()> {
|
||||||
|
// Populate buffer if depleted
|
||||||
|
if self.last_log.at_end() {
|
||||||
|
BufferLogger::with(|logger| {
|
||||||
|
let mut buffer = logger.buffer()?;
|
||||||
|
self.last_log = Sliceable::new(0, buffer.extract().as_bytes().to_vec());
|
||||||
|
if consume {
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}).map_err(|()| error!("error on getting log buffer"))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self.last_log.get_slice_satellite(data_slice))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_config_value_slice(&mut self, data_slice: &mut [u8; SAT_PAYLOAD_MAX_SIZE]) -> SliceMeta {
|
||||||
|
self.last_value.get_slice_satellite(data_slice)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_config_data(&mut self, data: &[u8], data_len: usize) {
|
||||||
|
self.config_payload.write_all(&data[..data_len]).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_config_data(&mut self) {
|
||||||
|
self.config_payload.get_mut().clear();
|
||||||
|
self.config_payload.set_position(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_config(&mut self) -> Result<(), drtioaux::Error<!>> {
|
||||||
|
let key = match self.config_payload.read_string() {
|
||||||
|
Ok(key) => key,
|
||||||
|
Err(err) => {
|
||||||
|
self.clear_config_data();
|
||||||
|
error!("error on reading key: {:?}", err);
|
||||||
|
return drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = self.config_payload.read_bytes().unwrap();
|
||||||
|
|
||||||
|
let succeeded = config::write(&key, &value).map_err(|err| {
|
||||||
|
error!("error on writing config: {:?}", err);
|
||||||
|
}).is_ok();
|
||||||
|
|
||||||
|
self.clear_config_data();
|
||||||
|
|
||||||
|
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn allocate_image_buffer(&mut self, image_size: usize) {
|
||||||
|
self.image_payload = Cursor::new(Vec::with_capacity(image_size));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_image_data(&mut self, data: &[u8], data_len: usize) {
|
||||||
|
self.image_payload.write_all(&data[..data_len]).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn flash_image(&self) {
|
||||||
|
let image = &self.image_payload.get_ref()[..];
|
||||||
|
|
||||||
|
let (expected_crc, mut image) = {
|
||||||
|
let (image, crc_slice) = image.split_at(image.len() - 4);
|
||||||
|
(NativeEndian::read_u32(crc_slice), image)
|
||||||
|
};
|
||||||
|
|
||||||
|
let actual_crc = crc32::checksum_ieee(image);
|
||||||
|
|
||||||
|
if actual_crc == expected_crc {
|
||||||
|
let bin_origins = [
|
||||||
|
("gateware" , 0 ),
|
||||||
|
("bootloader", mem::ROM_BASE ),
|
||||||
|
("firmware" , mem::FLASH_BOOT_ADDRESS),
|
||||||
|
];
|
||||||
|
|
||||||
|
for (name, origin) in bin_origins {
|
||||||
|
info!("flashing {} binary...", name);
|
||||||
|
let size = NativeEndian::read_u32(&image[..4]) as usize;
|
||||||
|
image = &image[4..];
|
||||||
|
|
||||||
|
let (bin, remaining) = image.split_at(size);
|
||||||
|
image = remaining;
|
||||||
|
|
||||||
|
unsafe { spiflash::flash_binary(origin, bin) };
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
panic!("CRC failed, images have not been written to flash.\n(actual {:08x}, expected {:08x})", actual_crc, expected_crc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@ 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 */
|
||||||
@ -56,6 +57,7 @@ impl Sliceable {
|
|||||||
self.data.extend(data);
|
self.data.extend(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get_slice_fn!(get_slice_satellite, SAT_PAYLOAD_MAX_SIZE);
|
||||||
get_slice_fn!(get_slice_master, MASTER_PAYLOAD_MAX_SIZE);
|
get_slice_fn!(get_slice_master, MASTER_PAYLOAD_MAX_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ import sys
|
|||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
import asyncio
|
||||||
import ssl
|
import ssl
|
||||||
import io
|
import io
|
||||||
import zipfile
|
import zipfile
|
||||||
@ -41,22 +42,27 @@ def zip_unarchive(data, directory):
|
|||||||
|
|
||||||
class Client:
|
class Client:
|
||||||
def __init__(self, server, port, cafile):
|
def __init__(self, server, port, cafile):
|
||||||
|
self.server = server
|
||||||
|
self.port = port
|
||||||
self.ssl_context = ssl.create_default_context(cafile=cafile)
|
self.ssl_context = ssl.create_default_context(cafile=cafile)
|
||||||
self.raw_socket = socket.create_connection((server, port))
|
self.reader = None
|
||||||
self.init_websocket(server)
|
self.writer = None
|
||||||
try:
|
|
||||||
self.socket = self.ssl_context.wrap_socket(self.raw_socket, server_hostname=server)
|
|
||||||
except:
|
|
||||||
self.raw_socket.close()
|
|
||||||
raise
|
|
||||||
self.fsocket = self.socket.makefile("rwb")
|
|
||||||
|
|
||||||
def init_websocket(self, server):
|
async def connect(self):
|
||||||
self.raw_socket.sendall("GET / HTTP/1.1\r\nHost: {}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n"
|
self.reader, self.writer = await asyncio.open_connection(
|
||||||
.format(server).encode())
|
host=self.server,
|
||||||
|
port=self.port,
|
||||||
|
happy_eyeballs_delay=0.25
|
||||||
|
)
|
||||||
|
await self.init_websocket()
|
||||||
|
await self.writer.start_tls(self.ssl_context)
|
||||||
|
|
||||||
|
async def init_websocket(self):
|
||||||
|
self.writer.write("GET / HTTP/1.1\r\nHost: {}\r\nConnection: Upgrade\r\nUpgrade: websocket\r\n\r\n"
|
||||||
|
.format(self.server).encode())
|
||||||
crlf_count = 0
|
crlf_count = 0
|
||||||
while crlf_count < 4:
|
while crlf_count < 4:
|
||||||
char = self.raw_socket.recv(1)
|
char = await self.reader.read(1)
|
||||||
if not char:
|
if not char:
|
||||||
return ValueError("Connection closed during WebSocket initialization")
|
return ValueError("Connection closed during WebSocket initialization")
|
||||||
if char == b"\r" or char == b"\n":
|
if char == b"\r" or char == b"\n":
|
||||||
@ -64,30 +70,33 @@ class Client:
|
|||||||
else:
|
else:
|
||||||
crlf_count = 0
|
crlf_count = 0
|
||||||
|
|
||||||
def close(self):
|
async def close(self):
|
||||||
self.socket.close()
|
if self.writer:
|
||||||
self.raw_socket.close()
|
self.writer.close()
|
||||||
|
await self.writer.wait_closed()
|
||||||
|
|
||||||
def send_command(self, *command):
|
async def send_command(self, *command):
|
||||||
self.fsocket.write((" ".join(command) + "\n").encode())
|
self.writer.write((" ".join(command) + "\n").encode())
|
||||||
self.fsocket.flush()
|
|
||||||
|
|
||||||
def read_line(self):
|
async def read_line(self):
|
||||||
return self.fsocket.readline().decode("ascii")
|
line = (await self.reader.readline()).decode("ascii")
|
||||||
|
if not line and self.reader.at_eof():
|
||||||
|
raise ConnectionError("connection was closed unexpectedly")
|
||||||
|
return line
|
||||||
|
|
||||||
def read_reply(self):
|
async def read_reply(self):
|
||||||
return self.fsocket.readline().decode("ascii").split()
|
return (await self.read_line()).split()
|
||||||
|
|
||||||
def read_json(self):
|
async def read_json(self):
|
||||||
return json.loads(self.fsocket.readline().decode("ascii"))
|
return json.loads((await self.read_line()))
|
||||||
|
|
||||||
def login(self, username, password):
|
async def login(self, username, password):
|
||||||
self.send_command("LOGIN", username, password)
|
await self.send_command("LOGIN", username, password)
|
||||||
return self.read_reply() == ["HELLO"]
|
return await self.read_reply() == ["HELLO"]
|
||||||
|
|
||||||
def build(self, major_ver, rev, variant, log, experimental_features):
|
async def build(self, major_ver, rev, variant, log, experimental_features):
|
||||||
if not variant:
|
if not variant:
|
||||||
variant = self.get_single_variant(error_msg="User can build more than 1 variant - need to specify")
|
variant = await self.get_single_variant(error_msg="User can build more than 1 variant - need to specify")
|
||||||
print("Building variant: {}".format(variant))
|
print("Building variant: {}".format(variant))
|
||||||
build_args = (
|
build_args = (
|
||||||
rev,
|
rev,
|
||||||
@ -96,25 +105,25 @@ class Client:
|
|||||||
major_ver,
|
major_ver,
|
||||||
*experimental_features,
|
*experimental_features,
|
||||||
)
|
)
|
||||||
self.send_command("BUILD", *build_args)
|
await self.send_command("BUILD", *build_args)
|
||||||
reply = self.read_reply()[0]
|
reply = (await self.read_reply())[0]
|
||||||
if reply != "BUILDING":
|
if reply != "BUILDING":
|
||||||
return reply, None
|
return reply, None
|
||||||
print("Build in progress. This may take 10-15 minutes.")
|
print("Build in progress. This may take 10-15 minutes.")
|
||||||
if log:
|
if log:
|
||||||
line = self.read_line()
|
line = await self.read_line()
|
||||||
while line != "" and line.startswith("LOG"):
|
while line != "" and line.startswith("LOG"):
|
||||||
print(line[4:], end="")
|
print(line[4:], end="")
|
||||||
line = self.read_line()
|
line = await self.read_line()
|
||||||
reply, status = line.split()
|
reply, status = line.split()
|
||||||
else:
|
else:
|
||||||
reply, status = self.read_reply()
|
reply, status = await self.read_reply()
|
||||||
if reply != "DONE":
|
if reply != "DONE":
|
||||||
raise ValueError("Unexpected server reply: expected 'DONE', got '{}'".format(reply))
|
raise ValueError("Unexpected server reply: expected 'DONE', got '{}'".format(reply))
|
||||||
if status != "done":
|
if status != "done":
|
||||||
return status, None
|
return status, None
|
||||||
print("Build completed. Downloading...")
|
print("Build completed. Downloading...")
|
||||||
reply, length = self.read_reply()
|
reply, length = await self.read_reply()
|
||||||
if reply != "PRODUCT":
|
if reply != "PRODUCT":
|
||||||
raise ValueError("Unexpected server reply: expected 'PRODUCT', got '{}'".format(reply))
|
raise ValueError("Unexpected server reply: expected 'PRODUCT', got '{}'".format(reply))
|
||||||
length = int(length)
|
length = int(length)
|
||||||
@ -123,25 +132,25 @@ class Client:
|
|||||||
total = 0
|
total = 0
|
||||||
while total != length:
|
while total != length:
|
||||||
chunk_len = min(4096, length-total)
|
chunk_len = min(4096, length-total)
|
||||||
contents += self.fsocket.read(chunk_len)
|
contents += await self.reader.read(chunk_len)
|
||||||
total += chunk_len
|
total += chunk_len
|
||||||
progress_bar.update(chunk_len)
|
progress_bar.update(chunk_len)
|
||||||
print("Download completed.")
|
print("Download completed.")
|
||||||
return "OK", contents
|
return "OK", contents
|
||||||
|
|
||||||
def passwd(self, password):
|
async def passwd(self, password):
|
||||||
self.send_command("PASSWD", password)
|
await self.send_command("PASSWD", password)
|
||||||
return self.read_reply() == ["OK"]
|
return (await self.read_reply()) == ["OK"]
|
||||||
|
|
||||||
def get_variants(self):
|
async def get_variants(self):
|
||||||
self.send_command("GET_VARIANTS")
|
await self.send_command("GET_VARIANTS")
|
||||||
reply = self.read_reply()[0]
|
reply = (await self.read_reply())[0]
|
||||||
if reply != "OK":
|
if reply != "OK":
|
||||||
raise ValueError("Unexpected server reply: expected 'OK', got '{}'".format(reply))
|
raise ValueError("Unexpected server reply: expected 'OK', got '{}'".format(reply))
|
||||||
return self.read_json()
|
return await self.read_json()
|
||||||
|
|
||||||
def get_single_variant(self, error_msg):
|
async def get_single_variant(self, error_msg):
|
||||||
variants = self.get_variants()
|
variants = await self.get_variants()
|
||||||
if len(variants) != 1:
|
if len(variants) != 1:
|
||||||
print(error_msg)
|
print(error_msg)
|
||||||
table = PrettyTable()
|
table = PrettyTable()
|
||||||
@ -152,13 +161,15 @@ class Client:
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
return variants[0][0]
|
return variants[0][0]
|
||||||
|
|
||||||
def get_json(self, variant):
|
async def get_json(self, variant):
|
||||||
self.send_command("GET_JSON", variant)
|
await self.send_command("GET_JSON", variant)
|
||||||
reply = self.read_reply()
|
reply = await self.read_reply()
|
||||||
if reply[0] != "OK":
|
if reply[0] != "OK":
|
||||||
return reply[0], None
|
return reply[0], None
|
||||||
length = int(reply[1])
|
length = int(reply[1])
|
||||||
json_bytes = self.fsocket.read(length)
|
json_bytes = await self.reader.read(length)
|
||||||
|
if length != len(json_bytes):
|
||||||
|
raise ValueError(f"Received data length ({len(json_bytes)}) doesn't match expected length ({length})")
|
||||||
return "OK", json_bytes
|
return "OK", json_bytes
|
||||||
|
|
||||||
|
|
||||||
@ -186,9 +197,10 @@ def get_argparser():
|
|||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
def main():
|
async def main_async():
|
||||||
args = get_argparser().parse_args()
|
args = get_argparser().parse_args()
|
||||||
client = Client(args.server, args.port, args.cert)
|
client = Client(args.server, args.port, args.cert)
|
||||||
|
await client.connect()
|
||||||
try:
|
try:
|
||||||
if args.action == "build":
|
if args.action == "build":
|
||||||
# do this before user enters password so errors are reported without unnecessary user action
|
# do this before user enters password so errors are reported without unnecessary user action
|
||||||
@ -219,7 +231,7 @@ def main():
|
|||||||
password = getpass("Current password: ")
|
password = getpass("Current password: ")
|
||||||
else:
|
else:
|
||||||
password = getpass()
|
password = getpass()
|
||||||
if not client.login(args.username, password):
|
if not await client.login(args.username, password):
|
||||||
print("Login failed")
|
print("Login failed")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
@ -232,12 +244,12 @@ def main():
|
|||||||
print("Passwords do not match")
|
print("Passwords do not match")
|
||||||
password = getpass("New password: ")
|
password = getpass("New password: ")
|
||||||
password_confirm = getpass("New password (again): ")
|
password_confirm = getpass("New password (again): ")
|
||||||
if not client.passwd(password):
|
if not await client.passwd(password):
|
||||||
print("Failed to change password")
|
print("Failed to change password")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
elif args.action == "build":
|
elif args.action == "build":
|
||||||
# build dir and version variables set up above
|
# build dir and version variables set up above
|
||||||
result, contents = client.build(major_ver, rev, args.variant, args.log, args.experimental)
|
result, contents = await client.build(major_ver, rev, args.variant, args.log, args.experimental)
|
||||||
if result != "OK":
|
if result != "OK":
|
||||||
if result == "UNAUTHORIZED":
|
if result == "UNAUTHORIZED":
|
||||||
print("You are not authorized to build this variant. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.")
|
print("You are not authorized to build this variant. Your firmware subscription may have expired. Contact helpdesk\x40m-labs.hk.")
|
||||||
@ -248,7 +260,7 @@ def main():
|
|||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
zip_unarchive(contents, args.directory)
|
zip_unarchive(contents, args.directory)
|
||||||
elif args.action == "get_variants":
|
elif args.action == "get_variants":
|
||||||
variants = client.get_variants()
|
variants = await client.get_variants()
|
||||||
table = PrettyTable()
|
table = PrettyTable()
|
||||||
table.field_names = ["Variant", "Expiry date"]
|
table.field_names = ["Variant", "Expiry date"]
|
||||||
for variant in variants:
|
for variant in variants:
|
||||||
@ -258,8 +270,8 @@ def main():
|
|||||||
if args.variant:
|
if args.variant:
|
||||||
variant = args.variant
|
variant = args.variant
|
||||||
else:
|
else:
|
||||||
variant = client.get_single_variant(error_msg="User can get JSON of more than 1 variant - need to specify")
|
variant = await client.get_single_variant(error_msg="User can get JSON of more than 1 variant - need to specify")
|
||||||
result, json_bytes = 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,8 +287,10 @@ def main():
|
|||||||
else:
|
else:
|
||||||
raise ValueError
|
raise ValueError
|
||||||
finally:
|
finally:
|
||||||
client.close()
|
await client.close()
|
||||||
|
|
||||||
|
def main():
|
||||||
|
asyncio.run(main_async())
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
@ -25,6 +25,9 @@ def get_argparser():
|
|||||||
help="Simulation - does not connect to device")
|
help="Simulation - does not connect to device")
|
||||||
parser.add_argument("core_addr", metavar="CORE_ADDR",
|
parser.add_argument("core_addr", metavar="CORE_ADDR",
|
||||||
help="hostname or IP address of the core device")
|
help="hostname or IP address of the core device")
|
||||||
|
parser.add_argument("-s", "--drtio-dest", default=0,
|
||||||
|
metavar="DRTIO_DEST", type=int,
|
||||||
|
help="specifies the DRTIO destination")
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -39,7 +42,7 @@ async def get_logs_sim(host):
|
|||||||
log_with_name("firmware.simulation", logging.INFO, "hello " + host)
|
log_with_name("firmware.simulation", logging.INFO, "hello " + host)
|
||||||
|
|
||||||
|
|
||||||
async def get_logs(host):
|
async def get_logs(host, drtio_dest):
|
||||||
try:
|
try:
|
||||||
reader, writer = await async_open_connection(
|
reader, writer = await async_open_connection(
|
||||||
host,
|
host,
|
||||||
@ -49,6 +52,7 @@ async def get_logs(host):
|
|||||||
max_fails=3,
|
max_fails=3,
|
||||||
)
|
)
|
||||||
writer.write(b"ARTIQ management\n")
|
writer.write(b"ARTIQ management\n")
|
||||||
|
writer.write(drtio_dest.to_bytes(1))
|
||||||
endian = await reader.readexactly(1)
|
endian = await reader.readexactly(1)
|
||||||
if endian == b"e":
|
if endian == b"e":
|
||||||
endian = "<"
|
endian = "<"
|
||||||
@ -96,7 +100,7 @@ def main():
|
|||||||
signal_handler.setup()
|
signal_handler.setup()
|
||||||
try:
|
try:
|
||||||
get_logs_task = asyncio.ensure_future(
|
get_logs_task = asyncio.ensure_future(
|
||||||
get_logs_sim(args.core_addr) if args.simulation else get_logs(args.core_addr),
|
get_logs_sim(args.core_addr) if args.simulation else get_logs(args.core_addr, args.drtio_dest),
|
||||||
loop=loop)
|
loop=loop)
|
||||||
try:
|
try:
|
||||||
server = Server({"corelog": PingTarget()}, None, True)
|
server = Server({"corelog": PingTarget()}, None, True)
|
||||||
|
@ -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)
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
|
import os
|
||||||
import struct
|
import struct
|
||||||
|
import tempfile
|
||||||
|
import atexit
|
||||||
|
|
||||||
from sipyco import common_args
|
from sipyco import common_args
|
||||||
|
|
||||||
@ -9,6 +12,7 @@ from artiq import __version__ as artiq_version
|
|||||||
from artiq.master.databases import DeviceDB
|
from artiq.master.databases import DeviceDB
|
||||||
from artiq.coredevice.comm_kernel import CommKernel
|
from artiq.coredevice.comm_kernel import CommKernel
|
||||||
from artiq.coredevice.comm_mgmt import CommMgmt
|
from artiq.coredevice.comm_mgmt import CommMgmt
|
||||||
|
from artiq.frontend.flash_tools import bit2bin, fetch_bin
|
||||||
|
|
||||||
|
|
||||||
def get_argparser():
|
def get_argparser():
|
||||||
@ -85,6 +89,20 @@ def get_argparser():
|
|||||||
t_boot = tools.add_parser("reboot",
|
t_boot = tools.add_parser("reboot",
|
||||||
help="reboot the running system")
|
help="reboot the running system")
|
||||||
|
|
||||||
|
# flashing
|
||||||
|
t_flash = tools.add_parser("flash",
|
||||||
|
help="flash the running system")
|
||||||
|
|
||||||
|
p_directory = t_flash.add_argument("directory",
|
||||||
|
metavar="DIRECTORY", type=str,
|
||||||
|
help="directory that contains the "
|
||||||
|
"binaries")
|
||||||
|
|
||||||
|
p_srcbuild = t_flash.add_argument("--srcbuild",
|
||||||
|
help="board binaries directory is laid "
|
||||||
|
"out as a source build tree",
|
||||||
|
default=False, action="store_true")
|
||||||
|
|
||||||
# misc debug
|
# misc debug
|
||||||
t_debug = tools.add_parser("debug",
|
t_debug = tools.add_parser("debug",
|
||||||
help="specialized debug functions")
|
help="specialized debug functions")
|
||||||
@ -95,6 +113,12 @@ def get_argparser():
|
|||||||
p_allocator = subparsers.add_parser("allocator",
|
p_allocator = subparsers.add_parser("allocator",
|
||||||
help="show heap layout")
|
help="show heap layout")
|
||||||
|
|
||||||
|
# manage target
|
||||||
|
p_drtio_dest = parser.add_argument("-s", "--drtio-dest", default=0,
|
||||||
|
metavar="DRTIO_DEST", type=int,
|
||||||
|
help="specify DRTIO destination that "
|
||||||
|
"receives this command")
|
||||||
|
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
|
|
||||||
@ -107,7 +131,7 @@ def main():
|
|||||||
core_addr = ddb.get("core", resolve_alias=True)["arguments"]["host"]
|
core_addr = ddb.get("core", resolve_alias=True)["arguments"]["host"]
|
||||||
else:
|
else:
|
||||||
core_addr = args.device
|
core_addr = args.device
|
||||||
mgmt = CommMgmt(core_addr)
|
mgmt = CommMgmt(core_addr, drtio_dest=args.drtio_dest)
|
||||||
|
|
||||||
if args.tool == "log":
|
if args.tool == "log":
|
||||||
if args.action == "set_level":
|
if args.action == "set_level":
|
||||||
@ -138,6 +162,39 @@ def main():
|
|||||||
if args.action == "erase":
|
if args.action == "erase":
|
||||||
mgmt.config_erase()
|
mgmt.config_erase()
|
||||||
|
|
||||||
|
if args.tool == "flash":
|
||||||
|
retrieved_bins = []
|
||||||
|
bin_dict = {
|
||||||
|
"zynq":[
|
||||||
|
["boot"]
|
||||||
|
],
|
||||||
|
"riscv": [
|
||||||
|
["gateware"],
|
||||||
|
["bootloader"],
|
||||||
|
["runtime", "satman"],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
for bin_list in bin_dict.values():
|
||||||
|
try:
|
||||||
|
bins = []
|
||||||
|
for bin_name in bin_list:
|
||||||
|
bins.append(fetch_bin(
|
||||||
|
args.directory, bin_name, args.srcbuild))
|
||||||
|
retrieved_bins.append(bins)
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if retrieved_bins is None:
|
||||||
|
raise FileNotFoundError("neither risc-v nor zynq binaries were found")
|
||||||
|
|
||||||
|
if len(retrieved_bins) > 1:
|
||||||
|
raise ValueError("both risc-v and zynq binaries were found, "
|
||||||
|
"please clean up your build directory. ")
|
||||||
|
|
||||||
|
bins = retrieved_bins[0]
|
||||||
|
mgmt.flash(bins)
|
||||||
|
|
||||||
if args.tool == "reboot":
|
if args.tool == "reboot":
|
||||||
mgmt.reboot()
|
mgmt.reboot()
|
||||||
|
|
||||||
|
@ -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":
|
||||||
|
@ -14,7 +14,7 @@ from sipyco import common_args
|
|||||||
|
|
||||||
from artiq import __version__ as artiq_version
|
from artiq import __version__ as artiq_version
|
||||||
from artiq.remoting import SSHClient, LocalClient
|
from artiq.remoting import SSHClient, LocalClient
|
||||||
from artiq.frontend.bit2bin import bit2bin
|
from artiq.frontend.flash_tools import artifact_path, bit2bin, fetch_bin
|
||||||
|
|
||||||
|
|
||||||
def get_argparser():
|
def get_argparser():
|
||||||
@ -302,46 +302,19 @@ def main():
|
|||||||
|
|
||||||
programmer = config["programmer"](client, preinit_script=args.preinit_command)
|
programmer = config["programmer"](client, preinit_script=args.preinit_command)
|
||||||
|
|
||||||
def artifact_path(this_binary_dir, *path_filename):
|
|
||||||
if args.srcbuild:
|
|
||||||
# source tree - use path elements to locate file
|
|
||||||
return os.path.join(this_binary_dir, *path_filename)
|
|
||||||
else:
|
|
||||||
# flat tree - all files in the same directory, discard path elements
|
|
||||||
*_, filename = path_filename
|
|
||||||
return os.path.join(this_binary_dir, filename)
|
|
||||||
|
|
||||||
def convert_gateware(bit_filename):
|
|
||||||
bin_handle, bin_filename = tempfile.mkstemp(
|
|
||||||
prefix="artiq_", suffix="_" + os.path.basename(bit_filename))
|
|
||||||
with open(bit_filename, "rb") as bit_file, open(bin_handle, "wb") as bin_file:
|
|
||||||
bit2bin(bit_file, bin_file)
|
|
||||||
atexit.register(lambda: os.unlink(bin_filename))
|
|
||||||
return bin_filename
|
|
||||||
|
|
||||||
for action in args.action:
|
for action in args.action:
|
||||||
if action == "gateware":
|
if action == "gateware":
|
||||||
gateware_bin = convert_gateware(
|
gateware_bin = fetch_bin(binary_dir, ["gateware"], args.srcbuild)
|
||||||
artifact_path(binary_dir, "gateware", "top.bit"))
|
|
||||||
programmer.write_binary(*config["gateware"], gateware_bin)
|
programmer.write_binary(*config["gateware"], gateware_bin)
|
||||||
elif action == "bootloader":
|
elif action == "bootloader":
|
||||||
bootloader_bin = artifact_path(binary_dir, "software", "bootloader", "bootloader.bin")
|
bootloader_bin = fetch_bin(binary_dir, ["bootloader"], args.srcbuild)
|
||||||
programmer.write_binary(*config["bootloader"], bootloader_bin)
|
programmer.write_binary(*config["bootloader"], bootloader_bin)
|
||||||
elif action == "storage":
|
elif action == "storage":
|
||||||
storage_img = args.storage
|
storage_img = args.storage
|
||||||
programmer.write_binary(*config["storage"], storage_img)
|
programmer.write_binary(*config["storage"], storage_img)
|
||||||
elif action == "firmware":
|
elif action == "firmware":
|
||||||
firmware_fbis = []
|
firmware_fbi = fetch_bin(binary_dir, ["satman", "runtime"], args.srcbuild)
|
||||||
for firmware in "satman", "runtime":
|
programmer.write_binary(*config["firmware"], firmware_fbi)
|
||||||
filename = artifact_path(binary_dir, "software", firmware, firmware + ".fbi")
|
|
||||||
if os.path.exists(filename):
|
|
||||||
firmware_fbis.append(filename)
|
|
||||||
if not firmware_fbis:
|
|
||||||
raise FileNotFoundError("no firmware found")
|
|
||||||
if len(firmware_fbis) > 1:
|
|
||||||
raise ValueError("more than one firmware file, please clean up your build directory. "
|
|
||||||
"Found firmware files: {}".format(" ".join(firmware_fbis)))
|
|
||||||
programmer.write_binary(*config["firmware"], firmware_fbis[0])
|
|
||||||
elif action == "load":
|
elif action == "load":
|
||||||
gateware_bit = artifact_path(binary_dir, "gateware", "top.bit")
|
gateware_bit = artifact_path(binary_dir, "gateware", "top.bit")
|
||||||
programmer.load(gateware_bit, 0)
|
programmer.load(gateware_bit, 0)
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
# Copyright 2014-2017 Robert Jordens <jordens@gmail.com>
|
|
||||||
# after
|
|
||||||
# https://github.com/mfischer/fpgadev-zynq/blob/master/top/python/bit_to_zynq_bin.py
|
|
||||||
|
|
||||||
import struct
|
|
||||||
|
|
||||||
|
|
||||||
def flip32(data):
|
|
||||||
sl = struct.Struct("<I")
|
|
||||||
sb = struct.Struct(">I")
|
|
||||||
b = memoryview(data)
|
|
||||||
d = bytearray(len(data))
|
|
||||||
for offset in range(0, len(data), sl.size):
|
|
||||||
sb.pack_into(d, offset, *sl.unpack_from(b, offset))
|
|
||||||
return d
|
|
||||||
|
|
||||||
|
|
||||||
def bit2bin(bit, bin, flip=False):
|
|
||||||
l, = struct.unpack(">H", bit.read(2))
|
|
||||||
if l != 9:
|
|
||||||
raise ValueError("Missing <0009> header, not a bit file")
|
|
||||||
_ = bit.read(l) # unknown data
|
|
||||||
l, = struct.unpack(">H", bit.read(2))
|
|
||||||
if l != 1:
|
|
||||||
raise ValueError("Missing <0001> header, not a bit file")
|
|
||||||
|
|
||||||
while True:
|
|
||||||
key = bit.read(1).decode()
|
|
||||||
if not key:
|
|
||||||
break
|
|
||||||
if key in "abcd":
|
|
||||||
d = bit.read(*struct.unpack(">H", bit.read(2)))
|
|
||||||
assert d.endswith(b"\x00")
|
|
||||||
d = d[:-1].decode()
|
|
||||||
name = {
|
|
||||||
"a": "Design",
|
|
||||||
"b": "Part name",
|
|
||||||
"c": "Date",
|
|
||||||
"d": "Time"
|
|
||||||
}[key]
|
|
||||||
print("{}: {}".format(name, d))
|
|
||||||
elif key == "e":
|
|
||||||
l, = struct.unpack(">I", bit.read(4))
|
|
||||||
print("Bitstream payload length: {:#x}".format(l))
|
|
||||||
d = bit.read(l)
|
|
||||||
if flip:
|
|
||||||
d = flip32(d)
|
|
||||||
bin.write(d)
|
|
||||||
else:
|
|
||||||
d = bit.read(*struct.unpack(">H", bit.read(2)))
|
|
||||||
print("Unexpected key: {}: {}".format(key, d))
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
import argparse
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="Convert FPGA bit files to raw bin format "
|
|
||||||
"suitable for flashing")
|
|
||||||
parser.add_argument("-f", "--flip", dest="flip", action="store_true",
|
|
||||||
default=False, help="Flip 32-bit endianess (needed for Zynq)")
|
|
||||||
parser.add_argument("bitfile", metavar="BITFILE",
|
|
||||||
help="Input bit file name")
|
|
||||||
parser.add_argument("binfile", metavar="BINFILE",
|
|
||||||
help="Output bin file name")
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
with open(args.bitfile, "rb") as f, open(args.binfile, "wb") as g:
|
|
||||||
bit2bin(f, g, args.flip)
|
|
112
artiq/frontend/flash_tools.py
Normal file
112
artiq/frontend/flash_tools.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import atexit
|
||||||
|
import os
|
||||||
|
import tempfile
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
def artifact_path(this_binary_dir, *path_filename, srcbuild=False):
|
||||||
|
if srcbuild:
|
||||||
|
# source tree - use path elements to locate file
|
||||||
|
return os.path.join(this_binary_dir, *path_filename)
|
||||||
|
else:
|
||||||
|
# flat tree - all files in the same directory, discard path elements
|
||||||
|
*_, filename = path_filename
|
||||||
|
return os.path.join(this_binary_dir, filename)
|
||||||
|
|
||||||
|
|
||||||
|
def fetch_bin(binary_dir, components, srcbuild=False):
|
||||||
|
def convert_gateware(bit_filename):
|
||||||
|
bin_handle, bin_filename = tempfile.mkstemp(
|
||||||
|
prefix="artiq_", suffix="_" + os.path.basename(bit_filename))
|
||||||
|
with open(bit_filename, "rb") as bit_file, open(bin_handle, "wb") as bin_file:
|
||||||
|
bit2bin(bit_file, bin_file)
|
||||||
|
atexit.register(lambda: os.unlink(bin_filename))
|
||||||
|
return bin_filename
|
||||||
|
|
||||||
|
if len(components) > 1:
|
||||||
|
bins = []
|
||||||
|
for option in components:
|
||||||
|
try:
|
||||||
|
bins.append(fetch_bin(binary_dir, [option], srcbuild))
|
||||||
|
except FileNotFoundError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if bins is None:
|
||||||
|
raise FileNotFoundError("multiple components not found: {}".format(
|
||||||
|
" ".join(components)))
|
||||||
|
|
||||||
|
if len(bins) > 1:
|
||||||
|
raise ValueError("more than one file, "
|
||||||
|
"please clean up your build directory. "
|
||||||
|
"Found files: {}".format(
|
||||||
|
" ".join(bins)))
|
||||||
|
|
||||||
|
return bins[0]
|
||||||
|
|
||||||
|
else:
|
||||||
|
component = components[0]
|
||||||
|
path = artifact_path(binary_dir, *{
|
||||||
|
"gateware": ["gateware", "top.bit"],
|
||||||
|
"boot": ["boot.bin"],
|
||||||
|
"bootloader": ["software", "bootloader", "bootloader.bin"],
|
||||||
|
"runtime": ["software", "runtime", "runtime.fbi"],
|
||||||
|
"satman": ["software", "satman", "satman.fbi"],
|
||||||
|
}[component], srcbuild=srcbuild)
|
||||||
|
|
||||||
|
if not os.path.exists(path):
|
||||||
|
raise FileNotFoundError("{} not found".format(component))
|
||||||
|
|
||||||
|
if component == "gateware":
|
||||||
|
path = convert_gateware(path)
|
||||||
|
|
||||||
|
return path
|
||||||
|
|
||||||
|
|
||||||
|
# Copyright 2014-2017 Robert Jordens <jordens@gmail.com>
|
||||||
|
# after
|
||||||
|
# https://github.com/mfischer/fpgadev-zynq/blob/master/top/python/bit_to_zynq_bin.py
|
||||||
|
|
||||||
|
def flip32(data):
|
||||||
|
sl = struct.Struct("<I")
|
||||||
|
sb = struct.Struct(">I")
|
||||||
|
b = memoryview(data)
|
||||||
|
d = bytearray(len(data))
|
||||||
|
for offset in range(0, len(data), sl.size):
|
||||||
|
sb.pack_into(d, offset, *sl.unpack_from(b, offset))
|
||||||
|
return d
|
||||||
|
|
||||||
|
|
||||||
|
def bit2bin(bit, bin, flip=False):
|
||||||
|
l, = struct.unpack(">H", bit.read(2))
|
||||||
|
if l != 9:
|
||||||
|
raise ValueError("Missing <0009> header, not a bit file")
|
||||||
|
_ = bit.read(l) # unknown data
|
||||||
|
l, = struct.unpack(">H", bit.read(2))
|
||||||
|
if l != 1:
|
||||||
|
raise ValueError("Missing <0001> header, not a bit file")
|
||||||
|
|
||||||
|
while True:
|
||||||
|
key = bit.read(1).decode()
|
||||||
|
if not key:
|
||||||
|
break
|
||||||
|
if key in "abcd":
|
||||||
|
d = bit.read(*struct.unpack(">H", bit.read(2)))
|
||||||
|
assert d.endswith(b"\x00")
|
||||||
|
d = d[:-1].decode()
|
||||||
|
name = {
|
||||||
|
"a": "Design",
|
||||||
|
"b": "Part name",
|
||||||
|
"c": "Date",
|
||||||
|
"d": "Time"
|
||||||
|
}[key]
|
||||||
|
print("{}: {}".format(name, d))
|
||||||
|
elif key == "e":
|
||||||
|
l, = struct.unpack(">I", bit.read(4))
|
||||||
|
print("Bitstream payload length: {:#x}".format(l))
|
||||||
|
d = bit.read(l)
|
||||||
|
if flip:
|
||||||
|
d = flip32(d)
|
||||||
|
bin.write(d)
|
||||||
|
else:
|
||||||
|
d = bit.read(*struct.unpack(">H", bit.read(2)))
|
||||||
|
print("Unexpected key: {}: {}".format(key, d))
|
@ -27,6 +27,7 @@ class Fastino(Module):
|
|||||||
|
|
||||||
# dac data words
|
# dac data words
|
||||||
dacs = [Signal(16) for i in range(32)]
|
dacs = [Signal(16) for i in range(32)]
|
||||||
|
self.probes = dacs
|
||||||
|
|
||||||
header = Record([
|
header = Record([
|
||||||
("cfg", 4),
|
("cfg", 4),
|
||||||
|
@ -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"])
|
||||||
|
if size is None:
|
||||||
self.write_pyon({"action": "embed_done"})
|
self.write_pyon({"action": "embed_done"})
|
||||||
elif action == "fix_initial_size":
|
else:
|
||||||
fix_initial_size_cb()
|
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())
|
||||||
|
@ -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:
|
||||||
|
@ -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):
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
|
||||||
|
@ -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> </g></svg>
|
|
||||||
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
@ -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
|
||||||
|
|
||||||
@ -84,22 +84,22 @@ class DictSyncModel(QtCore.QAbstractTableModel):
|
|||||||
key=lambda k: self.sort_key(k, self.backing_store[k]))
|
key=lambda k: self.sort_key(k, self.backing_store[k]))
|
||||||
QtCore.QAbstractTableModel.__init__(self)
|
QtCore.QAbstractTableModel.__init__(self)
|
||||||
|
|
||||||
def rowCount(self, parent):
|
def rowCount(self, parent=QtCore.QModelIndex()):
|
||||||
return len(self.backing_store)
|
return len(self.backing_store)
|
||||||
|
|
||||||
def columnCount(self, parent):
|
def columnCount(self, parent=QtCore.QModelIndex()):
|
||||||
return len(self.headers)
|
return len(self.headers)
|
||||||
|
|
||||||
def data(self, index, role):
|
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
|
||||||
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=QtCore.Qt.ItemDataRole.DisplayRole):
|
||||||
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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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.
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets
|
from PyQt6 import QtCore, QtGui, 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))
|
||||||
|
|
||||||
|
|
||||||
@ -88,11 +88,56 @@ class LayoutWidget(QtWidgets.QWidget):
|
|||||||
self.layout.addWidget(item, row, col, rowspan, colspan)
|
self.layout.addWidget(item, row, col, rowspan, colspan)
|
||||||
|
|
||||||
|
|
||||||
|
class SelectableColumnTableView(QtWidgets.QTableView):
|
||||||
|
"""A QTableView packaged up with a header row context menu that allows users to
|
||||||
|
show/hide columns using checkable entries.
|
||||||
|
|
||||||
|
By default, all columns are shown. If only one shown column remains, the entry is
|
||||||
|
disabled to prevent a situation where no columns are shown, which might be confusing
|
||||||
|
to the user.
|
||||||
|
|
||||||
|
Qt considers whether columns are shown to be part of the header state, i.e. it is
|
||||||
|
included in saveState()/restoreState().
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.horizontalHeader().setContextMenuPolicy(
|
||||||
|
QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
|
self.horizontalHeader().customContextMenuRequested.connect(
|
||||||
|
self.show_header_context_menu)
|
||||||
|
|
||||||
|
def show_header_context_menu(self, pos):
|
||||||
|
menu = QtWidgets.QMenu(self)
|
||||||
|
|
||||||
|
num_columns_total = self.model().columnCount()
|
||||||
|
num_columns_shown = sum(
|
||||||
|
(not self.isColumnHidden(i)) for i in range(num_columns_total))
|
||||||
|
for i in range(num_columns_total):
|
||||||
|
name = self.model().headerData(i, QtCore.Qt.Orientation.Horizontal)
|
||||||
|
action = QtGui.QAction(name, self)
|
||||||
|
action.setCheckable(True)
|
||||||
|
|
||||||
|
is_currently_hidden = self.isColumnHidden(i)
|
||||||
|
action.setChecked(not is_currently_hidden)
|
||||||
|
if not is_currently_hidden:
|
||||||
|
if num_columns_shown == 1:
|
||||||
|
# Don't allow hiding of the last visible column.
|
||||||
|
action.setEnabled(False)
|
||||||
|
|
||||||
|
action.triggered.connect(
|
||||||
|
lambda checked, i=i: self.setColumnHidden(i, not checked))
|
||||||
|
menu.addAction(action)
|
||||||
|
|
||||||
|
menu.exec(self.horizontalHeader().mapToGlobal(pos))
|
||||||
|
|
||||||
|
|
||||||
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 +164,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)
|
|
||||||
|
@ -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):
|
||||||
|
425
artiq/test/coredevice/test_ad9834.py
Normal file
425
artiq/test/coredevice/test_ad9834.py
Normal file
@ -0,0 +1,425 @@
|
|||||||
|
from artiq.coredevice.ad9834 import (
|
||||||
|
AD9834_B28,
|
||||||
|
AD9834_DIV2,
|
||||||
|
AD9834_FSEL,
|
||||||
|
AD9834_HLB,
|
||||||
|
AD9834_MODE,
|
||||||
|
AD9834_OPBITEN,
|
||||||
|
AD9834_PIN_SW,
|
||||||
|
AD9834_PSEL,
|
||||||
|
AD9834_RESET,
|
||||||
|
AD9834_SIGN_PIB,
|
||||||
|
AD9834_SLEEP1,
|
||||||
|
AD9834_SLEEP12,
|
||||||
|
)
|
||||||
|
from artiq.experiment import *
|
||||||
|
from artiq.language.units import MHz
|
||||||
|
from artiq.test.hardware_testbench import ExperimentCase
|
||||||
|
|
||||||
|
|
||||||
|
class AD9834Exp(EnvExperiment):
|
||||||
|
def build(self, runner):
|
||||||
|
self.setattr_device("core")
|
||||||
|
self.dev = self.get_device("dds0")
|
||||||
|
self.runner = runner
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
getattr(self, self.runner)()
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def instantiate(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def init(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.set_dataset("spi_freq", self.dev.spi_freq)
|
||||||
|
self.set_dataset("clk_freq", self.dev.clk_freq)
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def frequency_to_ftw_fail(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.frequency_to_ftw(37.6 * MHz)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def turns_to_phase_fail(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.turns_to_phase(1.1)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_frequency_reg_fail(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.set_frequency_reg(19, self.dev.frequency_to_ftw(10 * MHz))
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_frequency_reg(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.set_frequency_reg(1, self.dev.frequency_to_ftw(19 * MHz))
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_frequency_reg_msb(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.ctrl_reg |= AD9834_B28
|
||||||
|
self.dev.set_frequency_reg_msb(0, 0x1111)
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_frequency_reg_lsb(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.ctrl_reg |= AD9834_B28 | AD9834_HLB
|
||||||
|
self.dev.set_frequency_reg_lsb(1, 0xFFFF)
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def select_frequency_reg_0(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.select_frequency_reg(0)
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg & ~(AD9834_FSEL | AD9834_PIN_SW))
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def select_frequency_reg_1(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.select_frequency_reg(1)
|
||||||
|
self.set_dataset("ctrl_reg", (self.dev.ctrl_reg | AD9834_FSEL) & ~AD9834_PIN_SW)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_phase_reg_fail(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.set_phase_reg(19, 0x123)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_phase_reg(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.set_phase_reg(0, 0x123)
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def select_phase_reg_0(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.select_phase_reg(0)
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg & ~(AD9834_PSEL | AD9834_PIN_SW))
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def select_phase_reg_1(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.select_phase_reg(1)
|
||||||
|
self.set_dataset("ctrl_reg", (self.dev.ctrl_reg | AD9834_PSEL) & ~AD9834_PIN_SW)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def sleep_dac_powerdown(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.sleep(dac_pd=True)
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def sleep_internal_clk_disable(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.sleep(clk_dis=True)
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def sleep(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.sleep(dac_pd=True, clk_dis=True)
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def awake(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.awake()
|
||||||
|
self.set_dataset(
|
||||||
|
"ctrl_reg", self.dev.ctrl_reg & ~(AD9834_SLEEP1 | AD9834_SLEEP12)
|
||||||
|
)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def sign_bit_high_z(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.config_sign_bit_out()
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg & ~AD9834_OPBITEN)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def sign_bit_msb_2(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.config_sign_bit_out(msb_2=True)
|
||||||
|
self.set_dataset(
|
||||||
|
"ctrl_reg",
|
||||||
|
(self.dev.ctrl_reg | AD9834_OPBITEN)
|
||||||
|
& ~(AD9834_MODE | AD9834_SIGN_PIB | AD9834_DIV2),
|
||||||
|
)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def sign_bit_msb(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.config_sign_bit_out(msb=True)
|
||||||
|
self.set_dataset(
|
||||||
|
"ctrl_reg",
|
||||||
|
(self.dev.ctrl_reg | AD9834_MODE | AD9834_SIGN_PIB)
|
||||||
|
& ~(AD9834_MODE | AD9834_SIGN_PIB),
|
||||||
|
)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def sign_bit_comp_out(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.config_sign_bit_out(comp_out=True)
|
||||||
|
self.set_dataset(
|
||||||
|
"ctrl_reg",
|
||||||
|
(self.dev.ctrl_reg | AD9834_OPBITEN | AD9834_SIGN_PIB | AD9834_DIV2)
|
||||||
|
& ~AD9834_MODE,
|
||||||
|
)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def enable_triangular_waveform(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.enable_triangular_waveform()
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg | AD9834_MODE)
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def disable_triangular_waveform(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.disable_triangular_waveform()
|
||||||
|
self.set_dataset("ctrl_reg", self.dev.ctrl_reg & ~AD9834_MODE)
|
||||||
|
|
||||||
|
## The following tests should be hooked up to an oscilloscope
|
||||||
|
## to monitor the waveforms
|
||||||
|
@kernel
|
||||||
|
def single_tone(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.set_frequency_reg(0, self.dev.frequency_to_ftw(1 * MHz))
|
||||||
|
self.dev.select_frequency_reg(0)
|
||||||
|
self.dev.output_enable()
|
||||||
|
delay(5 * s)
|
||||||
|
self.dev.enable_reset()
|
||||||
|
self.core.wait_until_mu(now_mu())
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def toggle_frequency(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.set_frequency_reg(0, self.dev.frequency_to_ftw(1 * MHz))
|
||||||
|
self.dev.set_frequency_reg(1, self.dev.frequency_to_ftw(2 * MHz))
|
||||||
|
self.dev.select_frequency_reg(0)
|
||||||
|
self.dev.output_enable()
|
||||||
|
|
||||||
|
for _ in range(6):
|
||||||
|
self.dev.select_frequency_reg(0)
|
||||||
|
delay(1 * s)
|
||||||
|
self.dev.select_frequency_reg(1)
|
||||||
|
delay(1 * s)
|
||||||
|
|
||||||
|
self.dev.enable_reset()
|
||||||
|
self.core.wait_until_mu(now_mu())
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def toggle_phase(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.set_frequency_reg(0, self.dev.frequency_to_ftw(1 * MHz))
|
||||||
|
self.dev.select_frequency_reg(0)
|
||||||
|
self.dev.set_phase_reg(0, 0x0)
|
||||||
|
self.dev.set_phase_reg(1, 0x7FF)
|
||||||
|
self.dev.output_enable()
|
||||||
|
|
||||||
|
for _ in range(300000):
|
||||||
|
self.dev.select_phase_reg(0)
|
||||||
|
delay(10 * us)
|
||||||
|
self.dev.select_phase_reg(1)
|
||||||
|
delay(10 * us)
|
||||||
|
|
||||||
|
self.dev.enable_reset()
|
||||||
|
self.core.wait_until_mu(now_mu())
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set_mu(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
freq_word = self.dev.frequency_to_ftw(1 * MHz)
|
||||||
|
phase_word = self.dev.turns_to_phase(0.5)
|
||||||
|
self.dev.set_mu(freq_word, phase_word, 0, 1)
|
||||||
|
|
||||||
|
delay(5 * s)
|
||||||
|
|
||||||
|
self.dev.enable_reset()
|
||||||
|
self.core.wait_until_mu(now_mu())
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def set(self):
|
||||||
|
self.core.reset()
|
||||||
|
self.dev.init()
|
||||||
|
self.dev.set(2 * MHz, 0.5, 1, 0)
|
||||||
|
|
||||||
|
delay(5 * s)
|
||||||
|
|
||||||
|
self.dev.enable_reset()
|
||||||
|
self.core.wait_until_mu(now_mu())
|
||||||
|
|
||||||
|
|
||||||
|
class AD9834Test(ExperimentCase):
|
||||||
|
def test_instantiate(self):
|
||||||
|
self.execute(AD9834Exp, "instantiate")
|
||||||
|
|
||||||
|
def test_init(self):
|
||||||
|
self.execute(AD9834Exp, "init")
|
||||||
|
spi_freq = self.dataset_mgr.get("spi_freq")
|
||||||
|
clk_freq = self.dataset_mgr.get("clk_freq")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(spi_freq, 10 * MHz)
|
||||||
|
self.assertEqual(clk_freq, 75 * MHz)
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_frequency_to_ftw_fail(self):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
self.execute(AD9834Exp, "frequency_to_ftw_fail")
|
||||||
|
|
||||||
|
def test_turns_to_phase_fail(self):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
self.execute(AD9834Exp, "turns_to_phase_fail")
|
||||||
|
|
||||||
|
def test_set_frequency_reg_fail(self):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
self.execute(AD9834Exp, "set_frequency_reg_fail")
|
||||||
|
|
||||||
|
def test_set_frequency_reg(self):
|
||||||
|
self.execute(AD9834Exp, "set_frequency_reg")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_B28 | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_set_frequency_reg_msb(self):
|
||||||
|
self.execute(AD9834Exp, "set_frequency_reg_msb")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_HLB | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_set_frequency_reg_lsb(self):
|
||||||
|
self.execute(AD9834Exp, "set_frequency_reg_lsb")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_select_frequency_reg_0(self):
|
||||||
|
self.execute(AD9834Exp, "select_frequency_reg_0")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_select_frequency_reg_1(self):
|
||||||
|
self.execute(AD9834Exp, "select_frequency_reg_1")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_FSEL | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_set_phase_reg_fail(self):
|
||||||
|
with self.assertRaises(AssertionError):
|
||||||
|
self.execute(AD9834Exp, "set_phase_reg_fail")
|
||||||
|
|
||||||
|
def test_set_phase_reg(self):
|
||||||
|
self.execute(AD9834Exp, "set_phase_reg")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_select_phase_reg_0(self):
|
||||||
|
self.execute(AD9834Exp, "select_phase_reg_0")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_select_phase_reg_1(self):
|
||||||
|
self.execute(AD9834Exp, "select_phase_reg_1")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_PSEL | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_sleep_dac_powerdown(self):
|
||||||
|
self.execute(AD9834Exp, "sleep_dac_powerdown")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_SLEEP12 | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_sleep_internal_clk_disable(self):
|
||||||
|
self.execute(AD9834Exp, "sleep_internal_clk_disable")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_SLEEP1 | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_sleep(self):
|
||||||
|
self.execute(AD9834Exp, "sleep")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(
|
||||||
|
ctrl_reg, 0x0000 | AD9834_SLEEP1 | AD9834_SLEEP12 | AD9834_RESET
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_awake(self):
|
||||||
|
self.execute(AD9834Exp, "awake")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_sign_bit_high_z(self):
|
||||||
|
self.execute(AD9834Exp, "sign_bit_high_z")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_sign_bit_msb_2(self):
|
||||||
|
self.execute(AD9834Exp, "sign_bit_msb_2")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_OPBITEN | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_sign_bit_msb(self):
|
||||||
|
self.execute(AD9834Exp, "sign_bit_msb")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_OPBITEN | AD9834_DIV2 | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_sign_bit_comp_out(self):
|
||||||
|
self.execute(AD9834Exp, "sign_bit_comp_out")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(
|
||||||
|
ctrl_reg,
|
||||||
|
0x0000 | AD9834_OPBITEN | AD9834_SIGN_PIB | AD9834_DIV2 | AD9834_RESET,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_enble_triangular_waveform(self):
|
||||||
|
self.execute(AD9834Exp, "enable_triangular_waveform")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_MODE | AD9834_RESET)
|
||||||
|
|
||||||
|
def test_disble_triangular_waveform(self):
|
||||||
|
self.execute(AD9834Exp, "disable_triangular_waveform")
|
||||||
|
ctrl_reg = self.dataset_mgr.get("ctrl_reg")
|
||||||
|
self.assertEqual(ctrl_reg, 0x0000 | AD9834_RESET)
|
||||||
|
|
||||||
|
## Waveform Tests
|
||||||
|
def test_single_tone(self):
|
||||||
|
print("Running waveform test:", self._testMethodName)
|
||||||
|
self.execute(AD9834Exp, "single_tone")
|
||||||
|
|
||||||
|
def test_toggle_frequency(self):
|
||||||
|
print("Running waveform test:", self._testMethodName)
|
||||||
|
self.execute(AD9834Exp, "toggle_frequency")
|
||||||
|
|
||||||
|
def test_toggle_phase(self):
|
||||||
|
print("Running waveform test:", self._testMethodName)
|
||||||
|
self.execute(AD9834Exp, "toggle_phase")
|
||||||
|
|
||||||
|
def test_set_mu(self):
|
||||||
|
print("Running waveform test:", self._testMethodName)
|
||||||
|
self.execute(AD9834Exp, "set_mu")
|
||||||
|
|
||||||
|
def test_set(self):
|
||||||
|
print("Running waveform test:", self._testMethodName)
|
||||||
|
self.execute(AD9834Exp, "set")
|
@ -1,10 +1,94 @@
|
|||||||
import unittest
|
import re
|
||||||
import artiq.coredevice.exceptions as exceptions
|
|
||||||
|
|
||||||
from artiq.experiment import *
|
from artiq.experiment import *
|
||||||
|
from artiq.master.worker_db import DeviceError
|
||||||
from artiq.test.hardware_testbench import ExperimentCase
|
from artiq.test.hardware_testbench import ExperimentCase
|
||||||
from artiq.compiler.embedding import EmbeddingMap
|
from artiq.compiler.embedding import EmbeddingMap
|
||||||
from artiq.coredevice.core import test_exception_id_sync
|
from artiq.coredevice.core import test_exception_id_sync
|
||||||
|
import artiq.coredevice.exceptions as exceptions
|
||||||
|
|
||||||
|
|
||||||
|
class CustomException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class KernelFmtException(EnvExperiment):
|
||||||
|
def build(self):
|
||||||
|
self.setattr_device("core")
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def run(self):
|
||||||
|
self.throw()
|
||||||
|
|
||||||
|
def throw(self):
|
||||||
|
raise CustomException("{foo}")
|
||||||
|
|
||||||
|
|
||||||
|
class KernelNestedFmtException(EnvExperiment):
|
||||||
|
def build(self):
|
||||||
|
self.setattr_device("core")
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def run(self):
|
||||||
|
try:
|
||||||
|
self.throw_foo()
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
raise RTIOUnderflow("{bar}")
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
raise RTIOOverflow("{bizz}")
|
||||||
|
except:
|
||||||
|
self.throw_buzz()
|
||||||
|
|
||||||
|
def throw_foo(self):
|
||||||
|
raise CustomException("{foo}")
|
||||||
|
|
||||||
|
def throw_buzz(self):
|
||||||
|
raise RTIOUnderflow("{buzz}")
|
||||||
|
|
||||||
|
|
||||||
|
class KernelRTIOUnderflow(EnvExperiment):
|
||||||
|
def build(self):
|
||||||
|
self.setattr_device("core")
|
||||||
|
try:
|
||||||
|
self.setattr_device("led")
|
||||||
|
except DeviceError:
|
||||||
|
self.led = self.get_device("led0")
|
||||||
|
|
||||||
|
@kernel
|
||||||
|
def run(self):
|
||||||
|
self.core.reset()
|
||||||
|
at_mu(self.core.get_rtio_counter_mu() - 1000); self.led.on()
|
||||||
|
|
||||||
|
|
||||||
|
class ExceptionFormatTest(ExperimentCase):
|
||||||
|
def test_custom_formatted_kernel_exception(self):
|
||||||
|
with self.assertLogs() as captured:
|
||||||
|
with self.assertRaisesRegex(CustomException, r"CustomException\(\d+\): \{foo\}"):
|
||||||
|
self.execute(KernelFmtException)
|
||||||
|
captured_lines = captured.output[0].split('\n')
|
||||||
|
self.assertEqual([captured_lines[0], captured_lines[-1]],
|
||||||
|
["ERROR:artiq.coredevice.comm_kernel:Couldn't format exception message", "KeyError: 'foo'"])
|
||||||
|
|
||||||
|
def test_nested_formatted_kernel_exception(self):
|
||||||
|
with self.assertLogs() as captured:
|
||||||
|
with self.assertRaisesRegex(CustomException,
|
||||||
|
re.compile(
|
||||||
|
r"CustomException\(\d+\): \{foo\}.*?RTIOUnderflow\(\d+\): \{bar\}.*?RTIOOverflow\(\d+\): \{bizz\}.*?RTIOUnderflow\(\d+\): \{buzz\}",
|
||||||
|
re.DOTALL)):
|
||||||
|
self.execute(KernelNestedFmtException)
|
||||||
|
captured_lines = captured.output[0].split('\n')
|
||||||
|
self.assertEqual([captured_lines[0], captured_lines[-1]],
|
||||||
|
["ERROR:artiq.coredevice.comm_kernel:Couldn't format exception message", "KeyError: 'foo'"])
|
||||||
|
|
||||||
|
def test_rtio_underflow(self):
|
||||||
|
with self.assertRaisesRegex(RTIOUnderflow,
|
||||||
|
re.compile(
|
||||||
|
r"RTIO underflow at channel 0x[0-9a-fA-F]*?:led\d*?, \d+? mu, slack -\d+? mu.*?RTIOUnderflow\(\d+\): RTIO underflow at channel 0x([0-9a-fA-F]+?):led\d*?, \d+? mu, slack -\d+? mu",
|
||||||
|
re.DOTALL)):
|
||||||
|
self.execute(KernelRTIOUnderflow)
|
||||||
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
Test sync in exceptions raised between host and kernel
|
Test sync in exceptions raised between host and kernel
|
||||||
@ -38,7 +122,7 @@ class _TestExceptionSync(EnvExperiment):
|
|||||||
test_exception_id_sync(id)
|
test_exception_id_sync(id)
|
||||||
|
|
||||||
|
|
||||||
class ExceptionTest(ExperimentCase):
|
class ExceptionSyncTest(ExperimentCase):
|
||||||
def test_raise_exceptions_kernel(self):
|
def test_raise_exceptions_kernel(self):
|
||||||
exp = self.create(_TestExceptionSync)
|
exp = self.create(_TestExceptionSync)
|
||||||
|
|
||||||
@ -56,4 +140,3 @@ class ExceptionTest(ExperimentCase):
|
|||||||
name = name.split('.')[-1].split(':')[-1]
|
name = name.split('.')[-1].split(':')[-1]
|
||||||
with self.assertRaises(getattr(exceptions, name)) as ctx:
|
with self.assertRaises(getattr(exceptions, name)) as ctx:
|
||||||
exp.raise_exception_host(id)
|
exp.raise_exception_host(id)
|
||||||
|
|
||||||
|
@ -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])
|
||||||
|
@ -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])
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()):
|
||||||
|
@ -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
|
|
||||||
|
@ -61,18 +61,6 @@
|
|||||||
id="path319"
|
id="path319"
|
||||||
d="m 69.975,234.695 c -3.226,0.31 -6.272,0.602 -9.352,0.754 -1.868,0.093 -3.594,0.139 -5.278,0.139 -7.127,0 -13.339,-0.867 -18.903,-2.646 -0.794,-0.254 -1.576,-0.526 -2.345,-0.817 -11.329,-4.29 -16.078,-12.875 -13.733,-24.827 2.135,-10.872 7.632,-19.988 13.253,-28.221 1.117,-1.634 2.315,-3.259 3.474,-4.83 0.454,-0.616 0.909,-1.233 1.364,-1.857 L 28.098,162.386 c -7.526,9.307 -16.644,21.933 -20.824,37.338 -3.192,11.767 -2.23,22.453 2.783,30.906 5.009,8.446 13.909,14.409 25.738,17.245 6.106,1.465 12.57,2.177 19.76,2.177 3.754,-0.001 7.687,-0.192 12.023,-0.588 2.495,-0.227 4.928,-0.557 7.504,-0.906 0.973,-0.132 1.95,-0.265 2.934,-0.392 l -3.897,-13.857 c -1.413,0.124 -2.791,0.256 -4.144,0.386 z" /><path
|
d="m 69.975,234.695 c -3.226,0.31 -6.272,0.602 -9.352,0.754 -1.868,0.093 -3.594,0.139 -5.278,0.139 -7.127,0 -13.339,-0.867 -18.903,-2.646 -0.794,-0.254 -1.576,-0.526 -2.345,-0.817 -11.329,-4.29 -16.078,-12.875 -13.733,-24.827 2.135,-10.872 7.632,-19.988 13.253,-28.221 1.117,-1.634 2.315,-3.259 3.474,-4.83 0.454,-0.616 0.909,-1.233 1.364,-1.857 L 28.098,162.386 c -7.526,9.307 -16.644,21.933 -20.824,37.338 -3.192,11.767 -2.23,22.453 2.783,30.906 5.009,8.446 13.909,14.409 25.738,17.245 6.106,1.465 12.57,2.177 19.76,2.177 3.754,-0.001 7.687,-0.192 12.023,-0.588 2.495,-0.227 4.928,-0.557 7.504,-0.906 0.973,-0.132 1.95,-0.265 2.934,-0.392 l -3.897,-13.857 c -1.413,0.124 -2.791,0.256 -4.144,0.386 z" /><path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path321"
|
|
||||||
d="m 55.345,235.588 c 1.684,0 3.41,-0.046 5.278,-0.139 -1.868,0.093 -3.594,0.139 -5.278,0.139 z" /><path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path323"
|
|
||||||
d="m 36.442,232.942 c 5.564,1.778 11.776,2.646 18.903,2.646 -7.127,0 -13.339,-0.867 -18.903,-2.646 z" /><path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path325"
|
|
||||||
d="m 55.555,250.052 c -7.19,0 -13.654,-0.712 -19.76,-2.177 6.106,1.465 12.57,2.177 19.76,2.177 z" /><path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path327"
|
|
||||||
d="m 67.578,249.464 c -4.336,0.396 -8.269,0.587 -12.023,0.588 3.754,0 7.686,-0.193 12.023,-0.588 z" /><path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path329"
|
id="path329"
|
||||||
d="m 180.738,111.633 c -0.625,-4.189 -1.226,-8.218 -1.868,-12.238 -0.325,-2.036 -5.861,-6.224 -8.227,-6.224 -0.157,0 -0.292,0.02 -0.402,0.058 -4.172,1.46 -8.243,3.096 -12.552,4.827 -1.419,0.57 -2.854,1.146 -4.316,1.727 l 28,16.088 -0.635,-4.238 z" /><path
|
d="m 180.738,111.633 c -0.625,-4.189 -1.226,-8.218 -1.868,-12.238 -0.325,-2.036 -5.861,-6.224 -8.227,-6.224 -0.157,0 -0.292,0.02 -0.402,0.058 -4.172,1.46 -8.243,3.096 -12.552,4.827 -1.419,0.57 -2.854,1.146 -4.316,1.727 l 28,16.088 -0.635,-4.238 z" /><path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
@ -82,9 +70,6 @@
|
|||||||
id="path333"
|
id="path333"
|
||||||
d="m 116.53,81.312 0.102,-0.053 c 3.387,-1.754 6.785,-3.483 10.385,-5.316 l 4.048,-2.062 -17.233,-6.35 -3.985,13.866 c 1.804,0.81 2.685,1.17 3.452,1.17 0.585,0 1.174,-0.223 2.061,-0.658 0.341,-0.168 0.722,-0.364 1.17,-0.597 z" /><path
|
d="m 116.53,81.312 0.102,-0.053 c 3.387,-1.754 6.785,-3.483 10.385,-5.316 l 4.048,-2.062 -17.233,-6.35 -3.985,13.866 c 1.804,0.81 2.685,1.17 3.452,1.17 0.585,0 1.174,-0.223 2.061,-0.658 0.341,-0.168 0.722,-0.364 1.17,-0.597 z" /><path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path335"
|
|
||||||
d="m 115.359,81.909 c 0.341,-0.168 0.723,-0.364 1.172,-0.597 l 0.101,-0.053 -0.102,0.053 c -0.448,0.233 -0.829,0.429 -1.171,0.597 z" /><path
|
|
||||||
inkscape:connector-curvature="0"
|
|
||||||
id="path337"
|
id="path337"
|
||||||
d="M 231.324,171.353 221.01,161.63 c -0.9,2.513 -2.059,14.3 -1.457,19.737 l 11.771,-10.014 z" /><path
|
d="M 231.324,171.353 221.01,161.63 c -0.9,2.513 -2.059,14.3 -1.457,19.737 l 11.771,-10.014 z" /><path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
@ -103,11 +88,11 @@
|
|||||||
d="m -688.687,-1452.053 -28.083,60.892 0,1.124 14.16,0 4.202,-8.945 25.208,0 4.195,8.945 14.16,0 0,-1.124 -28.173,-60.892 -5.669,0 z m -5.437,41.26 8.215,-19.134 8.424,19.134 -16.639,0 z" /><path
|
d="m -688.687,-1452.053 -28.083,60.892 0,1.124 14.16,0 4.202,-8.945 25.208,0 4.195,8.945 14.16,0 0,-1.124 -28.173,-60.892 -5.669,0 z m -5.437,41.26 8.215,-19.134 8.424,19.134 -16.639,0 z" /><path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path349"
|
id="path349"
|
||||||
d="m -601.487,-1412.259 c 7.498,-5.083 10.755,-15.119 7.922,-24.407 -2.772,-9.089 -10.521,-14.536 -20.727,-14.573 l -26.715,0 0,61.202 14.157,0 0,-18.602 9.978,0 10.836,18.602 16.479,0 0,-1.06 -12.726,-20.623 0.796,-0.539 z m -12.802,-8.73 -12.562,0 0,-17.809 1.003,0 c 1.255,0 2.529,-0.01 3.811,-0.02 2.599,-0.021 5.226,-0.041 7.771,0.02 5.329,0.052 7.74,4.621 7.719,8.845 -0.02,4.454 -2.687,8.964 -7.742,8.964 z" /><polygon
|
d="m -601.487,-1412.259 c 7.498,-5.083 10.755,-15.119 7.922,-24.407 -2.772,-9.089 -10.521,-14.536 -20.727,-14.573 l -26.715,0 0,61.202 14.157,0 0,-18.602 9.978,0 10.836,18.602 16.479,0 0,-1.06 -12.726,-20.623 0.796,-0.539 z m -12.802,-8.73 -12.562,0 0,-17.809 1.003,0 c 1.255,0 2.529,-0.01 3.811,-0.02 2.599,-0.021 5.226,-0.041 7.771,0.02 5.329,0.052 7.74,4.621 7.719,8.845 -0.02,4.454 -2.687,8.964 -7.742,8.964 z" /><path
|
||||||
id="polygon351"
|
id="polygon351"
|
||||||
points="-564.883,-1438.798 -564.883,-1390.037 -550.906,-1390.037 -550.906,-1438.798 -535.466,-1438.798 -535.466,-1451.239 -580.414,-1451.239 -580.414,-1438.798 " /><polygon
|
d="m -564.883,-1438.798 v 48.761 h 13.977 v -48.761 h 15.44 v -12.441 h -44.948 v 12.441 z" /><path
|
||||||
id="polygon353"
|
id="polygon353"
|
||||||
points="-524.427,-1451.239 -524.427,-1439.159 -517.022,-1439.159 -517.022,-1402.208 -525.059,-1402.208 -525.059,-1390.037 -495.01,-1390.037 -495.01,-1402.208 -503.046,-1402.208 -503.046,-1439.159 -495.642,-1439.159 -495.642,-1451.239 " /><path
|
d="m -524.427,-1451.239 v 12.08 h 7.405 v 36.951 h -8.037 v 12.171 h 30.049 v -12.171 h -8.036 v -36.951 h 7.404 v -12.08 z" /><path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path355"
|
id="path355"
|
||||||
d="m -424.689,-1402.396 c 3.252,-5.109 4.9,-11.218 4.9,-18.153 0,-21.831 -16.284,-31.774 -31.414,-31.774 -15.13,0 -31.415,9.943 -31.415,31.774 0,21.683 16.173,31.56 31.199,31.56 5.542,0 10.948,-1.321 15.636,-3.82 l 0.886,-0.472 1.724,3.244 14.828,0 0,-0.794 -6.679,-11.039 0.335,-0.526 z m -26.514,0.053 c -8.094,0 -16.806,-5.697 -16.806,-18.206 0,-12.415 8.712,-18.069 16.806,-18.069 8.094,0 16.806,5.654 16.806,18.069 0,12.509 -8.712,18.206 -16.806,18.206 z" /></g><path
|
d="m -424.689,-1402.396 c 3.252,-5.109 4.9,-11.218 4.9,-18.153 0,-21.831 -16.284,-31.774 -31.414,-31.774 -15.13,0 -31.415,9.943 -31.415,31.774 0,21.683 16.173,31.56 31.199,31.56 5.542,0 10.948,-1.321 15.636,-3.82 l 0.886,-0.472 1.724,3.244 14.828,0 0,-0.794 -6.679,-11.039 0.335,-0.526 z m -26.514,0.053 c -8.094,0 -16.806,-5.697 -16.806,-18.206 0,-12.415 8.712,-18.069 16.806,-18.069 8.094,0 16.806,5.654 16.806,18.069 0,12.509 -8.712,18.206 -16.806,18.206 z" /></g><path
|
||||||
@ -116,12 +101,10 @@
|
|||||||
d="m 165.162,221.757 0.005,-0.025 C 140.75,212.523 116.912,200.021 94.188,184.476 69.636,167.679 51.561,151.284 37.304,132.88 28.589,121.633 23.537,112.163 20.924,102.175 c -3.068,-11.729 0.105,-20.54 9.177,-25.482 2.277,-1.241 4.833,-2.269 7.596,-3.054 7.576,-2.153 15.72,-2.812 25.2,-2.015 1.245,0.104 2.521,0.217 3.806,0.332 1.402,0.123 2.801,0.242 4.208,0.368 l 3.177,0.281 3.845,-13.919 c -0.947,-0.121 -1.893,-0.245 -2.83,-0.37 -2.538,-0.337 -4.935,-0.656 -7.25,-0.857 -4.688,-0.406 -8.803,-0.604 -12.578,-0.604 -8.74,0 -16.341,1.076 -23.239,3.29 -14.58,4.68 -23.05,13.281 -25.893,26.297 -1.944,8.9 -0.569,18.38 4.327,29.833 6.099,14.267 15.625,27.692 29.978,42.251 31.706,32.162 69.878,56.911 116.697,75.662 3.183,1.274 6.384,2.416 9.773,3.624 1.433,0.511 2.888,1.029 4.368,1.568 l 2.397,-8.365 -8.521,-9.258 z" /><path
|
d="m 165.162,221.757 0.005,-0.025 C 140.75,212.523 116.912,200.021 94.188,184.476 69.636,167.679 51.561,151.284 37.304,132.88 28.589,121.633 23.537,112.163 20.924,102.175 c -3.068,-11.729 0.105,-20.54 9.177,-25.482 2.277,-1.241 4.833,-2.269 7.596,-3.054 7.576,-2.153 15.72,-2.812 25.2,-2.015 1.245,0.104 2.521,0.217 3.806,0.332 1.402,0.123 2.801,0.242 4.208,0.368 l 3.177,0.281 3.845,-13.919 c -0.947,-0.121 -1.893,-0.245 -2.83,-0.37 -2.538,-0.337 -4.935,-0.656 -7.25,-0.857 -4.688,-0.406 -8.803,-0.604 -12.578,-0.604 -8.74,0 -16.341,1.076 -23.239,3.29 -14.58,4.68 -23.05,13.281 -25.893,26.297 -1.944,8.9 -0.569,18.38 4.327,29.833 6.099,14.267 15.625,27.692 29.978,42.251 31.706,32.162 69.878,56.911 116.697,75.662 3.183,1.274 6.384,2.416 9.773,3.624 1.433,0.511 2.888,1.029 4.368,1.568 l 2.397,-8.365 -8.521,-9.258 z" /><path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path359"
|
id="path359"
|
||||||
d="m 279.656,208.102 c -0.147,-0.262 -0.314,-0.56 -0.358,-0.905 -0.992,-8.005 -3.834,-16.142 -8.689,-24.875 -7.945,-14.297 -18.829,-27.683 -34.25,-42.126 -3.812,-3.572 -7.724,-6.949 -11.864,-10.523 -1.677,-1.448 -3.376,-2.915 -5.096,-4.419 -0.006,0.032 -0.011,0.062 -0.017,0.092 -0.062,0.355 -0.097,0.551 -0.09,0.713 l 0.149,3.794 c 0.176,4.559 0.358,9.272 0.669,13.896 0.046,0.706 0.615,1.672 1.521,2.583 2.133,2.144 4.345,4.286 6.484,6.358 3.806,3.687 7.742,7.5 11.388,11.467 11.612,12.634 19.076,24.245 23.489,36.543 2.048,5.705 2.706,10.802 2.011,15.581 -1.146,7.896 -6.144,13.235 -15.281,16.322 -2.455,0.829 -5.003,1.474 -7.658,1.956 l 9.738,12.6 c 1.551,-0.468 3.08,-0.975 4.576,-1.562 12.387,-4.858 19.753,-12.956 22.521,-24.758 l 0.87,-3.686 0,-8.847 c -0.036,-0.067 -0.075,-0.135 -0.113,-0.204 z" /><g
|
d="m 279.656,208.102 c -0.147,-0.262 -0.314,-0.56 -0.358,-0.905 -0.992,-8.005 -3.834,-16.142 -8.689,-24.875 -7.945,-14.297 -18.829,-27.683 -34.25,-42.126 -3.812,-3.572 -7.724,-6.949 -11.864,-10.523 -1.677,-1.448 -3.376,-2.915 -5.096,-4.419 -0.006,0.032 -0.011,0.062 -0.017,0.092 -0.062,0.355 -0.097,0.551 -0.09,0.713 l 0.149,3.794 c 0.176,4.559 0.358,9.272 0.669,13.896 0.046,0.706 0.615,1.672 1.521,2.583 2.133,2.144 4.345,4.286 6.484,6.358 3.806,3.687 7.742,7.5 11.388,11.467 11.612,12.634 19.076,24.245 23.489,36.543 2.048,5.705 2.706,10.802 2.011,15.581 -1.146,7.896 -6.144,13.235 -15.281,16.322 -2.455,0.829 -5.003,1.474 -7.658,1.956 l 9.738,12.6 c 1.551,-0.468 3.08,-0.975 4.576,-1.562 12.387,-4.858 19.753,-12.956 22.521,-24.758 l 0.87,-3.686 0,-8.847 c -0.036,-0.067 -0.075,-0.135 -0.113,-0.204 z" /><path
|
||||||
id="g361"
|
|
||||||
transform="translate(716.77,1821.033)"><path
|
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path363"
|
id="path363"
|
||||||
d="m -528.273,-1731.087 c 0.581,4.945 1.224,9.971 1.846,14.831 1.416,11.057 2.879,22.489 3.712,33.785 0.808,10.944 0.86,22.254 0.165,34.1 l 13,16.818 c 0.335,-3.384 0.644,-6.817 0.903,-10.349 1.854,-25.214 1.066,-50.093 -2.342,-73.945 -0.708,-4.964 -1.549,-9.816 -2.438,-14.955 -0.378,-2.185 -0.759,-4.387 -1.133,-6.617 l -14.161,3.555 c 0.044,0.257 0.086,0.5 0.128,0.734 0.129,0.741 0.242,1.39 0.32,2.043 z" /><path
|
d="m 188.497,89.946 c 0.581,4.945 1.224,9.971 1.846,14.831 1.416,11.057 2.879,22.489 3.712,33.785 0.808,10.944 0.86,22.254 0.165,34.1 l 13,16.818 c 0.335,-3.384 0.644,-6.817 0.903,-10.349 1.854,-25.214 1.066,-50.093 -2.342,-73.945 -0.708,-4.964 -1.549,-9.816 -2.438,-14.955 -0.378,-2.185 -0.759,-4.387 -1.133,-6.617 l -14.161,3.555 c 0.044,0.257 0.086,0.5 0.128,0.734 0.129,0.741 0.242,1.39 0.32,2.043 z" /><path
|
||||||
inkscape:connector-curvature="0"
|
inkscape:connector-curvature="0"
|
||||||
id="path365"
|
id="path365"
|
||||||
d="m -534.181,-1580.343 c -2.321,7.348 -4.98,14.184 -8.042,20.678 -3.967,8.416 -9.193,17.993 -17.877,25.219 -9.296,7.733 -19.083,7.701 -28.365,-0.092 -5.935,-4.982 -10.921,-11.633 -15.692,-20.929 -6.63,-12.926 -11.46,-27.311 -15.661,-46.642 l -0.072,-0.342 c -0.174,-0.828 -0.411,-1.962 -0.892,-2.284 -4.153,-2.786 -8.356,-5.448 -12.807,-8.267 -1.067,-0.677 -2.146,-1.359 -3.239,-2.054 0.164,0.969 0.321,1.911 0.475,2.834 0.433,2.596 0.842,5.047 1.304,7.478 4.702,24.702 10.704,42.76 19.462,58.551 7.542,13.604 17.86,28.05 37.208,32.08 l 8.319,0 c 17.949,-3.632 27.887,-16.568 35.241,-28.748 1.953,-3.234 3.717,-6.507 5.244,-9.726 2.388,-5.035 4.556,-10.249 6.533,-15.655 l -11.139,-12.101 z" /></g></svg>
|
d="m 182.589,240.69 c -2.321,7.348 -4.98,14.184 -8.042,20.678 -3.967,8.416 -9.193,17.993 -17.877,25.219 -9.296,7.733 -19.083,7.701 -28.365,-0.092 -5.935,-4.982 -10.921,-11.633 -15.692,-20.929 -6.63,-12.926 -11.46,-27.311 -15.661,-46.642 l -0.072,-0.342 c -0.174,-0.828 -0.411,-1.962 -0.892,-2.284 -4.153,-2.786 -8.356,-5.448 -12.807,-8.267 -1.067,-0.677 -2.146,-1.359 -3.239,-2.054 0.164,0.969 0.321,1.911 0.475,2.834 0.433,2.596 0.842,5.047 1.304,7.478 4.702,24.702 10.704,42.76 19.462,58.551 7.542,13.604 17.86,28.05 37.208,32.08 h 8.319 c 17.949,-3.632 27.887,-16.568 35.241,-28.748 1.953,-3.234 3.717,-6.507 5.244,-9.726 2.388,-5.035 4.556,-10.249 6.533,-15.655 z" /></svg>
|
||||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 12 KiB |
@ -33,13 +33,13 @@ Download and run the official installer. If using NixOS, note that this will req
|
|||||||
System description file
|
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.
|
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 file.
|
||||||
|
|
||||||
.. warning::
|
.. 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.
|
Not all core devices use system description files. Devices that use system description files for configuration are referred to as JSON variants (see :ref:`JSON variant devices <devices-table>`). Some rare or specialized boards use hardcoded variants, selected by a variant name such as ``nist_clock``, without needing a system description file (see :ref:`Hardcoded variant devices <devices-table>`). For the list of supported variants, see the :ref:`building` section. Writing new hardcoded variants is not a trivial task and is generally not recommended unless you are an experienced FPGA developer.
|
||||||
|
|
||||||
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.
|
If you already have your system description 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 description 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: ::
|
System descriptions do not need to be very complex. At its most basic, a system description looks something like: ::
|
||||||
|
|
||||||
@ -102,7 +102,7 @@ Nix development environment
|
|||||||
|
|
||||||
- If using NixOS, make the equivalent changes to your ``configuration.nix`` instead.
|
- If using NixOS, make the equivalent changes to your ``configuration.nix`` instead.
|
||||||
|
|
||||||
* 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.
|
* 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 :ref:`Zynq devices <devices-table>` (Kasli-SoC, ZC706, or EBAZ4205). 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).
|
* 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.
|
* Run ``nix develop`` at the root of the repository, where ``flake.nix`` is.
|
||||||
|
|
||||||
@ -138,7 +138,7 @@ The parallel command does exist for ARTIQ-Zynq: ::
|
|||||||
|
|
||||||
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 directly to set up the build, eliminating the requirement for any particular environment.
|
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 directly to set up the build, 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.
|
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, Zynq boards can also be flashed by writing to the SD card directly, which requires no further special tools. As long as you have a functioning Nix/Vivado installation with flakes enabled, you can progress directly to the building instructions below.
|
||||||
|
|
||||||
.. _building:
|
.. _building:
|
||||||
|
|
||||||
@ -158,8 +158,9 @@ With KC705, use: ::
|
|||||||
|
|
||||||
$ python -m artiq.gateware.targets.kc705 -V <variant>
|
$ 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: ::
|
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 connected by network or JTAG USB respectively: ::
|
||||||
|
|
||||||
|
$ artiq_coremgmt flash --srcbuild artiq_<board>/<variant>
|
||||||
$ artiq_flash --srcbuild [-t kc705] -d artiq_<board>/<variant>
|
$ artiq_flash --srcbuild [-t kc705] -d artiq_<board>/<variant>
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@ -169,10 +170,10 @@ This will create a directory ``artiq_kasli`` or ``artiq_kc705`` containing the b
|
|||||||
|
|
||||||
Look for the option ``-V VARIANT, --variant VARIANT``.
|
Look for the option ``-V VARIANT, --variant VARIANT``.
|
||||||
|
|
||||||
Kasli-SoC or ZC706 (ARTIQ on Zynq)
|
Kasli-SoC, ZC706 or EBAZ4205 (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.
|
The building process for :ref:`Zynq devices <devices-table>` 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: ::
|
For Kasli-SoC, run: ::
|
||||||
|
|
||||||
@ -180,32 +181,40 @@ For Kasli-SoC, run: ::
|
|||||||
|
|
||||||
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"``.
|
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: ::
|
For ZC706 or EBAZ4205, you can use a command of the same form (replace ``<target>`` with ``zc706`` or ``ebaz4205``): ::
|
||||||
|
|
||||||
$ 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'
|
$ 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="<target>"; variant="<variant>";}).<target>-<variant>-sd'
|
||||||
|
|
||||||
or you can use the more direct version: ::
|
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
|
$ nix build --print-build-logs git+https://git.m-labs.hk/m-labs/artiq-zynq\?ref=release-[number]#<target>-<variant>-sd
|
||||||
|
|
||||||
(which is possible for ZC706 because there is no need to be able to specify a system description file in the arguments.)
|
(which is possible for ZC706 and EBAZ4205 because there is no need to be able to specify a system description file in the arguments.)
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
To see supported ZC706 variants, you can run the following at the root of the repository: ::
|
To see supported variants for ZC705 or EBA4205, you can run the following at the root of the repository: ::
|
||||||
|
|
||||||
$ src/gateware/zc706.py --help
|
$ src/gateware/<target>.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: ::
|
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"
|
$ nix flake show git+https://git.m-labs.hk/m-labs/artiq-zynq\?ref=release-[number] | grep "package '<target>.*sd"
|
||||||
|
|
||||||
to see the list of suitable build targets directly.
|
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:
|
Any of these commands should produce a directory ``result`` which contains a file ``boot.bin``. If your core device is accessible by network, flash with: ::
|
||||||
|
|
||||||
|
$ artiq_coremgmt flash result
|
||||||
|
|
||||||
|
Otherwise:
|
||||||
|
|
||||||
1. Power off the board, extract the SD card and load ``boot.bin`` onto it manually.
|
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.
|
2. Insert the SD card back into the board.
|
||||||
3. Ensure that the DIP switches (labeled BOOT MODE) are set correctly, to SD.
|
3. Set to boot from SD card:
|
||||||
|
|
||||||
|
- For Kasli-SoC or ZC706, ensure that the DIP switches (labeled BOOT MODE) are set correctly, to SD.
|
||||||
|
- For EBAZ4205, set up the `boot select resistor <https://github.com/xjtuecho/EBAZ4205>`_ to boot from SD card.
|
||||||
|
|
||||||
4. Power the board back on.
|
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.
|
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.
|
||||||
@ -217,7 +226,7 @@ After a successful boot, the "FPGA DONE" light should be illuminated and the boa
|
|||||||
Booting over JTAG/Ethernet
|
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.
|
It is also possible to boot :ref:`Zynq devices <devices-table>` over USB and Ethernet (EBAZ4205 not currently supported). 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``.
|
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``.
|
||||||
|
|
||||||
@ -238,13 +247,13 @@ For Kasli-SoC:
|
|||||||
$ gateware/kasli_soc.py -g ../build/gateware <description.json>
|
$ gateware/kasli_soc.py -g ../build/gateware <description.json>
|
||||||
$ make TARGET=kasli_soc GWARGS="path/to/description.json" <fw-type>
|
$ make TARGET=kasli_soc GWARGS="path/to/description.json" <fw-type>
|
||||||
|
|
||||||
For ZC706:
|
For ZC706 or EBAZ4205:
|
||||||
::
|
::
|
||||||
|
|
||||||
$ gateware/zc706.py -g ../build/gateware -V <variant>
|
$ gateware/<target>.py -g ../build/gateware -V <variant>
|
||||||
$ make TARGET=zc706 GWARGS="-V <variant>" <fw-type>
|
$ make TARGET=<target> 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.
|
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, if supported, 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.
|
||||||
|
|
||||||
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: ::
|
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: ::
|
||||||
|
|
||||||
|
@ -267,3 +267,7 @@ In the synthetic example above, the compiler will be able to detect that the res
|
|||||||
for _ in range(100):
|
for _ in range(100):
|
||||||
delay_mu(precomputed_delay_mu)
|
delay_mu(precomputed_delay_mu)
|
||||||
self.worker.work()
|
self.worker.work()
|
||||||
|
|
||||||
|
Kernel invariants are defined for every object by the ``kernel_invariants`` atttribute, which is a set containing the names of every invariant attribute of this object.
|
||||||
|
|
||||||
|
At compile time it is possible to automatically detect attributes that are never altered in a kernel, and thus may be good candidates for inclusion into ``kernel_invariants``. This is done by specifying ``report_invariants=True`` when initializing the core device driver (in the dashboard you can do this using the "Override device arguments" option).
|
@ -34,8 +34,8 @@ mock_modules = ["artiq.gui.waitingspinnerwidget",
|
|||||||
"artiq.compiler.embedding",
|
"artiq.compiler.embedding",
|
||||||
"artiq.dashboard.waveform",
|
"artiq.dashboard.waveform",
|
||||||
"artiq.coredevice.jsondesc",
|
"artiq.coredevice.jsondesc",
|
||||||
"qasync", "lmdb", "dateutil.parser", "prettytable", "PyQt5",
|
"qasync", "lmdb", "dateutil.parser", "prettytable", "PyQt6",
|
||||||
"h5py", "llvmlite", "pythonparser", "tqdm", "jsonschema"]
|
"h5py", "llvmlite", "pythonparser", "tqdm", "jsonschema", "platformdirs"]
|
||||||
|
|
||||||
for module in mock_modules:
|
for module in mock_modules:
|
||||||
sys.modules[module] = Mock()
|
sys.modules[module] = Mock()
|
||||||
@ -70,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"
|
||||||
@ -330,3 +331,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'
|
@ -6,6 +6,9 @@ Networking and configuration
|
|||||||
Setting up core device networking
|
Setting up core device networking
|
||||||
---------------------------------
|
---------------------------------
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Satellite core devices (in a DRTIO setting, see :doc:`using_drtio_subkernels`) do not support independent networking and this section does not apply to them. Follow the instructions on this page for your master core device, and proceed to :ref:`configuring-satellite` once DRTIO communications are established.
|
||||||
|
|
||||||
For Kasli, insert a SFP/RJ45 transceiver (normally included with purchases from M-Labs and QUARTIQ) into the SFP0 port and connect it to an Ethernet port in your network. If the port is 10Mbps or 100Mbps and not 1000Mbps, make sure that the SFP/RJ45 transceiver supports the lower rate. Many SFP/RJ45 transceivers only support the 1000Mbps rate. If you do not have a SFP/RJ45 transceiver that supports 10Mbps and 100Mbps rates, you may instead use a gigabit Ethernet switch in the middle to perform rate conversion.
|
For Kasli, insert a SFP/RJ45 transceiver (normally included with purchases from M-Labs and QUARTIQ) into the SFP0 port and connect it to an Ethernet port in your network. If the port is 10Mbps or 100Mbps and not 1000Mbps, make sure that the SFP/RJ45 transceiver supports the lower rate. Many SFP/RJ45 transceivers only support the 1000Mbps rate. If you do not have a SFP/RJ45 transceiver that supports 10Mbps and 100Mbps rates, you may instead use a gigabit Ethernet switch in the middle to perform rate conversion.
|
||||||
|
|
||||||
You can also insert other types of SFP transceivers into Kasli if you wish to use it directly in e.g. an optical fiber Ethernet network. Kasli-SoC already directly features RJ45 10/100/1000 Ethernet.
|
You can also insert other types of SFP transceivers into Kasli if you wish to use it directly in e.g. an optical fiber Ethernet network. Kasli-SoC already directly features RJ45 10/100/1000 Ethernet.
|
||||||
@ -55,6 +58,9 @@ For Kasli-SoC:
|
|||||||
For ZC706:
|
For ZC706:
|
||||||
If the ``ip`` config is not set, ZC706 firmware defaults to using the IP address ``192.168.1.52``.
|
If the ``ip`` config is not set, ZC706 firmware defaults to using the IP address ``192.168.1.52``.
|
||||||
|
|
||||||
|
For EBAZ4205:
|
||||||
|
If the ``ip`` config is not set, EBAZ4205 firmware defaults to using the IP address ``192.168.1.57``.
|
||||||
|
|
||||||
For Kasli or KC705:
|
For Kasli or KC705:
|
||||||
If the ``ip`` config field is not set or set to ``use_dhcp``, the device will attempt to obtain an IP address and default gateway using DHCP. The chosen IP address will be in log output, which can be accessed via the :ref:`UART log <connecting-UART>`.
|
If the ``ip`` config field is not set or set to ``use_dhcp``, the device will attempt to obtain an IP address and default gateway using DHCP. The chosen IP address will be in log output, which can be accessed via the :ref:`UART log <connecting-UART>`.
|
||||||
|
|
||||||
@ -127,3 +133,16 @@ Load the DRTIO routing table
|
|||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
If you are using DRTIO and the default routing table (for a star topology) is not suitable to your needs, you will first need to prepare and load a different routing table. See :ref:`Using DRTIO <drtio-routing>`.
|
If you are using DRTIO and the default routing table (for a star topology) is not suitable to your needs, you will first need to prepare and load a different routing table. See :ref:`Using DRTIO <drtio-routing>`.
|
||||||
|
|
||||||
|
.. _configuring-satellite:
|
||||||
|
|
||||||
|
Configuring DRTIO satellites
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Once DRTIO communications are online, any satellite devices can be accessed as normal using :mod:`~artiq.frontend.artiq_coremgmt`, e.g.: ::
|
||||||
|
|
||||||
|
$ artiq_coremgmt -s <destination_number> log
|
||||||
|
|
||||||
|
The destination number corresponds to the number assigned to that satellite both in the device database and, earlier, in the system configuration file. See the notes in :ref:`drtio-routing` if you are not sure what destination number to use.
|
||||||
|
|
||||||
|
It is also possible to set configuration values, reflash, or reboot the device. Notably, :ref:`event spreading <sed-event-spreading>` is a per-device setting considered particularly useful on satellites. Most other configuration settings, e.g. networking, clocking, will not be used in practice in a satellite context -- satellites do not support direct network connections and are always bound to the master's clock.
|
@ -39,7 +39,7 @@ The core device reserves some storage space (either flash or directly on SD card
|
|||||||
``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 :mod:`~artiq.frontend.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 (see :ref:`Zynq devices <devices-table>`).
|
||||||
``panic_reset``
|
``panic_reset``
|
||||||
If set to ``1``, core device will restart automatically. Not applicable for ARTIQ-Zynq.
|
If set to ``1``, core device will restart automatically. Not applicable for ARTIQ-Zynq.
|
||||||
``no_flash_boot``
|
``no_flash_boot``
|
||||||
@ -106,6 +106,27 @@ If not using WRPLL, PLL can also be bypassed entirely with the options
|
|||||||
|
|
||||||
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.
|
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.
|
||||||
|
|
||||||
|
.. _types-of-boards:
|
||||||
|
|
||||||
|
Types of boards
|
||||||
|
---------------
|
||||||
|
|
||||||
|
To clarify the terminology used in ARTIQ, we can distinguish the boards into a few key groups. There are two primary ways to categorize them. The first is based on the ARTIQ platform itself: either ARTIQ or ARTIQ-Zynq. ARTIQ-Zynq boards specifically refer to those that feature a Xilinx Zynq FPGA. The second distinction is based on how the boards are configured: some use a :ref:`JSON system description file <system-description>`, while others do not.
|
||||||
|
|
||||||
|
Below are the current groups of boards:
|
||||||
|
|
||||||
|
.. _devices-table:
|
||||||
|
|
||||||
|
+------------------------------+------------------------------+
|
||||||
|
| **Device Type** | **Devices** |
|
||||||
|
+==============================+==============================+
|
||||||
|
| Zynq devices | Kasli-SoC, ZC706, EBAZ4205 |
|
||||||
|
+------------------------------+------------------------------+
|
||||||
|
| JSON variant devices | Kasli, Kasli-SoC |
|
||||||
|
+------------------------------+------------------------------+
|
||||||
|
| Hardcoded variant devices | KC705, ZC706, EBAZ4205 |
|
||||||
|
+------------------------------+------------------------------+
|
||||||
|
|
||||||
Board details
|
Board details
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
@ -140,6 +161,46 @@ 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.
|
||||||
|
|
||||||
|
EBAZ4205
|
||||||
|
^^^^^^^^
|
||||||
|
|
||||||
|
The `EBAZ4205 <https://github.com/xjtuecho/EBAZ4205>`_ Zynq-SoC control card, originally used in the Ebit E9+ BTC miner, is a low-cost development board (around $20-$30 USD), making it an ideal option for experimenting with ARTIQ. To use the EBAZ4205, it's important to carefully follow the board documentation to configure it to boot from the SD card, as network booting via ``artiq_netboot`` is currently unsupported. This is because the Ethernet PHY is routed through the EMIO, requiring the FPGA to be programmed before the board can establish a network connection.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Although both ``int_100`` and ``int_125`` are supported, ``int_150`` -- used to synthesize a 150MHz RTIO clock -- is not currently compatible with the EBAZ4205.
|
||||||
|
|
||||||
|
SD BOOT
|
||||||
|
"""""""
|
||||||
|
|
||||||
|
To enable the EBAZ4205 to boot from an SD card, you will need to modify the board's boot select resistors. By default, the board is set to boot from NAND, with a resistor placed on ``R2584``. To change the boot mode to SD card, move the resistor from ``R2584`` to ``R2577``. Be sure to carefully consult the `EBAZ4205 documentation <https://github.com/xjtuecho/EBAZ4205>`_ to confirm resistor locations and proper handling of the board.
|
||||||
|
|
||||||
|
AD9834 DDS
|
||||||
|
""""""""""
|
||||||
|
|
||||||
|
One useful application of the EBAZ4205 is controlling external devices like the AD9834 DDS Module from ZonRi Technology Co., Ltd. To establish communication between the EBAZ4205 and the AD9834 module, proper configuration of the SPI interface pins is essential. The board's flexibility allows for straightforward control of the DDS once the correct pinout is known. The table below details the necessary connections between the EBAZ4205 and the AD9834 module, including power, ground, and SPI signals.
|
||||||
|
|
||||||
|
+--------------------------+---------------------+----------------------------+
|
||||||
|
| Pin on AD9834 Module | Chip Function | Connection on EBAZ4205 |
|
||||||
|
+==========================+=====================+============================+
|
||||||
|
| SCLK | SCLK | CLK: DATA3-19 (Pin V20) |
|
||||||
|
+--------------------------+---------------------+----------------------------+
|
||||||
|
| DATA | SDATA | MOSI: DATA3-17 (Pin U20) |
|
||||||
|
+--------------------------+---------------------+----------------------------+
|
||||||
|
| SYNC | FSYNC | CS_N: DATA3-15 (Pin P19) |
|
||||||
|
+--------------------------+---------------------+----------------------------+
|
||||||
|
| FSE (Tied to GND) | FSELECT | N/A: Bit Controlled |
|
||||||
|
+--------------------------+---------------------+----------------------------+
|
||||||
|
| PSE (Tied to GND) | PSELECT | N/A: Bit Controlled |
|
||||||
|
+--------------------------+---------------------+----------------------------+
|
||||||
|
| GND | Ground | GND: J8-1, J8-3 |
|
||||||
|
+--------------------------+---------------------+----------------------------+
|
||||||
|
| VIN | AVDD/DVDD | 3.3V: J8-2 |
|
||||||
|
+--------------------------+---------------------+----------------------------+
|
||||||
|
| RESET (Unused) | RESET | N/A: Bit Controlled |
|
||||||
|
+--------------------------+---------------------+----------------------------+
|
||||||
|
|
||||||
|
For a guide, see the `EBAZ4205 and AD9834 setup guide <https://newell.github.io/projects/ebaz4205>`_.
|
||||||
|
|
||||||
Variant details
|
Variant details
|
||||||
---------------
|
---------------
|
||||||
|
|
||||||
|
@ -85,6 +85,12 @@ RF generation drivers
|
|||||||
.. automodule:: artiq.coredevice.ad9914
|
.. automodule:: artiq.coredevice.ad9914
|
||||||
:members:
|
:members:
|
||||||
|
|
||||||
|
:mod:`artiq.coredevice.ad9834` module
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. automodule:: artiq.coredevice.ad9834
|
||||||
|
:members:
|
||||||
|
|
||||||
:mod:`artiq.coredevice.mirny` module
|
:mod:`artiq.coredevice.mirny` module
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
407
doc/manual/extending_rtio.rst
Normal file
407
doc/manual/extending_rtio.rst
Normal file
@ -0,0 +1,407 @@
|
|||||||
|
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 innovation and contribution 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 :ref:`Hardcoded variant devices <devices-table>`, 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 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:
|
||||||
|
|
||||||
|
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 (see :ref:`JSON variant devices <devices-table>`), whereas their KC705, ZC706 and EBAZ4205 (see :ref:`Hardcoded variant devices <devices-table>`) 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-phy:
|
||||||
|
|
||||||
|
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_o)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
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-core-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 more long-term gateware change, ``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. See also :ref:`extending-system-description` below.
|
||||||
|
|
||||||
|
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 a custom EEM
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
Adding a custom EEM to a Kasli or Kasli-SoC system is not much more difficult than adding new gateware logic for existing hardware, and may in some cases be simpler, if no custom PHY is required. On the other hand, modifying :ref:`Hardcoded variant devices <devices-table>` is a different process, and gateware generation for these boards does not use the files and modules described below. Creating new hardcoded variants is not directly addressed in this tutorial. That said, it would begin and end largely in the respective target file, where the variants are defined.
|
||||||
|
|
||||||
|
Non-realtime hardware which does not need to connect directly to the core device or require gateware support should instead be handled through an NDSP, see :doc:`developing_a_ndsp`. This is a more accessible process in general and does not vary based on core device.
|
||||||
|
|
||||||
|
Extending gateware support
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
The first and most important file to look into is ``eem.py``, found in ``artiq/gateware``. This is where the classes for ARTIQ-supported EEM peripherals are defined, and where you can add your own class for a new EEM, following the model of the preexisting classes.
|
||||||
|
|
||||||
|
Your custom EEM class should subclass :class:`artiq.gateware.eem._EEM` and provide the two methods ``io()`` and ``add_std()``. The second, ``add_std()``, will be called to add this EEM to a gateware build. The first is called by ``add_extension()`` in :class:`~artiq.gateware.eem._EEM` itself. Your class should look something like: ::
|
||||||
|
|
||||||
|
class CustomEEM(_EEM):
|
||||||
|
@staticmethod
|
||||||
|
def io(*args, **kwargs iostandard=default_iostandard):
|
||||||
|
io = [ ... ] # A sequence of pad assignments
|
||||||
|
return io
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def add_std(cls, target, *args, **kwargs):
|
||||||
|
cls.add_extension(target, *args, **kwargs) # calls CustomEEM.io(*args, **kwargs)
|
||||||
|
|
||||||
|
# Request IO pads that were added in CustomEEM.io()
|
||||||
|
target.platform.request(...)
|
||||||
|
|
||||||
|
# Add submodule for PHY (pass IO pads in arguments)
|
||||||
|
phy = ...
|
||||||
|
phys.append(phy)
|
||||||
|
target.submodules += phy
|
||||||
|
|
||||||
|
# Add RTIO channel(s) for PHY
|
||||||
|
target.rtio_channels.append(rtio.Channel.from_phy(...))
|
||||||
|
|
||||||
|
Note that the pad assignments ``io()`` returns should be in Migen, usually comprised out of Migen ``Subsignal`` and ``Pin`` constructs. The predefined :func:`~artiq.gateware.eem._eem_signal` and :func:`~artiq.gateware.eem._eem_pin` functions (also provided in ``eem.py``) may be useful. Note also that ``add_std()`` covers essentially the same territory as the modifications we simply made directly to the target file for the LED tutorial. Depending on your use case, you may need to write a custom PHY for your hardware, or you may be able to make use of the PHYs ARTIQ already makes available. See :ref:`adding-phy`, if you haven't already. A single EEM may also generate several PHYs and/or claim several RTIO channels.
|
||||||
|
|
||||||
|
Now find the file ``eem_7series.py``, also in ``artiq/gateware``. The functions defined in this file mostly serve as wrappers for ``add_std()``, with some additional interpretation and checks on the parameters. Your own ``peripheral`` function should look something like: ::
|
||||||
|
|
||||||
|
def peripheral_custom(module, peripheral):
|
||||||
|
... # (interpret peripheral arguments)
|
||||||
|
CustomEEM.add_std(module, *args, **kwargs)
|
||||||
|
|
||||||
|
Once you have written this function, add it to the ``peripheral_processors`` dictionary at the end of the file, as: ::
|
||||||
|
|
||||||
|
peripheral_processors["custom_eem"] = peripheral_custom
|
||||||
|
|
||||||
|
Now your EEM is fully supported by the ARTIQ gateware infrastructure. All that remains is to add it to a build configuration.
|
||||||
|
|
||||||
|
.. _extending-system-description:
|
||||||
|
|
||||||
|
Target file and system description
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
In the :ref:`extending-gateware-logic` tutorial above, we made modifications directly to the target file, to hardcode a certain PHY for a certain set of pads. This is reasonable to do in the case of the core device LEDs, which are always present and cannot be rearranged. It is theoretically possible to hardcode the addition of your new EEM in the same way. In this case it would not be necessary to make modifications to ``eem.py`` and ``eem_7series.py``; the pad assignments, requisite PHYs, and RTIO channels could all be defined directly in the target file. This is essentially how things are done for :ref:`Hardcoded variant devices <devices-table>`.
|
||||||
|
|
||||||
|
However, with EEM cards, which can be present in different numbers and rearranged at will, it is preferable to be more flexible. This is the reason system description files are used. Assuming you have added your EEM to ``eem.py`` and the ``peripheral_processors`` dictionary, no modifications to the target file are actually necessarily. All Kasli and Kasli-SoC targets already contain the line: ::
|
||||||
|
|
||||||
|
eem_7series.add_peripherals(self, description["peripherals"], iostandard=eem_iostandard)
|
||||||
|
|
||||||
|
In other words, your custom EEM will be automatically included if it is in the ``description`` dictionary, which is interpreted directly from the JSON system description. Simply add an entry to your system description: ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "custom_eem",
|
||||||
|
"ports": [0]
|
||||||
|
# any other args to pass to add_std or io later:
|
||||||
|
...
|
||||||
|
}
|
||||||
|
|
||||||
|
Note however that before a build system descriptions are always checked against the corresponding JSON schema, which you can find as ``coredevice_generic_schema.json`` in ``artiq/coredevice``. Add the new format for your entry here as well, under ``definition``, ``peripheral``, and ``allOf``: ::
|
||||||
|
|
||||||
|
{
|
||||||
|
"title": "CustomEEM",
|
||||||
|
"if": {
|
||||||
|
"properties": {
|
||||||
|
"type": {
|
||||||
|
"const": "custom_eem"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"then": {
|
||||||
|
"properties": {
|
||||||
|
"ports": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"minItems": ...,
|
||||||
|
"maxItems": ...
|
||||||
|
},
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"required": ["ports", ...]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
Now it should be possible to :doc:`build the binaries <building_developing>`, using your system description and its custom entry.
|
||||||
|
|
||||||
|
Device database and driver
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
As usual, before you can use your hardware from a kernel, you will need to add an entry to your device database. You can use one of the existing ARTIQ core drivers, if applicable, or you can write your own custom driver, as we did in :ref:`adding-core-driver`.
|
||||||
|
|
||||||
|
There are a few options to determine the correct channel number. You can figure it out from the structure of your system description; you can add a print statement to ``add_std()``; or, most preferably, you can add support for your custom EEM in :mod:`~artiq.frontend.artiq_ddb_template`, so that the channel number can be handled automatically as it is for other peripherals.
|
||||||
|
|
||||||
|
The relevant file is in ``artiq/frontend``, named simply ``artiq_ddb_template.py``. You will want to add a method within ``PeripheralManager``, in the format: ::
|
||||||
|
|
||||||
|
def process_custom_eem(self, rtio_offset, peripheral):
|
||||||
|
self.gen("""
|
||||||
|
device_db["{name}"] = {{
|
||||||
|
"type": "local",
|
||||||
|
"module": "artiq.coredevice.custom_eem",
|
||||||
|
"class": "CustomDriver",
|
||||||
|
"arguments": {{"channel": 0x{channel:06x}}}
|
||||||
|
}}""",
|
||||||
|
name=self.get_name("custom_eem"),
|
||||||
|
channel=rtio_offset + next(channel))
|
||||||
|
return next(channel)
|
||||||
|
|
||||||
|
Further arguments can be passed on through ``arguments`` if necessary. Note that the peripheral manager's ``process`` method chooses which method to use by performing a simple string check, so your ``process_`` method *must* use the same name for your custom hardware as given in the system description's ``"type"``.
|
||||||
|
|
||||||
|
You should now be able to use :mod:`~artiq.frontend.artiq_ddb_template` to generate your device database, and from there, compile and run experiments with your new hardware. Congratulations!
|
||||||
|
|
||||||
|
Merging support
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Being an open-source project, ARTIQ welcomes contributions from outside sources. If you have successfully integrated additional gateware or new hardware into ARTIQ, and you think this might be useful to other ARTIQ users in the community, you might consider merging support -- having your additions incorporated into the canonical ARTIQ codebase. See `this pull request <https://github.com/m-labs/artiq/pull/1800>`_ for one example of such a community addition.
|
||||||
|
|
||||||
|
Merging support also means the opportunity to have your code reviewed by experts, and if your addition is accepted, that maintaining these additions and keeping them up-to-date through new ARTIQ versions may be handled by the developers of ARTIQ directly, instead of being solely your responsibility. Clean up your code, test it well, be sure that it plays well with existing ARTIQ features and interfaces, and follow the `contribution guidelines <https://github.com/m-labs/artiq/blob/master/CONTRIBUTING.rst#contributing-code>`_. Your effort is appreciated!
|
@ -65,11 +65,6 @@ Some things to consider:
|
|||||||
- 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.
|
- 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.
|
- 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 'no startup kernel found' / 'no idle kernel found' in the core log?
|
|
||||||
-----------------------------------------------------------------------
|
|
||||||
|
|
||||||
Don't. Note that these are ``INFO`` messages, and not ``ERROR`` or even ``WARN``. If you haven't flashed an idle or startup kernel yet, this is normal, and will not cause any problems; between experiments the core device will simply do nothing. The same applies to most other messages in the style of 'no configuration found' or 'falling back to default'. Your system will generally run just fine on its defaults until you get around to setting these configurations, though certain features may be limited until properly set up. See :doc:`configuring` and the list of keys in :ref:`core-device-flash-storage`.
|
|
||||||
|
|
||||||
fix 'Mismatch between gateware and software versions'?
|
fix 'Mismatch between gateware and software versions'?
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
|
|
||||||
@ -80,13 +75,6 @@ Either reflash your core device with a newer version of ARTIQ (see :doc:`flashin
|
|||||||
|
|
||||||
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.
|
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?
|
fix unreliable DRTIO master-satellite links?
|
||||||
--------------------------------------------
|
--------------------------------------------
|
||||||
|
|
||||||
@ -239,10 +227,12 @@ 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.
|
||||||
|
|
||||||
fix errors when restarting management system after a crash?
|
fix errors when restarting the management system after a crash?
|
||||||
-----------------------------------------------------------
|
---------------------------------------------------------------
|
||||||
|
|
||||||
On Windows in particular, abnormal shutdowns such as power outages or bluescreens 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. Note that GUI configuration ``.pyon`` files are kept in the user configuration directory, see below at :ref:`gui-config-files`
|
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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
create and use variable-length arrays in kernels?
|
create and use variable-length arrays in kernels?
|
||||||
-------------------------------------------------
|
-------------------------------------------------
|
||||||
|
@ -13,11 +13,11 @@ If you have an active firmware subscription with M-Labs or QUARTIQ, you can obta
|
|||||||
|
|
||||||
Run the command::
|
Run the command::
|
||||||
|
|
||||||
$ afws_client <username> build <afws_director> <variant>
|
$ afws_client <username> build <afws_directory> <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 :mod:`~artiq.frontend.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 :ref:`hardcoded variant devices <devices-table>` it is also possible to source firmware from `the M-Labs Hydra server <https://nixbld.m-labs.hk/project/artiq>`_ (in ``main`` and ``zynq``).
|
||||||
|
|
||||||
Without a subscription, you may build the firmware yourself from the open source code. See the section :doc:`building_developing`.
|
Without a subscription, you may build the firmware yourself from the open source code. See the section :doc:`building_developing`.
|
||||||
|
|
||||||
@ -25,7 +25,7 @@ Installing and configuring OpenOCD
|
|||||||
----------------------------------
|
----------------------------------
|
||||||
|
|
||||||
.. warning::
|
.. warning::
|
||||||
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`.
|
These instructions are not applicable to :ref:`Zynq devices <devices-table>`, 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 :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.
|
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.
|
||||||
|
|
||||||
@ -78,29 +78,25 @@ 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 :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.
|
If your device is already accessible over the network, all you need is an Ethernet connection and a correct IP address (supplied either with the ``-D`` option or in :ref:`your device database <device-db>`). ::
|
||||||
|
|
||||||
For Kasli-SoC or ZC706:
|
$ artiq_coremgmt [-D IP_address] flash <afws_directory>
|
||||||
::
|
$ artiq_coremgmt [-D IP_address] reboot
|
||||||
|
|
||||||
$ artiq_coremgmt [-D IP_address] config write -f boot <afws_directory>/boot.bin
|
If the device is not reachable due to corrupted firmware or networking problems, binaries can be loaded manually. On Kasli or KC705, connect the board directly to your computer by JTAG USB and use :mod:`~artiq.frontend.artiq_flash`, as follows: ::
|
||||||
$ 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.
|
$ artiq_flash [-t kc705] -d <afws_directory>
|
||||||
|
|
||||||
For Kasli:
|
Note the micro-USB in the Kasli front panel. On KC705, the SW13 switches need to be set to 00001.
|
||||||
::
|
|
||||||
|
|
||||||
$ artiq_flash -d <afws_directory>
|
For Zynq devices (Kasli-SoC, ZC706 or EBAZ4205), extract the SD card and copy ``boot.bin`` onto it manually.
|
||||||
|
|
||||||
For KC705:
|
Writing to satellite devices
|
||||||
::
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
$ artiq_flash -t kc705 -d <afws_directory>
|
Satellite devices can at any time be flashed directly through the SD card or :mod:`~artiq.frontend.artiq_flash`, as applicable. Satellite devices do not support individual networking and do not have IP addresses. If your DRTIO system is up and running and the routing table is in place, on the other hand, they can be flashed through the master's network connection: ::
|
||||||
|
|
||||||
The SW13 switches need to be set to 00001.
|
$ artiq_coremgmt [-D IP_address] -s <destination_number> flash <afws_directory>
|
||||||
|
|
||||||
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:
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ ARTIQ manual
|
|||||||
:caption: ARTIQ components
|
:caption: ARTIQ components
|
||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
|
overview
|
||||||
environment
|
environment
|
||||||
compiler
|
compiler
|
||||||
management_system
|
management_system
|
||||||
@ -60,4 +61,5 @@ ARTIQ manual
|
|||||||
:maxdepth: 2
|
:maxdepth: 2
|
||||||
|
|
||||||
nixos_handbook
|
nixos_handbook
|
||||||
|
extending_rtio
|
||||||
faq
|
faq
|
||||||
|
@ -20,7 +20,7 @@ User environment installation
|
|||||||
|
|
||||||
There are few options for accessing ARTIQ through Nix. The easiest way is to install it into the user environment: ::
|
There are few options for accessing ARTIQ through Nix. The easiest way is to install it into the user environment: ::
|
||||||
|
|
||||||
$ nix profile install git+https://github.com/m-labs/artiq.git?ref=release-8
|
$ 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 :ref:`installing-details` 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.
|
Answer "Yes" to the questions about setting Nix configuration options (for more details see :ref:`installing-details` 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.
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ Flake custom environments
|
|||||||
Modifying the environment and making additional packages visible to the ARTIQ commands requires using the Nix language and writing your own flake. Create an empty directory with a file ``flake.nix`` containing the following: ::
|
Modifying the environment and making additional packages visible to the ARTIQ commands requires using the Nix language and writing your own flake. Create an empty directory with a file ``flake.nix`` containing the following: ::
|
||||||
|
|
||||||
{
|
{
|
||||||
inputs.extrapkg.url = "git+https://git.m-labs.hk/M-Labs/artiq-extrapkg.git?ref=release-8";
|
inputs.extrapkg.url = "git+https://git.m-labs.hk/M-Labs/artiq-extrapkg.git";
|
||||||
outputs = { self, extrapkg }:
|
outputs = { self, extrapkg }:
|
||||||
let
|
let
|
||||||
pkgs = extrapkg.pkgs;
|
pkgs = extrapkg.pkgs;
|
||||||
@ -170,13 +170,13 @@ This will set your user as a trusted user, allowing you to specify untrusted sub
|
|||||||
Installing via MSYS2 (Windows)
|
Installing via MSYS2 (Windows)
|
||||||
------------------------------
|
------------------------------
|
||||||
|
|
||||||
We recommend using our `offline installer <https://nixbld.m-labs.hk/job/artiq/extra/msys2-offline-installer/latest>`_, which contains all the necessary packages and requires no additional configuration. After installation, simply launch ``MSYS2 with ARTIQ`` from the Windows Start menu.
|
We recommend using our `offline installer <https://nixbld.m-labs.hk/job/artiq/extra-beta/msys2-offline-installer/latest>`_, which contains all the necessary packages and requires no additional configuration. After installation, simply launch ``MSYS2 with ARTIQ`` from the Windows Start menu.
|
||||||
|
|
||||||
Alternatively, you may install `MSYS2 <https://msys2.org>`_, then edit ``C:\MINGW64\etc\pacman.conf`` and add at the end: ::
|
Alternatively, you may install `MSYS2 <https://msys2.org>`_, then edit ``C:\MINGW64\etc\pacman.conf`` and add at the end: ::
|
||||||
|
|
||||||
[artiq]
|
[artiq]
|
||||||
SigLevel = Optional TrustAll
|
SigLevel = Optional TrustAll
|
||||||
Server = https://msys2.m-labs.hk/artiq
|
Server = https://msys2.m-labs.hk/artiq-beta
|
||||||
|
|
||||||
Launch ``MSYS2 CLANG64`` from the Windows Start menu to open the MSYS2 shell, and enter the following commands: ::
|
Launch ``MSYS2 CLANG64`` from the Windows Start menu to open the MSYS2 shell, and enter the following commands: ::
|
||||||
|
|
||||||
@ -205,7 +205,7 @@ Controllers for third-party devices (e.g. Thorlabs TCube, Lab Brick Digital Atte
|
|||||||
|
|
||||||
Set up the Conda channel and install ARTIQ into a new Conda environment: ::
|
Set up the Conda channel and install ARTIQ into a new Conda environment: ::
|
||||||
|
|
||||||
$ conda config --prepend channels https://conda.m-labs.hk/artiq
|
$ conda config --prepend channels https://conda.m-labs.hk/artiq-beta
|
||||||
$ conda config --append channels conda-forge
|
$ conda config --append channels conda-forge
|
||||||
$ conda create -n artiq artiq
|
$ conda create -n artiq artiq
|
||||||
|
|
||||||
@ -253,4 +253,3 @@ Switching between Conda environments using commands such as ``$ conda deactivate
|
|||||||
You can list the environments you have created using::
|
You can list the environments you have created using::
|
||||||
|
|
||||||
$ conda env list
|
$ conda env list
|
||||||
|
|
||||||
|
@ -5,25 +5,21 @@ Introduction
|
|||||||
.. include:: ../../README.rst
|
.. include:: ../../README.rst
|
||||||
and including in README.rst does not work on github therefore just keep this content synchronized with README.rst
|
and including in README.rst does not work on github therefore just keep this content synchronized with README.rst
|
||||||
|
|
||||||
ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is the next-generation control system for quantum information experiments.
|
ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is a leading-edge control and data acquisition system for quantum information experiments.
|
||||||
It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Several other laboratories (e.g. at the University of Oxford, the Army Research Lab, and the University of Maryland) have later adopted ARTIQ as their control system and have contributed to it.
|
It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Many laboratories around the world have adopted ARTIQ as their control system and some have `contributed <https://m-labs.hk/experiment-control/funding/>`_ to it.
|
||||||
|
|
||||||
The system features a high-level programming language that helps describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results.
|
The system features a high-level programming language, capable of describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results.
|
||||||
|
|
||||||
ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware <https://github.com/sinara-hw>`_, and in particular the Kasli FPGA carrier, is designed to work with ARTIQ.
|
ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware <https://github.com/sinara-hw>`_, and in particular the Kasli FPGA carrier, are designed to work with ARTIQ. ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers. Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort.
|
||||||
ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers.
|
|
||||||
Several different configurations of a `high-end FPGA evaluation kit <http://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort.
|
|
||||||
|
|
||||||
ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and Conda packages (for Windows and Linux).
|
ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions. Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration. Like any open-source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_.
|
||||||
Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration.
|
|
||||||
Like any open source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_.
|
|
||||||
|
|
||||||
ARTIQ is supported by M-Labs and developed openly.
|
ARTIQ is supported by M-Labs and developed openly. Components, features, fixes, improvements, and extensions are often `funded <https://m-labs.hk/experiment-control/funding/>`_ by and developed for the partnering research groups.
|
||||||
Components, features, fixes, improvements, and extensions are funded by and developed for the partnering research groups.
|
|
||||||
|
|
||||||
Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt5 <https://www.qt.io/>`_.
|
Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt6 <https://www.qt.io/>`_.
|
||||||
|
|
||||||
Website: https://m-labs.hk/artiq
|
| Website: https://m-labs.hk/experiment-control/artiq
|
||||||
|
| (US-hosted mirror: https://m-labs-intl.com/experiment-control/artiq)
|
||||||
|
|
||||||
`Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``.
|
`Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``.
|
||||||
|
|
||||||
|
20
doc/manual/overview.rst
Normal file
20
doc/manual/overview.rst
Normal 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
212
doc/manual/overview.tex
Normal 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);
|
||||||
|
|
@ -105,6 +105,8 @@ Note however that in order for the controller manager to be able to start a cont
|
|||||||
|
|
||||||
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 :mod:`~artiq.frontend.artiq_run` or through the management system. To understand how to add controllers to the device database, see also :ref:`device-db`.
|
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 :mod:`~artiq.frontend.artiq_run` or through the management system. To understand how to add controllers to the device database, see also :ref:`device-db`.
|
||||||
|
|
||||||
|
.. _built-in-ctlrs:
|
||||||
|
|
||||||
ARTIQ built-in controllers
|
ARTIQ built-in controllers
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
73
flake.lock
generated
73
flake.lock
generated
@ -11,11 +11,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1720768567,
|
"lastModified": 1734270714,
|
||||||
"narHash": "sha256-3VoK7o5MtHtbHLrc6Pv+eQWFtaz5Gd/YWyV5TD3c5Ss=",
|
"narHash": "sha256-7bzGn/hXLIsLQHGQsvo+uoIFUrw9DjXSlMC449BY4ME=",
|
||||||
"owner": "m-labs",
|
"owner": "m-labs",
|
||||||
"repo": "artiq-comtools",
|
"repo": "artiq-comtools",
|
||||||
"rev": "f93570d8f2ed5a3cfb3e1c16ab00f2540551e994",
|
"rev": "7e3152314af8f5987370e33b347b2ec2697567ed",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -42,34 +42,18 @@
|
|||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"mozilla-overlay": {
|
|
||||||
"flake": false,
|
|
||||||
"locked": {
|
|
||||||
"lastModified": 1733136220,
|
|
||||||
"narHash": "sha256-Ga8AP/YPsKDVF5LcNN8v3RiLgnEeW7zBk9GyX7wa9Ug=",
|
|
||||||
"owner": "mozilla",
|
|
||||||
"repo": "nixpkgs-mozilla",
|
|
||||||
"rev": "ad7af231a95acf65ccc4afa0c766f5c0674ad3f1",
|
|
||||||
"type": "github"
|
|
||||||
},
|
|
||||||
"original": {
|
|
||||||
"owner": "mozilla",
|
|
||||||
"repo": "nixpkgs-mozilla",
|
|
||||||
"type": "github"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"nixpkgs": {
|
"nixpkgs": {
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1733550349,
|
"lastModified": 1736012469,
|
||||||
"narHash": "sha256-NcGumB4Lr6KSDq+nIqXtNA8QwAQKDSZT7N9OTGWbTrs=",
|
"narHash": "sha256-/qlNWm/IEVVH7GfgAIyP6EsVZI6zjAx1cV5zNyrs+rI=",
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"rev": "e2605d0744c2417b09f8bf850dfca42fcf537d34",
|
"rev": "8f3e1f807051e32d8c95cd12b9b421623850a34d",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
"owner": "NixOS",
|
"owner": "NixOS",
|
||||||
"ref": "nixos-24.11",
|
"ref": "nixos-unstable",
|
||||||
"repo": "nixpkgs",
|
"repo": "nixpkgs",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
}
|
}
|
||||||
@ -77,14 +61,35 @@
|
|||||||
"root": {
|
"root": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"artiq-comtools": "artiq-comtools",
|
"artiq-comtools": "artiq-comtools",
|
||||||
"mozilla-overlay": "mozilla-overlay",
|
|
||||||
"nixpkgs": "nixpkgs",
|
"nixpkgs": "nixpkgs",
|
||||||
|
"rust-overlay": "rust-overlay",
|
||||||
"sipyco": "sipyco",
|
"sipyco": "sipyco",
|
||||||
"src-migen": "src-migen",
|
"src-migen": "src-migen",
|
||||||
"src-misoc": "src-misoc",
|
"src-misoc": "src-misoc",
|
||||||
"src-pythonparser": "src-pythonparser"
|
"src-pythonparser": "src-pythonparser"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"rust-overlay": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": [
|
||||||
|
"nixpkgs"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1719454714,
|
||||||
|
"narHash": "sha256-MojqG0lyUINkEk0b3kM2drsU5vyaF8DFZe/FAlZVOGs=",
|
||||||
|
"owner": "oxalica",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"rev": "d1c527659cf076ecc4b96a91c702d080b213801e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "oxalica",
|
||||||
|
"ref": "snapshot/2024-08-01",
|
||||||
|
"repo": "rust-overlay",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
"sipyco": {
|
"sipyco": {
|
||||||
"inputs": {
|
"inputs": {
|
||||||
"nixpkgs": [
|
"nixpkgs": [
|
||||||
@ -92,11 +97,11 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1733319649,
|
"lastModified": 1734267097,
|
||||||
"narHash": "sha256-ATJV2UV9FXEiTF6/1BvZ2HmB0goF5TZ2ytgRBwD/BGg=",
|
"narHash": "sha256-aWg7XDiOlWnkXfDbKrBn9ITR46/JXfndvYHxFJ1vN78=",
|
||||||
"owner": "m-labs",
|
"owner": "m-labs",
|
||||||
"repo": "sipyco",
|
"repo": "sipyco",
|
||||||
"rev": "27312727bdb8a182bd6e222e4cbdd3f39ae41d4e",
|
"rev": "430978ada3fefe32de01f1b884b3031e48aaef96",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -108,11 +113,11 @@
|
|||||||
"src-migen": {
|
"src-migen": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1727677091,
|
"lastModified": 1735131698,
|
||||||
"narHash": "sha256-Zg3SQnTwMM/VkOGKogbPyuCC2NhLy8HB2SPEUWWNgCU=",
|
"narHash": "sha256-P4vaF+9iVekRAC2/mc9G7IwI6baBpPAxiDQ8uye4sAs=",
|
||||||
"owner": "m-labs",
|
"owner": "m-labs",
|
||||||
"repo": "migen",
|
"repo": "migen",
|
||||||
"rev": "c19ae9f8ae162ffe2d310a92bfce53ac2a821bc8",
|
"rev": "4c2ae8dfeea37f235b52acb8166f12acaaae4f7c",
|
||||||
"type": "github"
|
"type": "github"
|
||||||
},
|
},
|
||||||
"original": {
|
"original": {
|
||||||
@ -124,11 +129,11 @@
|
|||||||
"src-misoc": {
|
"src-misoc": {
|
||||||
"flake": false,
|
"flake": false,
|
||||||
"locked": {
|
"locked": {
|
||||||
"lastModified": 1729234629,
|
"lastModified": 1736302987,
|
||||||
"narHash": "sha256-TLsTCXV5AC2xh+bS7EhBVBKqdqIU3eKrnlWcFF9LtAM=",
|
"narHash": "sha256-DMbaAxjtyZDlA20FTaObalH6ov2roE5Gsh6lVdnzPVY=",
|
||||||
"ref": "refs/heads/master",
|
"ref": "refs/heads/master",
|
||||||
"rev": "6085a312bca26adeca6584e37d08c8ba2e1d6e38",
|
"rev": "adb3f111750cb458f7e390bd242deb1d86ad69cb",
|
||||||
"revCount": 2460,
|
"revCount": 2463,
|
||||||
"submodules": true,
|
"submodules": true,
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/m-labs/misoc.git"
|
"url": "https://github.com/m-labs/misoc.git"
|
||||||
|
329
flake.nix
329
flake.nix
@ -1,63 +1,93 @@
|
|||||||
{
|
{
|
||||||
description = "A leading-edge control system for quantum information experiments";
|
description = "A leading-edge control system for quantum information experiments";
|
||||||
|
|
||||||
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-24.11;
|
inputs = {
|
||||||
inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; };
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||||
inputs.sipyco.url = github:m-labs/sipyco;
|
|
||||||
inputs.sipyco.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
inputs.src-pythonparser = { url = github:m-labs/pythonparser; flake = false; };
|
|
||||||
inputs.artiq-comtools.url = github:m-labs/artiq-comtools;
|
|
||||||
inputs.artiq-comtools.inputs.nixpkgs.follows = "nixpkgs";
|
|
||||||
inputs.artiq-comtools.inputs.sipyco.follows = "sipyco";
|
|
||||||
|
|
||||||
inputs.src-migen = { url = github:m-labs/migen; flake = false; };
|
rust-overlay = {
|
||||||
inputs.src-misoc = { type = "git"; url = "https://github.com/m-labs/misoc.git"; submodules = true; flake = false; };
|
url = "github:oxalica/rust-overlay?ref=snapshot/2024-08-01";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
outputs = { self, nixpkgs, mozilla-overlay, sipyco, src-pythonparser, artiq-comtools, src-migen, src-misoc }:
|
artiq-comtools = {
|
||||||
let
|
url = "github:m-labs/artiq-comtools";
|
||||||
pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; };
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
pkgs-aarch64 = import nixpkgs { system = "aarch64-linux"; };
|
inputs.sipyco.follows = "sipyco";
|
||||||
|
};
|
||||||
|
|
||||||
artiqVersionMajor = 8;
|
sipyco = {
|
||||||
|
url = "github:m-labs/sipyco";
|
||||||
|
inputs.nixpkgs.follows = "nixpkgs";
|
||||||
|
};
|
||||||
|
|
||||||
|
src-migen = {
|
||||||
|
url = "github:m-labs/migen";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
src-misoc = {
|
||||||
|
url = "https://github.com/m-labs/misoc.git";
|
||||||
|
type = "git";
|
||||||
|
submodules = true;
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
src-pythonparser = {
|
||||||
|
url = "github:m-labs/pythonparser";
|
||||||
|
flake = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = {
|
||||||
|
self,
|
||||||
|
nixpkgs,
|
||||||
|
rust-overlay,
|
||||||
|
sipyco,
|
||||||
|
src-pythonparser,
|
||||||
|
artiq-comtools,
|
||||||
|
src-migen,
|
||||||
|
src-misoc,
|
||||||
|
}: let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
system = "x86_64-linux";
|
||||||
|
overlays = [(import rust-overlay)];
|
||||||
|
};
|
||||||
|
pkgs-aarch64 = import nixpkgs {system = "aarch64-linux";};
|
||||||
|
|
||||||
|
artiqVersionMajor = 9;
|
||||||
artiqVersionMinor = self.sourceInfo.revCount or 0;
|
artiqVersionMinor = self.sourceInfo.revCount or 0;
|
||||||
artiqVersionId = self.sourceInfo.shortRev or "unknown";
|
artiqVersionId = self.sourceInfo.shortRev or "unknown";
|
||||||
artiqVersion = (builtins.toString artiqVersionMajor) + "." + (builtins.toString artiqVersionMinor) + "+" + artiqVersionId;
|
artiqVersion = (builtins.toString artiqVersionMajor) + "." + (builtins.toString artiqVersionMinor) + "+" + artiqVersionId + ".beta";
|
||||||
artiqRev = self.sourceInfo.rev or "unknown";
|
artiqRev = self.sourceInfo.rev or "unknown";
|
||||||
|
|
||||||
qtPaths = {
|
qtPaths = let
|
||||||
QT_PLUGIN_PATH = "${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.dev.qtPluginPrefix}:${pkgs.qt5.qtsvg.bin}/${pkgs.qt5.qtbase.dev.qtPluginPrefix}";
|
inherit (pkgs.qt6) qtbase qtsvg;
|
||||||
QML2_IMPORT_PATH = "${pkgs.qt5.qtbase}/${pkgs.qt5.qtbase.dev.qtQmlPrefix}";
|
inherit (qtbase.dev) qtPluginPrefix qtQmlPrefix;
|
||||||
|
in {
|
||||||
|
QT_PLUGIN_PATH = "${qtbase}/${qtPluginPrefix}:${qtsvg}/${qtPluginPrefix}";
|
||||||
|
QML2_IMPORT_PATH = "${qtbase}/${qtQmlPrefix}";
|
||||||
};
|
};
|
||||||
|
|
||||||
rustManifest = pkgs.fetchurl {
|
rust = pkgs.rust-bin.nightly."2021-09-01".default.override {
|
||||||
url = "https://static.rust-lang.org/dist/2021-09-01/channel-rust-nightly.toml";
|
|
||||||
sha256 = "sha256-KYLZHfOkotnM6BZd7CU+vBA3w/VtiWxth3ngJlmA41U=";
|
|
||||||
};
|
|
||||||
|
|
||||||
targets = [];
|
|
||||||
rustChannelOfTargets = _channel: _date: targets:
|
|
||||||
(pkgs.lib.rustLib.fromManifestFile rustManifest {
|
|
||||||
inherit (pkgs) stdenv lib fetchurl patchelf;
|
|
||||||
}).rust.override {
|
|
||||||
inherit targets;
|
|
||||||
extensions = ["rust-src"];
|
extensions = ["rust-src"];
|
||||||
|
targets = [];
|
||||||
};
|
};
|
||||||
rust = rustChannelOfTargets "nightly" null targets;
|
rustPlatform = pkgs.makeRustPlatform {
|
||||||
rustPlatform = pkgs.recurseIntoAttrs (pkgs.makeRustPlatform {
|
|
||||||
rustc = rust;
|
rustc = rust;
|
||||||
cargo = rust;
|
cargo = rust;
|
||||||
});
|
};
|
||||||
|
|
||||||
vivadoDeps = pkgs: with pkgs; let
|
vivadoDeps = pkgs:
|
||||||
|
with pkgs; let
|
||||||
# Apply patch from https://github.com/nix-community/nix-environments/pull/54
|
# Apply patch from https://github.com/nix-community/nix-environments/pull/54
|
||||||
# to fix ncurses libtinfo.so's soname issue
|
# to fix ncurses libtinfo.so's soname issue
|
||||||
ncurses' = ncurses5.overrideAttrs (old: {
|
ncurses' = ncurses5.overrideAttrs (old: {
|
||||||
configureFlags = old.configureFlags ++ [ "--with-termlib" ];
|
configureFlags = old.configureFlags ++ ["--with-termlib"];
|
||||||
postFixup = "";
|
postFixup = "";
|
||||||
});
|
});
|
||||||
in [
|
in [
|
||||||
libxcrypt-legacy
|
libxcrypt-legacy
|
||||||
(ncurses'.override { unicodeSupport = false; })
|
(ncurses'.override {unicodeSupport = false;})
|
||||||
zlib
|
zlib
|
||||||
libuuid
|
libuuid
|
||||||
xorg.libSM
|
xorg.libSM
|
||||||
@ -76,27 +106,33 @@
|
|||||||
version = "1.4";
|
version = "1.4";
|
||||||
src = src-pythonparser;
|
src = src-pythonparser;
|
||||||
doCheck = false;
|
doCheck = false;
|
||||||
propagatedBuildInputs = with pkgs.python3Packages; [ regex ];
|
propagatedBuildInputs = with pkgs.python3Packages; [regex];
|
||||||
};
|
};
|
||||||
|
|
||||||
qasync = pkgs.python3Packages.buildPythonPackage rec {
|
qasync = pkgs.python3Packages.buildPythonPackage rec {
|
||||||
pname = "qasync";
|
pname = "qasync";
|
||||||
version = "0.24.1";
|
version = "0.27.1";
|
||||||
|
format = "pyproject";
|
||||||
src = pkgs.fetchFromGitHub {
|
src = pkgs.fetchFromGitHub {
|
||||||
owner = "CabbageDevelopment";
|
owner = "CabbageDevelopment";
|
||||||
repo = "qasync";
|
repo = "qasync";
|
||||||
rev = "v${version}";
|
rev = "refs/tags/v${version}";
|
||||||
sha256 = "sha256-DAzmobw+c29Pt/URGO3bWXHBxgu9bDHhdTUBE9QJDe4=";
|
sha256 = "sha256-oXzwilhJ1PhodQpOZjnV9gFuoDy/zXWva9LhhK3T00g=";
|
||||||
};
|
};
|
||||||
propagatedBuildInputs = [ pkgs.python3Packages.pyqt5 ];
|
postPatch = ''
|
||||||
nativeCheckInputs = [ pkgs.python3Packages.pytestCheckHook ];
|
rm qasync/_windows.py # Ignoring it is not taking effect and it will not be used on Linux
|
||||||
disabledTestPaths = [ "tests/test_qeventloop.py" ];
|
'';
|
||||||
|
buildInputs = [pkgs.python3Packages.poetry-core];
|
||||||
|
propagatedBuildInputs = [pkgs.python3Packages.pyqt6];
|
||||||
|
checkInputs = [pkgs.python3Packages.pytestCheckHook];
|
||||||
|
pythonImportsCheck = ["qasync"];
|
||||||
|
disabledTestPaths = ["tests/test_qeventloop.py"];
|
||||||
};
|
};
|
||||||
|
|
||||||
libartiq-support = pkgs.stdenv.mkDerivation {
|
libartiq-support = pkgs.stdenv.mkDerivation {
|
||||||
name = "libartiq-support";
|
name = "libartiq-support";
|
||||||
src = self;
|
src = self;
|
||||||
buildInputs = [ rust ];
|
buildInputs = [rust];
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
rustc $src/artiq/test/libartiq_support/lib.rs -Cpanic=unwind -g
|
rustc $src/artiq/test/libartiq_support/lib.rs -Cpanic=unwind -g
|
||||||
'';
|
'';
|
||||||
@ -120,7 +156,7 @@
|
|||||||
rev = "v${version}";
|
rev = "v${version}";
|
||||||
sha256 = "sha256-5QBSRDb28Bui9IOhGofj+c7Rk7J5fNv5nPksEPY/O5o=";
|
sha256 = "sha256-5QBSRDb28Bui9IOhGofj+c7Rk7J5fNv5nPksEPY/O5o=";
|
||||||
};
|
};
|
||||||
nativeBuildInputs = [ pkgs.llvm_15 ];
|
nativeBuildInputs = [pkgs.llvm_15];
|
||||||
# Disable static linking
|
# Disable static linking
|
||||||
# https://github.com/numba/llvmlite/issues/93
|
# https://github.com/numba/llvmlite/issues/93
|
||||||
postPatch = ''
|
postPatch = ''
|
||||||
@ -138,16 +174,16 @@
|
|||||||
version = artiqVersion;
|
version = artiqVersion;
|
||||||
src = self;
|
src = self;
|
||||||
|
|
||||||
preBuild =
|
preBuild = ''
|
||||||
''
|
|
||||||
export VERSIONEER_OVERRIDE=${version}
|
export VERSIONEER_OVERRIDE=${version}
|
||||||
export VERSIONEER_REV=${artiqRev}
|
export VERSIONEER_REV=${artiqRev}
|
||||||
'';
|
'';
|
||||||
|
|
||||||
nativeBuildInputs = [ pkgs.qt5.wrapQtAppsHook ];
|
nativeBuildInputs = [pkgs.qt6.wrapQtAppsHook];
|
||||||
# keep llvm_x and lld_x in sync with llvmlite
|
# keep llvm_x and lld_x in sync with llvmlite
|
||||||
propagatedBuildInputs = [ pkgs.llvm_15 pkgs.lld_15 sipyco.packages.x86_64-linux.sipyco pythonparser llvmlite-new pkgs.qt5.qtsvg artiq-comtools.packages.x86_64-linux.artiq-comtools ]
|
propagatedBuildInputs =
|
||||||
++ (with pkgs.python3Packages; [ pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial levenshtein h5py pyqt5 qasync tqdm lmdb jsonschema ]);
|
[pkgs.llvm_15 pkgs.lld_15 sipyco.packages.x86_64-linux.sipyco pythonparser llvmlite-new pkgs.qt6.qtsvg artiq-comtools.packages.x86_64-linux.artiq-comtools]
|
||||||
|
++ (with pkgs.python3Packages; [pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial levenshtein h5py pyqt6 qasync tqdm lmdb jsonschema platformdirs]);
|
||||||
|
|
||||||
dontWrapQtApps = true;
|
dontWrapQtApps = true;
|
||||||
postFixup = ''
|
postFixup = ''
|
||||||
@ -156,8 +192,7 @@
|
|||||||
wrapQtApp "$out/bin/artiq_session"
|
wrapQtApp "$out/bin/artiq_session"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
preFixup =
|
preFixup = ''
|
||||||
''
|
|
||||||
# Ensure that wrapProgram uses makeShellWrapper rather than makeBinaryWrapper
|
# Ensure that wrapProgram uses makeShellWrapper rather than makeBinaryWrapper
|
||||||
# brought in by wrapQtAppsHook. Only makeShellWrapper supports --run.
|
# brought in by wrapQtAppsHook. Only makeShellWrapper supports --run.
|
||||||
wrapProgram() { wrapProgramShell "$@"; }
|
wrapProgram() { wrapProgramShell "$@"; }
|
||||||
@ -172,7 +207,7 @@
|
|||||||
# FIXME: automatically propagate lld_15 llvm_15 dependencies
|
# FIXME: automatically propagate lld_15 llvm_15 dependencies
|
||||||
# cacert is required in the check stage only, as certificates are to be
|
# cacert is required in the check stage only, as certificates are to be
|
||||||
# obtained from system elsewhere
|
# obtained from system elsewhere
|
||||||
nativeCheckInputs = with pkgs; [ lld_15 llvm_15 lit outputcheck cacert ] ++ [ libartiq-support ];
|
nativeCheckInputs = with pkgs; [lld_15 llvm_15 lit outputcheck cacert] ++ [libartiq-support];
|
||||||
checkPhase = ''
|
checkPhase = ''
|
||||||
python -m unittest discover -v artiq.test
|
python -m unittest discover -v artiq.test
|
||||||
|
|
||||||
@ -182,17 +217,18 @@
|
|||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
artiq = artiq-upstream // {
|
artiq =
|
||||||
withExperimentalFeatures = features: artiq-upstream.overrideAttrs(oa:
|
artiq-upstream
|
||||||
{ patches = map (f: ./experimental-features/${f}.diff) features; });
|
// {
|
||||||
|
withExperimentalFeatures = features: artiq-upstream.overrideAttrs (oa: {patches = map (f: ./experimental-features/${f}.diff) features;});
|
||||||
};
|
};
|
||||||
|
|
||||||
migen = pkgs.python3Packages.buildPythonPackage rec {
|
migen = pkgs.python3Packages.buildPythonPackage rec {
|
||||||
name = "migen";
|
name = "migen";
|
||||||
src = src-migen;
|
src = src-migen;
|
||||||
format = "pyproject";
|
format = "pyproject";
|
||||||
nativeBuildInputs = [ pkgs.python3Packages.setuptools ];
|
nativeBuildInputs = [pkgs.python3Packages.setuptools];
|
||||||
propagatedBuildInputs = [ pkgs.python3Packages.colorama ];
|
propagatedBuildInputs = [pkgs.python3Packages.colorama];
|
||||||
};
|
};
|
||||||
|
|
||||||
asyncserial = pkgs.python3Packages.buildPythonPackage rec {
|
asyncserial = pkgs.python3Packages.buildPythonPackage rec {
|
||||||
@ -203,15 +239,14 @@
|
|||||||
repo = "asyncserial";
|
repo = "asyncserial";
|
||||||
rev = version;
|
rev = version;
|
||||||
sha256 = "sha256-ZHzgJnbsDVxVcp09LXq9JZp46+dorgdP8bAiTB59K28=";
|
sha256 = "sha256-ZHzgJnbsDVxVcp09LXq9JZp46+dorgdP8bAiTB59K28=";
|
||||||
|
|
||||||
};
|
};
|
||||||
propagatedBuildInputs = [ pkgs.python3Packages.pyserial ];
|
propagatedBuildInputs = [pkgs.python3Packages.pyserial];
|
||||||
};
|
};
|
||||||
|
|
||||||
misoc = pkgs.python3Packages.buildPythonPackage {
|
misoc = pkgs.python3Packages.buildPythonPackage {
|
||||||
name = "misoc";
|
name = "misoc";
|
||||||
src = src-misoc;
|
src = src-misoc;
|
||||||
propagatedBuildInputs = with pkgs.python3Packages; [ jinja2 numpy migen pyserial asyncserial ];
|
propagatedBuildInputs = with pkgs.python3Packages; [jinja2 numpy migen pyserial asyncserial];
|
||||||
};
|
};
|
||||||
|
|
||||||
microscope = pkgs.python3Packages.buildPythonPackage rec {
|
microscope = pkgs.python3Packages.buildPythonPackage rec {
|
||||||
@ -223,7 +258,7 @@
|
|||||||
rev = "c21afe7a53258f05bde57e5ebf2e2761f3d495e4";
|
rev = "c21afe7a53258f05bde57e5ebf2e2761f3d495e4";
|
||||||
sha256 = "sha256-jzyiLRuEf7p8LdhmZvOQj/dyQx8eUE8p6uRlwoiT8vg=";
|
sha256 = "sha256-jzyiLRuEf7p8LdhmZvOQj/dyQx8eUE8p6uRlwoiT8vg=";
|
||||||
};
|
};
|
||||||
propagatedBuildInputs = with pkgs.python3Packages; [ pyserial prettytable msgpack migen ];
|
propagatedBuildInputs = with pkgs.python3Packages; [pyserial prettytable msgpack migen];
|
||||||
};
|
};
|
||||||
|
|
||||||
vivadoEnv = pkgs.buildFHSEnv {
|
vivadoEnv = pkgs.buildFHSEnv {
|
||||||
@ -238,10 +273,15 @@
|
|||||||
runScript = "vivado";
|
runScript = "vivado";
|
||||||
};
|
};
|
||||||
|
|
||||||
makeArtiqBoardPackage = { target, variant, buildCommand ? "python -m artiq.gateware.targets.${target} -V ${variant}", experimentalFeatures ? [] }:
|
makeArtiqBoardPackage = {
|
||||||
|
target,
|
||||||
|
variant,
|
||||||
|
buildCommand ? "python -m artiq.gateware.targets.${target} -V ${variant}",
|
||||||
|
experimentalFeatures ? [],
|
||||||
|
}:
|
||||||
pkgs.stdenv.mkDerivation {
|
pkgs.stdenv.mkDerivation {
|
||||||
name = "artiq-board-${target}-${variant}";
|
name = "artiq-board-${target}-${variant}";
|
||||||
phases = [ "buildPhase" "checkPhase" "installPhase" ];
|
phases = ["buildPhase" "checkPhase" "installPhase"];
|
||||||
cargoDeps = rustPlatform.importCargoLock {
|
cargoDeps = rustPlatform.importCargoLock {
|
||||||
lockFile = ./artiq/firmware/Cargo.lock;
|
lockFile = ./artiq/firmware/Cargo.lock;
|
||||||
outputHashes = {
|
outputHashes = {
|
||||||
@ -250,7 +290,7 @@
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
(pkgs.python3.withPackages(ps: [ migen misoc (artiq.withExperimentalFeatures experimentalFeatures) ps.packaging ]))
|
(pkgs.python3.withPackages (ps: [migen misoc (artiq.withExperimentalFeatures experimentalFeatures) ps.packaging]))
|
||||||
rust
|
rust
|
||||||
pkgs.llvmPackages_15.clang-unwrapped
|
pkgs.llvmPackages_15.clang-unwrapped
|
||||||
pkgs.llvm_15
|
pkgs.llvm_15
|
||||||
@ -258,8 +298,7 @@
|
|||||||
vivado
|
vivado
|
||||||
rustPlatform.cargoSetupHook
|
rustPlatform.cargoSetupHook
|
||||||
];
|
];
|
||||||
buildPhase =
|
buildPhase = ''
|
||||||
''
|
|
||||||
ARTIQ_PATH=`python -c "import artiq; print(artiq.__path__[0])"`
|
ARTIQ_PATH=`python -c "import artiq; print(artiq.__path__[0])"`
|
||||||
ln -s $ARTIQ_PATH/firmware/Cargo.lock .
|
ln -s $ARTIQ_PATH/firmware/Cargo.lock .
|
||||||
cargoSetupPostUnpackHook
|
cargoSetupPostUnpackHook
|
||||||
@ -267,8 +306,7 @@
|
|||||||
${buildCommand}
|
${buildCommand}
|
||||||
'';
|
'';
|
||||||
doCheck = true;
|
doCheck = true;
|
||||||
checkPhase =
|
checkPhase = ''
|
||||||
''
|
|
||||||
# Search for PCREs in the Vivado output to check for errors
|
# Search for PCREs in the Vivado output to check for errors
|
||||||
check_log() {
|
check_log() {
|
||||||
grep -Pe "$1" artiq_${target}/${variant}/gateware/vivado.log && exit 1 || true
|
grep -Pe "$1" artiq_${target}/${variant}/gateware/vivado.log && exit 1 || true
|
||||||
@ -276,8 +314,7 @@
|
|||||||
check_log "\d+ constraint not met\."
|
check_log "\d+ constraint not met\."
|
||||||
check_log "Timing constraints are not met\."
|
check_log "Timing constraints are not met\."
|
||||||
'';
|
'';
|
||||||
installPhase =
|
installPhase = ''
|
||||||
''
|
|
||||||
mkdir $out
|
mkdir $out
|
||||||
cp artiq_${target}/${variant}/gateware/top.bit $out
|
cp artiq_${target}/${variant}/gateware/top.bit $out
|
||||||
if [ -e artiq_${target}/${variant}/software/bootloader/bootloader.bin ]
|
if [ -e artiq_${target}/${variant}/software/bootloader/bootloader.bin ]
|
||||||
@ -307,25 +344,45 @@
|
|||||||
sha256 = "1zqv47kzgvbn4c8cr019a6wcja7gn5h1z4kvw5bhpc72fyhagal9";
|
sha256 = "1zqv47kzgvbn4c8cr019a6wcja7gn5h1z4kvw5bhpc72fyhagal9";
|
||||||
};
|
};
|
||||||
phases = ["installPhase"];
|
phases = ["installPhase"];
|
||||||
installPhase =
|
installPhase = ''
|
||||||
''
|
|
||||||
mkdir -p $out/share/bscan-spi-bitstreams
|
mkdir -p $out/share/bscan-spi-bitstreams
|
||||||
cp $src/*.bit $out/share/bscan-spi-bitstreams
|
cp $src/*.bit $out/share/bscan-spi-bitstreams
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
in pkgs.buildEnv {
|
in
|
||||||
|
pkgs.buildEnv {
|
||||||
name = "openocd-bscanspi";
|
name = "openocd-bscanspi";
|
||||||
paths = [ pkgs.openocd bscan_spi_bitstreams-pkg ];
|
paths = [pkgs.openocd bscan_spi_bitstreams-pkg];
|
||||||
};
|
};
|
||||||
|
|
||||||
latex-artiq-manual = pkgs.texlive.combine {
|
latex-artiq-manual = pkgs.texlive.combine {
|
||||||
inherit (pkgs.texlive)
|
inherit
|
||||||
scheme-basic latexmk cmap collection-fontsrecommended fncychap
|
(pkgs.texlive)
|
||||||
titlesec tabulary varwidth framed fancyvrb float wrapfig parskip
|
scheme-basic
|
||||||
upquote capt-of needspace etoolbox booktabs xcolor;
|
latexmk
|
||||||
|
cmap
|
||||||
|
collection-fontsrecommended
|
||||||
|
fncychap
|
||||||
|
titlesec
|
||||||
|
tabulary
|
||||||
|
varwidth
|
||||||
|
framed
|
||||||
|
fancyvrb
|
||||||
|
float
|
||||||
|
wrapfig
|
||||||
|
parskip
|
||||||
|
upquote
|
||||||
|
capt-of
|
||||||
|
needspace
|
||||||
|
etoolbox
|
||||||
|
booktabs
|
||||||
|
pgf
|
||||||
|
pgfplots
|
||||||
|
;
|
||||||
};
|
};
|
||||||
|
|
||||||
artiq-frontend-dev-wrappers = pkgs.runCommandNoCC "artiq-frontend-dev-wrappers" {}
|
artiq-frontend-dev-wrappers =
|
||||||
|
pkgs.runCommandNoCC "artiq-frontend-dev-wrappers" {}
|
||||||
''
|
''
|
||||||
mkdir -p $out/bin
|
mkdir -p $out/bin
|
||||||
for program in ${self}/artiq/frontend/*.py; do
|
for program in ${self}/artiq/frontend/*.py; do
|
||||||
@ -356,10 +413,19 @@
|
|||||||
name = "artiq-manual-html-${version}";
|
name = "artiq-manual-html-${version}";
|
||||||
version = artiqVersion;
|
version = artiqVersion;
|
||||||
src = self;
|
src = self;
|
||||||
buildInputs = with pkgs.python3Packages; [
|
buildInputs = with pkgs.python3Packages;
|
||||||
sphinx sphinx_rtd_theme
|
[
|
||||||
sphinx-argparse sphinxcontrib-wavedrom
|
sphinx
|
||||||
] ++ [ artiq-comtools.packages.x86_64-linux.artiq-comtools ];
|
sphinx_rtd_theme
|
||||||
|
sphinxcontrib-tikz
|
||||||
|
sphinx-argparse
|
||||||
|
sphinxcontrib-wavedrom
|
||||||
|
]
|
||||||
|
++ [
|
||||||
|
latex-artiq-manual
|
||||||
|
artiq-comtools.packages.x86_64-linux.artiq-comtools
|
||||||
|
pkgs.pdf2svg
|
||||||
|
];
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
export VERSIONEER_OVERRIDE=${artiqVersion}
|
export VERSIONEER_OVERRIDE=${artiqVersion}
|
||||||
export SOURCE_DATE_EPOCH=${builtins.toString self.sourceInfo.lastModified}
|
export SOURCE_DATE_EPOCH=${builtins.toString self.sourceInfo.lastModified}
|
||||||
@ -376,10 +442,19 @@
|
|||||||
name = "artiq-manual-pdf-${version}";
|
name = "artiq-manual-pdf-${version}";
|
||||||
version = artiqVersion;
|
version = artiqVersion;
|
||||||
src = self;
|
src = self;
|
||||||
buildInputs = with pkgs.python3Packages; [
|
buildInputs = with pkgs.python3Packages;
|
||||||
sphinx sphinx_rtd_theme
|
[
|
||||||
sphinx-argparse sphinxcontrib-wavedrom
|
sphinx
|
||||||
] ++ [ latex-artiq-manual artiq-comtools.packages.x86_64-linux.artiq-comtools ];
|
sphinx_rtd_theme
|
||||||
|
sphinxcontrib-tikz
|
||||||
|
sphinx-argparse
|
||||||
|
sphinxcontrib-wavedrom
|
||||||
|
]
|
||||||
|
++ [
|
||||||
|
latex-artiq-manual
|
||||||
|
artiq-comtools.packages.x86_64-linux.artiq-comtools
|
||||||
|
pkgs.pdf2svg
|
||||||
|
];
|
||||||
buildPhase = ''
|
buildPhase = ''
|
||||||
export VERSIONEER_OVERRIDE=${artiq.version}
|
export VERSIONEER_OVERRIDE=${artiq.version}
|
||||||
export SOURCE_DATE_EPOCH=${builtins.toString self.sourceInfo.lastModified}
|
export SOURCE_DATE_EPOCH=${builtins.toString self.sourceInfo.lastModified}
|
||||||
@ -397,32 +472,47 @@
|
|||||||
|
|
||||||
inherit qtPaths makeArtiqBoardPackage openocd-bscanspi-f;
|
inherit qtPaths makeArtiqBoardPackage openocd-bscanspi-f;
|
||||||
|
|
||||||
defaultPackage.x86_64-linux = pkgs.python3.withPackages(ps: [ packages.x86_64-linux.artiq ]);
|
packages.x86_64-linux.default = pkgs.python3.withPackages (_: [packages.x86_64-linux.artiq]);
|
||||||
|
|
||||||
|
formatter.x86_64-linux = pkgs.alejandra;
|
||||||
|
|
||||||
|
devShells.x86_64-linux = {
|
||||||
# Main development shell with everything you need to develop ARTIQ on Linux.
|
# Main development shell with everything you need to develop ARTIQ on Linux.
|
||||||
# The current copy of the ARTIQ sources is added to PYTHONPATH so changes can be tested instantly.
|
# The current copy of the ARTIQ sources is added to PYTHONPATH so changes can be tested instantly.
|
||||||
# Additionally, executable wrappers that import the current ARTIQ sources for the ARTIQ frontends
|
# Additionally, executable wrappers that import the current ARTIQ sources for the ARTIQ frontends
|
||||||
# are added to PATH.
|
# are added to PATH.
|
||||||
devShells.x86_64-linux.default = pkgs.mkShell {
|
default = pkgs.mkShell {
|
||||||
name = "artiq-dev-shell";
|
name = "artiq-dev-shell";
|
||||||
buildInputs = [
|
packages = with pkgs;
|
||||||
(pkgs.python3.withPackages(ps: with packages.x86_64-linux; [ migen misoc ps.paramiko microscope ps.packaging ] ++ artiq.propagatedBuildInputs ))
|
[
|
||||||
|
git
|
||||||
|
lit
|
||||||
|
lld_15
|
||||||
|
llvm_15
|
||||||
|
llvmPackages_15.clang-unwrapped
|
||||||
|
outputcheck
|
||||||
|
pdf2svg
|
||||||
|
|
||||||
|
python3Packages.sphinx
|
||||||
|
python3Packages.sphinx-argparse
|
||||||
|
python3Packages.sphinxcontrib-tikz
|
||||||
|
python3Packages.sphinxcontrib-wavedrom
|
||||||
|
python3Packages.sphinx_rtd_theme
|
||||||
|
|
||||||
|
(python3.withPackages (ps: [migen misoc microscope ps.packaging ps.paramiko] ++ artiq.propagatedBuildInputs))
|
||||||
|
]
|
||||||
|
++ [
|
||||||
|
latex-artiq-manual
|
||||||
rust
|
rust
|
||||||
pkgs.llvmPackages_15.clang-unwrapped
|
|
||||||
pkgs.llvm_15
|
|
||||||
pkgs.lld_15
|
|
||||||
pkgs.git
|
|
||||||
artiq-frontend-dev-wrappers
|
artiq-frontend-dev-wrappers
|
||||||
|
|
||||||
# To manually run compiler tests:
|
# To manually run compiler tests:
|
||||||
pkgs.lit
|
|
||||||
pkgs.outputcheck
|
|
||||||
libartiq-support
|
libartiq-support
|
||||||
|
|
||||||
# use the vivado-env command to enter a FHS shell that lets you run the Vivado installer
|
# use the vivado-env command to enter a FHS shell that lets you run the Vivado installer
|
||||||
packages.x86_64-linux.vivadoEnv
|
packages.x86_64-linux.vivadoEnv
|
||||||
packages.x86_64-linux.vivado
|
packages.x86_64-linux.vivado
|
||||||
packages.x86_64-linux.openocd-bscanspi
|
packages.x86_64-linux.openocd-bscanspi
|
||||||
pkgs.python3Packages.sphinx pkgs.python3Packages.sphinx_rtd_theme
|
|
||||||
pkgs.python3Packages.sphinx-argparse pkgs.python3Packages.sphinxcontrib-wavedrom latex-artiq-manual
|
|
||||||
];
|
];
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
export LIBARTIQ_SUPPORT=`libartiq-support`
|
export LIBARTIQ_SUPPORT=`libartiq-support`
|
||||||
@ -431,20 +521,23 @@
|
|||||||
export PYTHONPATH=`git rev-parse --show-toplevel`:$PYTHONPATH
|
export PYTHONPATH=`git rev-parse --show-toplevel`:$PYTHONPATH
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
# Lighter development shell optimized for building firmware and flashing boards.
|
# Lighter development shell optimized for building firmware and flashing boards.
|
||||||
devShells.x86_64-linux.boards = pkgs.mkShell {
|
boards = pkgs.mkShell {
|
||||||
name = "artiq-boards-shell";
|
name = "artiq-boards-shell";
|
||||||
buildInputs = [
|
packages = [
|
||||||
(pkgs.python3.withPackages(ps: with packages.x86_64-linux; [ migen misoc artiq ps.packaging ]))
|
|
||||||
rust
|
rust
|
||||||
|
|
||||||
pkgs.llvmPackages_15.clang-unwrapped
|
pkgs.llvmPackages_15.clang-unwrapped
|
||||||
pkgs.llvm_15
|
pkgs.llvm_15
|
||||||
pkgs.lld_15
|
pkgs.lld_15
|
||||||
|
|
||||||
packages.x86_64-linux.vivado
|
packages.x86_64-linux.vivado
|
||||||
packages.x86_64-linux.openocd-bscanspi
|
packages.x86_64-linux.openocd-bscanspi
|
||||||
|
|
||||||
|
(pkgs.python3.withPackages (ps: [migen misoc artiq ps.packaging ps.paramiko]))
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
};
|
||||||
|
|
||||||
packages.aarch64-linux = {
|
packages.aarch64-linux = {
|
||||||
openocd-bscanspi = openocd-bscanspi-f pkgs-aarch64;
|
openocd-bscanspi = openocd-bscanspi-f pkgs-aarch64;
|
||||||
@ -455,11 +548,10 @@
|
|||||||
gateware-sim = pkgs.stdenvNoCC.mkDerivation {
|
gateware-sim = pkgs.stdenvNoCC.mkDerivation {
|
||||||
name = "gateware-sim";
|
name = "gateware-sim";
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
(pkgs.python3.withPackages(ps: with packages.x86_64-linux; [ migen misoc artiq ]))
|
(pkgs.python3.withPackages (ps: with packages.x86_64-linux; [migen misoc artiq]))
|
||||||
];
|
];
|
||||||
phases = [ "buildPhase" ];
|
phases = ["buildPhase"];
|
||||||
buildPhase =
|
buildPhase = ''
|
||||||
''
|
|
||||||
python -m unittest discover -v artiq.gateware.test
|
python -m unittest discover -v artiq.gateware.test
|
||||||
touch $out
|
touch $out
|
||||||
'';
|
'';
|
||||||
@ -472,19 +564,22 @@
|
|||||||
#__impure = true; # Nix 2.8+
|
#__impure = true; # Nix 2.8+
|
||||||
|
|
||||||
buildInputs = [
|
buildInputs = [
|
||||||
(pkgs.python3.withPackages(ps: with packages.x86_64-linux; [
|
(pkgs.python3.withPackages (
|
||||||
|
ps:
|
||||||
|
with packages.x86_64-linux;
|
||||||
|
[
|
||||||
artiq
|
artiq
|
||||||
ps.paramiko
|
ps.paramiko
|
||||||
] ++ ps.paramiko.optional-dependencies.ed25519
|
]
|
||||||
|
++ ps.paramiko.optional-dependencies.ed25519
|
||||||
))
|
))
|
||||||
pkgs.llvm_15
|
pkgs.llvm_15
|
||||||
pkgs.lld_15
|
pkgs.lld_15
|
||||||
pkgs.openssh
|
pkgs.openssh
|
||||||
packages.x86_64-linux.openocd-bscanspi # for the bscanspi bitstreams
|
packages.x86_64-linux.openocd-bscanspi # for the bscanspi bitstreams
|
||||||
];
|
];
|
||||||
phases = [ "buildPhase" ];
|
phases = ["buildPhase"];
|
||||||
buildPhase =
|
buildPhase = ''
|
||||||
''
|
|
||||||
export HOME=`mktemp -d`
|
export HOME=`mktemp -d`
|
||||||
mkdir $HOME/.ssh
|
mkdir $HOME/.ssh
|
||||||
cp /opt/hydra_id_ed25519 $HOME/.ssh/id_ed25519
|
cp /opt/hydra_id_ed25519 $HOME/.ssh/id_ed25519
|
||||||
@ -509,11 +604,15 @@
|
|||||||
# Read "Ok" line when remote successfully locked
|
# Read "Ok" line when remote successfully locked
|
||||||
read LOCK_OK
|
read LOCK_OK
|
||||||
|
|
||||||
|
export ARTIQ_ROOT=`python -c "import artiq; print(artiq.__path__[0])"`/examples/kc705_nist_clock
|
||||||
|
export ARTIQ_LOW_LATENCY=1
|
||||||
|
|
||||||
|
artiq_rtiomap --device-db $ARTIQ_ROOT/device_db.py device_map.bin
|
||||||
|
artiq_mkfs -s ip `python -c "import artiq.examples.kc705_nist_clock.device_db as ddb; print(ddb.core_addr)"`/24 -f device_map device_map.bin kc705_nist_clock.config
|
||||||
|
artiq_flash -t kc705 -H rpi-1 storage -f kc705_nist_clock.config
|
||||||
artiq_flash -t kc705 -H rpi-1 -d ${packages.x86_64-linux.artiq-board-kc705-nist_clock}
|
artiq_flash -t kc705 -H rpi-1 -d ${packages.x86_64-linux.artiq-board-kc705-nist_clock}
|
||||||
sleep 30
|
sleep 30
|
||||||
|
|
||||||
export ARTIQ_ROOT=`python -c "import artiq; print(artiq.__path__[0])"`/examples/kc705_nist_clock
|
|
||||||
export ARTIQ_LOW_LATENCY=1
|
|
||||||
python -m unittest discover -v artiq.test.coredevice
|
python -m unittest discover -v artiq.test.coredevice
|
||||||
)
|
)
|
||||||
|
|
||||||
|
7
setup.py
7
setup.py
@ -6,16 +6,17 @@ import sys
|
|||||||
import versioneer
|
import versioneer
|
||||||
|
|
||||||
|
|
||||||
if sys.version_info[:2] < (3, 7):
|
if sys.version_info[:2] < (3, 11):
|
||||||
raise Exception("You need Python 3.7+")
|
raise Exception("You need Python 3.11+")
|
||||||
|
|
||||||
|
|
||||||
# Depends on PyQt5, but setuptools cannot check for it.
|
# Depends on PyQt6, but setuptools cannot check for it.
|
||||||
requirements = [
|
requirements = [
|
||||||
"numpy", "scipy",
|
"numpy", "scipy",
|
||||||
"python-dateutil", "prettytable", "h5py", "lmdb",
|
"python-dateutil", "prettytable", "h5py", "lmdb",
|
||||||
"qasync", "pyqtgraph", "pygit2",
|
"qasync", "pyqtgraph", "pygit2",
|
||||||
"llvmlite", "pythonparser", "levenshtein",
|
"llvmlite", "pythonparser", "levenshtein",
|
||||||
|
"platformdirs",
|
||||||
]
|
]
|
||||||
|
|
||||||
console_scripts = [
|
console_scripts = [
|
||||||
|
@ -11,7 +11,7 @@ def get_rev():
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
return os.getenv("VERSIONEER_OVERRIDE", default="8.0+unknown")
|
return os.getenv("VERSIONEER_OVERRIDE", default="9.0+unknown.beta")
|
||||||
|
|
||||||
def get_rev():
|
def get_rev():
|
||||||
return os.getenv("VERSIONEER_REV", default="unknown")
|
return os.getenv("VERSIONEER_REV", default="unknown")
|
||||||
|
Loading…
Reference in New Issue
Block a user