forked from M-Labs/artiq
Compare commits
333 Commits
master
...
flashing_i
Author | SHA1 | Date |
---|---|---|
Simon Renblad | ea0a8a99d3 | |
Sebastien Bourdeauducq | 468ace0e6d | |
spaqin | 4fcb7cc408 | |
architeuthidae | 0623480c82 | |
mwojcik | e63ac3435f | |
mwojcik | 02479e4fb3 | |
mwojcik | c5c5708f49 | |
mwojcik | fb8dd01e8d | |
architeuthidae | e12bc586a5 | |
architeuthidae | d69c2b6aa2 | |
architeuthidae | 994a936f26 | |
Egor Savkin | 75ffbeba4d | |
Florian Agbuya | fbf11ca002 | |
architeuthidae | 61ac6da547 | |
architeuthidae | 2ec01a3c45 | |
architeuthidae | bac22b7163 | |
architeuthidae | 937f3811d1 | |
Simon Renblad | ab090f9caf | |
architeuthidae | 6698a6f80c | |
Sebastien Bourdeauducq | 191494e430 | |
Sebastien Bourdeauducq | b588295063 | |
spaqin | e7f906e47a | |
mwojcik | d6bcc64518 | |
mwojcik | c4c932020a | |
David Nadlinger | f7edb7b706 | |
David Nadlinger | 4bd328afe7 | |
David Nadlinger | 5dd2f7c4e8 | |
David Nadlinger | 9fbd6de30c | |
Florian Agbuya | 378d962edb | |
Simon Renblad | 11c5f537bb | |
Simon Renblad | ee3d93ce6a | |
Simon Renblad | bd3bcbce64 | |
architeuthidae | 0952c47934 | |
architeuthidae | e67bcfb36e | |
architeuthidae | 52b0f30216 | |
architeuthidae | b0d2705c38 | |
architeuthidae | 7e6a94c6b0 | |
architeuthidae | 5aee0df9f0 | |
Simon Renblad | 8a6a6042fd | |
architeuthidae | 7b13ffd9f3 | |
Simon Renblad | 8a775bc61b | |
architeuthidae | 499eb42c3e | |
Florian Agbuya | c05b109d5f | |
architeuthidae | 5dbf870e68 | |
architeuthidae | 4cd9b8a8cf | |
architeuthidae | c9a4f3b9ee | |
architeuthidae | bff46fcf1e | |
architeuthidae | a5327e383c | |
architeuthidae | 088ea36d53 | |
architeuthidae | a4bfb0d5dd | |
architeuthidae | 0e1b29c5d9 | |
architeuthis | 81106f3567 | |
mwojcik | 9087c8698d | |
architeuthidae | c2e323662b | |
architeuthidae | 26483d5daf | |
architeuthidae | d5f11387e8 | |
architeuthis | 158789b2e5 | |
architeuthis | e0e96fb08b | |
architeuthidae | 96e4949c37 | |
architeuthis | 8cfae86634 | |
architeuthis | b2955f2bbe | |
architeuthidae | 1032b9accf | |
architeuthidae | 708262615d | |
Harry Ying | 530f67f4cd | |
Egor Savkin | 4a88693c32 | |
Simon Renblad | e9f1b9d4ff | |
mwojcik | 5ab2602802 | |
mwojcik | 86c6d11ed4 | |
Sebastien Bourdeauducq | 7a50afd9a9 | |
mwojcik | af99a06919 | |
architeuthidae | 8a178a628a | |
architeuthis | 810ab20425 | |
architeuthis | a5437dd4f5 | |
architeuthis | de5cd99787 | |
Florian Agbuya | c785c763fe | |
architeuthidae | 8f0147f029 | |
Simon Renblad | 9c1482aa69 | |
Simon Renblad | 6044d810ca | |
abdul124 | dcc5307771 | |
Sebastien Bourdeauducq | d8cf91bfb2 | |
Simon Renblad | 24133ca04d | |
mwojcik | 868e4defda | |
mwojcik | e7faca81fc | |
architeuthidae | b8c12976db | |
Sebastien Bourdeauducq | 91a4315386 | |
Florian Agbuya | 688f3d9225 | |
Charles Baynham | 6f3322ea35 | |
Simon Renblad | fdb0668c8a | |
Simon Renblad | f1e8b8772a | |
architeuthis | 9386a7a16f | |
architeuthis | bcc760a3fb | |
Egor Savkin | a804be1a45 | |
Egor Savkin | 3cd6d50ad9 | |
Florian Agbuya | bd7daa5247 | |
architeuthis | 48cdf42016 | |
Egor Savkin | c568109f3f | |
architeuthis | 81cda2380d | |
architeuthis | ea22f67c4c | |
architeuthis | aa21b78681 | |
architeuthis | de1df88dcd | |
architeuthis | 0dc727c1fd | |
architeuthis | 45ef4d18d7 | |
architeuthis | edfa5aa957 | |
Harry Ying | a9a74398ab | |
Sébastien Bourdeauducq | e09fde6f51 | |
architeuthis | 02c1d2514f | |
architeuthis | 1ca7a3c6a3 | |
Harry Ying | 102506d603 | |
Egor Savkin | 19132ae0e3 | |
Sebastien Bourdeauducq | 85545a8447 | |
morgan | 77580b5bf6 | |
morgan | 84b97976c0 | |
Florian Agbuya | ff79854c46 | |
architeuthis | a167cc6043 | |
architeuthis | 1ee3988188 | |
architeuthidae | 2c945f260e | |
architeuthidae | f432529014 | |
Florian Agbuya | bfeac30c44 | |
Florian Agbuya | a901ab74b5 | |
architeuthis | 8b64315ecf | |
architeuthis | 4509ad86f8 | |
architeuthis | 59302da71c | |
Sebastien Bourdeauducq | ebc1e3fb76 | |
Sebastien Bourdeauducq | 927bb3b6b4 | |
Simon Renblad | 1bb3c503d9 | |
Simon Renblad | 5e73245cef | |
Simon Renblad | b74beac6b9 | |
Simon Renblad | c256d113de | |
Simon Renblad | 33d3688bfc | |
Simon Renblad | 88903fb38c | |
Simon Renblad | 7fae395b88 | |
Simon Renblad | d3d50d790a | |
Simon Renblad | 154f186f18 | |
Simon Renblad | ad170b469c | |
Simon Renblad | 9fc4cdea6b | |
Simon Renblad | 2fde21152a | |
Simon Renblad | 51837ce1a2 | |
Simon Renblad | 5cd21c7a6d | |
Simon Renblad | e742dc9503 | |
Sébastien Bourdeauducq | 9a770c15c5 | |
Sébastien Bourdeauducq | d252b12cf6 | |
mwojcik | f1e1e54940 | |
mwojcik | 20c67aca23 | |
mwojcik | 793f8a3c8c | |
Sebastien Bourdeauducq | f496e6da7c | |
architeuthis | d609ed4a58 | |
mwojcik | 48f3071ee8 | |
morgan | 49e402780b | |
morgan | 44cfacf2c4 | |
morgan | c5147d7744 | |
morgan | d5b1f04dcc | |
morgan | dad62c1aec | |
morgan | 77c50324ef | |
Sebastien Bourdeauducq | eefc07b495 | |
morgan | a10dd0520c | |
morgan | 0ac0e08170 | |
morgan | 5d9bc930fe | |
morgan | 5971d9e958 | |
morgan | 0d78e65f7a | |
morgan | 1b0586e6a8 | |
morgan | 57780e36be | |
morgan | 14a618b48d | |
morgan | 13830a27af | |
mwojcik | 51c15ac777 | |
mwojcik | 0a044cf424 | |
Egor Savkin | 2b31d38084 | |
mwojcik | c2d645ed0a | |
linuswck | 4de3273e7a | |
linuswck | 531640fa91 | |
mwojcik | c5d656ba32 | |
mwojcik | 688e643078 | |
mwojcik | c33c1df07f | |
Sebastien Bourdeauducq | 6d0821ecaf | |
Mikołaj Sowiński | 16e4b616ca | |
Simon Renblad | 7dff78e849 | |
Florian Agbuya | a8157cd5c9 | |
Sebastien Bourdeauducq | 193962f31e | |
Simon Renblad | 5fe47129ed | |
Sebastien Bourdeauducq | 24fe885b5c | |
mwojcik | 7204feae1f | |
mwojcik | acebc3d691 | |
mwojcik | a49ba3e350 | |
mwojcik | b1c305fd11 | |
mwojcik | b6ac052e9f | |
mwojcik | 76d704ac33 | |
Norman Krackow | baa58343ac | |
Sébastien Bourdeauducq | 1bcbee988d | |
Sébastien Bourdeauducq | ab206ac154 | |
Simon Renblad | 4a2352c2df | |
Simon Renblad | f9a447e8e0 | |
Simon Renblad | c4892cf285 | |
Simon Renblad | c1e6ae2193 | |
Simon Renblad | 4f302ee675 | |
Simon Renblad | 3ecd115252 | |
Simon Renblad | 400c1644b0 | |
Simon Renblad | 1b2a18c9c8 | |
Simon Renblad | 7d9199a2ee | |
Simon Renblad | 43edffc67e | |
Simon Renblad | 49930a2df2 | |
Simon Renblad | 9d3509d7b0 | |
Simon Renblad | b555f08ed8 | |
Simon Renblad | 65005ed45a | |
Simon Renblad | 6ac532a00e | |
Simon Renblad | 856e43fd61 | |
Simon Renblad | af11dc6b74 | |
Sebastien Bourdeauducq | 0fb31ddbb1 | |
Simon Renblad | 9bf5695ab2 | |
Sébastien Bourdeauducq | 5f49e582c8 | |
Simon Renblad | fddff13842 | |
Simon Renblad | 915d3613f1 | |
Simon Renblad | d463ccb218 | |
Simon Renblad | b4d070fa1b | |
Simon Renblad | 9934c756b2 | |
Simon Renblad | 47716badef | |
Simon Renblad | 8e68501081 | |
Simon Renblad | 19b652d4c0 | |
Florian Agbuya | dc0b803b19 | |
Florian Agbuya | bc8bc952d7 | |
Simon Renblad | aea5f04d74 | |
Sébastien Bourdeauducq | d0f893c01c | |
Simon Renblad | 7fa770fba9 | |
Simon Renblad | 5a8bc17e4d | |
Sébastien Bourdeauducq | 329e7189cc | |
Simon Renblad | 13a36bf911 | |
Simon Renblad | 88438e2d76 | |
Simon Renblad | 1a41b16fb6 | |
Simon Renblad | 6978101b1f | |
Simon Renblad | 244c73a592 | |
Simon Renblad | c4323e1179 | |
morgan | 609684664a | |
Simon Renblad | 7e6ed1655f | |
Simon Renblad | 332c9c0fcd | |
Simon Renblad | 27178c1478 | |
Simon Renblad | e56331248e | |
Sébastien Bourdeauducq | 692572a3b9 | |
Sébastien Bourdeauducq | 18f55bb196 | |
Sébastien Bourdeauducq | 3e8a853e53 | |
Sébastien Bourdeauducq | de29db0b35 | |
mwojcik | 42d3c3b4b2 | |
Sébastien Bourdeauducq | 450fe91e93 | |
Simon Renblad | 002325be17 | |
Sébastien Bourdeauducq | 92eb3947a4 | |
Florian Agbuya | 3609f95207 | |
Sébastien Bourdeauducq | 5e01661443 | |
Simon Renblad | a21805598a | |
Simon Renblad | c151f0c3ce | |
Simon Renblad | c794e51c1c | |
Sébastien Bourdeauducq | bafa69098a | |
Sébastien Bourdeauducq | b2ba087acd | |
Sébastien Bourdeauducq | a8a5fc213b | |
Sébastien Bourdeauducq | 7688f380b1 | |
Sébastien Bourdeauducq | a0450555e2 | |
Sébastien Bourdeauducq | b142428607 | |
Sébastien Bourdeauducq | 750fdf89b3 | |
Simon Renblad | 0a24d72b9f | |
Sébastien Bourdeauducq | 7c1274f254 | |
Sébastien Bourdeauducq | 716d0f556d | |
Charles Baynham | 20d7604f87 | |
Simon Renblad | 4c142ec3f1 | |
Simon Renblad | c49600a2fc | |
Simon Renblad | cda758ef53 | |
Simon Renblad | bd9e8b3977 | |
Simon Renblad | 779b7704ed | |
Simon Renblad | edd23977f8 | |
Simon Renblad | f460af3a6a | |
Simon Renblad | 1b0fd2e2d3 | |
Simon Renblad | 652bcc22c6 | |
Simon Renblad | de539a4d33 | |
Simon Renblad | 1749fa661f | |
Simon Renblad | 6ed6fb0bce | |
morgan | fc282d4e17 | |
Simon Renblad | 795b8ae4c6 | |
Simon Renblad | 21b77567f2 | |
Simon Renblad | d085c1e4a4 | |
Simon Renblad | 720cbb4490 | |
Simon Renblad | efb8aaf9f9 | |
Sebastien Bourdeauducq | 7c583b9c04 | |
Simon Renblad | 7f43c5c31a | |
Simon Renblad | 40cea30285 | |
Simon Renblad | 8b503c3b4f | |
Simon Renblad | 1e9070a2af | |
Simon Renblad | dcf1bba8c6 | |
Simon Renblad | a7b045a478 | |
Sebastien Bourdeauducq | 3aaa7e04f2 | |
mwojcik | b648a2930b | |
mwojcik | b64c75fd71 | |
mwojcik | 392533f8ee | |
mwojcik | 7fee68ede0 | |
mwojcik | 849b77fbf2 | |
mwojcik | 502204cab2 | |
mwojcik | d1ee0ffb83 | |
Simon Renblad | cbe7ac1cfd | |
Simon Renblad | 2d8de3ed93 | |
Simon Renblad | 5f3126f393 | |
mwojcik | 09462442f7 | |
mwojcik | 726cb092ca | |
mwojcik | fbbc8d3dd1 | |
mwojcik | 0ba0330b53 | |
mwojcik | 7d3bcc7cac | |
mwojcik | 171c7a6e11 | |
Simon Renblad | c087a47e45 | |
Simon Renblad | 28dfe1f9c6 | |
Simon Renblad | 3861d58749 | |
Simon Renblad | 6c9f1cbf7c | |
Simon Renblad | 06b908fd18 | |
Simon Renblad | e72f37eb4e | |
Simon Renblad | 847b4ee2a3 | |
Simon Renblad | 863daca2da | |
Simon Renblad | fcaf4a8af0 | |
Simon Renblad | 466d865e58 | |
Simon Renblad | 5036230ff3 | |
Simon Renblad | 12a44fad3c | |
Simon Renblad | 096664c1ba | |
Simon Renblad | 8a9b6a449b | |
Simon Renblad | 73be2257d3 | |
Simon Renblad | 9088ffa2ca | |
Simon Renblad | d44f55c6d9 | |
Simon Renblad | e393b3ab37 | |
Simon Renblad | 3af4c9d517 | |
Simon Renblad | 64567bc26f | |
Sebastien Bourdeauducq | da15e94c22 | |
Charles Baynham | 669edf17c5 | |
Simon Renblad | b215df2d25 | |
mwojcik | 6c0ff9a912 | |
mwojcik | c9e3771cd5 | |
mwojcik | c876acd5a5 | |
mwojcik | 4363cdf9fa | |
mwojcik | 95b92a178b | |
mwojcik | 1cc7398bc0 | |
mwojcik | 4956fac861 | |
mwojcik | 9bc66e5c14 | |
mwojcik | 4495f6035e | |
mwojcik | e556c29b40 |
|
@ -51,7 +51,7 @@ Closes #XXX
|
||||||
|
|
||||||
### Documentation Changes
|
### Documentation Changes
|
||||||
|
|
||||||
- [ ] Check, test, and update the documentation in [doc/](../doc/). Build documentation (`cd doc/manual/; make html`) to ensure no errors.
|
- [ ] Check, test, and update the documentation in [doc/](../doc/). Build documentation (`nix build .#artiq-manual-html; nix build .#artiq-manual-pdf`) to ensure no errors.
|
||||||
|
|
||||||
### Git Logistics
|
### Git Logistics
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@ __pycache__/
|
||||||
.ipynb_checkpoints
|
.ipynb_checkpoints
|
||||||
/doc/manual/_build
|
/doc/manual/_build
|
||||||
/build
|
/build
|
||||||
|
/result
|
||||||
/dist
|
/dist
|
||||||
/*.egg-info
|
/*.egg-info
|
||||||
/.coverage
|
/.coverage
|
||||||
|
@ -23,7 +24,8 @@ __pycache__/
|
||||||
/artiq/test/results
|
/artiq/test/results
|
||||||
/artiq/examples/*/results
|
/artiq/examples/*/results
|
||||||
/artiq/examples/*/last_rid.pyon
|
/artiq/examples/*/last_rid.pyon
|
||||||
/artiq/examples/*/dataset_db.pyon
|
/artiq/examples/*/dataset_db.mdb
|
||||||
|
/artiq/examples/*/dataset_db.mdb-lock
|
||||||
|
|
||||||
# when testing ad-hoc experiments at the root:
|
# when testing ad-hoc experiments at the root:
|
||||||
/repository/
|
/repository/
|
||||||
|
|
22
README.rst
22
README.rst
|
@ -5,31 +5,27 @@
|
||||||
:target: https://m-labs.hk/artiq
|
:target: https://m-labs.hk/artiq
|
||||||
|
|
||||||
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, with over a hundred Sinara hardware crates deployed, 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``.
|
||||||
|
|
||||||
License
|
License
|
||||||
=======
|
=======
|
||||||
|
|
||||||
Copyright (C) 2014-2023 M-Labs Limited.
|
Copyright (C) 2014-2024 M-Labs Limited.
|
||||||
|
|
||||||
ARTIQ is free software: you can redistribute it and/or modify
|
ARTIQ is free software: you can redistribute it and/or modify
|
||||||
it under the terms of the GNU Lesser General Public License as published by
|
it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
|
|
@ -3,9 +3,20 @@
|
||||||
Release notes
|
Release notes
|
||||||
=============
|
=============
|
||||||
|
|
||||||
ARTIQ-8 (Unreleased)
|
ARTIQ-9 (Unreleased)
|
||||||
--------------------
|
--------------------
|
||||||
|
|
||||||
|
* Zotino monitoring in the dashboard now displays the values in volts.
|
||||||
|
* afws_client now uses the "happy eyeballs" algorithm (RFC 6555) for a faster and more
|
||||||
|
reliable connection to the server.
|
||||||
|
* The Zadig driver installer was added to the MSYS2 offline installer.
|
||||||
|
* Fastino monitoring with Moninj is now supported.
|
||||||
|
* Qt6 support.
|
||||||
|
* Python 3.12 support.
|
||||||
|
|
||||||
|
ARTIQ-8
|
||||||
|
-------
|
||||||
|
|
||||||
Highlights:
|
Highlights:
|
||||||
|
|
||||||
* New hardware support:
|
* New hardware support:
|
||||||
|
@ -28,23 +39,38 @@ Highlights:
|
||||||
clock, to facilitate implementation of local processing on DRTIO satellites, and to slightly
|
clock, to facilitate implementation of local processing on DRTIO satellites, and to slightly
|
||||||
reduce RTIO latency.
|
reduce RTIO latency.
|
||||||
* Support for DRTIO-over-EEM, used with Shuttler.
|
* Support for DRTIO-over-EEM, used with Shuttler.
|
||||||
|
* Support for WRPLL low-noise clock recovery.
|
||||||
|
* Enabled event spreading on DRTIO satellites, using high watermark for lane switching.
|
||||||
* Added channel names to RTIO error messages.
|
* Added channel names to RTIO error messages.
|
||||||
|
* The RTIO analyzer is now proxied by ``aqctl_coreanalyzer_proxy`` typically running on the master
|
||||||
|
machine, similarly to ``aqctl_moninj_proxy``.
|
||||||
* GUI:
|
* GUI:
|
||||||
|
- Integrated waveform analyzer, removing the need for external VCD viewers such as GtkWave.
|
||||||
- Implemented Applet Request Interfaces which allow applets to modify datasets and set the
|
- Implemented Applet Request Interfaces which allow applets to modify datasets and set the
|
||||||
current values of widgets in the dashboard's experiment windows.
|
current values of widgets in the dashboard's experiment windows.
|
||||||
- Implemented a new EntryArea widget which allows argument entry widgets to be used in applets.
|
- Implemented a new ``EntryArea`` widget which allows argument entry widgets to be used in applets.
|
||||||
- The "Close all applets" command (shortcut: Ctrl-Alt-W) now ignores docked applets,
|
- The "Close all applets" command (shortcut: Ctrl-Alt-W) now ignores docked applets,
|
||||||
making it a convenient way to clean up after exploratory work without destroying a
|
making it a convenient way to clean up after exploratory work without destroying a
|
||||||
carefully arranged default workspace.
|
carefully arranged default workspace.
|
||||||
- Hotkeys now organize experiment windows in the order they were last interacted with:
|
- Hotkeys now organize experiment windows in the order they were last interacted with:
|
||||||
+ CTRL+SHIFT+T tiles experiment windows
|
+ CTRL+SHIFT+T tiles experiment windows
|
||||||
+ CTRL+SHIFT+C cascades experiment windows
|
+ CTRL+SHIFT+C cascades experiment windows
|
||||||
|
- By enabling the ``quickstyle`` option, ``EnumerationValue`` entry widgets can now alternatively display
|
||||||
|
its choices as buttons that submit the experiment on click.
|
||||||
|
* Datasets can now be associated with units and scale factors, and displayed accordingly in the dashboard
|
||||||
|
including applets, like widgets such as ``NumberValue`` already did in earlier ARTIQ versions.
|
||||||
|
* Experiments can now request arguments interactively from the user at any time.
|
||||||
* Persistent datasets are now stored in a LMDB database for improved performance.
|
* Persistent datasets are now stored in a LMDB database for improved performance.
|
||||||
* Python's built-in types (such as ``float``, or ``List[...]``) can now be used in type annotations on
|
* Python's built-in types (such as ``float``, or ``List[...]``) can now be used in type annotations on
|
||||||
kernel functions.
|
kernel functions.
|
||||||
* Full Python 3.10 support.
|
|
||||||
* MSYS2 packaging for Windows, which replaces Conda. Conda packages are still available to
|
* MSYS2 packaging for Windows, which replaces Conda. Conda packages are still available to
|
||||||
support legacy installations, but may be removed in a future release.
|
support legacy installations, but may be removed in a future release.
|
||||||
|
* Experiments can now be submitted with revisions set to a branch / tag name instead of only git hashes.
|
||||||
|
* Grabber image input now has an optional timeout.
|
||||||
|
* On NAR3-supported devices (Kasli-SoC, ZC706), when a Rust panic occurs, a minimal environment is started
|
||||||
|
where the network and ``artiq_coremgmt`` can be used. This allows the user to inspect logs, change
|
||||||
|
configuration options, update the firmware, and reboot the device.
|
||||||
|
* Full Python 3.11 support.
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -24,11 +24,11 @@ class XYPlot(pyqtgraph.PlotWidget):
|
||||||
y = value[self.args.y]
|
y = value[self.args.y]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return
|
return
|
||||||
x = value.get(self.args.x, (False, None))
|
x = value.get(self.args.x)
|
||||||
if x is None:
|
if x is None:
|
||||||
x = np.arange(len(y))
|
x = np.arange(len(y))
|
||||||
error = value.get(self.args.error, (False, None))
|
error = value.get(self.args.error)
|
||||||
fit = value.get(self.args.fit, (False, None))
|
fit = value.get(self.args.fit)
|
||||||
|
|
||||||
if not len(y) or len(y) != len(x):
|
if not len(y) or len(y) != len(x):
|
||||||
self.mismatch['X values'] = True
|
self.mismatch['X values'] = True
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -24,33 +24,34 @@ class _AppletRequestInterface:
|
||||||
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
|
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
|
||||||
"""
|
"""
|
||||||
Set a dataset.
|
Set a dataset.
|
||||||
See documentation of ``artiq.language.environment.set_dataset``.
|
See documentation of :meth:`~artiq.language.environment.HasEnvironment.set_dataset`.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def mutate_dataset(self, key, index, value):
|
def mutate_dataset(self, key, index, value):
|
||||||
"""
|
"""
|
||||||
Mutate a dataset.
|
Mutate a dataset.
|
||||||
See documentation of ``artiq.language.environment.mutate_dataset``.
|
See documentation of :meth:`~artiq.language.environment.HasEnvironment.mutate_dataset`.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def append_to_dataset(self, key, value):
|
def append_to_dataset(self, key, value):
|
||||||
"""
|
"""
|
||||||
Append to a dataset.
|
Append to a dataset.
|
||||||
See documentation of ``artiq.language.environment.append_to_dataset``.
|
See documentation of :meth:`~artiq.language.environment.HasEnvironment.append_to_dataset`.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def set_argument_value(self, expurl, name, value):
|
def set_argument_value(self, expurl, key, value):
|
||||||
"""
|
"""
|
||||||
Temporarily set the value of an argument in a experiment in the dashboard.
|
Temporarily set the value of an argument in a experiment in the dashboard.
|
||||||
The value resets to default value when recomputing the argument.
|
The value resets to default value when recomputing the argument.
|
||||||
|
|
||||||
:param expurl: Experiment URL identifying the experiment in the dashboard. Example: 'repo:ArgumentsDemo'.
|
:param expurl: Experiment URL identifying the experiment in the dashboard. Example: 'repo:ArgumentsDemo'.
|
||||||
:param name: Name of the argument in the experiment.
|
:param key: Name of the argument in the experiment.
|
||||||
:param value: Object representing the new temporary value of the argument. For ``Scannable`` arguments, this parameter
|
:param value: Object representing the new temporary value of the argument. For :class:`~artiq.language.scan.Scannable` arguments,
|
||||||
should be a ``ScanObject``. The type of the ``ScanObject`` will be set as the selected type when this function is called.
|
this parameter should be a :class:`~artiq.language.scan.ScanObject`. The type of the :class:`~artiq.language.scan.ScanObject`
|
||||||
|
will be set as the selected type when this function is called.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@ -77,10 +78,10 @@ class AppletRequestIPC(_AppletRequestInterface):
|
||||||
mod = {"action": "append", "path": [key, 1], "x": value}
|
mod = {"action": "append", "path": [key, 1], "x": value}
|
||||||
self.ipc.update_dataset(mod)
|
self.ipc.update_dataset(mod)
|
||||||
|
|
||||||
def set_argument_value(self, expurl, name, value):
|
def set_argument_value(self, expurl, key, value):
|
||||||
if isinstance(value, ScanObject):
|
if isinstance(value, ScanObject):
|
||||||
value = value.describe()
|
value = value.describe()
|
||||||
self.ipc.set_argument_value(expurl, name, value)
|
self.ipc.set_argument_value(expurl, key, value)
|
||||||
|
|
||||||
|
|
||||||
class AppletRequestRPC(_AppletRequestInterface):
|
class AppletRequestRPC(_AppletRequestInterface):
|
||||||
|
@ -136,9 +137,8 @@ class AppletIPCClient(AsyncioChildComm):
|
||||||
logger.error("unexpected action reply to embed request: %s",
|
logger.error("unexpected action reply to embed request: %s",
|
||||||
reply["action"])
|
reply["action"])
|
||||||
self.close_cb()
|
self.close_cb()
|
||||||
|
else:
|
||||||
def fix_initial_size(self):
|
return reply["size_w"], reply["size_h"]
|
||||||
self.write_pyon({"action": "fix_initial_size"})
|
|
||||||
|
|
||||||
async def listen(self):
|
async def listen(self):
|
||||||
data = None
|
data = None
|
||||||
|
@ -182,10 +182,10 @@ class AppletIPCClient(AsyncioChildComm):
|
||||||
self.write_pyon({"action": "update_dataset",
|
self.write_pyon({"action": "update_dataset",
|
||||||
"mod": mod})
|
"mod": mod})
|
||||||
|
|
||||||
def set_argument_value(self, expurl, name, value):
|
def set_argument_value(self, expurl, key, value):
|
||||||
self.write_pyon({"action": "set_argument_value",
|
self.write_pyon({"action": "set_argument_value",
|
||||||
"expurl": expurl,
|
"expurl": expurl,
|
||||||
"name": name,
|
"key": key,
|
||||||
"value": value})
|
"value": value})
|
||||||
|
|
||||||
|
|
||||||
|
@ -255,7 +255,7 @@ class SimpleApplet:
|
||||||
if self.embed is None:
|
if self.embed is None:
|
||||||
dataset_ctl = RPCClient()
|
dataset_ctl = RPCClient()
|
||||||
self.loop.run_until_complete(dataset_ctl.connect_rpc(
|
self.loop.run_until_complete(dataset_ctl.connect_rpc(
|
||||||
self.args.server, self.args.port_control, "master_dataset_db"))
|
self.args.server, self.args.port_control, "dataset_db"))
|
||||||
self.req = AppletRequestRPC(self.loop, dataset_ctl)
|
self.req = AppletRequestRPC(self.loop, dataset_ctl)
|
||||||
else:
|
else:
|
||||||
self.req = AppletRequestIPC(self.ipc)
|
self.req = AppletRequestIPC(self.ipc)
|
||||||
|
@ -272,7 +272,7 @@ class SimpleApplet:
|
||||||
# HACK: if the window has a frame, there will be garbage
|
# HACK: if the window has a frame, there will be garbage
|
||||||
# (usually white) displayed at its right and bottom borders
|
# (usually white) displayed at its right and bottom borders
|
||||||
# after it is embedded.
|
# after it is embedded.
|
||||||
self.main_widget.setWindowFlags(QtCore.Qt.FramelessWindowHint)
|
self.main_widget.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint)
|
||||||
self.main_widget.show()
|
self.main_widget.show()
|
||||||
win_id = int(self.main_widget.winId())
|
win_id = int(self.main_widget.winId())
|
||||||
self.loop.run_until_complete(self.ipc.embed(win_id))
|
self.loop.run_until_complete(self.ipc.embed(win_id))
|
||||||
|
@ -285,12 +285,13 @@ class SimpleApplet:
|
||||||
# 2. applet creates native window without showing it, and
|
# 2. applet creates native window without showing it, and
|
||||||
# gets its ID
|
# gets its ID
|
||||||
# 3. applet sends the ID to host, host embeds the widget
|
# 3. applet sends the ID to host, host embeds the widget
|
||||||
# 4. applet shows the widget
|
# and returns embedded size
|
||||||
# 5. parent resizes the widget
|
# 4. applet is resized to that given size
|
||||||
|
# 5. applet shows the widget
|
||||||
win_id = int(self.main_widget.winId())
|
win_id = int(self.main_widget.winId())
|
||||||
self.loop.run_until_complete(self.ipc.embed(win_id))
|
size_w, size_h = self.loop.run_until_complete(self.ipc.embed(win_id))
|
||||||
|
self.main_widget.resize(size_w, size_h)
|
||||||
self.main_widget.show()
|
self.main_widget.show()
|
||||||
self.ipc.fix_initial_size()
|
|
||||||
else:
|
else:
|
||||||
self.main_widget.show()
|
self.main_widget.show()
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
@ -33,7 +33,7 @@ class DatasetCtl:
|
||||||
try:
|
try:
|
||||||
remote = RPCClient()
|
remote = RPCClient()
|
||||||
await remote.connect_rpc(self.master_host, self.master_port,
|
await remote.connect_rpc(self.master_host, self.master_port,
|
||||||
"master_dataset_db")
|
"dataset_db")
|
||||||
try:
|
try:
|
||||||
if op_name == "set":
|
if op_name == "set":
|
||||||
await remote.set(key_or_mod, value, persist, metadata)
|
await remote.set(key_or_mod, value, persist, metadata)
|
||||||
|
@ -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,103 +4,42 @@ 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
|
||||||
|
|
||||||
from artiq import __artiq_dir__ as artiq_dir
|
from artiq import __artiq_dir__ as artiq_dir
|
||||||
from artiq.gui.tools import (LayoutWidget, WheelFilter,
|
from artiq.gui.tools import (LayoutWidget, log_level_to_name, get_open_file_name)
|
||||||
log_level_to_name, get_open_file_name)
|
from artiq.gui.entries import procdesc_to_entry, EntryTreeWidget
|
||||||
from artiq.gui.entries import procdesc_to_entry
|
|
||||||
from artiq.master.worker import Worker, log_worker_exception
|
from artiq.master.worker import Worker, log_worker_exception
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class _ArgumentEditor(QtWidgets.QTreeWidget):
|
class _ArgumentEditor(EntryTreeWidget):
|
||||||
def __init__(self, dock):
|
def __init__(self, dock):
|
||||||
QtWidgets.QTreeWidget.__init__(self)
|
EntryTreeWidget.__init__(self)
|
||||||
self.setColumnCount(3)
|
|
||||||
self.header().setStretchLastSection(False)
|
|
||||||
try:
|
|
||||||
set_resize_mode = self.header().setSectionResizeMode
|
|
||||||
except AttributeError:
|
|
||||||
set_resize_mode = self.header().setResizeMode
|
|
||||||
set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents)
|
|
||||||
set_resize_mode(1, QtWidgets.QHeaderView.Stretch)
|
|
||||||
set_resize_mode(2, QtWidgets.QHeaderView.ResizeToContents)
|
|
||||||
self.header().setVisible(False)
|
|
||||||
self.setSelectionMode(self.NoSelection)
|
|
||||||
self.setHorizontalScrollMode(self.ScrollPerPixel)
|
|
||||||
self.setVerticalScrollMode(self.ScrollPerPixel)
|
|
||||||
|
|
||||||
self.setStyleSheet("QTreeWidget {background: " +
|
|
||||||
self.palette().midlight().color().name() + " ;}")
|
|
||||||
|
|
||||||
self.viewport().installEventFilter(WheelFilter(self.viewport(), True))
|
|
||||||
|
|
||||||
self._groups = dict()
|
|
||||||
self._arg_to_widgets = dict()
|
|
||||||
self._dock = dock
|
self._dock = dock
|
||||||
|
|
||||||
if not self._dock.arguments:
|
if not self._dock.arguments:
|
||||||
self.addTopLevelItem(QtWidgets.QTreeWidgetItem(["No arguments"]))
|
self.insertTopLevelItem(0, QtWidgets.QTreeWidgetItem(["No arguments"]))
|
||||||
gradient = QtGui.QLinearGradient(
|
|
||||||
0, 0, 0, QtGui.QFontMetrics(self.font()).lineSpacing()*2.5)
|
|
||||||
gradient.setColorAt(0, self.palette().base().color())
|
|
||||||
gradient.setColorAt(1, self.palette().midlight().color())
|
|
||||||
|
|
||||||
for name, argument in self._dock.arguments.items():
|
for name, argument in self._dock.arguments.items():
|
||||||
widgets = dict()
|
self.set_argument(name, argument)
|
||||||
self._arg_to_widgets[name] = widgets
|
|
||||||
|
|
||||||
entry = procdesc_to_entry(argument["desc"])(argument)
|
self.quickStyleClicked.connect(self._dock._run_clicked)
|
||||||
widget_item = QtWidgets.QTreeWidgetItem([name])
|
|
||||||
if argument["tooltip"]:
|
|
||||||
widget_item.setToolTip(0, argument["tooltip"])
|
|
||||||
widgets["entry"] = entry
|
|
||||||
widgets["widget_item"] = widget_item
|
|
||||||
|
|
||||||
for col in range(3):
|
|
||||||
widget_item.setBackground(col, gradient)
|
|
||||||
font = widget_item.font(0)
|
|
||||||
font.setBold(True)
|
|
||||||
widget_item.setFont(0, font)
|
|
||||||
|
|
||||||
if argument["group"] is None:
|
|
||||||
self.addTopLevelItem(widget_item)
|
|
||||||
else:
|
|
||||||
self._get_group(argument["group"]).addChild(widget_item)
|
|
||||||
fix_layout = LayoutWidget()
|
|
||||||
widgets["fix_layout"] = fix_layout
|
|
||||||
fix_layout.addWidget(entry)
|
|
||||||
self.setItemWidget(widget_item, 1, fix_layout)
|
|
||||||
|
|
||||||
recompute_argument = QtWidgets.QToolButton()
|
|
||||||
recompute_argument.setToolTip("Re-run the experiment's build "
|
|
||||||
"method and take the default value")
|
|
||||||
recompute_argument.setIcon(
|
|
||||||
QtWidgets.QApplication.style().standardIcon(
|
|
||||||
QtWidgets.QStyle.SP_BrowserReload))
|
|
||||||
recompute_argument.clicked.connect(
|
|
||||||
partial(self._recompute_argument_clicked, name))
|
|
||||||
fix_layout = LayoutWidget()
|
|
||||||
fix_layout.addWidget(recompute_argument)
|
|
||||||
self.setItemWidget(widget_item, 2, fix_layout)
|
|
||||||
|
|
||||||
widget_item = QtWidgets.QTreeWidgetItem()
|
|
||||||
self.addTopLevelItem(widget_item)
|
|
||||||
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()
|
||||||
|
@ -108,21 +47,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
||||||
buttons.addWidget(load, 1, 2)
|
buttons.addWidget(load, 1, 2)
|
||||||
for i, s in enumerate((1, 0, 0, 1)):
|
for i, s in enumerate((1, 0, 0, 1)):
|
||||||
buttons.layout.setColumnStretch(i, s)
|
buttons.layout.setColumnStretch(i, s)
|
||||||
self.setItemWidget(widget_item, 1, buttons)
|
self.setItemWidget(self.bottom_item, 1, buttons)
|
||||||
|
|
||||||
def _get_group(self, name):
|
|
||||||
if name in self._groups:
|
|
||||||
return self._groups[name]
|
|
||||||
group = QtWidgets.QTreeWidgetItem([name])
|
|
||||||
for col in range(3):
|
|
||||||
group.setBackground(col, self.palette().mid())
|
|
||||||
group.setForeground(col, self.palette().brightText())
|
|
||||||
font = group.font(col)
|
|
||||||
font.setBold(True)
|
|
||||||
group.setFont(col, font)
|
|
||||||
self.addTopLevelItem(group)
|
|
||||||
self._groups[name] = group
|
|
||||||
return group
|
|
||||||
|
|
||||||
def _load_clicked(self):
|
def _load_clicked(self):
|
||||||
asyncio.ensure_future(self._dock.load_hdf5_task())
|
asyncio.ensure_future(self._dock.load_hdf5_task())
|
||||||
|
@ -130,8 +55,8 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
||||||
def _recompute_arguments_clicked(self):
|
def _recompute_arguments_clicked(self):
|
||||||
asyncio.ensure_future(self._dock._recompute_arguments())
|
asyncio.ensure_future(self._dock._recompute_arguments())
|
||||||
|
|
||||||
def _recompute_argument_clicked(self, name):
|
def reset_entry(self, key):
|
||||||
asyncio.ensure_future(self._recompute_argument(name))
|
asyncio.ensure_future(self._recompute_argument(key))
|
||||||
|
|
||||||
async def _recompute_argument(self, name):
|
async def _recompute_argument(self, name):
|
||||||
try:
|
try:
|
||||||
|
@ -146,29 +71,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
||||||
state = procdesc_to_entry(procdesc).default_state(procdesc)
|
state = procdesc_to_entry(procdesc).default_state(procdesc)
|
||||||
argument["desc"] = procdesc
|
argument["desc"] = procdesc
|
||||||
argument["state"] = state
|
argument["state"] = state
|
||||||
|
self.update_argument(name, argument)
|
||||||
widgets = self._arg_to_widgets[name]
|
|
||||||
|
|
||||||
widgets["entry"].deleteLater()
|
|
||||||
widgets["entry"] = procdesc_to_entry(procdesc)(argument)
|
|
||||||
widgets["fix_layout"] = LayoutWidget()
|
|
||||||
widgets["fix_layout"].addWidget(widgets["entry"])
|
|
||||||
self.setItemWidget(widgets["widget_item"], 1, widgets["fix_layout"])
|
|
||||||
self.updateGeometries()
|
|
||||||
|
|
||||||
def save_state(self):
|
|
||||||
expanded = []
|
|
||||||
for k, v in self._groups.items():
|
|
||||||
if v.isExpanded():
|
|
||||||
expanded.append(k)
|
|
||||||
return {"expanded": expanded}
|
|
||||||
|
|
||||||
def restore_state(self, state):
|
|
||||||
for e in state["expanded"]:
|
|
||||||
try:
|
|
||||||
self._groups[e].setExpanded(True)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||||
|
@ -183,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()
|
||||||
|
@ -223,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)
|
||||||
|
@ -277,8 +180,8 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
state = self.argeditor.save_state()
|
state = self.argeditor.save_state()
|
||||||
self.argeditor.deleteLater()
|
self.argeditor.deleteLater()
|
||||||
self.argeditor = _ArgumentEditor(self)
|
self.argeditor = _ArgumentEditor(self)
|
||||||
self.argeditor.restore_state(state)
|
|
||||||
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
||||||
|
self.argeditor.restore_state(state)
|
||||||
|
|
||||||
async def load_hdf5_task(self, filename=None):
|
async def load_hdf5_task(self, filename=None):
|
||||||
if filename is None:
|
if filename is None:
|
||||||
|
@ -413,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):
|
||||||
|
@ -466,6 +369,8 @@ class ExperimentsArea(QtWidgets.QMdiArea):
|
||||||
def initialize_submission_arguments(self, arginfo):
|
def initialize_submission_arguments(self, arginfo):
|
||||||
arguments = OrderedDict()
|
arguments = OrderedDict()
|
||||||
for name, (procdesc, group, tooltip) in arginfo.items():
|
for name, (procdesc, group, tooltip) in arginfo.items():
|
||||||
|
if procdesc["ty"] == "EnumerationValue" and procdesc["quickstyle"]:
|
||||||
|
procdesc["quickstyle"] = False
|
||||||
state = procdesc_to_entry(procdesc).default_state(procdesc)
|
state = procdesc_to_entry(procdesc).default_state(procdesc)
|
||||||
arguments[name] = {
|
arguments[name] = {
|
||||||
"desc": procdesc,
|
"desc": procdesc,
|
||||||
|
@ -501,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,35 +69,35 @@ class ZoomIconView(QtWidgets.QListView):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QtWidgets.QListView.__init__(self)
|
QtWidgets.QListView.__init__(self)
|
||||||
self._char_width = QtGui.QFontMetrics(self.font()).averageCharWidth()
|
self._char_width = QtGui.QFontMetrics(self.font()).averageCharWidth()
|
||||||
self.setViewMode(self.IconMode)
|
self.setViewMode(self.ViewMode.IconMode)
|
||||||
w = self._char_width*self.default_size
|
w = self._char_width*self.default_size
|
||||||
self.setIconSize(QtCore.QSize(w, int(w*self.aspect)))
|
self.setIconSize(QtCore.QSize(w, int(w*self.aspect)))
|
||||||
self.setFlow(self.LeftToRight)
|
self.setFlow(self.Flow.LeftToRight)
|
||||||
self.setResizeMode(self.Adjust)
|
self.setResizeMode(self.ResizeMode.Adjust)
|
||||||
self.setWrapping(True)
|
self.setWrapping(True)
|
||||||
|
|
||||||
def wheelEvent(self, ev):
|
def wheelEvent(self, ev):
|
||||||
if ev.modifiers() & QtCore.Qt.ControlModifier:
|
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
||||||
a = self._char_width*self.min_size
|
a = self._char_width*self.min_size
|
||||||
b = self._char_width*self.max_size
|
b = self._char_width*self.max_size
|
||||||
w = self.iconSize().width()*self.zoom_step**(
|
w = self.iconSize().width()*self.zoom_step**(
|
||||||
ev.angleDelta().y()/120.)
|
ev.angleDelta().y()/120.)
|
||||||
if a <= w <= b:
|
if a <= w <= b:
|
||||||
self.setIconSize(QtCore.QSize(w, w*self.aspect))
|
self.setIconSize(QtCore.QSize(int(w), int(w*self.aspect)))
|
||||||
else:
|
else:
|
||||||
QtWidgets.QListView.wheelEvent(self, ev)
|
QtWidgets.QListView.wheelEvent(self, ev)
|
||||||
|
|
||||||
|
|
||||||
class Hdf5FileSystemModel(QtWidgets.QFileSystemModel):
|
class Hdf5FileSystemModel(QtGui.QFileSystemModel):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
QtWidgets.QFileSystemModel.__init__(self)
|
QtGui.QFileSystemModel.__init__(self)
|
||||||
self.setFilter(QtCore.QDir.Drives | QtCore.QDir.NoDotAndDotDot |
|
self.setFilter(QtCore.QDir.Filter.Drives | QtCore.QDir.Filter.NoDotAndDotDot |
|
||||||
QtCore.QDir.AllDirs | QtCore.QDir.Files)
|
QtCore.QDir.Filter.AllDirs | QtCore.QDir.Filter.Files)
|
||||||
self.setNameFilterDisables(False)
|
self.setNameFilterDisables(False)
|
||||||
self.setIconProvider(ThumbnailIconProvider())
|
self.setIconProvider(ThumbnailIconProvider())
|
||||||
|
|
||||||
def data(self, idx, role):
|
def data(self, idx, role):
|
||||||
if role == QtCore.Qt.ToolTipRole:
|
if role == QtCore.Qt.ItemDataRole.ToolTipRole:
|
||||||
info = self.fileInfo(idx)
|
info = self.fileInfo(idx)
|
||||||
h5 = open_h5(info)
|
h5 = open_h5(info)
|
||||||
if h5 is not None:
|
if h5 is not None:
|
||||||
|
@ -114,7 +114,7 @@ class Hdf5FileSystemModel(QtWidgets.QFileSystemModel):
|
||||||
except:
|
except:
|
||||||
logger.warning("unable to read metadata from %s",
|
logger.warning("unable to read metadata from %s",
|
||||||
info.filePath(), exc_info=True)
|
info.filePath(), exc_info=True)
|
||||||
return QtWidgets.QFileSystemModel.data(self, idx, role)
|
return QtGui.QFileSystemModel.data(self, idx, role)
|
||||||
|
|
||||||
|
|
||||||
class FilesDock(QtWidgets.QDockWidget):
|
class FilesDock(QtWidgets.QDockWidget):
|
||||||
|
@ -125,7 +125,7 @@ class FilesDock(QtWidgets.QDockWidget):
|
||||||
def __init__(self, datasets, browse_root=""):
|
def __init__(self, datasets, browse_root=""):
|
||||||
QtWidgets.QDockWidget.__init__(self, "Files")
|
QtWidgets.QDockWidget.__init__(self, "Files")
|
||||||
self.setObjectName("Files")
|
self.setObjectName("Files")
|
||||||
self.setFeatures(self.DockWidgetMovable | self.DockWidgetFloatable)
|
self.setFeatures(self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable)
|
||||||
|
|
||||||
self.splitter = QtWidgets.QSplitter()
|
self.splitter = QtWidgets.QSplitter()
|
||||||
self.setWidget(self.splitter)
|
self.setWidget(self.splitter)
|
||||||
|
@ -147,8 +147,8 @@ class FilesDock(QtWidgets.QDockWidget):
|
||||||
self.rt.setRootIndex(rt_model.mapFromSource(
|
self.rt.setRootIndex(rt_model.mapFromSource(
|
||||||
self.model.setRootPath(browse_root)))
|
self.model.setRootPath(browse_root)))
|
||||||
self.rt.setHeaderHidden(True)
|
self.rt.setHeaderHidden(True)
|
||||||
self.rt.setSelectionBehavior(self.rt.SelectRows)
|
self.rt.setSelectionBehavior(self.rt.SelectionBehavior.SelectRows)
|
||||||
self.rt.setSelectionMode(self.rt.SingleSelection)
|
self.rt.setSelectionMode(self.rt.SelectionMode.SingleSelection)
|
||||||
self.rt.selectionModel().currentChanged.connect(
|
self.rt.selectionModel().currentChanged.connect(
|
||||||
self.tree_current_changed)
|
self.tree_current_changed)
|
||||||
self.rt.setRootIsDecorated(False)
|
self.rt.setRootIsDecorated(False)
|
||||||
|
@ -252,7 +252,7 @@ class FilesDock(QtWidgets.QDockWidget):
|
||||||
100,
|
100,
|
||||||
lambda: self.rt.scrollTo(
|
lambda: self.rt.scrollTo(
|
||||||
self.rt.model().mapFromSource(self.model.index(path)),
|
self.rt.model().mapFromSource(self.model.index(path)),
|
||||||
self.rt.PositionAtCenter)
|
self.rt.ScrollHint.PositionAtCenter)
|
||||||
)
|
)
|
||||||
self.model.directoryLoaded.connect(scroll_when_loaded)
|
self.model.directoryLoaded.connect(scroll_when_loaded)
|
||||||
idx = self.rt.model().mapFromSource(idx)
|
idx = self.rt.model().mapFromSource(idx)
|
||||||
|
|
|
@ -253,6 +253,12 @@ def fn_subkernel_await():
|
||||||
def fn_subkernel_preload():
|
def fn_subkernel_preload():
|
||||||
return types.TBuiltinFunction("subkernel_preload")
|
return types.TBuiltinFunction("subkernel_preload")
|
||||||
|
|
||||||
|
def fn_subkernel_send():
|
||||||
|
return types.TBuiltinFunction("subkernel_send")
|
||||||
|
|
||||||
|
def fn_subkernel_recv():
|
||||||
|
return types.TBuiltinFunction("subkernel_recv")
|
||||||
|
|
||||||
# Accessors
|
# Accessors
|
||||||
|
|
||||||
def is_none(typ):
|
def is_none(typ):
|
||||||
|
|
|
@ -47,8 +47,15 @@ class SpecializedFunction:
|
||||||
return hash((self.instance_type, self.host_function))
|
return hash((self.instance_type, self.host_function))
|
||||||
|
|
||||||
|
|
||||||
|
class SubkernelMessageType:
|
||||||
|
def __init__(self, name, value_type):
|
||||||
|
self.name = name
|
||||||
|
self.value_type = value_type
|
||||||
|
self.send_loc = None
|
||||||
|
self.recv_loc = None
|
||||||
|
|
||||||
class EmbeddingMap:
|
class EmbeddingMap:
|
||||||
def __init__(self):
|
def __init__(self, old_embedding_map=None):
|
||||||
self.object_current_key = 0
|
self.object_current_key = 0
|
||||||
self.object_forward_map = {}
|
self.object_forward_map = {}
|
||||||
self.object_reverse_map = {}
|
self.object_reverse_map = {}
|
||||||
|
@ -65,6 +72,22 @@ class EmbeddingMap:
|
||||||
self.str_forward_map = {}
|
self.str_forward_map = {}
|
||||||
self.str_reverse_map = {}
|
self.str_reverse_map = {}
|
||||||
|
|
||||||
|
# mapping `name` to object ID
|
||||||
|
self.subkernel_message_map = {}
|
||||||
|
|
||||||
|
# subkernels: dict of ID: function, just like object_forward_map
|
||||||
|
# allow the embedding map to be aware of subkernels from other kernels
|
||||||
|
if not old_embedding_map is None:
|
||||||
|
for key, obj_ref in old_embedding_map.subkernels().items():
|
||||||
|
self.object_forward_map[key] = obj_ref
|
||||||
|
obj_id = id(obj_ref)
|
||||||
|
self.object_reverse_map[obj_id] = key
|
||||||
|
for msg_id, msg_type in old_embedding_map.subkernel_messages().items():
|
||||||
|
self.object_forward_map[msg_id] = msg_type
|
||||||
|
obj_id = id(msg_type)
|
||||||
|
self.subkernel_message_map[msg_type.name] = msg_id
|
||||||
|
self.object_reverse_map[obj_id] = msg_id
|
||||||
|
|
||||||
self.preallocate_runtime_exception_names(["RuntimeError",
|
self.preallocate_runtime_exception_names(["RuntimeError",
|
||||||
"RTIOUnderflow",
|
"RTIOUnderflow",
|
||||||
"RTIOOverflow",
|
"RTIOOverflow",
|
||||||
|
@ -165,6 +188,11 @@ class EmbeddingMap:
|
||||||
return self.object_reverse_map[obj_id]
|
return self.object_reverse_map[obj_id]
|
||||||
|
|
||||||
self.object_current_key += 1
|
self.object_current_key += 1
|
||||||
|
while self.object_forward_map.get(self.object_current_key):
|
||||||
|
# make sure there's no collisions with previously inserted subkernels
|
||||||
|
# their identifiers must be consistent across all kernels/subkernels
|
||||||
|
self.object_current_key += 1
|
||||||
|
|
||||||
self.object_forward_map[self.object_current_key] = obj_ref
|
self.object_forward_map[self.object_current_key] = obj_ref
|
||||||
self.object_reverse_map[obj_id] = self.object_current_key
|
self.object_reverse_map[obj_id] = self.object_current_key
|
||||||
return self.object_current_key
|
return self.object_current_key
|
||||||
|
@ -177,7 +205,7 @@ class EmbeddingMap:
|
||||||
obj_ref = self.object_forward_map[obj_id]
|
obj_ref = self.object_forward_map[obj_id]
|
||||||
if isinstance(obj_ref, (pytypes.FunctionType, pytypes.MethodType,
|
if isinstance(obj_ref, (pytypes.FunctionType, pytypes.MethodType,
|
||||||
pytypes.BuiltinFunctionType, pytypes.ModuleType,
|
pytypes.BuiltinFunctionType, pytypes.ModuleType,
|
||||||
SpecializedFunction)):
|
SpecializedFunction, SubkernelMessageType)):
|
||||||
continue
|
continue
|
||||||
elif isinstance(obj_ref, type):
|
elif isinstance(obj_ref, type):
|
||||||
_, obj_typ = self.type_map[obj_ref]
|
_, obj_typ = self.type_map[obj_ref]
|
||||||
|
@ -193,6 +221,35 @@ class EmbeddingMap:
|
||||||
subkernels[k] = v
|
subkernels[k] = v
|
||||||
return subkernels
|
return subkernels
|
||||||
|
|
||||||
|
def store_subkernel_message(self, name, value_type, function_type, function_loc):
|
||||||
|
if name in self.subkernel_message_map:
|
||||||
|
msg_id = self.subkernel_message_map[name]
|
||||||
|
else:
|
||||||
|
msg_id = self.store_object(SubkernelMessageType(name, value_type))
|
||||||
|
self.subkernel_message_map[name] = msg_id
|
||||||
|
subkernel_msg = self.retrieve_object(msg_id)
|
||||||
|
if function_type == "send":
|
||||||
|
subkernel_msg.send_loc = function_loc
|
||||||
|
elif function_type == "recv":
|
||||||
|
subkernel_msg.recv_loc = function_loc
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
return msg_id, subkernel_msg
|
||||||
|
|
||||||
|
def subkernel_messages(self):
|
||||||
|
messages = {}
|
||||||
|
for msg_id in self.subkernel_message_map.values():
|
||||||
|
messages[msg_id] = self.retrieve_object(msg_id)
|
||||||
|
return messages
|
||||||
|
|
||||||
|
def subkernel_messages_unpaired(self):
|
||||||
|
unpaired = []
|
||||||
|
for msg_id in self.subkernel_message_map.values():
|
||||||
|
msg_obj = self.retrieve_object(msg_id)
|
||||||
|
if msg_obj.send_loc is None or msg_obj.recv_loc is None:
|
||||||
|
unpaired.append(msg_obj)
|
||||||
|
return unpaired
|
||||||
|
|
||||||
def has_rpc(self):
|
def has_rpc(self):
|
||||||
return any(filter(
|
return any(filter(
|
||||||
lambda x: (inspect.isfunction(x) or inspect.ismethod(x)) and \
|
lambda x: (inspect.isfunction(x) or inspect.ismethod(x)) and \
|
||||||
|
@ -200,10 +257,6 @@ class EmbeddingMap:
|
||||||
self.object_forward_map.values()
|
self.object_forward_map.values()
|
||||||
))
|
))
|
||||||
|
|
||||||
def has_rpc_or_subkernel(self):
|
|
||||||
return any(filter(lambda x: inspect.isfunction(x) or inspect.ismethod(x),
|
|
||||||
self.object_forward_map.values()))
|
|
||||||
|
|
||||||
|
|
||||||
class ASTSynthesizer:
|
class ASTSynthesizer:
|
||||||
def __init__(self, embedding_map, value_map, quote_function=None, expanded_from=None):
|
def __init__(self, embedding_map, value_map, quote_function=None, expanded_from=None):
|
||||||
|
@ -694,9 +747,9 @@ class StitchingInferencer(Inferencer):
|
||||||
if elt.__class__ == float:
|
if elt.__class__ == float:
|
||||||
state |= IS_FLOAT
|
state |= IS_FLOAT
|
||||||
elif elt.__class__ == int:
|
elif elt.__class__ == int:
|
||||||
if -2**31 < elt < 2**31-1:
|
if -2**31 <= elt <= 2**31-1:
|
||||||
state |= IS_INT32
|
state |= IS_INT32
|
||||||
elif -2**63 < elt < 2**63-1:
|
elif -2**63 <= elt <= 2**63-1:
|
||||||
state |= IS_INT64
|
state |= IS_INT64
|
||||||
else:
|
else:
|
||||||
state = -1
|
state = -1
|
||||||
|
@ -794,7 +847,7 @@ class TypedtreeHasher(algorithm.Visitor):
|
||||||
return hash(tuple(freeze(getattr(node, field_name)) for field_name in fields))
|
return hash(tuple(freeze(getattr(node, field_name)) for field_name in fields))
|
||||||
|
|
||||||
class Stitcher:
|
class Stitcher:
|
||||||
def __init__(self, core, dmgr, engine=None, print_as_rpc=True, destination=0, subkernel_arg_types=[]):
|
def __init__(self, core, dmgr, engine=None, print_as_rpc=True, destination=0, subkernel_arg_types=[], old_embedding_map=None):
|
||||||
self.core = core
|
self.core = core
|
||||||
self.dmgr = dmgr
|
self.dmgr = dmgr
|
||||||
if engine is None:
|
if engine is None:
|
||||||
|
@ -816,7 +869,7 @@ class Stitcher:
|
||||||
|
|
||||||
self.functions = {}
|
self.functions = {}
|
||||||
|
|
||||||
self.embedding_map = EmbeddingMap()
|
self.embedding_map = EmbeddingMap(old_embedding_map)
|
||||||
self.value_map = defaultdict(lambda: [])
|
self.value_map = defaultdict(lambda: [])
|
||||||
self.definitely_changed = False
|
self.definitely_changed = False
|
||||||
|
|
||||||
|
|
|
@ -1047,6 +1047,42 @@ class Builtin(Instruction):
|
||||||
def opcode(self):
|
def opcode(self):
|
||||||
return "builtin({})".format(self.op)
|
return "builtin({})".format(self.op)
|
||||||
|
|
||||||
|
class BuiltinInvoke(Terminator):
|
||||||
|
"""
|
||||||
|
A builtin operation which can raise exceptions.
|
||||||
|
|
||||||
|
:ivar op: (string) operation name
|
||||||
|
"""
|
||||||
|
|
||||||
|
"""
|
||||||
|
:param op: (string) operation name
|
||||||
|
:param normal: (:class:`BasicBlock`) normal target
|
||||||
|
:param exn: (:class:`BasicBlock`) exceptional target
|
||||||
|
"""
|
||||||
|
def __init__(self, op, operands, typ, normal, exn, name=None):
|
||||||
|
assert isinstance(op, str)
|
||||||
|
for operand in operands: assert isinstance(operand, Value)
|
||||||
|
assert isinstance(normal, BasicBlock)
|
||||||
|
assert isinstance(exn, BasicBlock)
|
||||||
|
if name is None:
|
||||||
|
name = "BLTINV.{}".format(op)
|
||||||
|
super().__init__(operands + [normal, exn], typ, name)
|
||||||
|
self.op = op
|
||||||
|
|
||||||
|
def copy(self, mapper):
|
||||||
|
self_copy = super().copy(mapper)
|
||||||
|
self_copy.op = self.op
|
||||||
|
return self_copy
|
||||||
|
|
||||||
|
def normal_target(self):
|
||||||
|
return self.operands[-2]
|
||||||
|
|
||||||
|
def exception_target(self):
|
||||||
|
return self.operands[-1]
|
||||||
|
|
||||||
|
def opcode(self):
|
||||||
|
return "builtinInvokable({})".format(self.op)
|
||||||
|
|
||||||
class Closure(Instruction):
|
class Closure(Instruction):
|
||||||
"""
|
"""
|
||||||
A closure creation operation.
|
A closure creation operation.
|
||||||
|
|
|
@ -61,22 +61,6 @@ unary_fp_runtime_calls = [
|
||||||
("cbrt", "cbrt"),
|
("cbrt", "cbrt"),
|
||||||
]
|
]
|
||||||
|
|
||||||
#: float -> float numpy.* math functions lowered to runtime calls.
|
|
||||||
unary_fp_runtime_calls = [
|
|
||||||
("tan", "tan"),
|
|
||||||
("arcsin", "asin"),
|
|
||||||
("arccos", "acos"),
|
|
||||||
("arctan", "atan"),
|
|
||||||
("sinh", "sinh"),
|
|
||||||
("cosh", "cosh"),
|
|
||||||
("tanh", "tanh"),
|
|
||||||
("arcsinh", "asinh"),
|
|
||||||
("arccosh", "acosh"),
|
|
||||||
("arctanh", "atanh"),
|
|
||||||
("expm1", "expm1"),
|
|
||||||
("cbrt", "cbrt"),
|
|
||||||
]
|
|
||||||
|
|
||||||
scipy_special_unary_runtime_calls = [
|
scipy_special_unary_runtime_calls = [
|
||||||
("erf", "erf"),
|
("erf", "erf"),
|
||||||
("erfc", "erfc"),
|
("erfc", "erfc"),
|
||||||
|
|
|
@ -59,4 +59,6 @@ def globals():
|
||||||
# ARTIQ subkernel utility functions
|
# ARTIQ subkernel utility functions
|
||||||
"subkernel_await": builtins.fn_subkernel_await(),
|
"subkernel_await": builtins.fn_subkernel_await(),
|
||||||
"subkernel_preload": builtins.fn_subkernel_preload(),
|
"subkernel_preload": builtins.fn_subkernel_preload(),
|
||||||
|
"subkernel_send": builtins.fn_subkernel_send(),
|
||||||
|
"subkernel_recv": builtins.fn_subkernel_recv(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -2537,17 +2537,29 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
||||||
timeout = self.visit(node.args[1])
|
timeout = self.visit(node.args[1])
|
||||||
elif len(node.args) == 1 and len(node.keywords) == 0:
|
elif len(node.args) == 1 and len(node.keywords) == 0:
|
||||||
fn = node.args[0].type
|
fn = node.args[0].type
|
||||||
timeout = ir.Constant(10_000, builtins.TInt64())
|
timeout = ir.Constant(-1, builtins.TInt64())
|
||||||
else:
|
else:
|
||||||
assert False
|
assert False
|
||||||
if types.is_method(fn):
|
if types.is_method(fn):
|
||||||
fn = types.get_method_function(fn)
|
fn = types.get_method_function(fn)
|
||||||
sid = ir.Constant(fn.sid, builtins.TInt32())
|
sid = ir.Constant(fn.sid, builtins.TInt32())
|
||||||
if not builtins.is_none(fn.ret):
|
if not builtins.is_none(fn.ret):
|
||||||
ret = self.append(ir.Builtin("subkernel_retrieve_return", [sid, timeout], fn.ret))
|
if self.unwind_target is None:
|
||||||
|
ret = self.append(ir.Builtin("subkernel_retrieve_return", [sid, timeout], fn.ret))
|
||||||
|
else:
|
||||||
|
after_invoke = self.add_block("invoke")
|
||||||
|
ret = self.append(ir.BuiltinInvoke("subkernel_retrieve_return", [sid, timeout],
|
||||||
|
fn.ret, after_invoke, self.unwind_target))
|
||||||
|
self.current_block = after_invoke
|
||||||
else:
|
else:
|
||||||
ret = ir.Constant(None, builtins.TNone())
|
ret = ir.Constant(None, builtins.TNone())
|
||||||
self.append(ir.Builtin("subkernel_await_finish", [sid, timeout], builtins.TNone()))
|
if self.unwind_target is None:
|
||||||
|
self.append(ir.Builtin("subkernel_await_finish", [sid, timeout], builtins.TNone()))
|
||||||
|
else:
|
||||||
|
after_invoke = self.add_block("invoke")
|
||||||
|
self.append(ir.BuiltinInvoke("subkernel_await_finish", [sid, timeout],
|
||||||
|
builtins.TNone(), after_invoke, self.unwind_target))
|
||||||
|
self.current_block = after_invoke
|
||||||
return ret
|
return ret
|
||||||
elif types.is_builtin(typ, "subkernel_preload"):
|
elif types.is_builtin(typ, "subkernel_preload"):
|
||||||
if len(node.args) == 1 and len(node.keywords) == 0:
|
if len(node.args) == 1 and len(node.keywords) == 0:
|
||||||
|
@ -2557,7 +2569,51 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
||||||
if types.is_method(fn):
|
if types.is_method(fn):
|
||||||
fn = types.get_method_function(fn)
|
fn = types.get_method_function(fn)
|
||||||
sid = ir.Constant(fn.sid, builtins.TInt32())
|
sid = ir.Constant(fn.sid, builtins.TInt32())
|
||||||
return self.append(ir.Builtin("subkernel_preload", [sid], builtins.TNone()))
|
dest = ir.Constant(fn.destination, builtins.TInt32())
|
||||||
|
return self.append(ir.Builtin("subkernel_preload", [sid, dest], builtins.TNone()))
|
||||||
|
elif types.is_builtin(typ, "subkernel_send"):
|
||||||
|
if len(node.args) == 3 and len(node.keywords) == 0:
|
||||||
|
dest = self.visit(node.args[0])
|
||||||
|
name = node.args[1].s
|
||||||
|
value = self.visit(node.args[2])
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
msg_id, msg = self.embedding_map.store_subkernel_message(name, value.type, "send", node.loc)
|
||||||
|
msg_id = ir.Constant(msg_id, builtins.TInt32())
|
||||||
|
if value.type != msg.value_type:
|
||||||
|
diag = diagnostic.Diagnostic("error",
|
||||||
|
"type mismatch for subkernel message '{name}', receiver expects {recv} while sending {send}",
|
||||||
|
{"name": name, "recv": msg.value_type, "send": value.type},
|
||||||
|
node.loc)
|
||||||
|
self.engine.process(diag)
|
||||||
|
return self.append(ir.Builtin("subkernel_send", [msg_id, dest, value], builtins.TNone()))
|
||||||
|
elif types.is_builtin(typ, "subkernel_recv"):
|
||||||
|
if len(node.args) == 2 and len(node.keywords) == 0:
|
||||||
|
name = node.args[0].s
|
||||||
|
vartype = node.args[1].value
|
||||||
|
timeout = ir.Constant(-1, builtins.TInt64())
|
||||||
|
elif len(node.args) == 3 and len(node.keywords) == 0:
|
||||||
|
name = node.args[0].s
|
||||||
|
vartype = node.args[1].value
|
||||||
|
timeout = self.visit(node.args[2])
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
msg_id, msg = self.embedding_map.store_subkernel_message(name, vartype, "recv", node.loc)
|
||||||
|
msg_id = ir.Constant(msg_id, builtins.TInt32())
|
||||||
|
if vartype != msg.value_type:
|
||||||
|
diag = diagnostic.Diagnostic("error",
|
||||||
|
"type mismatch for subkernel message '{name}', receiver expects {recv} while sending {send}",
|
||||||
|
{"name": name, "recv": vartype, "send": msg.value_type},
|
||||||
|
node.loc)
|
||||||
|
self.engine.process(diag)
|
||||||
|
if self.unwind_target is None:
|
||||||
|
ret = self.append(ir.Builtin("subkernel_recv", [msg_id, timeout], vartype))
|
||||||
|
else:
|
||||||
|
after_invoke = self.add_block("invoke")
|
||||||
|
ret = self.append(ir.BuiltinInvoke("subkernel_recv", [msg_id, timeout],
|
||||||
|
vartype, after_invoke, self.unwind_target))
|
||||||
|
self.current_block = after_invoke
|
||||||
|
return ret
|
||||||
elif types.is_exn_constructor(typ):
|
elif types.is_exn_constructor(typ):
|
||||||
return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args])
|
return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args])
|
||||||
elif types.is_constructor(typ):
|
elif types.is_constructor(typ):
|
||||||
|
@ -2909,7 +2965,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
||||||
format_string += ")"
|
format_string += ")"
|
||||||
elif builtins.is_exception(value.type):
|
elif builtins.is_exception(value.type):
|
||||||
# message may not be an actual string...
|
# message may not be an actual string...
|
||||||
# so we cannot really print it
|
# so we cannot really print itInvoke
|
||||||
name = self.append(ir.GetAttr(value, "#__name__"))
|
name = self.append(ir.GetAttr(value, "#__name__"))
|
||||||
param1 = self.append(ir.GetAttr(value, "#__param0__"))
|
param1 = self.append(ir.GetAttr(value, "#__param0__"))
|
||||||
param2 = self.append(ir.GetAttr(value, "#__param1__"))
|
param2 = self.append(ir.GetAttr(value, "#__param1__"))
|
||||||
|
|
|
@ -1343,6 +1343,57 @@ class Inferencer(algorithm.Visitor):
|
||||||
node.loc, None)
|
node.loc, None)
|
||||||
else:
|
else:
|
||||||
diagnose(valid_forms())
|
diagnose(valid_forms())
|
||||||
|
elif types.is_builtin(typ, "subkernel_send"):
|
||||||
|
valid_forms = lambda: [
|
||||||
|
valid_form("subkernel_send(dest: numpy.int?, name: str, value: V) -> None"),
|
||||||
|
]
|
||||||
|
self._unify(node.type, builtins.TNone(),
|
||||||
|
node.loc, None)
|
||||||
|
if len(node.args) == 3:
|
||||||
|
arg0 = node.args[0]
|
||||||
|
if types.is_var(arg0.type):
|
||||||
|
pass # undetermined yet
|
||||||
|
else:
|
||||||
|
if builtins.is_int(arg0.type):
|
||||||
|
self._unify(arg0.type, builtins.TInt8(),
|
||||||
|
arg0.loc, None)
|
||||||
|
else:
|
||||||
|
diagnose(valid_forms())
|
||||||
|
arg1 = node.args[1]
|
||||||
|
self._unify(arg1.type, builtins.TStr(),
|
||||||
|
arg1.loc, None)
|
||||||
|
else:
|
||||||
|
diagnose(valid_forms())
|
||||||
|
elif types.is_builtin(typ, "subkernel_recv"):
|
||||||
|
valid_forms = lambda: [
|
||||||
|
valid_form("subkernel_recv(name: str, value_type: type) -> value_type"),
|
||||||
|
valid_form("subkernel_recv(name: str, value_type: type, timeout: numpy.int64) -> value_type"),
|
||||||
|
]
|
||||||
|
if 2 <= len(node.args) <= 3:
|
||||||
|
arg0 = node.args[0]
|
||||||
|
if types.is_var(arg0.type):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self._unify(arg0.type, builtins.TStr(),
|
||||||
|
arg0.loc, None)
|
||||||
|
arg1 = node.args[1]
|
||||||
|
if types.is_var(arg1.type):
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self._unify(node.type, arg1.value,
|
||||||
|
node.loc, None)
|
||||||
|
if len(node.args) == 3:
|
||||||
|
arg2 = node.args[2]
|
||||||
|
if types.is_var(arg2.type):
|
||||||
|
pass
|
||||||
|
elif builtins.is_int(arg2.type):
|
||||||
|
# promote to TInt64
|
||||||
|
self._unify(arg2.type, builtins.TInt64(),
|
||||||
|
arg2.loc, None)
|
||||||
|
else:
|
||||||
|
diagnose(valid_forms())
|
||||||
|
else:
|
||||||
|
diagnose(valid_forms())
|
||||||
else:
|
else:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,9 @@ class IntMonomorphizer(algorithm.Visitor):
|
||||||
def visit_NumT(self, node):
|
def visit_NumT(self, node):
|
||||||
if builtins.is_int(node.type):
|
if builtins.is_int(node.type):
|
||||||
if types.is_var(node.type["width"]):
|
if types.is_var(node.type["width"]):
|
||||||
if -2**31 < node.n < 2**31-1:
|
if -2**31 <= node.n <= 2**31-1:
|
||||||
width = 32
|
width = 32
|
||||||
elif -2**63 < node.n < 2**63-1:
|
elif -2**63 <= node.n <= 2**63-1:
|
||||||
width = 64
|
width = 64
|
||||||
else:
|
else:
|
||||||
diag = diagnostic.Diagnostic("error",
|
diag = diagnostic.Diagnostic("error",
|
||||||
|
|
|
@ -399,9 +399,9 @@ class LLVMIRGenerator:
|
||||||
llty = ll.FunctionType(lli32, [llptr])
|
llty = ll.FunctionType(lli32, [llptr])
|
||||||
|
|
||||||
elif name == "subkernel_send_message":
|
elif name == "subkernel_send_message":
|
||||||
llty = ll.FunctionType(llvoid, [lli32, lli8, llsliceptr, llptrptr])
|
llty = ll.FunctionType(llvoid, [lli32, lli1, lli8, lli8, llsliceptr, llptrptr])
|
||||||
elif name == "subkernel_load_run":
|
elif name == "subkernel_load_run":
|
||||||
llty = ll.FunctionType(llvoid, [lli32, lli1])
|
llty = ll.FunctionType(llvoid, [lli32, lli8, lli1])
|
||||||
elif name == "subkernel_await_finish":
|
elif name == "subkernel_await_finish":
|
||||||
llty = ll.FunctionType(llvoid, [lli32, lli64])
|
llty = ll.FunctionType(llvoid, [lli32, lli64])
|
||||||
elif name == "subkernel_await_message":
|
elif name == "subkernel_await_message":
|
||||||
|
@ -1417,8 +1417,61 @@ class LLVMIRGenerator:
|
||||||
return self._build_rpc_recv(insn.type, llstackptr)
|
return self._build_rpc_recv(insn.type, llstackptr)
|
||||||
elif insn.op == "subkernel_preload":
|
elif insn.op == "subkernel_preload":
|
||||||
llsid = self.map(insn.operands[0])
|
llsid = self.map(insn.operands[0])
|
||||||
return self.llbuilder.call(self.llbuiltin("subkernel_load_run"), [llsid, ll.Constant(lli1, 0)],
|
lldest = ll.Constant(lli8, insn.operands[1].value)
|
||||||
|
return self.llbuilder.call(self.llbuiltin("subkernel_load_run"), [llsid, lldest, ll.Constant(lli1, 0)],
|
||||||
name="subkernel.preload")
|
name="subkernel.preload")
|
||||||
|
elif insn.op == "subkernel_send":
|
||||||
|
llmsgid = self.map(insn.operands[0])
|
||||||
|
lldest = self.map(insn.operands[1])
|
||||||
|
return self._build_subkernel_message(llmsgid, lldest, [insn.operands[2]])
|
||||||
|
elif insn.op == "subkernel_recv":
|
||||||
|
llmsgid = self.map(insn.operands[0])
|
||||||
|
lltimeout = self.map(insn.operands[1])
|
||||||
|
lltagptr = self._build_subkernel_tags([insn.type])
|
||||||
|
self.llbuilder.call(self.llbuiltin("subkernel_await_message"),
|
||||||
|
[llmsgid, lltimeout, lltagptr, ll.Constant(lli8, 1), ll.Constant(lli8, 1)],
|
||||||
|
name="subkernel.await.message")
|
||||||
|
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
|
||||||
|
name="subkernel.arg.stack")
|
||||||
|
return self._build_rpc_recv(insn.type, llstackptr)
|
||||||
|
else:
|
||||||
|
assert False
|
||||||
|
|
||||||
|
def process_BuiltinInvoke(self, insn):
|
||||||
|
llnormalblock = self.map(insn.normal_target())
|
||||||
|
llunwindblock = self.map(insn.exception_target())
|
||||||
|
if insn.op == "subkernel_retrieve_return":
|
||||||
|
llsid = self.map(insn.operands[0])
|
||||||
|
lltimeout = self.map(insn.operands[1])
|
||||||
|
lltagptr = self._build_subkernel_tags([insn.type])
|
||||||
|
llheadu = self.llbuilder.append_basic_block(name="subkernel.await.unwind")
|
||||||
|
self.llbuilder.invoke(self.llbuiltin("subkernel_await_message"),
|
||||||
|
[llsid, lltimeout, lltagptr, ll.Constant(lli8, 1), ll.Constant(lli8, 1)],
|
||||||
|
llheadu, llunwindblock,
|
||||||
|
name="subkernel.await.message")
|
||||||
|
self.llbuilder.position_at_end(llheadu)
|
||||||
|
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
|
||||||
|
name="subkernel.arg.stack")
|
||||||
|
return self._build_rpc_recv(insn.type, llstackptr, llnormalblock, llunwindblock)
|
||||||
|
elif insn.op == "subkernel_await_finish":
|
||||||
|
llsid = self.map(insn.operands[0])
|
||||||
|
lltimeout = self.map(insn.operands[1])
|
||||||
|
return self.llbuilder.invoke(self.llbuiltin("subkernel_await_finish"), [llsid, lltimeout],
|
||||||
|
llnormalblock, llunwindblock,
|
||||||
|
name="subkernel.await.finish")
|
||||||
|
elif insn.op == "subkernel_recv":
|
||||||
|
llmsgid = self.map(insn.operands[0])
|
||||||
|
lltimeout = self.map(insn.operands[1])
|
||||||
|
lltagptr = self._build_subkernel_tags([insn.type])
|
||||||
|
llheadu = self.llbuilder.append_basic_block(name="subkernel.await.unwind")
|
||||||
|
self.llbuilder.invoke(self.llbuiltin("subkernel_await_message"),
|
||||||
|
[llmsgid, lltimeout, lltagptr, ll.Constant(lli8, 1), ll.Constant(lli8, 1)],
|
||||||
|
llheadu, llunwindblock,
|
||||||
|
name="subkernel.await.message")
|
||||||
|
self.llbuilder.position_at_end(llheadu)
|
||||||
|
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
|
||||||
|
name="subkernel.arg.stack")
|
||||||
|
return self._build_rpc_recv(insn.type, llstackptr, llnormalblock, llunwindblock)
|
||||||
else:
|
else:
|
||||||
assert False
|
assert False
|
||||||
|
|
||||||
|
@ -1427,7 +1480,7 @@ class LLVMIRGenerator:
|
||||||
llmax = self.map(insn.operands[1])
|
llmax = self.map(insn.operands[1])
|
||||||
lltagptr = self._build_subkernel_tags(insn.arg_types)
|
lltagptr = self._build_subkernel_tags(insn.arg_types)
|
||||||
return self.llbuilder.call(self.llbuiltin("subkernel_await_message"),
|
return self.llbuilder.call(self.llbuiltin("subkernel_await_message"),
|
||||||
[ll.Constant(lli32, 0), ll.Constant(lli64, 10_000), lltagptr, llmin, llmax],
|
[ll.Constant(lli32, -1), ll.Constant(lli64, 10_000), lltagptr, llmin, llmax],
|
||||||
name="subkernel.await.args")
|
name="subkernel.await.args")
|
||||||
|
|
||||||
def process_Closure(self, insn):
|
def process_Closure(self, insn):
|
||||||
|
@ -1579,11 +1632,8 @@ class LLVMIRGenerator:
|
||||||
self.llbuilder.branch(llnormalblock)
|
self.llbuilder.branch(llnormalblock)
|
||||||
return llret
|
return llret
|
||||||
|
|
||||||
def _build_rpc(self, fun_loc, fun_type, args, llnormalblock, llunwindblock):
|
def _build_arg_tag(self, args, call_type):
|
||||||
llservice = ll.Constant(lli32, fun_type.service)
|
|
||||||
|
|
||||||
tag = b""
|
tag = b""
|
||||||
|
|
||||||
for arg in args:
|
for arg in args:
|
||||||
def arg_error_handler(typ):
|
def arg_error_handler(typ):
|
||||||
printer = types.TypePrinter()
|
printer = types.TypePrinter()
|
||||||
|
@ -1592,12 +1642,18 @@ class LLVMIRGenerator:
|
||||||
{"type": printer.name(typ)},
|
{"type": printer.name(typ)},
|
||||||
arg.loc)
|
arg.loc)
|
||||||
diag = diagnostic.Diagnostic("error",
|
diag = diagnostic.Diagnostic("error",
|
||||||
"type {type} is not supported in remote procedure calls",
|
"type {type} is not supported in {call_type} calls",
|
||||||
{"type": printer.name(arg.type)},
|
{"type": printer.name(arg.type), "call_type": call_type},
|
||||||
arg.loc, notes=[note])
|
arg.loc, notes=[note])
|
||||||
self.engine.process(diag)
|
self.engine.process(diag)
|
||||||
tag += ir.rpc_tag(arg.type, arg_error_handler)
|
tag += ir.rpc_tag(arg.type, arg_error_handler)
|
||||||
tag += b":"
|
tag += b":"
|
||||||
|
return tag
|
||||||
|
|
||||||
|
def _build_rpc(self, fun_loc, fun_type, args, llnormalblock, llunwindblock):
|
||||||
|
llservice = ll.Constant(lli32, fun_type.service)
|
||||||
|
|
||||||
|
tag = self._build_arg_tag(args, call_type="remote procedure")
|
||||||
|
|
||||||
def ret_error_handler(typ):
|
def ret_error_handler(typ):
|
||||||
printer = types.TypePrinter()
|
printer = types.TypePrinter()
|
||||||
|
@ -1660,59 +1716,48 @@ class LLVMIRGenerator:
|
||||||
|
|
||||||
def _build_subkernel_call(self, fun_loc, fun_type, args):
|
def _build_subkernel_call(self, fun_loc, fun_type, args):
|
||||||
llsid = ll.Constant(lli32, fun_type.sid)
|
llsid = ll.Constant(lli32, fun_type.sid)
|
||||||
tag = b""
|
lldest = ll.Constant(lli8, fun_type.destination)
|
||||||
|
|
||||||
for arg in args:
|
|
||||||
def arg_error_handler(typ):
|
|
||||||
printer = types.TypePrinter()
|
|
||||||
note = diagnostic.Diagnostic("note",
|
|
||||||
"value of type {type}",
|
|
||||||
{"type": printer.name(typ)},
|
|
||||||
arg.loc)
|
|
||||||
diag = diagnostic.Diagnostic("error",
|
|
||||||
"type {type} is not supported in subkernel calls",
|
|
||||||
{"type": printer.name(arg.type)},
|
|
||||||
arg.loc, notes=[note])
|
|
||||||
self.engine.process(diag)
|
|
||||||
tag += ir.rpc_tag(arg.type, arg_error_handler)
|
|
||||||
tag += b":"
|
|
||||||
|
|
||||||
# run the kernel first
|
# run the kernel first
|
||||||
self.llbuilder.call(self.llbuiltin("subkernel_load_run"), [llsid, ll.Constant(lli1, 1)])
|
self.llbuilder.call(self.llbuiltin("subkernel_load_run"), [llsid, lldest, ll.Constant(lli1, 1)])
|
||||||
|
|
||||||
|
if args:
|
||||||
|
# only send args if there's anything to send, 'self' is excluded
|
||||||
|
self._build_subkernel_message(llsid, lldest, args)
|
||||||
|
|
||||||
|
return llsid
|
||||||
|
|
||||||
|
def _build_subkernel_message(self, llid, lldest, args):
|
||||||
|
# args (or messages) are sent in the same vein as RPC
|
||||||
|
tag = self._build_arg_tag(args, call_type="subkernel")
|
||||||
|
|
||||||
# arg sent in the same vein as RPC
|
|
||||||
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
|
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
|
||||||
name="subkernel.stack")
|
name="subkernel.stack")
|
||||||
|
|
||||||
lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr()))
|
lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr()))
|
||||||
lltagptr = self.llbuilder.alloca(lltag.type)
|
lltagptr = self.llbuilder.alloca(lltag.type)
|
||||||
self.llbuilder.store(lltag, lltagptr)
|
self.llbuilder.store(lltag, lltagptr)
|
||||||
|
|
||||||
if args:
|
llargs = self.llbuilder.alloca(llptr, ll.Constant(lli32, len(args)),
|
||||||
# only send args if there's anything to send, 'self' is excluded
|
name="subkernel.args")
|
||||||
llargs = self.llbuilder.alloca(llptr, ll.Constant(lli32, len(args)),
|
for index, arg in enumerate(args):
|
||||||
name="subkernel.args")
|
if builtins.is_none(arg.type):
|
||||||
for index, arg in enumerate(args):
|
llargslot = self.llbuilder.alloca(llunit,
|
||||||
if builtins.is_none(arg.type):
|
name="subkernel.arg{}".format(index))
|
||||||
llargslot = self.llbuilder.alloca(llunit,
|
else:
|
||||||
name="subkernel.arg{}".format(index))
|
llarg = self.map(arg)
|
||||||
else:
|
llargslot = self.llbuilder.alloca(llarg.type,
|
||||||
llarg = self.map(arg)
|
name="subkernel.arg{}".format(index))
|
||||||
llargslot = self.llbuilder.alloca(llarg.type,
|
self.llbuilder.store(llarg, llargslot)
|
||||||
name="subkernel.arg{}".format(index))
|
llargslot = self.llbuilder.bitcast(llargslot, llptr)
|
||||||
self.llbuilder.store(llarg, llargslot)
|
|
||||||
llargslot = self.llbuilder.bitcast(llargslot, llptr)
|
|
||||||
|
|
||||||
llargptr = self.llbuilder.gep(llargs, [ll.Constant(lli32, index)])
|
llargptr = self.llbuilder.gep(llargs, [ll.Constant(lli32, index)])
|
||||||
self.llbuilder.store(llargslot, llargptr)
|
self.llbuilder.store(llargslot, llargptr)
|
||||||
|
|
||||||
llargcount = ll.Constant(lli8, len(args))
|
llargcount = ll.Constant(lli8, len(args))
|
||||||
|
|
||||||
self.llbuilder.call(self.llbuiltin("subkernel_send_message"),
|
llisreturn = ll.Constant(lli1, False)
|
||||||
[llsid, llargcount, lltagptr, llargs])
|
self.llbuilder.call(self.llbuiltin("subkernel_send_message"),
|
||||||
self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr])
|
[llid, llisreturn, lldest, llargcount, lltagptr, llargs])
|
||||||
|
return self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr])
|
||||||
return llsid
|
|
||||||
|
|
||||||
def _build_subkernel_return(self, insn):
|
def _build_subkernel_return(self, insn):
|
||||||
# builds a remote return.
|
# builds a remote return.
|
||||||
|
@ -1746,10 +1791,12 @@ class LLVMIRGenerator:
|
||||||
llretslot = self.llbuilder.bitcast(llretslot, llptr)
|
llretslot = self.llbuilder.bitcast(llretslot, llptr)
|
||||||
self.llbuilder.store(llretslot, llrets)
|
self.llbuilder.store(llretslot, llrets)
|
||||||
|
|
||||||
llsid = ll.Constant(lli32, 0) # return goes back to master, sid is ignored
|
llsid = ll.Constant(lli32, 0) # return goes back to the caller, sid is ignored
|
||||||
lltagcount = ll.Constant(lli8, 1) # only one thing is returned
|
lltagcount = ll.Constant(lli8, 1) # only one thing is returned
|
||||||
|
llisreturn = ll.Constant(lli1, True) # it's a return, so destination is ignored
|
||||||
|
lldest = ll.Constant(lli8, 0)
|
||||||
self.llbuilder.call(self.llbuiltin("subkernel_send_message"),
|
self.llbuilder.call(self.llbuiltin("subkernel_send_message"),
|
||||||
[llsid, lltagcount, lltagptr, llrets])
|
[llsid, llisreturn, lldest, lltagcount, lltagptr, llrets])
|
||||||
|
|
||||||
def process_Call(self, insn):
|
def process_Call(self, insn):
|
||||||
functiontyp = insn.target_function().type
|
functiontyp = insn.target_function().type
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
""""RTIO driver for the Analog Devices AD53[67][0123] family of multi-channel
|
"""RTIO driver for the Analog Devices AD53[67][0123] family of multi-channel
|
||||||
Digital to Analog Converters.
|
Digital to Analog Converters.
|
||||||
|
|
||||||
Output event replacement is not supported and issuing commands at the same
|
Output event replacement is not supported and issuing commands at the same
|
||||||
time is an error.
|
time results in a collision error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Designed from the data sheets and somewhat after the linux kernel
|
# Designed from the data sheets and somewhat after the linux kernel
|
||||||
|
@ -131,10 +131,10 @@ class AD53xx:
|
||||||
optimized for speed; datasheet says t22: 25ns min SCLK edge to SDO
|
optimized for speed; datasheet says t22: 25ns min SCLK edge to SDO
|
||||||
valid, and suggests the SPI speed for reads should be <=20 MHz)
|
valid, and suggests the SPI speed for reads should be <=20 MHz)
|
||||||
:param vref: DAC reference voltage (default: 5.)
|
:param vref: DAC reference voltage (default: 5.)
|
||||||
:param offset_dacs: Initial register value for the two offset DACs, device
|
:param offset_dacs: Initial register value for the two offset DACs
|
||||||
dependent and must be set correctly for correct voltage to mu
|
(default: 8192). Device dependent and must be set correctly for
|
||||||
conversions. Knowledge of his state is not transferred between
|
correct voltage-to-mu conversions. Knowledge of this state is
|
||||||
experiments. (default: 8192)
|
not transferred between experiments.
|
||||||
:param core_device: Core device name (default: "core")
|
:param core_device: Core device name (default: "core")
|
||||||
"""
|
"""
|
||||||
kernel_invariants = {"bus", "ldac", "clr", "chip_select", "div_write",
|
kernel_invariants = {"bus", "ldac", "clr", "chip_select", "div_write",
|
||||||
|
@ -202,7 +202,7 @@ class AD53xx:
|
||||||
:param op: Operation to perform, one of :const:`AD53XX_READ_X1A`,
|
:param op: Operation to perform, one of :const:`AD53XX_READ_X1A`,
|
||||||
:const:`AD53XX_READ_X1B`, :const:`AD53XX_READ_OFFSET`,
|
:const:`AD53XX_READ_X1B`, :const:`AD53XX_READ_OFFSET`,
|
||||||
:const:`AD53XX_READ_GAIN` etc. (default: :const:`AD53XX_READ_X1A`).
|
:const:`AD53XX_READ_GAIN` etc. (default: :const:`AD53XX_READ_X1A`).
|
||||||
:return: The 16 bit register value
|
:return: The 16-bit register value
|
||||||
"""
|
"""
|
||||||
self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8)
|
self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8)
|
||||||
self.bus.set_config_mu(SPI_AD53XX_CONFIG | spi.SPI_INPUT, 24,
|
self.bus.set_config_mu(SPI_AD53XX_CONFIG | spi.SPI_INPUT, 24,
|
||||||
|
@ -309,7 +309,7 @@ class AD53xx:
|
||||||
|
|
||||||
This method does not advance the timeline; write events are scheduled
|
This method does not advance the timeline; write events are scheduled
|
||||||
in the past. The DACs will synchronously start changing their output
|
in the past. The DACs will synchronously start changing their output
|
||||||
levels `now`.
|
levels ``now``.
|
||||||
|
|
||||||
If no LDAC device was defined, the LDAC pulse is skipped.
|
If no LDAC device was defined, the LDAC pulse is skipped.
|
||||||
|
|
||||||
|
@ -364,8 +364,8 @@ class AD53xx:
|
||||||
high) can be calibrated in this fashion.
|
high) can be calibrated in this fashion.
|
||||||
|
|
||||||
:param channel: The number of the calibrated channel
|
:param channel: The number of the calibrated channel
|
||||||
:params vzs: Measured voltage with the DAC set to zero-scale (0x0000)
|
:param vzs: Measured voltage with the DAC set to zero-scale (0x0000)
|
||||||
:params vfs: Measured voltage with the DAC set to full-scale (0xffff)
|
:param vfs: Measured voltage with the DAC set to full-scale (0xffff)
|
||||||
"""
|
"""
|
||||||
offset_err = voltage_to_mu(vzs, self.offset_dacs, self.vref)
|
offset_err = voltage_to_mu(vzs, self.offset_dacs, self.vref)
|
||||||
gain_err = voltage_to_mu(vfs, self.offset_dacs, self.vref) - (
|
gain_err = voltage_to_mu(vfs, self.offset_dacs, self.vref) - (
|
||||||
|
|
|
@ -114,27 +114,27 @@ class AD9910:
|
||||||
(as configured through CFG_MASK_NU), 4-7 for individual channels.
|
(as configured through CFG_MASK_NU), 4-7 for individual channels.
|
||||||
:param cpld_device: Name of the Urukul CPLD this device is on.
|
:param cpld_device: Name of the Urukul CPLD this device is on.
|
||||||
:param sw_device: Name of the RF switch device. The RF switch is a
|
:param sw_device: Name of the RF switch device. The RF switch is a
|
||||||
TTLOut channel available as the :attr:`sw` attribute of this instance.
|
TTLOut channel available as the ``sw`` attribute of this instance.
|
||||||
:param pll_n: DDS PLL multiplier. The DDS sample clock is
|
:param pll_n: DDS PLL multiplier. The DDS sample clock is
|
||||||
f_ref/clk_div*pll_n where f_ref is the reference frequency and
|
``f_ref / clk_div * pll_n`` where ``f_ref`` is the reference frequency and
|
||||||
clk_div is the reference clock divider (both set in the parent
|
``clk_div`` is the reference clock divider (both set in the parent
|
||||||
Urukul CPLD instance).
|
Urukul CPLD instance).
|
||||||
:param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1).
|
:param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1).
|
||||||
Note that when bypassing the PLL the red front panel LED may remain on.
|
Note that when bypassing the PLL the red front panel LED may remain on.
|
||||||
:param pll_cp: DDS PLL charge pump setting.
|
:param pll_cp: DDS PLL charge pump setting.
|
||||||
:param pll_vco: DDS PLL VCO range selection.
|
:param pll_vco: DDS PLL VCO range selection.
|
||||||
:param sync_delay_seed: SYNC_IN delay tuning starting value.
|
:param sync_delay_seed: ``SYNC_IN`` delay tuning starting value.
|
||||||
To stabilize the SYNC_IN delay tuning, run :meth:`tune_sync_delay` once
|
To stabilize the ``SYNC_IN`` delay tuning, run :meth:`tune_sync_delay` once
|
||||||
and set this to the delay tap number returned (default: -1 to signal no
|
and set this to the delay tap number returned (default: -1 to signal no
|
||||||
synchronization and no tuning during :meth:`init`).
|
synchronization and no tuning during :meth:`init`).
|
||||||
Can be a string of the form "eeprom_device:byte_offset" to read the
|
Can be a string of the form ``eeprom_device:byte_offset`` to read the
|
||||||
value from a I2C EEPROM; in which case, `io_update_delay` must be set
|
value from a I2C EEPROM, in which case ``io_update_delay`` must be set
|
||||||
to the same string value.
|
to the same string value.
|
||||||
:param io_update_delay: IO_UPDATE pulse alignment delay.
|
:param io_update_delay: ``IO_UPDATE`` pulse alignment delay.
|
||||||
To align IO_UPDATE to SYNC_CLK, run :meth:`tune_io_update_delay` and
|
To align ``IO_UPDATE`` to ``SYNC_CLK``, run :meth:`tune_io_update_delay` and
|
||||||
set this to the delay tap number returned.
|
set this to the delay tap number returned.
|
||||||
Can be a string of the form "eeprom_device:byte_offset" to read the
|
Can be a string of the form ``eeprom_device:byte_offset`` to read the
|
||||||
value from a I2C EEPROM; in which case, `sync_delay_seed` must be set
|
value from a I2C EEPROM, in which case ``sync_delay_seed`` must be set
|
||||||
to the same string value.
|
to the same string value.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -188,9 +188,7 @@ class AD9910:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_phase_mode(self, phase_mode: TInt32):
|
def set_phase_mode(self, phase_mode: TInt32):
|
||||||
r"""Set the default phase mode.
|
r"""Set the default phase mode for future calls to :meth:`set` and
|
||||||
|
|
||||||
for future calls to :meth:`set` and
|
|
||||||
:meth:`set_mu`. Supported phase modes are:
|
:meth:`set_mu`. Supported phase modes are:
|
||||||
|
|
||||||
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged
|
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged
|
||||||
|
@ -233,7 +231,7 @@ class AD9910:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write16(self, addr: TInt32, data: TInt32):
|
def write16(self, addr: TInt32, data: TInt32):
|
||||||
"""Write to 16 bit register.
|
"""Write to 16-bit register.
|
||||||
|
|
||||||
:param addr: Register address
|
:param addr: Register address
|
||||||
:param data: Data to be written
|
:param data: Data to be written
|
||||||
|
@ -244,7 +242,7 @@ class AD9910:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write32(self, addr: TInt32, data: TInt32):
|
def write32(self, addr: TInt32, data: TInt32):
|
||||||
"""Write to 32 bit register.
|
"""Write to 32-bit register.
|
||||||
|
|
||||||
:param addr: Register address
|
:param addr: Register address
|
||||||
:param data: Data to be written
|
:param data: Data to be written
|
||||||
|
@ -258,7 +256,7 @@ class AD9910:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read16(self, addr: TInt32) -> TInt32:
|
def read16(self, addr: TInt32) -> TInt32:
|
||||||
"""Read from 16 bit register.
|
"""Read from 16-bit register.
|
||||||
|
|
||||||
:param addr: Register address
|
:param addr: Register address
|
||||||
"""
|
"""
|
||||||
|
@ -273,7 +271,7 @@ class AD9910:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read32(self, addr: TInt32) -> TInt32:
|
def read32(self, addr: TInt32) -> TInt32:
|
||||||
"""Read from 32 bit register.
|
"""Read from 32-bit register.
|
||||||
|
|
||||||
:param addr: Register address
|
:param addr: Register address
|
||||||
"""
|
"""
|
||||||
|
@ -288,10 +286,10 @@ class AD9910:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read64(self, addr: TInt32) -> TInt64:
|
def read64(self, addr: TInt32) -> TInt64:
|
||||||
"""Read from 64 bit register.
|
"""Read from 64-bit register.
|
||||||
|
|
||||||
:param addr: Register address
|
:param addr: Register address
|
||||||
:return: 64 bit integer register value
|
:return: 64-bit integer register value
|
||||||
"""
|
"""
|
||||||
self.bus.set_config_mu(
|
self.bus.set_config_mu(
|
||||||
urukul.SPI_CONFIG, 8,
|
urukul.SPI_CONFIG, 8,
|
||||||
|
@ -311,10 +309,10 @@ class AD9910:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write64(self, addr: TInt32, data_high: TInt32, data_low: TInt32):
|
def write64(self, addr: TInt32, data_high: TInt32, data_low: TInt32):
|
||||||
"""Write to 64 bit register.
|
"""Write to 64-bit register.
|
||||||
|
|
||||||
:param addr: Register address
|
:param addr: Register address
|
||||||
:param data_high: High (MSB) 32 bits of the data
|
:param data_high: High (MSB) 32 data bits
|
||||||
:param data_low: Low (LSB) 32 data bits
|
:param data_low: Low (LSB) 32 data bits
|
||||||
"""
|
"""
|
||||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
|
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
|
||||||
|
@ -332,8 +330,9 @@ class AD9910:
|
||||||
"""Write data to RAM.
|
"""Write data to RAM.
|
||||||
|
|
||||||
The profile to write to and the step, start, and end address
|
The profile to write to and the step, start, and end address
|
||||||
need to be configured before and separately using
|
need to be configured in advance and separately using
|
||||||
:meth:`set_profile_ram` and the parent CPLD `set_profile`.
|
:meth:`set_profile_ram` and the parent CPLD
|
||||||
|
:meth:`~artiq.coredevice.urukul.CPLD.set_profile`.
|
||||||
|
|
||||||
:param data: Data to be written to RAM.
|
:param data: Data to be written to RAM.
|
||||||
"""
|
"""
|
||||||
|
@ -354,7 +353,8 @@ class AD9910:
|
||||||
|
|
||||||
The profile to read from and the step, start, and end address
|
The profile to read from and the step, start, and end address
|
||||||
need to be configured before and separately using
|
need to be configured before and separately using
|
||||||
:meth:`set_profile_ram` and the parent CPLD `set_profile`.
|
:meth:`set_profile_ram` and the parent CPLD
|
||||||
|
:meth:`~artiq.coredevice.urukul.CPLD.set_profile`.
|
||||||
|
|
||||||
:param data: List to be filled with data read from RAM.
|
:param data: List to be filled with data read from RAM.
|
||||||
"""
|
"""
|
||||||
|
@ -390,9 +390,9 @@ class AD9910:
|
||||||
manual_osk_external: TInt32 = 0,
|
manual_osk_external: TInt32 = 0,
|
||||||
osk_enable: TInt32 = 0,
|
osk_enable: TInt32 = 0,
|
||||||
select_auto_osk: TInt32 = 0):
|
select_auto_osk: TInt32 = 0):
|
||||||
"""Set CFR1. See the AD9910 datasheet for parameter meanings.
|
"""Set CFR1. See the AD9910 datasheet for parameter meanings and sizes.
|
||||||
|
|
||||||
This method does not pulse IO_UPDATE.
|
This method does not pulse ``IO_UPDATE.``
|
||||||
|
|
||||||
:param power_down: Power down bits.
|
:param power_down: Power down bits.
|
||||||
:param phase_autoclear: Autoclear phase accumulator.
|
:param phase_autoclear: Autoclear phase accumulator.
|
||||||
|
@ -429,9 +429,9 @@ class AD9910:
|
||||||
effective_ftw: TInt32 = 1,
|
effective_ftw: TInt32 = 1,
|
||||||
sync_validation_disable: TInt32 = 0,
|
sync_validation_disable: TInt32 = 0,
|
||||||
matched_latency_enable: TInt32 = 0):
|
matched_latency_enable: TInt32 = 0):
|
||||||
"""Set CFR2. See the AD9910 datasheet for parameter meanings.
|
"""Set CFR2. See the AD9910 datasheet for parameter meanings and sizes.
|
||||||
|
|
||||||
This method does not pulse IO_UPDATE.
|
This method does not pulse ``IO_UPDATE``.
|
||||||
|
|
||||||
:param asf_profile_enable: Enable amplitude scale from single tone profiles.
|
:param asf_profile_enable: Enable amplitude scale from single tone profiles.
|
||||||
:param drg_enable: Digital ramp enable.
|
:param drg_enable: Digital ramp enable.
|
||||||
|
@ -456,14 +456,14 @@ class AD9910:
|
||||||
"""Initialize and configure the DDS.
|
"""Initialize and configure the DDS.
|
||||||
|
|
||||||
Sets up SPI mode, confirms chip presence, powers down unused blocks,
|
Sets up SPI mode, confirms chip presence, powers down unused blocks,
|
||||||
configures the PLL, waits for PLL lock. Uses the
|
configures the PLL, waits for PLL lock. Uses the ``IO_UPDATE``
|
||||||
IO_UPDATE signal multiple times.
|
signal multiple times.
|
||||||
|
|
||||||
:param blind: Do not read back DDS identity and do not wait for lock.
|
:param blind: Do not read back DDS identity and do not wait for lock.
|
||||||
"""
|
"""
|
||||||
self.sync_data.init()
|
self.sync_data.init()
|
||||||
if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div:
|
if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div:
|
||||||
raise ValueError("parent cpld does not drive SYNC")
|
raise ValueError("parent CPLD does not drive SYNC")
|
||||||
if self.sync_data.sync_delay_seed >= 0:
|
if self.sync_data.sync_delay_seed >= 0:
|
||||||
if self.sysclk_per_mu != self.sysclk * self.core.ref_period:
|
if self.sysclk_per_mu != self.sysclk * self.core.ref_period:
|
||||||
raise ValueError("incorrect clock ratio for synchronization")
|
raise ValueError("incorrect clock ratio for synchronization")
|
||||||
|
@ -514,7 +514,7 @@ class AD9910:
|
||||||
def power_down(self, bits: TInt32 = 0b1111):
|
def power_down(self, bits: TInt32 = 0b1111):
|
||||||
"""Power down DDS.
|
"""Power down DDS.
|
||||||
|
|
||||||
:param bits: Power down bits, see datasheet
|
:param bits: Power-down bits, see datasheet
|
||||||
"""
|
"""
|
||||||
self.set_cfr1(power_down=bits)
|
self.set_cfr1(power_down=bits)
|
||||||
self.cpld.io_update.pulse(1 * us)
|
self.cpld.io_update.pulse(1 * us)
|
||||||
|
@ -534,23 +534,23 @@ class AD9910:
|
||||||
After the SPI transfer, the shared IO update pin is pulsed to
|
After the SPI transfer, the shared IO update pin is pulsed to
|
||||||
activate the data.
|
activate the data.
|
||||||
|
|
||||||
.. seealso: :meth:`set_phase_mode` for a definition of the different
|
.. seealso: :meth:`AD9910.set_phase_mode` for a definition of the different
|
||||||
phase modes.
|
phase modes.
|
||||||
|
|
||||||
:param ftw: Frequency tuning word: 32 bit.
|
:param ftw: Frequency tuning word: 32-bit.
|
||||||
:param pow_: Phase tuning word: 16 bit unsigned.
|
:param pow_: Phase tuning word: 16-bit unsigned.
|
||||||
:param asf: Amplitude scale factor: 14 bit unsigned.
|
:param asf: Amplitude scale factor: 14-bit unsigned.
|
||||||
:param phase_mode: If specified, overrides the default phase mode set
|
:param phase_mode: If specified, overrides the default phase mode set
|
||||||
by :meth:`set_phase_mode` for this call.
|
by :meth:`set_phase_mode` for this call.
|
||||||
:param ref_time_mu: Fiducial time used to compute absolute or tracking
|
:param ref_time_mu: Fiducial time used to compute absolute or tracking
|
||||||
phase updates. In machine units as obtained by `now_mu()`.
|
phase updates. In machine units as obtained by :meth:`~artiq.language.core.now_mu()`.
|
||||||
:param profile: Single tone profile number to set (0-7, default: 7).
|
:param profile: Single tone profile number to set (0-7, default: 7).
|
||||||
Ineffective if `ram_destination` is specified.
|
Ineffective if ``ram_destination`` is specified.
|
||||||
:param ram_destination: RAM destination (:const:`RAM_DEST_FTW`,
|
:param ram_destination: RAM destination (:const:`RAM_DEST_FTW`,
|
||||||
:const:`RAM_DEST_POW`, :const:`RAM_DEST_ASF`,
|
:const:`RAM_DEST_POW`, :const:`RAM_DEST_ASF`,
|
||||||
:const:`RAM_DEST_POWASF`). If specified, write free DDS parameters
|
:const:`RAM_DEST_POWASF`). If specified, write free DDS parameters
|
||||||
to the ASF/FTW/POW registers instead of to the single tone profile
|
to the ASF/FTW/POW registers instead of to the single tone profile
|
||||||
register (default behaviour, see `profile`).
|
register (default behaviour, see ``profile``).
|
||||||
:return: Resulting phase offset word after application of phase
|
:return: Resulting phase offset word after application of phase
|
||||||
tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in
|
tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in
|
||||||
subsequent calls, use this value as the "current" phase.
|
subsequent calls, use this value as the "current" phase.
|
||||||
|
@ -598,10 +598,10 @@ class AD9910:
|
||||||
"""Get the frequency tuning word, phase offset word,
|
"""Get the frequency tuning word, phase offset word,
|
||||||
and amplitude scale factor.
|
and amplitude scale factor.
|
||||||
|
|
||||||
.. seealso:: :meth:`get`
|
See also :meth:`AD9910.get`.
|
||||||
|
|
||||||
:param profile: Profile number to get (0-7, default: 7)
|
:param profile: Profile number to get (0-7, default: 7)
|
||||||
:return: A tuple ``(ftw, pow, asf)``
|
:return: A tuple (FTW, POW, ASF)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Read data
|
# Read data
|
||||||
|
@ -617,12 +617,12 @@ class AD9910:
|
||||||
profile: TInt32 = _DEFAULT_PROFILE_RAM,
|
profile: TInt32 = _DEFAULT_PROFILE_RAM,
|
||||||
nodwell_high: TInt32 = 0, zero_crossing: TInt32 = 0,
|
nodwell_high: TInt32 = 0, zero_crossing: TInt32 = 0,
|
||||||
mode: TInt32 = 1):
|
mode: TInt32 = 1):
|
||||||
"""Set the RAM profile settings.
|
"""Set the RAM profile settings. See also AD9910 datasheet.
|
||||||
|
|
||||||
:param start: Profile start address in RAM.
|
:param start: Profile start address in RAM (10-bit).
|
||||||
:param end: Profile end address in RAM (last address).
|
:param end: Profile end address in RAM, inclusive (10-bit).
|
||||||
:param step: Profile time step in units of t_DDS, typically 4 ns
|
:param step: Profile time step, counted in DDS sample clock
|
||||||
(default: 1).
|
cycles, typically 4 ns (16-bit, default: 1)
|
||||||
:param profile: Profile index (0 to 7) (default: 0).
|
:param profile: Profile index (0 to 7) (default: 0).
|
||||||
:param nodwell_high: No-dwell high bit (default: 0,
|
:param nodwell_high: No-dwell high bit (default: 0,
|
||||||
see AD9910 documentation).
|
see AD9910 documentation).
|
||||||
|
@ -850,7 +850,7 @@ class AD9910:
|
||||||
ram_destination: TInt32 = -1) -> TFloat:
|
ram_destination: TInt32 = -1) -> TFloat:
|
||||||
"""Set DDS data in SI units.
|
"""Set DDS data in SI units.
|
||||||
|
|
||||||
.. seealso:: :meth:`set_mu`
|
See also :meth:`AD9910.set_mu`.
|
||||||
|
|
||||||
:param frequency: Frequency in Hz
|
:param frequency: Frequency in Hz
|
||||||
:param phase: Phase tuning word in turns
|
:param phase: Phase tuning word in turns
|
||||||
|
@ -871,10 +871,10 @@ class AD9910:
|
||||||
) -> TTuple([TFloat, TFloat, TFloat]):
|
) -> TTuple([TFloat, TFloat, TFloat]):
|
||||||
"""Get the frequency, phase, and amplitude.
|
"""Get the frequency, phase, and amplitude.
|
||||||
|
|
||||||
.. seealso:: :meth:`get_mu`
|
See also :meth:`AD9910.get_mu`.
|
||||||
|
|
||||||
:param profile: Profile number to get (0-7, default: 7)
|
:param profile: Profile number to get (0-7, default: 7)
|
||||||
:return: A tuple ``(frequency, phase, amplitude)``
|
:return: A tuple (frequency, phase, amplitude)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Get values
|
# Get values
|
||||||
|
@ -887,11 +887,10 @@ class AD9910:
|
||||||
def set_att_mu(self, att: TInt32):
|
def set_att_mu(self, att: TInt32):
|
||||||
"""Set digital step attenuator in machine units.
|
"""Set digital step attenuator in machine units.
|
||||||
|
|
||||||
This method will write the attenuator settings of all four channels.
|
This method will write the attenuator settings of all four channels. See also
|
||||||
|
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.set_att_mu>`.
|
||||||
|
|
||||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.set_att_mu`
|
:param att: Attenuation setting, 8-bit digital.
|
||||||
|
|
||||||
:param att: Attenuation setting, 8 bit digital.
|
|
||||||
"""
|
"""
|
||||||
self.cpld.set_att_mu(self.chip_select - 4, att)
|
self.cpld.set_att_mu(self.chip_select - 4, att)
|
||||||
|
|
||||||
|
@ -899,9 +898,8 @@ class AD9910:
|
||||||
def set_att(self, att: TFloat):
|
def set_att(self, att: TFloat):
|
||||||
"""Set digital step attenuator in SI units.
|
"""Set digital step attenuator in SI units.
|
||||||
|
|
||||||
This method will write the attenuator settings of all four channels.
|
This method will write the attenuator settings of all four channels. See also
|
||||||
|
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.set_att>`.
|
||||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.set_att`
|
|
||||||
|
|
||||||
:param att: Attenuation in dB.
|
:param att: Attenuation in dB.
|
||||||
"""
|
"""
|
||||||
|
@ -909,19 +907,17 @@ class AD9910:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def get_att_mu(self) -> TInt32:
|
def get_att_mu(self) -> TInt32:
|
||||||
"""Get digital step attenuator value in machine units.
|
"""Get digital step attenuator value in machine units. See also
|
||||||
|
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.get_channel_att_mu>`.
|
||||||
|
|
||||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu`
|
:return: Attenuation setting, 8-bit digital.
|
||||||
|
|
||||||
:return: Attenuation setting, 8 bit digital.
|
|
||||||
"""
|
"""
|
||||||
return self.cpld.get_channel_att_mu(self.chip_select - 4)
|
return self.cpld.get_channel_att_mu(self.chip_select - 4)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def get_att(self) -> TFloat:
|
def get_att(self) -> TFloat:
|
||||||
"""Get digital step attenuator value in SI units.
|
"""Get digital step attenuator value in SI units. See also
|
||||||
|
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.get_channel_att>`.
|
||||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att`
|
|
||||||
|
|
||||||
:return: Attenuation in dB.
|
:return: Attenuation in dB.
|
||||||
"""
|
"""
|
||||||
|
@ -943,16 +939,16 @@ class AD9910:
|
||||||
window: TInt32,
|
window: TInt32,
|
||||||
en_sync_gen: TInt32 = 0):
|
en_sync_gen: TInt32 = 0):
|
||||||
"""Set the relevant parameters in the multi device synchronization
|
"""Set the relevant parameters in the multi device synchronization
|
||||||
register. See the AD9910 datasheet for details. The SYNC clock
|
register. See the AD9910 datasheet for details. The ``SYNC`` clock
|
||||||
generator preset value is set to zero, and the SYNC_OUT generator is
|
generator preset value is set to zero, and the ``SYNC_OUT`` generator is
|
||||||
disabled by default.
|
disabled by default.
|
||||||
|
|
||||||
:param in_delay: SYNC_IN delay tap (0-31) in steps of ~75ps
|
:param in_delay: ``SYNC_IN`` delay tap (0-31) in steps of ~75ps
|
||||||
:param window: Symmetric SYNC_IN validation window (0-15) in
|
:param window: Symmetric ``SYNC_IN`` validation window (0-15) in
|
||||||
steps of ~75ps for both hold and setup margin.
|
steps of ~75ps for both hold and setup margin.
|
||||||
:param en_sync_gen: Whether to enable the DDS-internal sync generator
|
:param en_sync_gen: Whether to enable the DDS-internal sync generator
|
||||||
(SYNC_OUT, cf. sync_sel == 1). Should be left off for the normal
|
(``SYNC_OUT``, cf. ``sync_sel == 1``). Should be left off for the normal
|
||||||
use case, where the SYNC clock is supplied by the core device.
|
use case, where the ``SYNC`` clock is supplied by the core device.
|
||||||
"""
|
"""
|
||||||
self.write32(_AD9910_REG_SYNC,
|
self.write32(_AD9910_REG_SYNC,
|
||||||
(window << 28) | # SYNC S/H validation delay
|
(window << 28) | # SYNC S/H validation delay
|
||||||
|
@ -965,9 +961,9 @@ class AD9910:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def clear_smp_err(self):
|
def clear_smp_err(self):
|
||||||
"""Clear the SMP_ERR flag and enables SMP_ERR validity monitoring.
|
"""Clear the ``SMP_ERR`` flag and enables ``SMP_ERR`` validity monitoring.
|
||||||
|
|
||||||
Violations of the SYNC_IN sample and hold margins will result in
|
Violations of the ``SYNC_IN`` sample and hold margins will result in
|
||||||
SMP_ERR being asserted. This then also activates the red LED on
|
SMP_ERR being asserted. This then also activates the red LED on
|
||||||
the respective Urukul channel.
|
the respective Urukul channel.
|
||||||
|
|
||||||
|
@ -982,9 +978,9 @@ class AD9910:
|
||||||
@kernel
|
@kernel
|
||||||
def tune_sync_delay(self,
|
def tune_sync_delay(self,
|
||||||
search_seed: TInt32 = 15) -> TTuple([TInt32, TInt32]):
|
search_seed: TInt32 = 15) -> TTuple([TInt32, TInt32]):
|
||||||
"""Find a stable SYNC_IN delay.
|
"""Find a stable ``SYNC_IN`` delay.
|
||||||
|
|
||||||
This method first locates a valid SYNC_IN delay at zero validation
|
This method first locates a valid ``SYNC_IN`` delay at zero validation
|
||||||
window size (setup/hold margin) by scanning around `search_seed`. It
|
window size (setup/hold margin) by scanning around `search_seed`. It
|
||||||
then looks for similar valid delays at successively larger validation
|
then looks for similar valid delays at successively larger validation
|
||||||
window sizes until none can be found. It then decreases the validation
|
window sizes until none can be found. It then decreases the validation
|
||||||
|
@ -993,13 +989,13 @@ class AD9910:
|
||||||
|
|
||||||
This method and :meth:`tune_io_update_delay` can be run in any order.
|
This method and :meth:`tune_io_update_delay` can be run in any order.
|
||||||
|
|
||||||
:param search_seed: Start value for valid SYNC_IN delay search.
|
:param search_seed: Start value for valid ``SYNC_IN`` delay search.
|
||||||
Defaults to 15 (half range).
|
Defaults to 15 (half range).
|
||||||
:return: Tuple of optimal delay and window size.
|
:return: Tuple of optimal delay and window size.
|
||||||
"""
|
"""
|
||||||
if not self.cpld.sync_div:
|
if not self.cpld.sync_div:
|
||||||
raise ValueError("parent cpld does not drive SYNC")
|
raise ValueError("parent cpld does not drive SYNC")
|
||||||
search_span = 31
|
search_span = 13
|
||||||
# FIXME https://github.com/sinara-hw/Urukul/issues/16
|
# FIXME https://github.com/sinara-hw/Urukul/issues/16
|
||||||
# should both be 2-4 once kasli sync_in jitter is identified
|
# should both be 2-4 once kasli sync_in jitter is identified
|
||||||
min_window = 0
|
min_window = 0
|
||||||
|
@ -1040,16 +1036,16 @@ class AD9910:
|
||||||
def measure_io_update_alignment(self, delay_start: TInt64,
|
def measure_io_update_alignment(self, delay_start: TInt64,
|
||||||
delay_stop: TInt64) -> TInt32:
|
delay_stop: TInt64) -> TInt32:
|
||||||
"""Use the digital ramp generator to locate the alignment between
|
"""Use the digital ramp generator to locate the alignment between
|
||||||
IO_UPDATE and SYNC_CLK.
|
``IO_UPDATE`` and ``SYNC_CLK``.
|
||||||
|
|
||||||
The ramp generator is set up to a linear frequency ramp
|
The ramp generator is set up to a linear frequency ramp
|
||||||
(dFTW/t_SYNC_CLK=1) and started at a coarse RTIO time stamp plus
|
``(dFTW/t_SYNC_CLK=1)`` and started at a coarse RTIO time stamp plus
|
||||||
`delay_start` and stopped at a coarse RTIO time stamp plus
|
``delay_start`` and stopped at a coarse RTIO time stamp plus
|
||||||
`delay_stop`.
|
``delay_stop``.
|
||||||
|
|
||||||
:param delay_start: Start IO_UPDATE delay in machine units.
|
:param delay_start: Start ``IO_UPDATE`` delay in machine units.
|
||||||
:param delay_stop: Stop IO_UPDATE delay in machine units.
|
:param delay_stop: Stop ``IO_UPDATE`` delay in machine units.
|
||||||
:return: Odd/even SYNC_CLK cycle indicator.
|
:return: Odd/even ``SYNC_CLK`` cycle indicator.
|
||||||
"""
|
"""
|
||||||
# set up DRG
|
# set up DRG
|
||||||
self.set_cfr1(drg_load_lrr=1, drg_autoclear=1)
|
self.set_cfr1(drg_load_lrr=1, drg_autoclear=1)
|
||||||
|
@ -1081,19 +1077,19 @@ class AD9910:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def tune_io_update_delay(self) -> TInt32:
|
def tune_io_update_delay(self) -> TInt32:
|
||||||
"""Find a stable IO_UPDATE delay alignment.
|
"""Find a stable ``IO_UPDATE`` delay alignment.
|
||||||
|
|
||||||
Scan through increasing IO_UPDATE delays until a delay is found that
|
Scan through increasing ``IO_UPDATE`` delays until a delay is found that
|
||||||
lets IO_UPDATE be registered in the next SYNC_CLK cycle. Return a
|
lets ``IO_UPDATE`` be registered in the next ``SYNC_CLK`` cycle. Return a
|
||||||
IO_UPDATE delay that is as far away from that SYNC_CLK edge
|
``IO_UPDATE`` delay that is as far away from that ``SYNC_CLK`` edge
|
||||||
as possible.
|
as possible.
|
||||||
|
|
||||||
This method assumes that the IO_UPDATE TTLOut device has one machine
|
This method assumes that the ``IO_UPDATE`` TTLOut device has one machine
|
||||||
unit resolution (SERDES).
|
unit resolution (SERDES).
|
||||||
|
|
||||||
This method and :meth:`tune_sync_delay` can be run in any order.
|
This method and :meth:`tune_sync_delay` can be run in any order.
|
||||||
|
|
||||||
:return: Stable IO_UPDATE delay to be passed to the constructor
|
:return: Stable ``IO_UPDATE`` delay to be passed to the constructor
|
||||||
:class:`AD9910` via the device database.
|
:class:`AD9910` via the device database.
|
||||||
"""
|
"""
|
||||||
period = self.sysclk_per_mu * 4 # SYNC_CLK period
|
period = self.sysclk_per_mu * 4 # SYNC_CLK period
|
||||||
|
|
|
@ -11,7 +11,7 @@ from artiq.coredevice import urukul
|
||||||
|
|
||||||
class AD9912:
|
class AD9912:
|
||||||
"""
|
"""
|
||||||
AD9912 DDS channel on Urukul
|
AD9912 DDS channel on Urukul.
|
||||||
|
|
||||||
This class supports a single DDS channel and exposes the DDS,
|
This class supports a single DDS channel and exposes the DDS,
|
||||||
the digital step attenuator, and the RF switch.
|
the digital step attenuator, and the RF switch.
|
||||||
|
@ -22,9 +22,9 @@ class AD9912:
|
||||||
:param sw_device: Name of the RF switch device. The RF switch is a
|
:param sw_device: Name of the RF switch device. The RF switch is a
|
||||||
TTLOut channel available as the :attr:`sw` attribute of this instance.
|
TTLOut channel available as the :attr:`sw` attribute of this instance.
|
||||||
:param pll_n: DDS PLL multiplier. The DDS sample clock is
|
:param pll_n: DDS PLL multiplier. The DDS sample clock is
|
||||||
f_ref/clk_div*pll_n where f_ref is the reference frequency and clk_div
|
``f_ref / clk_div * pll_n`` where ``f_ref`` is the reference frequency and
|
||||||
is the reference clock divider (both set in the parent Urukul CPLD
|
``clk_div`` is the reference clock divider (both set in the parent
|
||||||
instance).
|
Urukul CPLD instance).
|
||||||
:param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1).
|
:param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1).
|
||||||
Note that when bypassing the PLL the red front panel LED may remain on.
|
Note that when bypassing the PLL the red front panel LED may remain on.
|
||||||
"""
|
"""
|
||||||
|
@ -44,7 +44,11 @@ class AD9912:
|
||||||
self.pll_en = pll_en
|
self.pll_en = pll_en
|
||||||
self.pll_n = pll_n
|
self.pll_n = pll_n
|
||||||
if pll_en:
|
if pll_en:
|
||||||
sysclk = self.cpld.refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n
|
refclk = self.cpld.refclk
|
||||||
|
if refclk < 11e6:
|
||||||
|
# use SYSCLK PLL Doubler
|
||||||
|
refclk = refclk * 2
|
||||||
|
sysclk = refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n
|
||||||
else:
|
else:
|
||||||
sysclk = self.cpld.refclk
|
sysclk = self.cpld.refclk
|
||||||
assert sysclk <= 1e9
|
assert sysclk <= 1e9
|
||||||
|
@ -97,7 +101,7 @@ class AD9912:
|
||||||
|
|
||||||
Sets up SPI mode, confirms chip presence, powers down unused blocks,
|
Sets up SPI mode, confirms chip presence, powers down unused blocks,
|
||||||
and configures the PLL. Does not wait for PLL lock. Uses the
|
and configures the PLL. Does not wait for PLL lock. Uses the
|
||||||
IO_UPDATE signal multiple times.
|
``IO_UPDATE`` signal multiple times.
|
||||||
"""
|
"""
|
||||||
# SPI mode
|
# SPI mode
|
||||||
self.write(AD9912_SER_CONF, 0x99, length=1)
|
self.write(AD9912_SER_CONF, 0x99, length=1)
|
||||||
|
@ -115,7 +119,11 @@ class AD9912:
|
||||||
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1)
|
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1)
|
||||||
self.cpld.io_update.pulse(2 * us)
|
self.cpld.io_update.pulse(2 * us)
|
||||||
# I_cp = 375 µA, VCO high range
|
# I_cp = 375 µA, VCO high range
|
||||||
self.write(AD9912_PLLCFG, 0b00000101, length=1)
|
if self.cpld.refclk < 11e6:
|
||||||
|
# enable SYSCLK PLL Doubler
|
||||||
|
self.write(AD9912_PLLCFG, 0b00001101, length=1)
|
||||||
|
else:
|
||||||
|
self.write(AD9912_PLLCFG, 0b00000101, length=1)
|
||||||
self.cpld.io_update.pulse(2 * us)
|
self.cpld.io_update.pulse(2 * us)
|
||||||
delay(1 * ms)
|
delay(1 * ms)
|
||||||
|
|
||||||
|
@ -125,9 +133,9 @@ class AD9912:
|
||||||
|
|
||||||
This method will write the attenuator settings of all four channels.
|
This method will write the attenuator settings of all four channels.
|
||||||
|
|
||||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.set_att_mu`
|
See also :meth:`~artiq.coredevice.urukul.CPLD.set_att_mu`.
|
||||||
|
|
||||||
:param att: Attenuation setting, 8 bit digital.
|
:param att: Attenuation setting, 8-bit digital.
|
||||||
"""
|
"""
|
||||||
self.cpld.set_att_mu(self.chip_select - 4, att)
|
self.cpld.set_att_mu(self.chip_select - 4, att)
|
||||||
|
|
||||||
|
@ -137,7 +145,7 @@ class AD9912:
|
||||||
|
|
||||||
This method will write the attenuator settings of all four channels.
|
This method will write the attenuator settings of all four channels.
|
||||||
|
|
||||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.set_att`
|
See also :meth:`~artiq.coredevice.urukul.CPLD.set_att`.
|
||||||
|
|
||||||
:param att: Attenuation in dB. Higher values mean more attenuation.
|
:param att: Attenuation in dB. Higher values mean more attenuation.
|
||||||
"""
|
"""
|
||||||
|
@ -147,9 +155,9 @@ class AD9912:
|
||||||
def get_att_mu(self) -> TInt32:
|
def get_att_mu(self) -> TInt32:
|
||||||
"""Get digital step attenuator value in machine units.
|
"""Get digital step attenuator value in machine units.
|
||||||
|
|
||||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu`
|
See also :meth:`~artiq.coredevice.urukul.CPLD.get_channel_att_mu`.
|
||||||
|
|
||||||
:return: Attenuation setting, 8 bit digital.
|
:return: Attenuation setting, 8-bit digital.
|
||||||
"""
|
"""
|
||||||
return self.cpld.get_channel_att_mu(self.chip_select - 4)
|
return self.cpld.get_channel_att_mu(self.chip_select - 4)
|
||||||
|
|
||||||
|
@ -157,7 +165,7 @@ class AD9912:
|
||||||
def get_att(self) -> TFloat:
|
def get_att(self) -> TFloat:
|
||||||
"""Get digital step attenuator value in SI units.
|
"""Get digital step attenuator value in SI units.
|
||||||
|
|
||||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att`
|
See also :meth:`~artiq.coredevice.urukul.CPLD.get_channel_att`.
|
||||||
|
|
||||||
:return: Attenuation in dB.
|
:return: Attenuation in dB.
|
||||||
"""
|
"""
|
||||||
|
@ -170,8 +178,8 @@ class AD9912:
|
||||||
After the SPI transfer, the shared IO update pin is pulsed to
|
After the SPI transfer, the shared IO update pin is pulsed to
|
||||||
activate the data.
|
activate the data.
|
||||||
|
|
||||||
:param ftw: Frequency tuning word: 48 bit unsigned.
|
:param ftw: Frequency tuning word: 48-bit unsigned.
|
||||||
:param pow_: Phase tuning word: 16 bit unsigned.
|
:param pow_: Phase tuning word: 16-bit unsigned.
|
||||||
"""
|
"""
|
||||||
# streaming transfer of FTW and POW
|
# streaming transfer of FTW and POW
|
||||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
|
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
|
||||||
|
@ -189,9 +197,9 @@ class AD9912:
|
||||||
def get_mu(self) -> TTuple([TInt64, TInt32]):
|
def get_mu(self) -> TTuple([TInt64, TInt32]):
|
||||||
"""Get the frequency tuning word and phase offset word.
|
"""Get the frequency tuning word and phase offset word.
|
||||||
|
|
||||||
.. seealso:: :meth:`get`
|
See also :meth:`AD9912.get`.
|
||||||
|
|
||||||
:return: A tuple ``(ftw, pow)``.
|
:return: A tuple (FTW, POW).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Read data
|
# Read data
|
||||||
|
@ -239,7 +247,7 @@ class AD9912:
|
||||||
def set(self, frequency: TFloat, phase: TFloat = 0.0):
|
def set(self, frequency: TFloat, phase: TFloat = 0.0):
|
||||||
"""Set profile 0 data in SI units.
|
"""Set profile 0 data in SI units.
|
||||||
|
|
||||||
.. seealso:: :meth:`set_mu`
|
See also :meth:`AD9912.set_mu`.
|
||||||
|
|
||||||
:param frequency: Frequency in Hz
|
:param frequency: Frequency in Hz
|
||||||
:param phase: Phase tuning word in turns
|
:param phase: Phase tuning word in turns
|
||||||
|
@ -251,9 +259,9 @@ class AD9912:
|
||||||
def get(self) -> TTuple([TFloat, TFloat]):
|
def get(self) -> TTuple([TFloat, TFloat]):
|
||||||
"""Get the frequency and phase.
|
"""Get the frequency and phase.
|
||||||
|
|
||||||
.. seealso:: :meth:`get_mu`
|
See also :meth:`AD9912.get_mu`.
|
||||||
|
|
||||||
:return: A tuple ``(frequency, phase)``.
|
:return: A tuple (frequency, phase).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# Get values
|
# Get values
|
||||||
|
|
|
@ -49,7 +49,7 @@ class AD9914:
|
||||||
The time cursor is not modified by any function in this class.
|
The time cursor is not modified by any function in this class.
|
||||||
|
|
||||||
Output event replacement is not supported and issuing commands at the same
|
Output event replacement is not supported and issuing commands at the same
|
||||||
time is an error.
|
time results in collision errors.
|
||||||
|
|
||||||
:param sysclk: DDS system frequency. The DDS system clock must be a
|
:param sysclk: DDS system frequency. The DDS system clock must be a
|
||||||
phase-locked multiple of the RTIO clock.
|
phase-locked multiple of the RTIO clock.
|
||||||
|
@ -134,7 +134,7 @@ class AD9914:
|
||||||
timing margin.
|
timing margin.
|
||||||
|
|
||||||
:param sync_delay: integer from 0 to 0x3f that sets the value of
|
:param sync_delay: integer from 0 to 0x3f that sets the value of
|
||||||
SYNC_OUT (bits 3-5) and SYNC_IN (bits 0-2) delay ADJ bits.
|
``SYNC_OUT`` (bits 3-5) and ``SYNC_IN`` (bits 0-2) delay ADJ bits.
|
||||||
"""
|
"""
|
||||||
delay_mu(-self.init_sync_duration_mu)
|
delay_mu(-self.init_sync_duration_mu)
|
||||||
self.write(AD9914_GPIO, (1 << self.channel) << 1)
|
self.write(AD9914_GPIO, (1 << self.channel) << 1)
|
||||||
|
|
|
@ -84,10 +84,11 @@ class ADF5356:
|
||||||
|
|
||||||
:param blind: Do not attempt to verify presence.
|
:param blind: Do not attempt to verify presence.
|
||||||
"""
|
"""
|
||||||
|
self.sync()
|
||||||
if not blind:
|
if not blind:
|
||||||
# MUXOUT = VDD
|
# MUXOUT = VDD
|
||||||
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 1)
|
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 1)
|
||||||
self.sync()
|
self.write(self.regs[4])
|
||||||
delay(1000 * us)
|
delay(1000 * us)
|
||||||
if not self.read_muxout():
|
if not self.read_muxout():
|
||||||
raise ValueError("MUXOUT not high")
|
raise ValueError("MUXOUT not high")
|
||||||
|
@ -95,7 +96,7 @@ class ADF5356:
|
||||||
|
|
||||||
# MUXOUT = DGND
|
# MUXOUT = DGND
|
||||||
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 2)
|
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 2)
|
||||||
self.sync()
|
self.write(self.regs[4])
|
||||||
delay(1000 * us)
|
delay(1000 * us)
|
||||||
if self.read_muxout():
|
if self.read_muxout():
|
||||||
raise ValueError("MUXOUT not low")
|
raise ValueError("MUXOUT not low")
|
||||||
|
@ -103,8 +104,7 @@ class ADF5356:
|
||||||
|
|
||||||
# MUXOUT = digital lock-detect
|
# MUXOUT = digital lock-detect
|
||||||
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 6)
|
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 6)
|
||||||
else:
|
self.write(self.regs[4])
|
||||||
self.sync()
|
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_att(self, att):
|
def set_att(self, att):
|
||||||
|
@ -112,7 +112,7 @@ class ADF5356:
|
||||||
|
|
||||||
This method will write the attenuator settings of the channel.
|
This method will write the attenuator settings of the channel.
|
||||||
|
|
||||||
.. seealso:: :meth:`artiq.coredevice.mirny.Mirny.set_att`
|
See also :meth:`Mirny.set_att<artiq.coredevice.mirny.Mirny.set_att>`.
|
||||||
|
|
||||||
:param att: Attenuation in dB.
|
:param att: Attenuation in dB.
|
||||||
"""
|
"""
|
||||||
|
@ -122,7 +122,7 @@ class ADF5356:
|
||||||
def set_att_mu(self, att):
|
def set_att_mu(self, att):
|
||||||
"""Set digital step attenuator in machine units.
|
"""Set digital step attenuator in machine units.
|
||||||
|
|
||||||
:param att: Attenuation setting, 8 bit digital.
|
:param att: Attenuation setting, 8-bit digital.
|
||||||
"""
|
"""
|
||||||
self.cpld.set_att_mu(self.channel, att)
|
self.cpld.set_att_mu(self.channel, att)
|
||||||
|
|
||||||
|
@ -531,14 +531,14 @@ class ADF5356:
|
||||||
@portable
|
@portable
|
||||||
def _compute_pfd_frequency(self, r, d, t) -> TInt64:
|
def _compute_pfd_frequency(self, r, d, t) -> TInt64:
|
||||||
"""
|
"""
|
||||||
Calculate the PFD frequency from the given reference path parameters
|
Calculate the PFD frequency from the given reference path parameters.
|
||||||
"""
|
"""
|
||||||
return int64(self.sysclk * ((1 + d) / (r * (1 + t))))
|
return int64(self.sysclk * ((1 + d) / (r * (1 + t))))
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def _compute_reference_counter(self) -> TInt32:
|
def _compute_reference_counter(self) -> TInt32:
|
||||||
"""
|
"""
|
||||||
Determine the reference counter R that maximizes the PFD frequency
|
Determine the reference counter R that maximizes the PFD frequency.
|
||||||
"""
|
"""
|
||||||
d = ADF5356_REG4_R_DOUBLER_GET(self.regs[4])
|
d = ADF5356_REG4_R_DOUBLER_GET(self.regs[4])
|
||||||
t = ADF5356_REG4_R_DIVIDER_GET(self.regs[4])
|
t = ADF5356_REG4_R_DIVIDER_GET(self.regs[4])
|
||||||
|
@ -565,14 +565,15 @@ def calculate_pll(f_vco: TInt64, f_pfd: TInt64):
|
||||||
"""
|
"""
|
||||||
Calculate fractional-N PLL parameters such that
|
Calculate fractional-N PLL parameters such that
|
||||||
|
|
||||||
``f_vco`` = ``f_pfd`` * (``n`` + (``frac1`` + ``frac2``/``mod2``) / ``mod1``)
|
``f_vco = f_pfd * (n + (frac1 + frac2/mod2) / mod1)``
|
||||||
|
|
||||||
where
|
where
|
||||||
``mod1 = 2**24`` and ``mod2 <= 2**28``
|
|
||||||
|
``mod1 = 2**24`` and ``mod2 <= 2**28``
|
||||||
|
|
||||||
:param f_vco: target VCO frequency
|
:param f_vco: target VCO frequency
|
||||||
:param f_pfd: PFD frequency
|
:param f_pfd: PFD frequency
|
||||||
:return: ``(n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb))``
|
:return: (``n``, ``frac1``, ``(frac2_msb, frac2_lsb)``, ``(mod2_msb, mod2_lsb)``)
|
||||||
"""
|
"""
|
||||||
f_pfd = int64(f_pfd)
|
f_pfd = int64(f_pfd)
|
||||||
f_vco = int64(f_vco)
|
f_vco = int64(f_vco)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
from artiq.language.core import kernel, portable
|
from artiq.language.core import kernel, portable
|
||||||
|
from artiq.language.units import us
|
||||||
|
|
||||||
from numpy import int32
|
from numpy import int32
|
||||||
|
|
||||||
|
@ -16,12 +17,12 @@ ALMAZNY_LEGACY_SPIT_WR = 32
|
||||||
|
|
||||||
class AlmaznyLegacy:
|
class AlmaznyLegacy:
|
||||||
"""
|
"""
|
||||||
Almazny (High frequency mezzanine board for Mirny)
|
Almazny (High-frequency mezzanine board for Mirny)
|
||||||
|
|
||||||
This applies to Almazny hardware v1.1 and earlier.
|
This applies to Almazny hardware v1.1 and earlier.
|
||||||
Use :class:`artiq.coredevice.almazny.AlmaznyChannel` for Almazny v1.2 and later.
|
Use :class:`~artiq.coredevice.almazny.AlmaznyChannel` for Almazny v1.2 and later.
|
||||||
|
|
||||||
:param host_mirny: Mirny device Almazny is connected to
|
:param host_mirny: :class:`~artiq.coredevice.mirny.Mirny` device Almazny is connected to
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, dmgr, host_mirny):
|
def __init__(self, dmgr, host_mirny):
|
||||||
|
@ -117,20 +118,16 @@ class AlmaznyLegacy:
|
||||||
)
|
)
|
||||||
delay(100 * us)
|
delay(100 * us)
|
||||||
|
|
||||||
@kernel
|
|
||||||
def _update_all_registers(self):
|
|
||||||
for i in range(4):
|
|
||||||
self._update_register(i)
|
|
||||||
|
|
||||||
|
|
||||||
class AlmaznyChannel:
|
class AlmaznyChannel:
|
||||||
"""
|
"""
|
||||||
One Almazny channel
|
Driver for one Almazny channel.
|
||||||
|
|
||||||
Almazny is a mezzanine for the Quad PLL RF source Mirny that exposes and
|
Almazny is a mezzanine for the Quad PLL RF source Mirny that exposes and
|
||||||
controls the frequency-doubled outputs.
|
controls the frequency-doubled outputs.
|
||||||
This driver requires Almazny hardware revision v1.2 or later
|
This driver requires Almazny hardware revision v1.2 or later
|
||||||
and Mirny CPLD gateware v0.3 or later.
|
and Mirny CPLD gateware v0.3 or later.
|
||||||
Use :class:`artiq.coredevice.almazny.AlmaznyLegacy` for Almazny hardware v1.1 and earlier.
|
Use :class:`~artiq.coredevice.almazny.AlmaznyLegacy` for Almazny hardware v1.1 and earlier.
|
||||||
|
|
||||||
:param host_mirny: Mirny CPLD device name
|
:param host_mirny: Mirny CPLD device name
|
||||||
:param channel: channel index (0-3)
|
:param channel: channel index (0-3)
|
||||||
|
|
|
@ -21,9 +21,9 @@ class CoreCache:
|
||||||
"""Extract a value from the core device cache.
|
"""Extract a value from the core device cache.
|
||||||
After a value is extracted, it cannot be replaced with another value using
|
After a value is extracted, it cannot be replaced with another value using
|
||||||
:meth:`put` until all kernel functions finish executing; attempting
|
:meth:`put` until all kernel functions finish executing; attempting
|
||||||
to replace it will result in a :class:`artiq.coredevice.exceptions.CacheError`.
|
to replace it will result in a :class:`~artiq.coredevice.exceptions.CacheError`.
|
||||||
|
|
||||||
If the cache does not contain any value associated with ``key``, an empty list
|
If the cache does not contain any value associated with `key`, an empty list
|
||||||
is returned.
|
is returned.
|
||||||
|
|
||||||
The value is not copied, so mutating it will change what's stored in the cache.
|
The value is not copied, so mutating it will change what's stored in the cache.
|
||||||
|
|
|
@ -2,15 +2,22 @@ from operator import itemgetter
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from itertools import count
|
from itertools import count
|
||||||
from contextlib import contextmanager
|
from contextlib import contextmanager
|
||||||
|
from sipyco import keepalive
|
||||||
|
import asyncio
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
import struct
|
import struct
|
||||||
import logging
|
import logging
|
||||||
import socket
|
import socket
|
||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
DEFAULT_REF_PERIOD = 1e-9
|
||||||
|
ANALYZER_MAGIC = b"ARTIQ Analyzer Proxy\n"
|
||||||
|
|
||||||
|
|
||||||
class MessageType(Enum):
|
class MessageType(Enum):
|
||||||
output = 0b00
|
output = 0b00
|
||||||
input = 0b01
|
input = 0b01
|
||||||
|
@ -34,6 +41,13 @@ class ExceptionType(Enum):
|
||||||
i_overflow = 0b100001
|
i_overflow = 0b100001
|
||||||
|
|
||||||
|
|
||||||
|
class WaveformType(Enum):
|
||||||
|
ANALOG = 0
|
||||||
|
BIT = 1
|
||||||
|
VECTOR = 2
|
||||||
|
LOG = 3
|
||||||
|
|
||||||
|
|
||||||
def get_analyzer_dump(host, port=1382):
|
def get_analyzer_dump(host, port=1382):
|
||||||
sock = socket.create_connection((host, port))
|
sock = socket.create_connection((host, port))
|
||||||
try:
|
try:
|
||||||
|
@ -104,6 +118,8 @@ def decode_dump(data):
|
||||||
(sent_bytes, total_byte_count,
|
(sent_bytes, total_byte_count,
|
||||||
error_occurred, log_channel, dds_onehot_sel) = parts
|
error_occurred, log_channel, dds_onehot_sel) = parts
|
||||||
|
|
||||||
|
logger.debug("analyzer dump has length %d", sent_bytes)
|
||||||
|
|
||||||
expected_len = sent_bytes + 15
|
expected_len = sent_bytes + 15
|
||||||
if expected_len != len(data):
|
if expected_len != len(data):
|
||||||
raise ValueError("analyzer dump has incorrect length "
|
raise ValueError("analyzer dump has incorrect length "
|
||||||
|
@ -115,15 +131,83 @@ def decode_dump(data):
|
||||||
if total_byte_count > sent_bytes:
|
if total_byte_count > sent_bytes:
|
||||||
logger.info("analyzer ring buffer has wrapped %d times",
|
logger.info("analyzer ring buffer has wrapped %d times",
|
||||||
total_byte_count//sent_bytes)
|
total_byte_count//sent_bytes)
|
||||||
|
if sent_bytes == 0:
|
||||||
|
logger.warning("analyzer dump is empty")
|
||||||
|
|
||||||
position = 15
|
position = 15
|
||||||
messages = []
|
messages = []
|
||||||
for _ in range(sent_bytes//32):
|
for _ in range(sent_bytes//32):
|
||||||
messages.append(decode_message(data[position:position+32]))
|
messages.append(decode_message(data[position:position+32]))
|
||||||
position += 32
|
position += 32
|
||||||
|
|
||||||
|
if len(messages) == 1 and isinstance(messages[0], StoppedMessage):
|
||||||
|
logger.warning("analyzer dump is empty aside from stop message")
|
||||||
|
|
||||||
return DecodedDump(log_channel, bool(dds_onehot_sel), messages)
|
return DecodedDump(log_channel, bool(dds_onehot_sel), messages)
|
||||||
|
|
||||||
|
|
||||||
|
# simplified from sipyco broadcast Receiver
|
||||||
|
class AnalyzerProxyReceiver:
|
||||||
|
def __init__(self, receive_cb, disconnect_cb=None):
|
||||||
|
self.receive_cb = receive_cb
|
||||||
|
self.disconnect_cb = disconnect_cb
|
||||||
|
|
||||||
|
async def connect(self, host, port):
|
||||||
|
self.reader, self.writer = \
|
||||||
|
await keepalive.async_open_connection(host, port)
|
||||||
|
try:
|
||||||
|
line = await self.reader.readline()
|
||||||
|
assert line == ANALYZER_MAGIC
|
||||||
|
self.receive_task = asyncio.create_task(self._receive_cr())
|
||||||
|
except:
|
||||||
|
self.writer.close()
|
||||||
|
del self.reader
|
||||||
|
del self.writer
|
||||||
|
raise
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
self.disconnect_cb = None
|
||||||
|
try:
|
||||||
|
self.receive_task.cancel()
|
||||||
|
try:
|
||||||
|
await self.receive_task
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
finally:
|
||||||
|
self.writer.close()
|
||||||
|
del self.reader
|
||||||
|
del self.writer
|
||||||
|
|
||||||
|
async def _receive_cr(self):
|
||||||
|
try:
|
||||||
|
while True:
|
||||||
|
data = bytearray()
|
||||||
|
data.extend(await self.reader.read(1))
|
||||||
|
if len(data) == 0:
|
||||||
|
# EOF reached, connection lost
|
||||||
|
return
|
||||||
|
if data[0] == ord("E"):
|
||||||
|
endian = '>'
|
||||||
|
elif data[0] == ord("e"):
|
||||||
|
endian = '<'
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
data.extend(await self.reader.readexactly(4))
|
||||||
|
payload_length = struct.unpack(endian + "I", data[1:5])[0]
|
||||||
|
if payload_length > 10 * 512 * 1024:
|
||||||
|
# 10x buffer size of firmware
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
# The remaining header length is 11 bytes.
|
||||||
|
data.extend(await self.reader.readexactly(payload_length + 11))
|
||||||
|
self.receive_cb(data)
|
||||||
|
except Exception:
|
||||||
|
logger.error("analyzer receiver connection terminating with exception", exc_info=True)
|
||||||
|
finally:
|
||||||
|
if self.disconnect_cb is not None:
|
||||||
|
self.disconnect_cb()
|
||||||
|
|
||||||
|
|
||||||
def vcd_codes():
|
def vcd_codes():
|
||||||
codechars = [chr(i) for i in range(33, 127)]
|
codechars = [chr(i) for i in range(33, 127)]
|
||||||
for n in count():
|
for n in count():
|
||||||
|
@ -150,38 +234,129 @@ class VCDChannel:
|
||||||
integer_cast = struct.unpack(">Q", struct.pack(">d", x))[0]
|
integer_cast = struct.unpack(">Q", struct.pack(">d", x))[0]
|
||||||
self.set_value("{:064b}".format(integer_cast))
|
self.set_value("{:064b}".format(integer_cast))
|
||||||
|
|
||||||
|
def set_log(self, log_message):
|
||||||
|
value = ""
|
||||||
|
for c in log_message:
|
||||||
|
value += "{:08b}".format(ord(c))
|
||||||
|
self.set_value(value)
|
||||||
|
|
||||||
|
|
||||||
class VCDManager:
|
class VCDManager:
|
||||||
def __init__(self, fileobj):
|
def __init__(self, fileobj):
|
||||||
self.out = fileobj
|
self.out = fileobj
|
||||||
self.codes = vcd_codes()
|
self.codes = vcd_codes()
|
||||||
self.current_time = None
|
self.current_time = None
|
||||||
|
self.start_time = 0
|
||||||
|
|
||||||
def set_timescale_ps(self, timescale):
|
def set_timescale_ps(self, timescale):
|
||||||
self.out.write("$timescale {}ps $end\n".format(round(timescale)))
|
self.out.write("$timescale {}ps $end\n".format(round(timescale)))
|
||||||
|
|
||||||
def get_channel(self, name, width):
|
def get_channel(self, name, width, ty, precision=0, unit=""):
|
||||||
code = next(self.codes)
|
code = next(self.codes)
|
||||||
self.out.write("$var wire {width} {code} {name} $end\n"
|
self.out.write("$var wire {width} {code} {name} $end\n"
|
||||||
.format(name=name, code=code, width=width))
|
.format(name=name, code=code, width=width))
|
||||||
return VCDChannel(self.out, code)
|
return VCDChannel(self.out, code)
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def scope(self, name):
|
def scope(self, scope, name):
|
||||||
self.out.write("$scope module {} $end\n".format(name))
|
self.out.write("$scope module {}/{} $end\n".format(scope, name))
|
||||||
yield
|
yield
|
||||||
self.out.write("$upscope $end\n")
|
self.out.write("$upscope $end\n")
|
||||||
|
|
||||||
def set_time(self, time):
|
def set_time(self, time):
|
||||||
|
time -= self.start_time
|
||||||
if time != self.current_time:
|
if time != self.current_time:
|
||||||
self.out.write("#{}\n".format(time))
|
self.out.write("#{}\n".format(time))
|
||||||
self.current_time = time
|
self.current_time = time
|
||||||
|
|
||||||
|
def set_start_time(self, time):
|
||||||
|
self.start_time = time
|
||||||
|
|
||||||
|
def set_end_time(self, time):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WaveformManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.current_time = 0
|
||||||
|
self.start_time = 0
|
||||||
|
self.end_time = 0
|
||||||
|
self.channels = list()
|
||||||
|
self.current_scope = ""
|
||||||
|
self.trace = {"timescale": 1, "stopped_x": None, "logs": dict(), "data": dict()}
|
||||||
|
|
||||||
|
def set_timescale_ps(self, timescale):
|
||||||
|
self.trace["timescale"] = int(timescale)
|
||||||
|
|
||||||
|
def get_channel(self, name, width, ty, precision=0, unit=""):
|
||||||
|
if ty == WaveformType.LOG:
|
||||||
|
self.trace["logs"][self.current_scope + name] = (ty, width, precision, unit)
|
||||||
|
data = self.trace["data"][self.current_scope + name] = list()
|
||||||
|
channel = WaveformChannel(data, self.current_time)
|
||||||
|
self.channels.append(channel)
|
||||||
|
return channel
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def scope(self, scope, name):
|
||||||
|
old_scope = self.current_scope
|
||||||
|
self.current_scope = scope + "/"
|
||||||
|
yield
|
||||||
|
self.current_scope = old_scope
|
||||||
|
|
||||||
|
def set_time(self, time):
|
||||||
|
time -= self.start_time
|
||||||
|
for channel in self.channels:
|
||||||
|
channel.set_time(time)
|
||||||
|
|
||||||
|
def set_start_time(self, time):
|
||||||
|
self.start_time = time
|
||||||
|
if self.trace["stopped_x"] is not None:
|
||||||
|
self.trace["stopped_x"] = self.end_time - self.start_time
|
||||||
|
|
||||||
|
def set_end_time(self, time):
|
||||||
|
self.end_time = time
|
||||||
|
self.trace["stopped_x"] = self.end_time - self.start_time
|
||||||
|
|
||||||
|
|
||||||
|
class WaveformChannel:
|
||||||
|
def __init__(self, data, current_time):
|
||||||
|
self.data = data
|
||||||
|
self.current_time = current_time
|
||||||
|
|
||||||
|
def set_value(self, value):
|
||||||
|
self.data.append((self.current_time, value))
|
||||||
|
|
||||||
|
def set_value_double(self, x):
|
||||||
|
self.data.append((self.current_time, x))
|
||||||
|
|
||||||
|
def set_time(self, time):
|
||||||
|
self.current_time = time
|
||||||
|
|
||||||
|
def set_log(self, log_message):
|
||||||
|
self.data.append((self.current_time, log_message))
|
||||||
|
|
||||||
|
|
||||||
|
class ChannelSignatureManager:
|
||||||
|
def __init__(self):
|
||||||
|
self.current_scope = ""
|
||||||
|
self.channels = dict()
|
||||||
|
|
||||||
|
def get_channel(self, name, width, ty, precision=0, unit=""):
|
||||||
|
self.channels[self.current_scope + name] = (ty, width, precision, unit)
|
||||||
|
return None
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def scope(self, scope, name):
|
||||||
|
old_scope = self.current_scope
|
||||||
|
self.current_scope = scope + "/"
|
||||||
|
yield
|
||||||
|
self.current_scope = old_scope
|
||||||
|
|
||||||
|
|
||||||
class TTLHandler:
|
class TTLHandler:
|
||||||
def __init__(self, vcd_manager, name):
|
def __init__(self, manager, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.channel_value = vcd_manager.get_channel("ttl/" + name, 1)
|
self.channel_value = manager.get_channel("ttl/" + name, 1, ty=WaveformType.BIT)
|
||||||
self.last_value = "X"
|
self.last_value = "X"
|
||||||
self.oe = True
|
self.oe = True
|
||||||
|
|
||||||
|
@ -206,11 +381,12 @@ class TTLHandler:
|
||||||
|
|
||||||
|
|
||||||
class TTLClockGenHandler:
|
class TTLClockGenHandler:
|
||||||
def __init__(self, vcd_manager, name, ref_period):
|
def __init__(self, manager, name, ref_period):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.ref_period = ref_period
|
self.ref_period = ref_period
|
||||||
self.channel_frequency = vcd_manager.get_channel(
|
precision = max(0, math.ceil(math.log10(2**24 * ref_period) + 6))
|
||||||
"ttl_clkgen/" + name, 64)
|
self.channel_frequency = manager.get_channel(
|
||||||
|
"ttl_clkgen/" + name, 64, ty=WaveformType.ANALOG, precision=precision, unit="MHz")
|
||||||
|
|
||||||
def process_message(self, message):
|
def process_message(self, message):
|
||||||
if isinstance(message, OutputMessage):
|
if isinstance(message, OutputMessage):
|
||||||
|
@ -221,8 +397,8 @@ class TTLClockGenHandler:
|
||||||
|
|
||||||
|
|
||||||
class DDSHandler:
|
class DDSHandler:
|
||||||
def __init__(self, vcd_manager, onehot_sel, sysclk):
|
def __init__(self, manager, onehot_sel, sysclk):
|
||||||
self.vcd_manager = vcd_manager
|
self.manager = manager
|
||||||
self.onehot_sel = onehot_sel
|
self.onehot_sel = onehot_sel
|
||||||
self.sysclk = sysclk
|
self.sysclk = sysclk
|
||||||
|
|
||||||
|
@ -231,11 +407,18 @@ class DDSHandler:
|
||||||
|
|
||||||
def add_dds_channel(self, name, dds_channel_nr):
|
def add_dds_channel(self, name, dds_channel_nr):
|
||||||
dds_channel = dict()
|
dds_channel = dict()
|
||||||
with self.vcd_manager.scope("dds/{}".format(name)):
|
frequency_precision = max(0, math.ceil(math.log10(2**32 / self.sysclk) + 6))
|
||||||
|
phase_precision = max(0, math.ceil(math.log10(2**16)))
|
||||||
|
with self.manager.scope("dds", name):
|
||||||
dds_channel["vcd_frequency"] = \
|
dds_channel["vcd_frequency"] = \
|
||||||
self.vcd_manager.get_channel(name + "/frequency", 64)
|
self.manager.get_channel(name + "/frequency", 64,
|
||||||
|
ty=WaveformType.ANALOG,
|
||||||
|
precision=frequency_precision,
|
||||||
|
unit="MHz")
|
||||||
dds_channel["vcd_phase"] = \
|
dds_channel["vcd_phase"] = \
|
||||||
self.vcd_manager.get_channel(name + "/phase", 64)
|
self.manager.get_channel(name + "/phase", 64,
|
||||||
|
ty=WaveformType.ANALOG,
|
||||||
|
precision=phase_precision)
|
||||||
dds_channel["ftw"] = [None, None]
|
dds_channel["ftw"] = [None, None]
|
||||||
dds_channel["pow"] = None
|
dds_channel["pow"] = None
|
||||||
self.dds_channels[dds_channel_nr] = dds_channel
|
self.dds_channels[dds_channel_nr] = dds_channel
|
||||||
|
@ -285,10 +468,10 @@ class DDSHandler:
|
||||||
|
|
||||||
|
|
||||||
class WishboneHandler:
|
class WishboneHandler:
|
||||||
def __init__(self, vcd_manager, name, read_bit):
|
def __init__(self, manager, name, read_bit):
|
||||||
self._reads = []
|
self._reads = []
|
||||||
self._read_bit = read_bit
|
self._read_bit = read_bit
|
||||||
self.stb = vcd_manager.get_channel("{}/{}".format(name, "stb"), 1)
|
self.stb = manager.get_channel(name + "/stb", 1, ty=WaveformType.BIT)
|
||||||
|
|
||||||
def process_message(self, message):
|
def process_message(self, message):
|
||||||
self.stb.set_value("1")
|
self.stb.set_value("1")
|
||||||
|
@ -318,16 +501,17 @@ class WishboneHandler:
|
||||||
|
|
||||||
|
|
||||||
class SPIMasterHandler(WishboneHandler):
|
class SPIMasterHandler(WishboneHandler):
|
||||||
def __init__(self, vcd_manager, name):
|
def __init__(self, manager, name):
|
||||||
self.channels = {}
|
self.channels = {}
|
||||||
with vcd_manager.scope("spi/{}".format(name)):
|
self.scope = "spi"
|
||||||
super().__init__(vcd_manager, name, read_bit=0b100)
|
with manager.scope("spi", name):
|
||||||
|
super().__init__(manager, name, read_bit=0b100)
|
||||||
for reg_name, reg_width in [
|
for reg_name, reg_width in [
|
||||||
("config", 32), ("chip_select", 16),
|
("config", 32), ("chip_select", 16),
|
||||||
("write_length", 8), ("read_length", 8),
|
("write_length", 8), ("read_length", 8),
|
||||||
("write", 32), ("read", 32)]:
|
("write", 32), ("read", 32)]:
|
||||||
self.channels[reg_name] = vcd_manager.get_channel(
|
self.channels[reg_name] = manager.get_channel(
|
||||||
"{}/{}".format(name, reg_name), reg_width)
|
"{}/{}".format(name, reg_name), reg_width, ty=WaveformType.VECTOR)
|
||||||
|
|
||||||
def process_write(self, address, data):
|
def process_write(self, address, data):
|
||||||
if address == 0:
|
if address == 0:
|
||||||
|
@ -352,11 +536,12 @@ class SPIMasterHandler(WishboneHandler):
|
||||||
|
|
||||||
|
|
||||||
class SPIMaster2Handler(WishboneHandler):
|
class SPIMaster2Handler(WishboneHandler):
|
||||||
def __init__(self, vcd_manager, name):
|
def __init__(self, manager, name):
|
||||||
self._reads = []
|
self._reads = []
|
||||||
self.channels = {}
|
self.channels = {}
|
||||||
with vcd_manager.scope("spi2/{}".format(name)):
|
self.scope = "spi2"
|
||||||
self.stb = vcd_manager.get_channel("{}/{}".format(name, "stb"), 1)
|
with manager.scope("spi2", name):
|
||||||
|
self.stb = manager.get_channel(name + "/stb", 1, ty=WaveformType.BIT)
|
||||||
for reg_name, reg_width in [
|
for reg_name, reg_width in [
|
||||||
("flags", 8),
|
("flags", 8),
|
||||||
("length", 5),
|
("length", 5),
|
||||||
|
@ -364,8 +549,8 @@ class SPIMaster2Handler(WishboneHandler):
|
||||||
("chip_select", 8),
|
("chip_select", 8),
|
||||||
("write", 32),
|
("write", 32),
|
||||||
("read", 32)]:
|
("read", 32)]:
|
||||||
self.channels[reg_name] = vcd_manager.get_channel(
|
self.channels[reg_name] = manager.get_channel(
|
||||||
"{}/{}".format(name, reg_name), reg_width)
|
"{}/{}".format(name, reg_name), reg_width, ty=WaveformType.VECTOR)
|
||||||
|
|
||||||
def process_message(self, message):
|
def process_message(self, message):
|
||||||
self.stb.set_value("1")
|
self.stb.set_value("1")
|
||||||
|
@ -413,11 +598,12 @@ def _extract_log_chars(data):
|
||||||
|
|
||||||
|
|
||||||
class LogHandler:
|
class LogHandler:
|
||||||
def __init__(self, vcd_manager, vcd_log_channels):
|
def __init__(self, manager, log_channels):
|
||||||
self.vcd_channels = dict()
|
self.channels = dict()
|
||||||
for name, maxlength in vcd_log_channels.items():
|
for name, maxlength in log_channels.items():
|
||||||
self.vcd_channels[name] = vcd_manager.get_channel("log/" + name,
|
self.channels[name] = manager.get_channel("logs/" + name,
|
||||||
maxlength*8)
|
maxlength * 8,
|
||||||
|
ty=WaveformType.LOG)
|
||||||
self.current_entry = ""
|
self.current_entry = ""
|
||||||
|
|
||||||
def process_message(self, message):
|
def process_message(self, message):
|
||||||
|
@ -425,15 +611,12 @@ class LogHandler:
|
||||||
self.current_entry += _extract_log_chars(message.data)
|
self.current_entry += _extract_log_chars(message.data)
|
||||||
if len(self.current_entry) > 1 and self.current_entry[-1] == "\x1D":
|
if len(self.current_entry) > 1 and self.current_entry[-1] == "\x1D":
|
||||||
channel_name, log_message = self.current_entry[:-1].split("\x1E", maxsplit=1)
|
channel_name, log_message = self.current_entry[:-1].split("\x1E", maxsplit=1)
|
||||||
vcd_value = ""
|
self.channels[channel_name].set_log(log_message)
|
||||||
for c in log_message:
|
|
||||||
vcd_value += "{:08b}".format(ord(c))
|
|
||||||
self.vcd_channels[channel_name].set_value(vcd_value)
|
|
||||||
self.current_entry = ""
|
self.current_entry = ""
|
||||||
|
|
||||||
|
|
||||||
def get_vcd_log_channels(log_channel, messages):
|
def get_log_channels(log_channel, messages):
|
||||||
vcd_log_channels = dict()
|
log_channels = dict()
|
||||||
log_entry = ""
|
log_entry = ""
|
||||||
for message in messages:
|
for message in messages:
|
||||||
if (isinstance(message, OutputMessage)
|
if (isinstance(message, OutputMessage)
|
||||||
|
@ -442,13 +625,13 @@ def get_vcd_log_channels(log_channel, messages):
|
||||||
if len(log_entry) > 1 and log_entry[-1] == "\x1D":
|
if len(log_entry) > 1 and log_entry[-1] == "\x1D":
|
||||||
channel_name, log_message = log_entry[:-1].split("\x1E", maxsplit=1)
|
channel_name, log_message = log_entry[:-1].split("\x1E", maxsplit=1)
|
||||||
l = len(log_message)
|
l = len(log_message)
|
||||||
if channel_name in vcd_log_channels:
|
if channel_name in log_channels:
|
||||||
if vcd_log_channels[channel_name] < l:
|
if log_channels[channel_name] < l:
|
||||||
vcd_log_channels[channel_name] = l
|
log_channels[channel_name] = l
|
||||||
else:
|
else:
|
||||||
vcd_log_channels[channel_name] = l
|
log_channels[channel_name] = l
|
||||||
log_entry = ""
|
log_entry = ""
|
||||||
return vcd_log_channels
|
return log_channels
|
||||||
|
|
||||||
|
|
||||||
def get_single_device_argument(devices, module, cls, argument):
|
def get_single_device_argument(devices, module, cls, argument):
|
||||||
|
@ -475,7 +658,7 @@ def get_dds_sysclk(devices):
|
||||||
("AD9914",), "sysclk")
|
("AD9914",), "sysclk")
|
||||||
|
|
||||||
|
|
||||||
def create_channel_handlers(vcd_manager, devices, ref_period,
|
def create_channel_handlers(manager, devices, ref_period,
|
||||||
dds_sysclk, dds_onehot_sel):
|
dds_sysclk, dds_onehot_sel):
|
||||||
channel_handlers = dict()
|
channel_handlers = dict()
|
||||||
for name, desc in sorted(devices.items(), key=itemgetter(0)):
|
for name, desc in sorted(devices.items(), key=itemgetter(0)):
|
||||||
|
@ -483,11 +666,11 @@ def create_channel_handlers(vcd_manager, devices, ref_period,
|
||||||
if (desc["module"] == "artiq.coredevice.ttl"
|
if (desc["module"] == "artiq.coredevice.ttl"
|
||||||
and desc["class"] in {"TTLOut", "TTLInOut"}):
|
and desc["class"] in {"TTLOut", "TTLInOut"}):
|
||||||
channel = desc["arguments"]["channel"]
|
channel = desc["arguments"]["channel"]
|
||||||
channel_handlers[channel] = TTLHandler(vcd_manager, name)
|
channel_handlers[channel] = TTLHandler(manager, name)
|
||||||
if (desc["module"] == "artiq.coredevice.ttl"
|
if (desc["module"] == "artiq.coredevice.ttl"
|
||||||
and desc["class"] == "TTLClockGen"):
|
and desc["class"] == "TTLClockGen"):
|
||||||
channel = desc["arguments"]["channel"]
|
channel = desc["arguments"]["channel"]
|
||||||
channel_handlers[channel] = TTLClockGenHandler(vcd_manager, name, ref_period)
|
channel_handlers[channel] = TTLClockGenHandler(manager, name, ref_period)
|
||||||
if (desc["module"] == "artiq.coredevice.ad9914"
|
if (desc["module"] == "artiq.coredevice.ad9914"
|
||||||
and desc["class"] == "AD9914"):
|
and desc["class"] == "AD9914"):
|
||||||
dds_bus_channel = desc["arguments"]["bus_channel"]
|
dds_bus_channel = desc["arguments"]["bus_channel"]
|
||||||
|
@ -495,37 +678,60 @@ def create_channel_handlers(vcd_manager, devices, ref_period,
|
||||||
if dds_bus_channel in channel_handlers:
|
if dds_bus_channel in channel_handlers:
|
||||||
dds_handler = channel_handlers[dds_bus_channel]
|
dds_handler = channel_handlers[dds_bus_channel]
|
||||||
else:
|
else:
|
||||||
dds_handler = DDSHandler(vcd_manager, dds_onehot_sel, dds_sysclk)
|
dds_handler = DDSHandler(manager, dds_onehot_sel, dds_sysclk)
|
||||||
channel_handlers[dds_bus_channel] = dds_handler
|
channel_handlers[dds_bus_channel] = dds_handler
|
||||||
dds_handler.add_dds_channel(name, dds_channel)
|
dds_handler.add_dds_channel(name, dds_channel)
|
||||||
if (desc["module"] == "artiq.coredevice.spi2" and
|
if (desc["module"] == "artiq.coredevice.spi2" and
|
||||||
desc["class"] == "SPIMaster"):
|
desc["class"] == "SPIMaster"):
|
||||||
channel = desc["arguments"]["channel"]
|
channel = desc["arguments"]["channel"]
|
||||||
channel_handlers[channel] = SPIMaster2Handler(
|
channel_handlers[channel] = SPIMaster2Handler(
|
||||||
vcd_manager, name)
|
manager, name)
|
||||||
return channel_handlers
|
return channel_handlers
|
||||||
|
|
||||||
|
|
||||||
|
def get_channel_list(devices):
|
||||||
|
manager = ChannelSignatureManager()
|
||||||
|
create_channel_handlers(manager, devices, 1e-9, 3e9, False)
|
||||||
|
ref_period = get_ref_period(devices)
|
||||||
|
if ref_period is None:
|
||||||
|
ref_period = DEFAULT_REF_PERIOD
|
||||||
|
precision = max(0, math.ceil(math.log10(1 / ref_period) - 6))
|
||||||
|
manager.get_channel("rtio_slack", 64, ty=WaveformType.ANALOG, precision=precision, unit="us")
|
||||||
|
return manager.channels
|
||||||
|
|
||||||
|
|
||||||
def get_message_time(message):
|
def get_message_time(message):
|
||||||
return getattr(message, "timestamp", message.rtio_counter)
|
return getattr(message, "timestamp", message.rtio_counter)
|
||||||
|
|
||||||
|
|
||||||
def decoded_dump_to_vcd(fileobj, devices, dump, uniform_interval=False):
|
def decoded_dump_to_vcd(fileobj, devices, dump, uniform_interval=False):
|
||||||
vcd_manager = VCDManager(fileobj)
|
vcd_manager = VCDManager(fileobj)
|
||||||
|
decoded_dump_to_target(vcd_manager, devices, dump, uniform_interval)
|
||||||
|
|
||||||
|
|
||||||
|
def decoded_dump_to_waveform_data(devices, dump, uniform_interval=False):
|
||||||
|
manager = WaveformManager()
|
||||||
|
decoded_dump_to_target(manager, devices, dump, uniform_interval)
|
||||||
|
return manager.trace
|
||||||
|
|
||||||
|
|
||||||
|
def decoded_dump_to_target(manager, devices, dump, uniform_interval):
|
||||||
ref_period = get_ref_period(devices)
|
ref_period = get_ref_period(devices)
|
||||||
|
|
||||||
if ref_period is not None:
|
if ref_period is None:
|
||||||
if not uniform_interval:
|
|
||||||
vcd_manager.set_timescale_ps(ref_period*1e12)
|
|
||||||
else:
|
|
||||||
logger.warning("unable to determine core device ref_period")
|
logger.warning("unable to determine core device ref_period")
|
||||||
ref_period = 1e-9 # guess
|
ref_period = DEFAULT_REF_PERIOD
|
||||||
|
if not uniform_interval:
|
||||||
|
manager.set_timescale_ps(ref_period*1e12)
|
||||||
dds_sysclk = get_dds_sysclk(devices)
|
dds_sysclk = get_dds_sysclk(devices)
|
||||||
if dds_sysclk is None:
|
if dds_sysclk is None:
|
||||||
logger.warning("unable to determine DDS sysclk")
|
logger.warning("unable to determine DDS sysclk")
|
||||||
dds_sysclk = 3e9 # guess
|
dds_sysclk = 3e9 # guess
|
||||||
|
|
||||||
if isinstance(dump.messages[-1], StoppedMessage):
|
if isinstance(dump.messages[-1], StoppedMessage):
|
||||||
|
m = dump.messages[-1]
|
||||||
|
end_time = get_message_time(m)
|
||||||
|
manager.set_end_time(end_time)
|
||||||
messages = dump.messages[:-1]
|
messages = dump.messages[:-1]
|
||||||
else:
|
else:
|
||||||
logger.warning("StoppedMessage missing")
|
logger.warning("StoppedMessage missing")
|
||||||
|
@ -533,38 +739,39 @@ def decoded_dump_to_vcd(fileobj, devices, dump, uniform_interval=False):
|
||||||
messages = sorted(messages, key=get_message_time)
|
messages = sorted(messages, key=get_message_time)
|
||||||
|
|
||||||
channel_handlers = create_channel_handlers(
|
channel_handlers = create_channel_handlers(
|
||||||
vcd_manager, devices, ref_period,
|
manager, devices, ref_period,
|
||||||
dds_sysclk, dump.dds_onehot_sel)
|
dds_sysclk, dump.dds_onehot_sel)
|
||||||
vcd_log_channels = get_vcd_log_channels(dump.log_channel, messages)
|
log_channels = get_log_channels(dump.log_channel, messages)
|
||||||
channel_handlers[dump.log_channel] = LogHandler(
|
channel_handlers[dump.log_channel] = LogHandler(
|
||||||
vcd_manager, vcd_log_channels)
|
manager, log_channels)
|
||||||
if uniform_interval:
|
if uniform_interval:
|
||||||
# RTIO event timestamp in machine units
|
# RTIO event timestamp in machine units
|
||||||
timestamp = vcd_manager.get_channel("timestamp", 64)
|
timestamp = manager.get_channel("timestamp", 64, ty=WaveformType.VECTOR)
|
||||||
# RTIO time interval between this and the next timed event
|
# RTIO time interval between this and the next timed event
|
||||||
# in SI seconds
|
# in SI seconds
|
||||||
interval = vcd_manager.get_channel("interval", 64)
|
interval = manager.get_channel("interval", 64, ty=WaveformType.ANALOG)
|
||||||
slack = vcd_manager.get_channel("rtio_slack", 64)
|
slack = manager.get_channel("rtio_slack", 64, ty=WaveformType.ANALOG)
|
||||||
|
|
||||||
vcd_manager.set_time(0)
|
manager.set_time(0)
|
||||||
start_time = 0
|
start_time = 0
|
||||||
for m in messages:
|
for m in messages:
|
||||||
start_time = get_message_time(m)
|
start_time = get_message_time(m)
|
||||||
if start_time:
|
if start_time:
|
||||||
break
|
break
|
||||||
|
if not uniform_interval:
|
||||||
t0 = 0
|
manager.set_start_time(start_time)
|
||||||
|
t0 = start_time
|
||||||
for i, message in enumerate(messages):
|
for i, message in enumerate(messages):
|
||||||
if message.channel in channel_handlers:
|
if message.channel in channel_handlers:
|
||||||
t = get_message_time(message) - start_time
|
t = get_message_time(message)
|
||||||
if t >= 0:
|
if t >= 0:
|
||||||
if uniform_interval:
|
if uniform_interval:
|
||||||
interval.set_value_double((t - t0)*ref_period)
|
interval.set_value_double((t - t0)*ref_period)
|
||||||
vcd_manager.set_time(i)
|
manager.set_time(i)
|
||||||
timestamp.set_value("{:064b}".format(t))
|
timestamp.set_value("{:064b}".format(t))
|
||||||
t0 = t
|
t0 = t
|
||||||
else:
|
else:
|
||||||
vcd_manager.set_time(t)
|
manager.set_time(t)
|
||||||
channel_handlers[message.channel].process_message(message)
|
channel_handlers[message.channel].process_message(message)
|
||||||
if isinstance(message, OutputMessage):
|
if isinstance(message, OutputMessage):
|
||||||
slack.set_value_double(
|
slack.set_value_double(
|
||||||
|
|
|
@ -465,12 +465,12 @@ class CommKernel:
|
||||||
self._write_bool(value)
|
self._write_bool(value)
|
||||||
elif tag == "i":
|
elif tag == "i":
|
||||||
check(isinstance(value, (int, numpy.int32)) and
|
check(isinstance(value, (int, numpy.int32)) and
|
||||||
(-2**31 <= value < 2**31),
|
(-2**31 <= value <= 2**31-1),
|
||||||
lambda: "32-bit int")
|
lambda: "32-bit int")
|
||||||
self._write_int32(value)
|
self._write_int32(value)
|
||||||
elif tag == "I":
|
elif tag == "I":
|
||||||
check(isinstance(value, (int, numpy.int32, numpy.int64)) and
|
check(isinstance(value, (int, numpy.int32, numpy.int64)) and
|
||||||
(-2**63 <= value < 2**63),
|
(-2**63 <= value <= 2**63-1),
|
||||||
lambda: "64-bit int")
|
lambda: "64-bit int")
|
||||||
self._write_int64(value)
|
self._write_int64(value)
|
||||||
elif tag == "f":
|
elif tag == "f":
|
||||||
|
@ -479,8 +479,8 @@ class CommKernel:
|
||||||
self._write_float64(value)
|
self._write_float64(value)
|
||||||
elif tag == "F":
|
elif tag == "F":
|
||||||
check(isinstance(value, Fraction) and
|
check(isinstance(value, Fraction) and
|
||||||
(-2**63 <= value.numerator < 2**63) and
|
(-2**63 <= value.numerator <= 2**63-1) and
|
||||||
(-2**63 <= value.denominator < 2**63),
|
(-2**63 <= value.denominator <= 2**63-1),
|
||||||
lambda: "64-bit Fraction")
|
lambda: "64-bit Fraction")
|
||||||
self._write_int64(value.numerator)
|
self._write_int64(value.numerator)
|
||||||
self._write_int64(value.denominator)
|
self._write_int64(value.denominator)
|
||||||
|
|
|
@ -94,9 +94,7 @@ class CommMonInj:
|
||||||
self.injection_status_cb(channel, override, value)
|
self.injection_status_cb(channel, override, value)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Unknown packet type", ty)
|
raise ValueError("Unknown packet type", ty)
|
||||||
except asyncio.CancelledError:
|
except Exception:
|
||||||
raise
|
|
||||||
except:
|
|
||||||
logger.error("Moninj connection terminating with exception", exc_info=True)
|
logger.error("Moninj connection terminating with exception", exc_info=True)
|
||||||
finally:
|
finally:
|
||||||
if self.disconnect_cb is not None:
|
if self.disconnect_cb is not None:
|
||||||
|
|
|
@ -73,8 +73,8 @@ class Core:
|
||||||
On platforms that use clock multiplication and SERDES-based PHYs,
|
On platforms that use clock multiplication and SERDES-based PHYs,
|
||||||
this is the period after multiplication. For example, with a RTIO core
|
this is the period after multiplication. For example, with a RTIO core
|
||||||
clocked at 125MHz and a SERDES multiplication factor of 8, the
|
clocked at 125MHz and a SERDES multiplication factor of 8, the
|
||||||
reference period is 1ns.
|
reference period is ``1 ns``.
|
||||||
The time machine unit is equal to this period.
|
The machine time unit (``mu``) is equal to this period.
|
||||||
:param ref_multiplier: ratio between the RTIO fine timestamp frequency
|
:param ref_multiplier: ratio between the RTIO fine timestamp frequency
|
||||||
and the RTIO coarse timestamp frequency (e.g. SERDES multiplication
|
and the RTIO coarse timestamp frequency (e.g. SERDES multiplication
|
||||||
factor).
|
factor).
|
||||||
|
@ -116,17 +116,21 @@ class Core:
|
||||||
self.trigger_analyzer_proxy()
|
self.trigger_analyzer_proxy()
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
"""Disconnect core device and close sockets.
|
||||||
|
"""
|
||||||
self.comm.close()
|
self.comm.close()
|
||||||
|
|
||||||
def compile(self, function, args, kwargs, set_result=None,
|
def compile(self, function, args, kwargs, set_result=None,
|
||||||
attribute_writeback=True, print_as_rpc=True,
|
attribute_writeback=True, print_as_rpc=True,
|
||||||
target=None, destination=0, subkernel_arg_types=[]):
|
target=None, destination=0, subkernel_arg_types=[],
|
||||||
|
old_embedding_map=None):
|
||||||
try:
|
try:
|
||||||
engine = _DiagnosticEngine(all_errors_are_fatal=True)
|
engine = _DiagnosticEngine(all_errors_are_fatal=True)
|
||||||
|
|
||||||
stitcher = Stitcher(engine=engine, core=self, dmgr=self.dmgr,
|
stitcher = Stitcher(engine=engine, core=self, dmgr=self.dmgr,
|
||||||
print_as_rpc=print_as_rpc,
|
print_as_rpc=print_as_rpc,
|
||||||
destination=destination, subkernel_arg_types=subkernel_arg_types)
|
destination=destination, subkernel_arg_types=subkernel_arg_types,
|
||||||
|
old_embedding_map=old_embedding_map)
|
||||||
stitcher.stitch_call(function, args, kwargs, set_result)
|
stitcher.stitch_call(function, args, kwargs, set_result)
|
||||||
stitcher.finalize()
|
stitcher.finalize()
|
||||||
|
|
||||||
|
@ -165,7 +169,7 @@ class Core:
|
||||||
self._run_compiled(kernel_library, embedding_map, symbolizer, demangler)
|
self._run_compiled(kernel_library, embedding_map, symbolizer, demangler)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def compile_subkernel(self, sid, subkernel_fn, embedding_map, args, subkernel_arg_types):
|
def compile_subkernel(self, sid, subkernel_fn, embedding_map, args, subkernel_arg_types, subkernels):
|
||||||
# pass self to subkernels (if applicable)
|
# pass self to subkernels (if applicable)
|
||||||
# assuming the first argument is self
|
# assuming the first argument is self
|
||||||
subkernel_args = getfullargspec(subkernel_fn.artiq_embedded.function)
|
subkernel_args = getfullargspec(subkernel_fn.artiq_embedded.function)
|
||||||
|
@ -179,17 +183,49 @@ class Core:
|
||||||
object_map, kernel_library, _, _, _ = \
|
object_map, kernel_library, _, _, _ = \
|
||||||
self.compile(subkernel_fn, self_arg, {}, attribute_writeback=False,
|
self.compile(subkernel_fn, self_arg, {}, attribute_writeback=False,
|
||||||
print_as_rpc=False, target=target, destination=destination,
|
print_as_rpc=False, target=target, destination=destination,
|
||||||
subkernel_arg_types=subkernel_arg_types.get(sid, []))
|
subkernel_arg_types=subkernel_arg_types.get(sid, []),
|
||||||
if object_map.has_rpc_or_subkernel():
|
old_embedding_map=embedding_map)
|
||||||
raise ValueError("Subkernel must not use RPC or subkernels in other destinations")
|
if object_map.has_rpc():
|
||||||
return destination, kernel_library
|
raise ValueError("Subkernel must not use RPC")
|
||||||
|
return destination, kernel_library, object_map
|
||||||
|
|
||||||
def compile_and_upload_subkernels(self, embedding_map, args, subkernel_arg_types):
|
def compile_and_upload_subkernels(self, embedding_map, args, subkernel_arg_types):
|
||||||
for sid, subkernel_fn in embedding_map.subkernels().items():
|
subkernels = embedding_map.subkernels()
|
||||||
destination, kernel_library = \
|
subkernels_compiled = []
|
||||||
self.compile_subkernel(sid, subkernel_fn, embedding_map,
|
while True:
|
||||||
args, subkernel_arg_types)
|
new_subkernels = {}
|
||||||
self.comm.upload_subkernel(kernel_library, sid, destination)
|
for sid, subkernel_fn in subkernels.items():
|
||||||
|
if sid in subkernels_compiled:
|
||||||
|
continue
|
||||||
|
destination, kernel_library, embedding_map = \
|
||||||
|
self.compile_subkernel(sid, subkernel_fn, embedding_map,
|
||||||
|
args, subkernel_arg_types, subkernels)
|
||||||
|
self.comm.upload_subkernel(kernel_library, sid, destination)
|
||||||
|
new_subkernels.update(embedding_map.subkernels())
|
||||||
|
subkernels_compiled.append(sid)
|
||||||
|
if new_subkernels == subkernels:
|
||||||
|
break
|
||||||
|
subkernels.update(new_subkernels)
|
||||||
|
# check for messages without a send/recv pair
|
||||||
|
unpaired_messages = embedding_map.subkernel_messages_unpaired()
|
||||||
|
if unpaired_messages:
|
||||||
|
for unpaired_message in unpaired_messages:
|
||||||
|
engine = _DiagnosticEngine(all_errors_are_fatal=False)
|
||||||
|
# errors are non-fatal in order to display
|
||||||
|
# all unpaired message errors before raising an excption
|
||||||
|
if unpaired_message.send_loc is None:
|
||||||
|
diag = diagnostic.Diagnostic("error",
|
||||||
|
"subkernel message '{name}' only has a receiver but no sender",
|
||||||
|
{"name": unpaired_message.name},
|
||||||
|
unpaired_message.recv_loc)
|
||||||
|
else:
|
||||||
|
diag = diagnostic.Diagnostic("error",
|
||||||
|
"subkernel message '{name}' only has a sender but no receiver",
|
||||||
|
{"name": unpaired_message.name},
|
||||||
|
unpaired_message.send_loc)
|
||||||
|
engine.process(diag)
|
||||||
|
raise ValueError("Found subkernel message(s) without a full send/recv pair")
|
||||||
|
|
||||||
|
|
||||||
def precompile(self, function, *args, **kwargs):
|
def precompile(self, function, *args, **kwargs):
|
||||||
"""Precompile a kernel and return a callable that executes it on the core device
|
"""Precompile a kernel and return a callable that executes it on the core device
|
||||||
|
@ -207,8 +243,8 @@ class Core:
|
||||||
Similarly, modified values are not written back, and explicit RPC should be used
|
Similarly, modified values are not written back, and explicit RPC should be used
|
||||||
to modify host objects.
|
to modify host objects.
|
||||||
Carefully review the source code of drivers calls used in precompiled kernels, as
|
Carefully review the source code of drivers calls used in precompiled kernels, as
|
||||||
they may rely on host object attributes being transfered between kernel calls.
|
they may rely on host object attributes being transferred between kernel calls.
|
||||||
Examples include code used to control DDS phase, and Urukul RF switch control
|
Examples include code used to control DDS phase and Urukul RF switch control
|
||||||
via the CPLD register.
|
via the CPLD register.
|
||||||
|
|
||||||
The return value of the callable is the return value of the kernel, if any.
|
The return value of the callable is the return value of the kernel, if any.
|
||||||
|
@ -239,7 +275,7 @@ class Core:
|
||||||
@portable
|
@portable
|
||||||
def seconds_to_mu(self, seconds):
|
def seconds_to_mu(self, seconds):
|
||||||
"""Convert seconds to the corresponding number of machine units
|
"""Convert seconds to the corresponding number of machine units
|
||||||
(RTIO cycles).
|
(fine RTIO cycles).
|
||||||
|
|
||||||
:param seconds: time (in seconds) to convert.
|
:param seconds: time (in seconds) to convert.
|
||||||
"""
|
"""
|
||||||
|
@ -247,7 +283,7 @@ class Core:
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def mu_to_seconds(self, mu):
|
def mu_to_seconds(self, mu):
|
||||||
"""Convert machine units (RTIO cycles) to seconds.
|
"""Convert machine units (fine RTIO cycles) to seconds.
|
||||||
|
|
||||||
:param mu: cycle count to convert.
|
:param mu: cycle count to convert.
|
||||||
"""
|
"""
|
||||||
|
@ -262,7 +298,7 @@ class Core:
|
||||||
for the actual value of the hardware register at the instant when
|
for the actual value of the hardware register at the instant when
|
||||||
execution resumes in the caller.
|
execution resumes in the caller.
|
||||||
|
|
||||||
For a more detailed description of these concepts, see :doc:`/rtio`.
|
For a more detailed description of these concepts, see :doc:`rtio`.
|
||||||
"""
|
"""
|
||||||
return rtio_get_counter()
|
return rtio_get_counter()
|
||||||
|
|
||||||
|
@ -281,7 +317,7 @@ class Core:
|
||||||
def get_rtio_destination_status(self, destination):
|
def get_rtio_destination_status(self, destination):
|
||||||
"""Returns whether the specified RTIO destination is up.
|
"""Returns whether the specified RTIO destination is up.
|
||||||
This is particularly useful in startup kernels to delay
|
This is particularly useful in startup kernels to delay
|
||||||
startup until certain DRTIO destinations are up."""
|
startup until certain DRTIO destinations are available."""
|
||||||
return rtio_get_destination_status(destination)
|
return rtio_get_destination_status(destination)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
|
@ -309,7 +345,7 @@ class Core:
|
||||||
|
|
||||||
Returns only after the dump has been retrieved from the device.
|
Returns only after the dump has been retrieved from the device.
|
||||||
|
|
||||||
Raises IOError if no analyzer proxy has been configured, or if the
|
Raises :exc:`IOError` if no analyzer proxy has been configured, or if the
|
||||||
analyzer proxy fails. In the latter case, more details would be
|
analyzer proxy fails. In the latter case, more details would be
|
||||||
available in the proxy log.
|
available in the proxy log.
|
||||||
"""
|
"""
|
||||||
|
@ -319,6 +355,4 @@ class Core:
|
||||||
if self.analyzer_proxy is None:
|
if self.analyzer_proxy is None:
|
||||||
raise IOError("No analyzer proxy configured")
|
raise IOError("No analyzer proxy configured")
|
||||||
else:
|
else:
|
||||||
success = self.analyzer_proxy.trigger()
|
self.analyzer_proxy.trigger()
|
||||||
if not success:
|
|
||||||
raise IOError("Analyzer proxy reported failure")
|
|
||||||
|
|
|
@ -49,6 +49,10 @@
|
||||||
"default": 125e6,
|
"default": 125e6,
|
||||||
"description": "RTIO frequency"
|
"description": "RTIO frequency"
|
||||||
},
|
},
|
||||||
|
"enable_wrpll": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
"core_addr": {
|
"core_addr": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "ipv4",
|
"format": "ipv4",
|
||||||
|
@ -308,8 +312,7 @@
|
||||||
"clk_div": {
|
"clk_div": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"minimum": 0,
|
"minimum": 0,
|
||||||
"maximum": 3,
|
"maximum": 3
|
||||||
"default": 0
|
|
||||||
},
|
},
|
||||||
"pll_n": {
|
"pll_n": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
@ -631,6 +634,10 @@
|
||||||
},
|
},
|
||||||
"drtio_destination": {
|
"drtio_destination": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"hw_rev": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["v1.0", "v1.1"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["ports"]
|
"required": ["ports"]
|
||||||
|
|
|
@ -76,11 +76,11 @@ class CoreDMA:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def record(self, name, enable_ddma=False):
|
def record(self, name, enable_ddma=False):
|
||||||
"""Returns a context manager that will record a DMA trace called ``name``.
|
"""Returns a context manager that will record a DMA trace called `name`.
|
||||||
Any previously recorded trace with the same name is overwritten.
|
Any previously recorded trace with the same name is overwritten.
|
||||||
The trace will persist across kernel switches.
|
The trace will persist across kernel switches.
|
||||||
|
|
||||||
In DRTIO context, distributed DMA can be toggled with ``enable_ddma``.
|
In DRTIO context, distributed DMA can be toggled with `enable_ddma`.
|
||||||
Enabling it allows running DMA on satellites, rather than sending all
|
Enabling it allows running DMA on satellites, rather than sending all
|
||||||
events from the master.
|
events from the master.
|
||||||
|
|
||||||
|
@ -116,7 +116,7 @@ class CoreDMA:
|
||||||
def playback_handle(self, handle):
|
def playback_handle(self, handle):
|
||||||
"""Replays a handle obtained with :meth:`get_handle`. Using this function
|
"""Replays a handle obtained with :meth:`get_handle`. Using this function
|
||||||
is much faster than :meth:`playback` for replaying a set of traces repeatedly,
|
is much faster than :meth:`playback` for replaying a set of traces repeatedly,
|
||||||
but incurs the overhead of managing the handles onto the programmer."""
|
but offloads the overhead of managing the handles onto the programmer."""
|
||||||
(epoch, advance_mu, ptr, uses_ddma) = handle
|
(epoch, advance_mu, ptr, uses_ddma) = handle
|
||||||
if self.epoch != epoch:
|
if self.epoch != epoch:
|
||||||
raise DMAError("Invalid handle")
|
raise DMAError("Invalid handle")
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
"""Driver for RTIO-enabled TTL edge counter.
|
"""Driver for RTIO-enabled TTL edge counter.
|
||||||
|
|
||||||
Like for the TTL input PHYs, sensitivity can be configured over RTIO
|
As for the TTL input PHYs, sensitivity can be configured over RTIO
|
||||||
(``gate_rising()``, etc.). In contrast to the former, however, the count is
|
(:meth:`gate_rising<EdgeCounter.gate_rising>`, etc.). In contrast to the former, however, the count is
|
||||||
accumulated in gateware, and only a single input event is generated at the end
|
accumulated in gateware, and only a single input event is generated at the end
|
||||||
of each gate period::
|
of each gate period: ::
|
||||||
|
|
||||||
with parallel:
|
with parallel:
|
||||||
doppler_cool()
|
doppler_cool()
|
||||||
|
@ -17,12 +17,12 @@ of each gate period::
|
||||||
print("Readout counts:", self.pmt_counter.fetch_count())
|
print("Readout counts:", self.pmt_counter.fetch_count())
|
||||||
|
|
||||||
For applications where the timestamps of the individual input events are not
|
For applications where the timestamps of the individual input events are not
|
||||||
required, this has two advantages over ``TTLInOut.count()`` beyond raw
|
required, this has two advantages over :meth:`TTLInOut.count<artiq.coredevice.ttl.TTLInOut.count>`
|
||||||
throughput. First, it is easy to count events during multiple separate periods
|
beyond raw throughput. First, it is easy to count events during multiple separate
|
||||||
without blocking to read back counts in between, as illustrated in the above
|
periods without blocking to read back counts in between, as illustrated in the
|
||||||
example. Secondly, as each count total only takes up a single input event, it
|
above example. Secondly, as each count total only takes up a single input event,
|
||||||
is much easier to acquire counts on several channels in parallel without
|
it is much easier to acquire counts on several channels in parallel without
|
||||||
risking input FIFO overflows::
|
risking input RTIO overflows: ::
|
||||||
|
|
||||||
# Using the TTLInOut driver, pmt_1 input events are only processed
|
# Using the TTLInOut driver, pmt_1 input events are only processed
|
||||||
# after pmt_0 is done counting. To avoid RTIOOverflows, a round-robin
|
# after pmt_0 is done counting. To avoid RTIOOverflows, a round-robin
|
||||||
|
@ -35,8 +35,6 @@ risking input FIFO overflows::
|
||||||
counts_0 = self.pmt_0.count(now_mu()) # blocks
|
counts_0 = self.pmt_0.count(now_mu()) # blocks
|
||||||
counts_1 = self.pmt_1.count(now_mu())
|
counts_1 = self.pmt_1.count(now_mu())
|
||||||
|
|
||||||
#
|
|
||||||
|
|
||||||
# Using gateware counters, only a single input event each is
|
# Using gateware counters, only a single input event each is
|
||||||
# generated, greatly reducing the load on the input FIFOs:
|
# generated, greatly reducing the load on the input FIFOs:
|
||||||
|
|
||||||
|
@ -47,7 +45,7 @@ risking input FIFO overflows::
|
||||||
counts_0 = self.pmt_0_counter.fetch_count() # blocks
|
counts_0 = self.pmt_0_counter.fetch_count() # blocks
|
||||||
counts_1 = self.pmt_1_counter.fetch_count()
|
counts_1 = self.pmt_1_counter.fetch_count()
|
||||||
|
|
||||||
See :mod:`artiq.gateware.rtio.phy.edge_counter` and
|
See the sources of :mod:`artiq.gateware.rtio.phy.edge_counter` and
|
||||||
:meth:`artiq.gateware.eem.DIO.add_std` for the gateware components.
|
:meth:`artiq.gateware.eem.DIO.add_std` for the gateware components.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -176,13 +174,13 @@ class EdgeCounter:
|
||||||
"""Emit an RTIO event at the current timeline position to set the
|
"""Emit an RTIO event at the current timeline position to set the
|
||||||
gateware configuration.
|
gateware configuration.
|
||||||
|
|
||||||
For most use cases, the `gate_*` wrappers will be more convenient.
|
For most use cases, the ``gate_*`` wrappers will be more convenient.
|
||||||
|
|
||||||
:param count_rising: Whether to count rising signal edges.
|
:param count_rising: Whether to count rising signal edges.
|
||||||
:param count_falling: Whether to count falling signal edges.
|
:param count_falling: Whether to count falling signal edges.
|
||||||
:param send_count_event: If `True`, an input event with the current
|
:param send_count_event: If ``True``, an input event with the current
|
||||||
counter value is generated on the next clock cycle (once).
|
counter value is generated on the next clock cycle (once).
|
||||||
:param reset_to_zero: If `True`, the counter value is reset to zero on
|
:param reset_to_zero: If ``True``, the counter value is reset to zero on
|
||||||
the next clock cycle (once).
|
the next clock cycle (once).
|
||||||
"""
|
"""
|
||||||
config = int32(0)
|
config = int32(0)
|
||||||
|
|
|
@ -137,7 +137,7 @@ class RTIOOverflow(Exception):
|
||||||
|
|
||||||
|
|
||||||
class RTIODestinationUnreachable(Exception):
|
class RTIODestinationUnreachable(Exception):
|
||||||
"""Raised with a RTIO operation could not be completed due to a DRTIO link
|
"""Raised when a RTIO operation could not be completed due to a DRTIO link
|
||||||
being down.
|
being down.
|
||||||
"""
|
"""
|
||||||
artiq_builtin = True
|
artiq_builtin = True
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""RTIO driver for the Fastino 32channel, 16 bit, 2.5 MS/s per channel,
|
"""RTIO driver for the Fastino 32-channel, 16-bit, 2.5 MS/s per channel
|
||||||
streaming DAC.
|
streaming DAC.
|
||||||
"""
|
"""
|
||||||
from numpy import int32, int64
|
from numpy import int32, int64
|
||||||
|
@ -17,22 +17,22 @@ class Fastino:
|
||||||
to the DAC RTIO addresses, if a channel is not "held" by setting its bit
|
to the DAC RTIO addresses, if a channel is not "held" by setting its bit
|
||||||
using :meth:`set_hold`, the next frame will contain the update. For the
|
using :meth:`set_hold`, the next frame will contain the update. For the
|
||||||
DACs held, the update is triggered explicitly by setting the corresponding
|
DACs held, the update is triggered explicitly by setting the corresponding
|
||||||
bit using :meth:`set_update`. Update is self-clearing. This enables atomic
|
bit using :meth:`update`. Update is self-clearing. This enables atomic
|
||||||
DAC updates synchronized to a frame edge.
|
DAC updates synchronized to a frame edge.
|
||||||
|
|
||||||
The `log2_width=0` RTIO layout uses one DAC channel per RTIO address and a
|
The ``log2_width=0`` RTIO layout uses one DAC channel per RTIO address and a
|
||||||
dense RTIO address space. The RTIO words are narrow (32 bit) and
|
dense RTIO address space. The RTIO words are narrow (32-bit) and
|
||||||
few-channel updates are efficient. There is the least amount of DAC state
|
few-channel updates are efficient. There is the least amount of DAC state
|
||||||
tracking in kernels, at the cost of more DMA and RTIO data.
|
tracking in kernels, at the cost of more DMA and RTIO data.
|
||||||
The setting here and in the RTIO PHY (gateware) must match.
|
The setting here and in the RTIO PHY (gateware) must match.
|
||||||
|
|
||||||
Other `log2_width` (up to `log2_width=5`) settings pack multiple
|
Other ``log2_width`` (up to ``log2_width=5``) settings pack multiple
|
||||||
(in powers of two) DAC channels into one group and into one RTIO write.
|
(in powers of two) DAC channels into one group and into one RTIO write.
|
||||||
The RTIO data width increases accordingly. The `log2_width`
|
The RTIO data width increases accordingly. The ``log2_width``
|
||||||
LSBs of the RTIO address for a DAC channel write must be zero and the
|
LSBs of the RTIO address for a DAC channel write must be zero and the
|
||||||
address space is sparse. For `log2_width=5` the RTIO data is 512 bit wide.
|
address space is sparse. For ``log2_width=5`` the RTIO data is 512-bit wide.
|
||||||
|
|
||||||
If `log2_width` is zero, the :meth:`set_dac`/:meth:`set_dac_mu` interface
|
If ``log2_width`` is zero, the :meth:`set_dac`/:meth:`set_dac_mu` interface
|
||||||
must be used. If non-zero, the :meth:`set_group`/:meth:`set_group_mu`
|
must be used. If non-zero, the :meth:`set_group`/:meth:`set_group_mu`
|
||||||
interface must be used.
|
interface must be used.
|
||||||
|
|
||||||
|
@ -63,15 +63,16 @@ class Fastino:
|
||||||
* disables RESET, DAC_CLR, enables AFE_PWR
|
* disables RESET, DAC_CLR, enables AFE_PWR
|
||||||
* clears error counters, enables error counting
|
* clears error counters, enables error counting
|
||||||
* turns LEDs off
|
* turns LEDs off
|
||||||
* clears `hold` and `continuous` on all channels
|
* clears ``hold`` and ``continuous`` on all channels
|
||||||
* clear and resets interpolators to unit rate change on all
|
* clear and resets interpolators to unit rate change on all
|
||||||
channels
|
channels
|
||||||
|
|
||||||
It does not change set channel voltages and does not reset the PLLs or clock
|
It does not change set channel voltages and does not reset the PLLs or clock
|
||||||
domains.
|
domains.
|
||||||
|
|
||||||
Note: On Fastino gateware before v0.2 this may lead to 0 voltage being emitted
|
.. warning::
|
||||||
transiently.
|
On Fastino gateware before v0.2 this may lead to 0 voltage being emitted
|
||||||
|
transiently.
|
||||||
"""
|
"""
|
||||||
self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=1)
|
self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=1)
|
||||||
delay_mu(self.t_frame)
|
delay_mu(self.t_frame)
|
||||||
|
@ -115,7 +116,7 @@ class Fastino:
|
||||||
"""Write DAC data in machine units.
|
"""Write DAC data in machine units.
|
||||||
|
|
||||||
:param dac: DAC channel to write to (0-31).
|
:param dac: DAC channel to write to (0-31).
|
||||||
:param data: DAC word to write, 16 bit unsigned integer, in machine
|
:param data: DAC word to write, 16-bit unsigned integer, in machine
|
||||||
units.
|
units.
|
||||||
"""
|
"""
|
||||||
self.write(dac, data)
|
self.write(dac, data)
|
||||||
|
@ -124,9 +125,9 @@ class Fastino:
|
||||||
def set_group_mu(self, dac: TInt32, data: TList(TInt32)):
|
def set_group_mu(self, dac: TInt32, data: TList(TInt32)):
|
||||||
"""Write a group of DAC channels in machine units.
|
"""Write a group of DAC channels in machine units.
|
||||||
|
|
||||||
:param dac: First channel in DAC channel group (0-31). The `log2_width`
|
:param dac: First channel in DAC channel group (0-31). The ``log2_width``
|
||||||
LSBs must be zero.
|
LSBs must be zero.
|
||||||
:param data: List of DAC data pairs (2x16 bit unsigned) to write,
|
:param data: List of DAC data pairs (2x16-bit unsigned) to write,
|
||||||
in machine units. Data exceeding group size is ignored.
|
in machine units. Data exceeding group size is ignored.
|
||||||
If the list length is less than group size, the remaining
|
If the list length is less than group size, the remaining
|
||||||
DAC channels within the group are cleared to 0 (machine units).
|
DAC channels within the group are cleared to 0 (machine units).
|
||||||
|
@ -137,10 +138,10 @@ class Fastino:
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def voltage_to_mu(self, voltage):
|
def voltage_to_mu(self, voltage):
|
||||||
"""Convert SI Volts to DAC machine units.
|
"""Convert SI volts to DAC machine units.
|
||||||
|
|
||||||
:param voltage: Voltage in SI Volts.
|
:param voltage: Voltage in SI volts.
|
||||||
:return: DAC data word in machine units, 16 bit integer.
|
:return: DAC data word in machine units, 16-bit integer.
|
||||||
"""
|
"""
|
||||||
data = int32(round((0x8000/10.)*voltage)) + int32(0x8000)
|
data = int32(round((0x8000/10.)*voltage)) + int32(0x8000)
|
||||||
if data < 0 or data > 0xffff:
|
if data < 0 or data > 0xffff:
|
||||||
|
@ -149,9 +150,9 @@ class Fastino:
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def voltage_group_to_mu(self, voltage, data):
|
def voltage_group_to_mu(self, voltage, data):
|
||||||
"""Convert SI Volts to packed DAC channel group machine units.
|
"""Convert SI volts to packed DAC channel group machine units.
|
||||||
|
|
||||||
:param voltage: List of SI Volt voltages.
|
:param voltage: List of SI volt voltages.
|
||||||
:param data: List of DAC channel data pairs to write to.
|
:param data: List of DAC channel data pairs to write to.
|
||||||
Half the length of `voltage`.
|
Half the length of `voltage`.
|
||||||
"""
|
"""
|
||||||
|
@ -185,7 +186,7 @@ class Fastino:
|
||||||
def update(self, update):
|
def update(self, update):
|
||||||
"""Schedule channels for update.
|
"""Schedule channels for update.
|
||||||
|
|
||||||
:param update: Bit mask of channels to update (32 bit).
|
:param update: Bit mask of channels to update (32-bit).
|
||||||
"""
|
"""
|
||||||
self.write(0x20, update)
|
self.write(0x20, update)
|
||||||
|
|
||||||
|
@ -193,7 +194,7 @@ class Fastino:
|
||||||
def set_hold(self, hold):
|
def set_hold(self, hold):
|
||||||
"""Set channels to manual update.
|
"""Set channels to manual update.
|
||||||
|
|
||||||
:param hold: Bit mask of channels to hold (32 bit).
|
:param hold: Bit mask of channels to hold (32-bit).
|
||||||
"""
|
"""
|
||||||
self.write(0x21, hold)
|
self.write(0x21, hold)
|
||||||
|
|
||||||
|
@ -214,9 +215,9 @@ class Fastino:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_leds(self, leds):
|
def set_leds(self, leds):
|
||||||
"""Set the green user-defined LEDs
|
"""Set the green user-defined LEDs.
|
||||||
|
|
||||||
:param leds: LED status, 8 bit integer each bit corresponding to one
|
:param leds: LED status, 8-bit integer each bit corresponding to one
|
||||||
green LED.
|
green LED.
|
||||||
"""
|
"""
|
||||||
self.write(0x23, leds)
|
self.write(0x23, leds)
|
||||||
|
@ -245,16 +246,16 @@ class Fastino:
|
||||||
def stage_cic(self, rate) -> TInt32:
|
def stage_cic(self, rate) -> TInt32:
|
||||||
"""Compute and stage interpolator configuration.
|
"""Compute and stage interpolator configuration.
|
||||||
|
|
||||||
This method approximates the desired interpolation rate using a 10 bit
|
This method approximates the desired interpolation rate using a 10-bit
|
||||||
floating point representation (6 bit mantissa, 4 bit exponent) and
|
floating point representation (6-bit mantissa, 4-bit exponent) and
|
||||||
then determines an optimal interpolation gain compensation exponent
|
then determines an optimal interpolation gain compensation exponent
|
||||||
to avoid clipping. Gains for rates that are powers of two are accurately
|
to avoid clipping. Gains for rates that are powers of two are accurately
|
||||||
compensated. Other rates lead to overall less than unity gain (but more
|
compensated. Other rates lead to overall less than unity gain (but more
|
||||||
than 0.5 gain).
|
than 0.5 gain).
|
||||||
|
|
||||||
The overall gain including gain compensation is
|
The overall gain including gain compensation is ``actual_rate ** order /
|
||||||
`actual_rate**order/2**ceil(log2(actual_rate**order))`
|
2 ** ceil(log2(actual_rate ** order))``
|
||||||
where `order = 3`.
|
where ``order = 3``.
|
||||||
|
|
||||||
Returns the actual interpolation rate.
|
Returns the actual interpolation rate.
|
||||||
"""
|
"""
|
||||||
|
@ -293,7 +294,7 @@ class Fastino:
|
||||||
their output is supposed to be constant.
|
their output is supposed to be constant.
|
||||||
|
|
||||||
This method resets and settles the affected interpolators. There will be
|
This method resets and settles the affected interpolators. There will be
|
||||||
no output updates for the next `order = 3` input samples.
|
no output updates for the next ``order = 3`` input samples.
|
||||||
Affected channels will only accept one input sample per input sample
|
Affected channels will only accept one input sample per input sample
|
||||||
period. This method synchronizes the input sample period to the current
|
period. This method synchronizes the input sample period to the current
|
||||||
frame on the affected channels.
|
frame on the affected channels.
|
||||||
|
|
|
@ -2,7 +2,7 @@ from numpy import int32, int64
|
||||||
|
|
||||||
from artiq.language.core import *
|
from artiq.language.core import *
|
||||||
from artiq.language.types import *
|
from artiq.language.types import *
|
||||||
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
from artiq.coredevice.rtio import rtio_output, rtio_input_timestamped_data
|
||||||
|
|
||||||
|
|
||||||
class OutOfSyncException(Exception):
|
class OutOfSyncException(Exception):
|
||||||
|
@ -11,6 +11,11 @@ class OutOfSyncException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class GrabberTimeoutException(Exception):
|
||||||
|
"""Raised when a timeout occurs while attempting to read Grabber RTIO input events."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Grabber:
|
class Grabber:
|
||||||
"""Driver for the Grabber camera interface."""
|
"""Driver for the Grabber camera interface."""
|
||||||
kernel_invariants = {"core", "channel_base", "sentinel"}
|
kernel_invariants = {"core", "channel_base", "sentinel"}
|
||||||
|
@ -82,10 +87,10 @@ class Grabber:
|
||||||
self.gate_roi(0)
|
self.gate_roi(0)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def input_mu(self, data):
|
def input_mu(self, data, timeout_mu=-1):
|
||||||
"""
|
"""
|
||||||
Retrieves the accumulated values for one frame from the ROI engines.
|
Retrieves the accumulated values for one frame from the ROI engines.
|
||||||
Blocks until values are available.
|
Blocks until values are available or timeout is reached.
|
||||||
|
|
||||||
The input list must be a list of integers of the same length as there
|
The input list must be a list of integers of the same length as there
|
||||||
are enabled ROI engines. This method replaces the elements of the
|
are enabled ROI engines. This method replaces the elements of the
|
||||||
|
@ -95,15 +100,26 @@ class Grabber:
|
||||||
If the number of elements in the list does not match the number of
|
If the number of elements in the list does not match the number of
|
||||||
ROI engines that produced output, an exception will be raised during
|
ROI engines that produced output, an exception will be raised during
|
||||||
this call or the next.
|
this call or the next.
|
||||||
|
|
||||||
|
If the timeout is reached before data is available, the exception
|
||||||
|
:exc:`GrabberTimeoutException` is raised.
|
||||||
|
|
||||||
|
:param timeout_mu: Timestamp at which a timeout will occur. Set to -1
|
||||||
|
(default) to disable timeout.
|
||||||
"""
|
"""
|
||||||
channel = self.channel_base + 1
|
channel = self.channel_base + 1
|
||||||
|
|
||||||
sentinel = rtio_input_data(channel)
|
timestamp, sentinel = rtio_input_timestamped_data(timeout_mu, channel)
|
||||||
|
if timestamp == -1:
|
||||||
|
raise GrabberTimeoutException("Timeout before Grabber frame available")
|
||||||
if sentinel != self.sentinel:
|
if sentinel != self.sentinel:
|
||||||
raise OutOfSyncException
|
raise OutOfSyncException
|
||||||
|
|
||||||
for i in range(len(data)):
|
for i in range(len(data)):
|
||||||
roi_output = rtio_input_data(channel)
|
timestamp, roi_output = rtio_input_timestamped_data(timeout_mu, channel)
|
||||||
if roi_output == self.sentinel:
|
if roi_output == self.sentinel:
|
||||||
raise OutOfSyncException
|
raise OutOfSyncException
|
||||||
|
if timestamp == -1:
|
||||||
|
raise GrabberTimeoutException(
|
||||||
|
"Timeout retrieving ROIs (attempting to read more ROIs than enabled?)")
|
||||||
data[i] = roi_output
|
data[i] = roi_output
|
||||||
|
|
|
@ -43,7 +43,7 @@ def i2c_poll(busno, busaddr):
|
||||||
"""Poll I2C device at address.
|
"""Poll I2C device at address.
|
||||||
|
|
||||||
:param busno: I2C bus number
|
:param busno: I2C bus number
|
||||||
:param busaddr: 8 bit I2C device address (LSB=0)
|
:param busaddr: 8-bit I2C device address (LSB=0)
|
||||||
:returns: True if the poll was ACKed
|
:returns: True if the poll was ACKed
|
||||||
"""
|
"""
|
||||||
i2c_start(busno)
|
i2c_start(busno)
|
||||||
|
@ -57,7 +57,7 @@ def i2c_write_byte(busno, busaddr, data, ack=True):
|
||||||
"""Write one byte to a device.
|
"""Write one byte to a device.
|
||||||
|
|
||||||
:param busno: I2C bus number
|
:param busno: I2C bus number
|
||||||
:param busaddr: 8 bit I2C device address (LSB=0)
|
:param busaddr: 8-bit I2C device address (LSB=0)
|
||||||
:param data: Data byte to be written
|
:param data: Data byte to be written
|
||||||
:param nack: Allow NACK
|
:param nack: Allow NACK
|
||||||
"""
|
"""
|
||||||
|
@ -76,7 +76,7 @@ def i2c_read_byte(busno, busaddr):
|
||||||
"""Read one byte from a device.
|
"""Read one byte from a device.
|
||||||
|
|
||||||
:param busno: I2C bus number
|
:param busno: I2C bus number
|
||||||
:param busaddr: 8 bit I2C device address (LSB=0)
|
:param busaddr: 8-bit I2C device address (LSB=0)
|
||||||
:returns: Byte read
|
:returns: Byte read
|
||||||
"""
|
"""
|
||||||
i2c_start(busno)
|
i2c_start(busno)
|
||||||
|
@ -95,10 +95,10 @@ def i2c_write_many(busno, busaddr, addr, data, ack_last=True):
|
||||||
"""Transfer multiple bytes to a device.
|
"""Transfer multiple bytes to a device.
|
||||||
|
|
||||||
:param busno: I2c bus number
|
:param busno: I2c bus number
|
||||||
:param busaddr: 8 bit I2C device address (LSB=0)
|
:param busaddr: 8-bit I2C device address (LSB=0)
|
||||||
:param addr: 8 bit data address
|
:param addr: 8-bit data address
|
||||||
:param data: Data bytes to be written
|
:param data: Data bytes to be written
|
||||||
:param ack_last: Expect I2C ACK of the last byte written. If `False`,
|
:param ack_last: Expect I2C ACK of the last byte written. If ``False``,
|
||||||
the last byte may be NACKed (e.g. EEPROM full page writes).
|
the last byte may be NACKed (e.g. EEPROM full page writes).
|
||||||
"""
|
"""
|
||||||
n = len(data)
|
n = len(data)
|
||||||
|
@ -121,8 +121,8 @@ def i2c_read_many(busno, busaddr, addr, data):
|
||||||
"""Transfer multiple bytes from a device.
|
"""Transfer multiple bytes from a device.
|
||||||
|
|
||||||
:param busno: I2c bus number
|
:param busno: I2c bus number
|
||||||
:param busaddr: 8 bit I2C device address (LSB=0)
|
:param busaddr: 8-bit I2C device address (LSB=0)
|
||||||
:param addr: 8 bit data address
|
:param addr: 8-bit data address
|
||||||
:param data: List of integers to be filled with the data read.
|
:param data: List of integers to be filled with the data read.
|
||||||
One entry ber byte.
|
One entry ber byte.
|
||||||
"""
|
"""
|
||||||
|
@ -147,7 +147,7 @@ class I2CSwitch:
|
||||||
|
|
||||||
PCA954X (or other) type detection is done by the CPU during I2C init.
|
PCA954X (or other) type detection is done by the CPU during I2C init.
|
||||||
|
|
||||||
I2C transactions not real-time, and are performed by the CPU without
|
I2C transactions are not real-time, and are performed by the CPU without
|
||||||
involving RTIO.
|
involving RTIO.
|
||||||
|
|
||||||
On the KC705, this chip is used for selecting the I2C buses on the two FMC
|
On the KC705, this chip is used for selecting the I2C buses on the two FMC
|
||||||
|
@ -176,7 +176,7 @@ class I2CSwitch:
|
||||||
class TCA6424A:
|
class TCA6424A:
|
||||||
"""Driver for the TCA6424A I2C I/O expander.
|
"""Driver for the TCA6424A I2C I/O expander.
|
||||||
|
|
||||||
I2C transactions not real-time, and are performed by the CPU without
|
I2C transactions are not real-time, and are performed by the CPU without
|
||||||
involving RTIO.
|
involving RTIO.
|
||||||
|
|
||||||
On the NIST QC2 hardware, this chip is used for switching the directions
|
On the NIST QC2 hardware, this chip is used for switching the directions
|
||||||
|
@ -212,7 +212,7 @@ class TCA6424A:
|
||||||
class PCF8574A:
|
class PCF8574A:
|
||||||
"""Driver for the PCF8574 I2C remote 8-bit I/O expander.
|
"""Driver for the PCF8574 I2C remote 8-bit I/O expander.
|
||||||
|
|
||||||
I2C transactions not real-time, and are performed by the CPU without
|
I2C transactions are not real-time, and are performed by the CPU without
|
||||||
involving RTIO.
|
involving RTIO.
|
||||||
"""
|
"""
|
||||||
def __init__(self, dmgr, busno=0, address=0x7c, core_device="core"):
|
def __init__(self, dmgr, busno=0, address=0x7c, core_device="core"):
|
||||||
|
|
|
@ -25,14 +25,14 @@ port_mapping = {
|
||||||
|
|
||||||
|
|
||||||
class KasliEEPROM:
|
class KasliEEPROM:
|
||||||
def __init__(self, dmgr, port, busno=0,
|
def __init__(self, dmgr, port, address=0xa0, busno=0,
|
||||||
core_device="core", sw0_device="i2c_switch0", sw1_device="i2c_switch1"):
|
core_device="core", sw0_device="i2c_switch0", sw1_device="i2c_switch1"):
|
||||||
self.core = dmgr.get(core_device)
|
self.core = dmgr.get(core_device)
|
||||||
self.sw0 = dmgr.get(sw0_device)
|
self.sw0 = dmgr.get(sw0_device)
|
||||||
self.sw1 = dmgr.get(sw1_device)
|
self.sw1 = dmgr.get(sw1_device)
|
||||||
self.busno = busno
|
self.busno = busno
|
||||||
self.port = port_mapping[port]
|
self.port = port_mapping[port]
|
||||||
self.address = 0xa0 # i2c 8 bit
|
self.address = address # i2c 8 bit
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def select(self):
|
def select(self):
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
"""RTIO driver for Mirny (4 channel GHz PLLs)
|
"""RTIO driver for Mirny (4-channel GHz PLLs)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from artiq.language.core import kernel, delay, portable
|
from artiq.language.core import kernel, delay, portable
|
||||||
|
@ -82,7 +82,7 @@ class Mirny:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read_reg(self, addr):
|
def read_reg(self, addr):
|
||||||
"""Read a register"""
|
"""Read a register."""
|
||||||
self.bus.set_config_mu(
|
self.bus.set_config_mu(
|
||||||
SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 24, SPIT_RD, SPI_CS
|
SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 24, SPIT_RD, SPI_CS
|
||||||
)
|
)
|
||||||
|
@ -91,7 +91,7 @@ class Mirny:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write_reg(self, addr, data):
|
def write_reg(self, addr, data):
|
||||||
"""Write a register"""
|
"""Write a register."""
|
||||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, SPIT_WR, SPI_CS)
|
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, SPIT_WR, SPI_CS)
|
||||||
self.bus.write((addr << 25) | WE | ((data & 0xFFFF) << 8))
|
self.bus.write((addr << 25) | WE | ((data & 0xFFFF) << 8))
|
||||||
|
|
||||||
|
@ -101,9 +101,9 @@ class Mirny:
|
||||||
Initialize and detect Mirny.
|
Initialize and detect Mirny.
|
||||||
|
|
||||||
Select the clock source based the board's hardware revision.
|
Select the clock source based the board's hardware revision.
|
||||||
Raise ValueError if the board's hardware revision is not supported.
|
Raise :exc:`ValueError` if the board's hardware revision is not supported.
|
||||||
|
|
||||||
:param blind: Verify presence and protocol compatibility. Raise ValueError on failure.
|
:param blind: Verify presence and protocol compatibility. Raise :exc:`ValueError` on failure.
|
||||||
"""
|
"""
|
||||||
reg0 = self.read_reg(0)
|
reg0 = self.read_reg(0)
|
||||||
self.hw_rev = reg0 & 0x3
|
self.hw_rev = reg0 & 0x3
|
||||||
|
@ -138,7 +138,7 @@ class Mirny:
|
||||||
def set_att_mu(self, channel, att):
|
def set_att_mu(self, channel, att):
|
||||||
"""Set digital step attenuator in machine units.
|
"""Set digital step attenuator in machine units.
|
||||||
|
|
||||||
:param att: Attenuation setting, 8 bit digital.
|
:param att: Attenuation setting, 8-bit digital.
|
||||||
"""
|
"""
|
||||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 16, SPIT_WR, SPI_CS)
|
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 16, SPIT_WR, SPI_CS)
|
||||||
self.bus.write(((channel | 8) << 25) | (att << 16))
|
self.bus.write(((channel | 8) << 25) | (att << 16))
|
||||||
|
@ -149,7 +149,7 @@ class Mirny:
|
||||||
|
|
||||||
This method will write the attenuator settings of the selected channel.
|
This method will write the attenuator settings of the selected channel.
|
||||||
|
|
||||||
.. seealso:: :meth:`set_att_mu`
|
See also :meth:`Mirny.set_att_mu`.
|
||||||
|
|
||||||
:param channel: Attenuator channel (0-3).
|
:param channel: Attenuator channel (0-3).
|
||||||
:param att: Attenuation setting in dB. Higher value is more
|
:param att: Attenuation setting in dB. Higher value is more
|
||||||
|
@ -160,7 +160,7 @@ class Mirny:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write_ext(self, addr, length, data, ext_div=SPIT_WR):
|
def write_ext(self, addr, length, data, ext_div=SPIT_WR):
|
||||||
"""Perform SPI write to a prefixed address"""
|
"""Perform SPI write to a prefixed address."""
|
||||||
self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_WR, SPI_CS)
|
self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_WR, SPI_CS)
|
||||||
self.bus.write(addr << 25)
|
self.bus.write(addr << 25)
|
||||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, length, ext_div, SPI_CS)
|
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, length, ext_div, SPI_CS)
|
||||||
|
|
|
@ -16,31 +16,31 @@ SPI_CS_SR = 2
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def adc_ctrl(channel=1, softspan=0b111, valid=1):
|
def adc_ctrl(channel=1, softspan=0b111, valid=1):
|
||||||
"""Build a LTC2335-16 control word"""
|
"""Build a LTC2335-16 control word."""
|
||||||
return (valid << 7) | (channel << 3) | softspan
|
return (valid << 7) | (channel << 3) | softspan
|
||||||
|
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def adc_softspan(data):
|
def adc_softspan(data):
|
||||||
"""Return the softspan configuration index from a result packet"""
|
"""Return the softspan configuration index from a result packet."""
|
||||||
return data & 0x7
|
return data & 0x7
|
||||||
|
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def adc_channel(data):
|
def adc_channel(data):
|
||||||
"""Return the channel index from a result packet"""
|
"""Return the channel index from a result packet."""
|
||||||
return (data >> 3) & 0x7
|
return (data >> 3) & 0x7
|
||||||
|
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def adc_data(data):
|
def adc_data(data):
|
||||||
"""Return the ADC value from a result packet"""
|
"""Return the ADC value from a result packet."""
|
||||||
return (data >> 8) & 0xffff
|
return (data >> 8) & 0xffff
|
||||||
|
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def adc_value(data, v_ref=5.):
|
def adc_value(data, v_ref=5.):
|
||||||
"""Convert a ADC result packet to SI units (Volt)"""
|
"""Convert a ADC result packet to SI units (volts)."""
|
||||||
softspan = adc_softspan(data)
|
softspan = adc_softspan(data)
|
||||||
data = adc_data(data)
|
data = adc_data(data)
|
||||||
g = 625
|
g = 625
|
||||||
|
@ -107,7 +107,7 @@ class Novogorny:
|
||||||
def configure(self, data):
|
def configure(self, data):
|
||||||
"""Set up the ADC sequencer.
|
"""Set up the ADC sequencer.
|
||||||
|
|
||||||
:param data: List of 8 bit control words to write into the sequencer
|
:param data: List of 8-bit control words to write into the sequencer
|
||||||
table.
|
table.
|
||||||
"""
|
"""
|
||||||
if len(data) > 1:
|
if len(data) > 1:
|
||||||
|
@ -137,12 +137,10 @@ class Novogorny:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def sample(self, next_ctrl=0):
|
def sample(self, next_ctrl=0):
|
||||||
"""Acquire a sample
|
"""Acquire a sample. See also :meth:`Novogorny.sample_mu`.
|
||||||
|
|
||||||
.. seealso:: :meth:`sample_mu`
|
|
||||||
|
|
||||||
:param next_ctrl: ADC control word for the next sample
|
:param next_ctrl: ADC control word for the next sample
|
||||||
:return: The ADC result packet (Volt)
|
:return: The ADC result packet (volts)
|
||||||
"""
|
"""
|
||||||
return adc_value(self.sample_mu(), self.v_ref)
|
return adc_value(self.sample_mu(), self.v_ref)
|
||||||
|
|
||||||
|
@ -151,7 +149,7 @@ class Novogorny:
|
||||||
"""Acquire a burst of samples.
|
"""Acquire a burst of samples.
|
||||||
|
|
||||||
If the burst is too long and the sample rate too high, there will be
|
If the burst is too long and the sample rate too high, there will be
|
||||||
RTIO input overflows.
|
:exc:RTIOOverflow exceptions.
|
||||||
|
|
||||||
High sample rates lead to gain errors since the impedance between the
|
High sample rates lead to gain errors since the impedance between the
|
||||||
instrumentation amplifier and the ADC is high.
|
instrumentation amplifier and the ADC is high.
|
||||||
|
|
|
@ -85,7 +85,7 @@ SERVO_T_CYCLE = (32+12+192+24+4)*ns # Must match gateware ADC parameters
|
||||||
class Phaser:
|
class Phaser:
|
||||||
"""Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver.
|
"""Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver.
|
||||||
|
|
||||||
Phaser contains a 4 channel, 1 GS/s DAC chip with integrated upconversion,
|
Phaser contains a 4-channel, 1 GS/s DAC chip with integrated upconversion,
|
||||||
quadrature modulation compensation and interpolation features.
|
quadrature modulation compensation and interpolation features.
|
||||||
|
|
||||||
The coredevice RTIO PHY and the Phaser gateware come in different modes
|
The coredevice RTIO PHY and the Phaser gateware come in different modes
|
||||||
|
@ -109,9 +109,9 @@ class Phaser:
|
||||||
**Base mode**
|
**Base mode**
|
||||||
|
|
||||||
The coredevice produces 2 IQ (in-phase and quadrature) data streams with 25
|
The coredevice produces 2 IQ (in-phase and quadrature) data streams with 25
|
||||||
MS/s and 14 bit per quadrature. Each data stream supports 5 independent
|
MS/s and 14 bits per quadrature. Each data stream supports 5 independent
|
||||||
numerically controlled IQ oscillators (NCOs, DDSs with 32 bit frequency, 16
|
numerically controlled IQ oscillators (NCOs, DDSs with 32-bit frequency,
|
||||||
bit phase, 15 bit amplitude, and phase accumulator clear functionality)
|
16-bit phase, 15-bit amplitude, and phase accumulator clear functionality)
|
||||||
added together. See :class:`PhaserChannel` and :class:`PhaserOscillator`.
|
added together. See :class:`PhaserChannel` and :class:`PhaserOscillator`.
|
||||||
|
|
||||||
Together with a data clock, framing marker, a checksum and metadata for
|
Together with a data clock, framing marker, a checksum and metadata for
|
||||||
|
@ -119,30 +119,28 @@ class Phaser:
|
||||||
FastLink via a single EEM connector from coredevice to Phaser.
|
FastLink via a single EEM connector from coredevice to Phaser.
|
||||||
|
|
||||||
On Phaser in the FPGA the data streams are buffered and interpolated
|
On Phaser in the FPGA the data streams are buffered and interpolated
|
||||||
from 25 MS/s to 500 MS/s 16 bit followed by a 500 MS/s digital upconverter
|
from 25 MS/s to 500 MS/s 16-bit followed by a 500 MS/s digital upconverter
|
||||||
with adjustable frequency and phase. The interpolation passband is 20 MHz
|
with adjustable frequency and phase. The interpolation passband is 20 MHz
|
||||||
wide, passband ripple is less than 1e-3 amplitude, stopband attenuation
|
wide, passband ripple is less than 1e-3 amplitude, stopband attenuation
|
||||||
is better than 75 dB at offsets > 15 MHz and better than 90 dB at offsets
|
is better than 75 dB at offsets > 15 MHz and better than 90 dB at offsets
|
||||||
> 30 MHz.
|
> 30 MHz.
|
||||||
|
|
||||||
The four 16 bit 500 MS/s DAC data streams are sent via a 32 bit parallel
|
The four 16-bit 500 MS/s DAC data streams are sent via a 32-bit parallel
|
||||||
LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC (Texas
|
LVDS bus operating at 1 Gb/s per pin pair and processed in the DAC (Texas
|
||||||
Instruments DAC34H84). On the DAC 2x interpolation, sinx/x compensation,
|
Instruments DAC34H84). On the DAC 2x interpolation, sinx/x compensation,
|
||||||
quadrature modulator compensation, fine and coarse mixing as well as group
|
quadrature modulator compensation, fine and coarse mixing as well as group
|
||||||
delay capabilities are available. If desired, these features my be
|
delay capabilities are available. If desired, these features my be
|
||||||
configured via the `dac` dictionary.
|
configured via the ``dac`` dictionary.
|
||||||
|
|
||||||
The latency/group delay from the RTIO events setting
|
The latency/group delay from the RTIO events setting
|
||||||
:class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the
|
:class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the
|
||||||
way to the DAC outputs is deterministic. This enables deterministic
|
way to the DAC outputs is deterministic. This enables deterministic
|
||||||
absolute phase with respect to other RTIO input and output events
|
absolute phase with respect to other RTIO input and output events
|
||||||
(see `get_next_frame_mu()`).
|
(see :meth:`get_next_frame_mu()`).
|
||||||
|
|
||||||
**Miqro mode**
|
**Miqro mode**
|
||||||
|
|
||||||
See :class:`Miqro`
|
See :class:`Miqro`. Here the DAC operates in 4x interpolation.
|
||||||
|
|
||||||
Here the DAC operates in 4x interpolation.
|
|
||||||
|
|
||||||
**Analog flow**
|
**Analog flow**
|
||||||
|
|
||||||
|
@ -171,7 +169,7 @@ class Phaser:
|
||||||
and Q datastreams from the DUC by the IIR output. The IIR state is updated at
|
and Q datastreams from the DUC by the IIR output. The IIR state is updated at
|
||||||
the 3.788 MHz ADC sampling rate.
|
the 3.788 MHz ADC sampling rate.
|
||||||
|
|
||||||
Each channel IIR features 4 profiles, each consisting of the [b0, b1, a1] filter
|
Each channel IIR features 4 profiles, each consisting of the ``[b0, b1, a1]`` filter
|
||||||
coefficients as well as an output offset. The coefficients and offset can be
|
coefficients as well as an output offset. The coefficients and offset can be
|
||||||
set for each profile individually and the profiles each have their own ``y0``,
|
set for each profile individually and the profiles each have their own ``y0``,
|
||||||
``y1`` output registers (the ``x0``, ``x1`` inputs are shared). To avoid
|
``y1`` output registers (the ``x0``, ``x1`` inputs are shared). To avoid
|
||||||
|
@ -185,25 +183,25 @@ class Phaser:
|
||||||
still ingests samples and updates its input ``x0`` and ``x1`` registers, but
|
still ingests samples and updates its input ``x0`` and ``x1`` registers, but
|
||||||
does not update the ``y0``, ``y1`` output registers.
|
does not update the ``y0``, ``y1`` output registers.
|
||||||
|
|
||||||
After power-up the servo is disabled, in profile 0, with coefficients [0, 0, 0]
|
After power-up the servo is disabled, in profile 0, with coefficients ``[0, 0, 0]``
|
||||||
and hold is enabled. If older gateware without ther servo is loaded onto the
|
and hold is enabled. If older gateware without ther servo is loaded onto the
|
||||||
Phaser FPGA, the device simply behaves as if the servo is disabled and none of
|
Phaser FPGA, the device simply behaves as if the servo is disabled and none of
|
||||||
the servo functions have any effect.
|
the servo functions have any effect.
|
||||||
|
|
||||||
.. note:: Various register settings of the DAC and the quadrature
|
.. note:: Various register settings of the DAC and the quadrature
|
||||||
upconverters are available to be modified through the `dac`, `trf0`,
|
upconverters are available to be modified through the ``dac``, ``trf0``,
|
||||||
`trf1` dictionaries. These can be set through the device database
|
``trf1`` dictionaries. These can be set through the device database
|
||||||
(`device_db.py`). The settings are frozen during instantiation of the
|
(``device_db.py``). The settings are frozen during instantiation of the
|
||||||
class and applied during `init()`. See the :class:`DAC34H84` and
|
class and applied during ``init()``. See the :class:`dac34H84` and
|
||||||
:class:`TRF372017` source for details.
|
:class:`trf372017` source for details.
|
||||||
|
|
||||||
.. note:: To establish deterministic latency between RTIO time base and DAC
|
.. note:: To establish deterministic latency between RTIO time base and DAC
|
||||||
output, the DAC FIFO read pointer value (`fifo_offset`) must be
|
output, the DAC FIFO read pointer value (``fifo_offset``) must be
|
||||||
fixed. If `tune_fifo_offset=True` (the default) a value with maximum
|
fixed. If `tune_fifo_offset` = ``True`` (the default) a value with maximum
|
||||||
margin is determined automatically by `dac_tune_fifo_offset` each time
|
margin is determined automatically by `dac_tune_fifo_offset` each time
|
||||||
`init()` is called. This value should be used for the `fifo_offset` key
|
:meth:`init` is called. This value should be used for the ``fifo_offset`` key
|
||||||
of the `dac` settings of Phaser in `device_db.py` and automatic
|
of the ``dac`` settings of Phaser in ``device_db.py`` and automatic
|
||||||
tuning should be disabled by `tune_fifo_offset=False`.
|
tuning should be disabled by `tune_fifo_offset` = ``False```.
|
||||||
|
|
||||||
:param channel: Base RTIO channel number
|
:param channel: Base RTIO channel number
|
||||||
:param core_device: Core device name (default: "core")
|
:param core_device: Core device name (default: "core")
|
||||||
|
@ -219,9 +217,9 @@ class Phaser:
|
||||||
:param trf1: Channel 1 TRF372017 quadrature upconverter settings as a
|
:param trf1: Channel 1 TRF372017 quadrature upconverter settings as a
|
||||||
dictionary.
|
dictionary.
|
||||||
|
|
||||||
Attributes:
|
**Attributes:**
|
||||||
|
|
||||||
* :attr:`channel`: List of two :class:`PhaserChannel`
|
* :attr:`channel`: List of two instances of :class:`PhaserChannel`
|
||||||
To access oscillators, digital upconverters, PLL/VCO analog
|
To access oscillators, digital upconverters, PLL/VCO analog
|
||||||
quadrature upconverters and attenuators.
|
quadrature upconverters and attenuators.
|
||||||
"""
|
"""
|
||||||
|
@ -463,8 +461,8 @@ class Phaser:
|
||||||
def write8(self, addr, data):
|
def write8(self, addr, data):
|
||||||
"""Write data to FPGA register.
|
"""Write data to FPGA register.
|
||||||
|
|
||||||
:param addr: Address to write to (7 bit)
|
:param addr: Address to write to (7-bit)
|
||||||
:param data: Data to write (8 bit)
|
:param data: Data to write (8-bit)
|
||||||
"""
|
"""
|
||||||
rtio_output((self.channel_base << 8) | (addr & 0x7f) | 0x80, data)
|
rtio_output((self.channel_base << 8) | (addr & 0x7f) | 0x80, data)
|
||||||
delay_mu(int64(self.t_frame))
|
delay_mu(int64(self.t_frame))
|
||||||
|
@ -473,8 +471,8 @@ class Phaser:
|
||||||
def read8(self, addr) -> TInt32:
|
def read8(self, addr) -> TInt32:
|
||||||
"""Read from FPGA register.
|
"""Read from FPGA register.
|
||||||
|
|
||||||
:param addr: Address to read from (7 bit)
|
:param addr: Address to read from (7-bit)
|
||||||
:return: Data read (8 bit)
|
:return: Data read (8-bit)
|
||||||
"""
|
"""
|
||||||
rtio_output((self.channel_base << 8) | (addr & 0x7f), 0)
|
rtio_output((self.channel_base << 8) | (addr & 0x7f), 0)
|
||||||
response = rtio_input_data(self.channel_base)
|
response = rtio_input_data(self.channel_base)
|
||||||
|
@ -482,13 +480,13 @@ class Phaser:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write16(self, addr, data: TInt32):
|
def write16(self, addr, data: TInt32):
|
||||||
"""Write 16 bit to a sequence of FPGA registers."""
|
"""Write 16 bits to a sequence of FPGA registers."""
|
||||||
self.write8(addr, data >> 8)
|
self.write8(addr, data >> 8)
|
||||||
self.write8(addr + 1, data)
|
self.write8(addr + 1, data)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write32(self, addr, data: TInt32):
|
def write32(self, addr, data: TInt32):
|
||||||
"""Write 32 bit to a sequence of FPGA registers."""
|
"""Write 32 bits to a sequence of FPGA registers."""
|
||||||
for offset in range(4):
|
for offset in range(4):
|
||||||
byte = data >> 24
|
byte = data >> 24
|
||||||
self.write8(addr + offset, byte)
|
self.write8(addr + offset, byte)
|
||||||
|
@ -496,7 +494,7 @@ class Phaser:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read32(self, addr) -> TInt32:
|
def read32(self, addr) -> TInt32:
|
||||||
"""Read 32 bit from a sequence of FPGA registers."""
|
"""Read 32 bits from a sequence of FPGA registers."""
|
||||||
data = 0
|
data = 0
|
||||||
for offset in range(4):
|
for offset in range(4):
|
||||||
data <<= 8
|
data <<= 8
|
||||||
|
@ -508,15 +506,15 @@ class Phaser:
|
||||||
def set_leds(self, leds):
|
def set_leds(self, leds):
|
||||||
"""Set the front panel LEDs.
|
"""Set the front panel LEDs.
|
||||||
|
|
||||||
:param leds: LED settings (6 bit)
|
:param leds: LED settings (6-bit)
|
||||||
"""
|
"""
|
||||||
self.write8(PHASER_ADDR_LED, leds)
|
self.write8(PHASER_ADDR_LED, leds)
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_fan_mu(self, pwm):
|
def set_fan_mu(self, pwm):
|
||||||
"""Set the fan duty cycle.
|
"""Set the fan duty cycle in machine units.
|
||||||
|
|
||||||
:param pwm: Duty cycle in machine units (8 bit)
|
:param pwm: Duty cycle in machine units (8-bit)
|
||||||
"""
|
"""
|
||||||
self.write8(PHASER_ADDR_FAN, pwm)
|
self.write8(PHASER_ADDR_FAN, pwm)
|
||||||
|
|
||||||
|
@ -581,10 +579,10 @@ class Phaser:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def measure_frame_timestamp(self):
|
def measure_frame_timestamp(self):
|
||||||
"""Measure the timestamp of an arbitrary frame and store it in `self.frame_tstamp`.
|
"""Measure the timestamp of an arbitrary frame and store it in ``self.frame_tstamp``.
|
||||||
|
|
||||||
To be used as reference for aligning updates to the FastLink frames.
|
To be used as reference for aligning updates to the FastLink frames.
|
||||||
See `get_next_frame_mu()`.
|
See :meth:`get_next_frame_mu()`.
|
||||||
"""
|
"""
|
||||||
rtio_output(self.channel_base << 8, 0) # read any register
|
rtio_output(self.channel_base << 8, 0) # read any register
|
||||||
self.frame_tstamp = rtio_input_timestamp(now_mu() + 4 * self.t_frame, self.channel_base)
|
self.frame_tstamp = rtio_input_timestamp(now_mu() + 4 * self.t_frame, self.channel_base)
|
||||||
|
@ -592,10 +590,10 @@ class Phaser:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def get_next_frame_mu(self):
|
def get_next_frame_mu(self):
|
||||||
"""Return the timestamp of the frame strictly after `now_mu()`.
|
"""Return the timestamp of the frame strictly after :meth:`~artiq.language.core.now_mu()`.
|
||||||
|
|
||||||
Register updates (DUC, DAC, TRF, etc.) scheduled at this timestamp and multiples
|
Register updates (DUC, DAC, TRF, etc.) scheduled at this timestamp and multiples
|
||||||
of `self.t_frame` later will have deterministic latency to output.
|
of ``self.t_frame`` later will have deterministic latency to output.
|
||||||
"""
|
"""
|
||||||
n = int64((now_mu() - self.frame_tstamp) / self.t_frame)
|
n = int64((now_mu() - self.frame_tstamp) / self.t_frame)
|
||||||
return self.frame_tstamp + (n + 1) * self.t_frame
|
return self.frame_tstamp + (n + 1) * self.t_frame
|
||||||
|
@ -658,7 +656,7 @@ class Phaser:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def dac_write(self, addr, data):
|
def dac_write(self, addr, data):
|
||||||
"""Write 16 bit to a DAC register.
|
"""Write 16 bits to a DAC register.
|
||||||
|
|
||||||
:param addr: Register address
|
:param addr: Register address
|
||||||
:param data: Register data to write
|
:param data: Register data to write
|
||||||
|
@ -708,16 +706,16 @@ class Phaser:
|
||||||
def dac_sync(self):
|
def dac_sync(self):
|
||||||
"""Trigger DAC synchronisation for both output channels.
|
"""Trigger DAC synchronisation for both output channels.
|
||||||
|
|
||||||
The DAC sif_sync is de-asserts, then asserted. The synchronisation is
|
The DAC ``sif_sync`` is de-asserted, then asserted. The synchronisation is
|
||||||
triggered on assertion.
|
triggered on assertion.
|
||||||
|
|
||||||
By default, the fine-mixer (NCO) and QMC are synchronised. This
|
By default, the fine-mixer (NCO) and QMC are synchronised. This
|
||||||
includes applying the latest register settings.
|
includes applying the latest register settings.
|
||||||
|
|
||||||
The synchronisation sources may be configured through the `syncsel_x`
|
The synchronisation sources may be configured through the ``syncsel_x``
|
||||||
fields in the `dac` configuration dictionary (see `__init__()`).
|
fields in the ``dac`` configuration dictionary (see :class:`Phaser`).
|
||||||
|
|
||||||
.. note:: Synchronising the NCO clears the phase-accumulator
|
.. note:: Synchronising the NCO clears the phase-accumulator.
|
||||||
"""
|
"""
|
||||||
config1f = self.dac_read(0x1f)
|
config1f = self.dac_read(0x1f)
|
||||||
delay(.4*ms)
|
delay(.4*ms)
|
||||||
|
@ -726,11 +724,11 @@ class Phaser:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_dac_cmix(self, fs_8_step):
|
def set_dac_cmix(self, fs_8_step):
|
||||||
"""Set the DAC coarse mixer frequency for both channels
|
"""Set the DAC coarse mixer frequency for both channels.
|
||||||
|
|
||||||
Use of the coarse mixer requires the DAC mixer to be enabled. The mixer
|
Use of the coarse mixer requires the DAC mixer to be enabled. The mixer
|
||||||
can be configured via the `dac` configuration dictionary (see
|
can be configured via the ``dac`` configuration dictionary (see
|
||||||
`__init__()`).
|
:class:`Phaser`).
|
||||||
|
|
||||||
The selected coarse mixer frequency becomes active without explicit
|
The selected coarse mixer frequency becomes active without explicit
|
||||||
synchronisation.
|
synchronisation.
|
||||||
|
@ -763,8 +761,8 @@ class Phaser:
|
||||||
def dac_iotest(self, pattern) -> TInt32:
|
def dac_iotest(self, pattern) -> TInt32:
|
||||||
"""Performs a DAC IO test according to the datasheet.
|
"""Performs a DAC IO test according to the datasheet.
|
||||||
|
|
||||||
:param pattern: List of four int32 containing the pattern
|
:param pattern: List of four int32s containing the pattern
|
||||||
:return: Bit error mask (16 bits)
|
:return: Bit error mask (16-bit)
|
||||||
"""
|
"""
|
||||||
if len(pattern) != 4:
|
if len(pattern) != 4:
|
||||||
raise ValueError("pattern length out of bounds")
|
raise ValueError("pattern length out of bounds")
|
||||||
|
@ -803,9 +801,9 @@ class Phaser:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def dac_tune_fifo_offset(self):
|
def dac_tune_fifo_offset(self):
|
||||||
"""Scan through `fifo_offset` and configure midpoint setting.
|
"""Scan through ``fifo_offset`` and configure midpoint setting.
|
||||||
|
|
||||||
:return: Optimal `fifo_offset` setting with maximum margin to write
|
:return: Optimal ``fifo_offset`` setting with maximum margin to write
|
||||||
pointer.
|
pointer.
|
||||||
"""
|
"""
|
||||||
# expect two or three error free offsets:
|
# expect two or three error free offsets:
|
||||||
|
@ -865,7 +863,7 @@ class PhaserChannel:
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
|
|
||||||
* :attr:`oscillator`: List of five :class:`PhaserOscillator`.
|
* :attr:`oscillator`: List of five instances of :class:`PhaserOscillator`.
|
||||||
* :attr:`miqro`: A :class:`Miqro`.
|
* :attr:`miqro`: A :class:`Miqro`.
|
||||||
|
|
||||||
.. note:: The amplitude sum of the oscillators must be less than one to
|
.. note:: The amplitude sum of the oscillators must be less than one to
|
||||||
|
@ -879,7 +877,7 @@ class PhaserChannel:
|
||||||
changes in oscillator parameters, the overshoot can lead to clipping
|
changes in oscillator parameters, the overshoot can lead to clipping
|
||||||
or overflow after the interpolation. Either band-limit any changes
|
or overflow after the interpolation. Either band-limit any changes
|
||||||
in the oscillator parameters or back off the amplitude sufficiently.
|
in the oscillator parameters or back off the amplitude sufficiently.
|
||||||
Miqro is not affected by this. But both the oscillators and Miqro can
|
Miqro is not affected by this, but both the oscillators and Miqro can
|
||||||
be affected by intrinsic overshoot of the interpolator on the DAC.
|
be affected by intrinsic overshoot of the interpolator on the DAC.
|
||||||
"""
|
"""
|
||||||
kernel_invariants = {"index", "phaser", "trf_mmap"}
|
kernel_invariants = {"index", "phaser", "trf_mmap"}
|
||||||
|
@ -899,7 +897,7 @@ class PhaserChannel:
|
||||||
The data is split accross multiple registers and thus the data
|
The data is split accross multiple registers and thus the data
|
||||||
is only valid if constant.
|
is only valid if constant.
|
||||||
|
|
||||||
:return: DAC data as 32 bit IQ. I/DACA/DACC in the 16 LSB,
|
:return: DAC data as 32-bit IQ. I/DACA/DACC in the 16 LSB,
|
||||||
Q/DACB/DACD in the 16 MSB
|
Q/DACB/DACD in the 16 MSB
|
||||||
"""
|
"""
|
||||||
return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.index << 4))
|
return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.index << 4))
|
||||||
|
@ -908,7 +906,7 @@ class PhaserChannel:
|
||||||
def set_dac_test(self, data: TInt32):
|
def set_dac_test(self, data: TInt32):
|
||||||
"""Set the DAC test data.
|
"""Set the DAC test data.
|
||||||
|
|
||||||
:param data: 32 bit IQ test data, I/DACA/DACC in the 16 LSB,
|
:param data: 32-bit IQ test data, I/DACA/DACC in the 16 LSB,
|
||||||
Q/DACB/DACD in the 16 MSB
|
Q/DACB/DACD in the 16 MSB
|
||||||
"""
|
"""
|
||||||
self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.index << 4), data)
|
self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.index << 4), data)
|
||||||
|
@ -930,7 +928,7 @@ class PhaserChannel:
|
||||||
def set_duc_frequency_mu(self, ftw):
|
def set_duc_frequency_mu(self, ftw):
|
||||||
"""Set the DUC frequency.
|
"""Set the DUC frequency.
|
||||||
|
|
||||||
:param ftw: DUC frequency tuning word (32 bit)
|
:param ftw: DUC frequency tuning word (32-bit)
|
||||||
"""
|
"""
|
||||||
self.phaser.write32(PHASER_ADDR_DUC0_F + (self.index << 4), ftw)
|
self.phaser.write32(PHASER_ADDR_DUC0_F + (self.index << 4), ftw)
|
||||||
|
|
||||||
|
@ -948,7 +946,7 @@ class PhaserChannel:
|
||||||
def set_duc_phase_mu(self, pow):
|
def set_duc_phase_mu(self, pow):
|
||||||
"""Set the DUC phase offset.
|
"""Set the DUC phase offset.
|
||||||
|
|
||||||
:param pow: DUC phase offset word (16 bit)
|
:param pow: DUC phase offset word (16-bit)
|
||||||
"""
|
"""
|
||||||
addr = PHASER_ADDR_DUC0_P + (self.index << 4)
|
addr = PHASER_ADDR_DUC0_P + (self.index << 4)
|
||||||
self.phaser.write8(addr, pow >> 8)
|
self.phaser.write8(addr, pow >> 8)
|
||||||
|
@ -970,10 +968,10 @@ class PhaserChannel:
|
||||||
This method stages the new NCO frequency, but does not apply it.
|
This method stages the new NCO frequency, but does not apply it.
|
||||||
|
|
||||||
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
|
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
|
||||||
can be configured via the `dac` configuration dictionary (see
|
can be configured via the ``dac`` configuration dictionary (see
|
||||||
`__init__()`).
|
:class:`Phaser`).
|
||||||
|
|
||||||
:param ftw: NCO frequency tuning word (32 bit)
|
:param ftw: NCO frequency tuning word (32-bit)
|
||||||
"""
|
"""
|
||||||
self.phaser.dac_write(0x15 + (self.index << 1), ftw >> 16)
|
self.phaser.dac_write(0x15 + (self.index << 1), ftw >> 16)
|
||||||
self.phaser.dac_write(0x14 + (self.index << 1), ftw)
|
self.phaser.dac_write(0x14 + (self.index << 1), ftw)
|
||||||
|
@ -985,8 +983,8 @@ class PhaserChannel:
|
||||||
This method stages the new NCO frequency, but does not apply it.
|
This method stages the new NCO frequency, but does not apply it.
|
||||||
|
|
||||||
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
|
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
|
||||||
can be configured via the `dac` configuration dictionary (see
|
can be configured via the ``dac`` configuration dictionary (see
|
||||||
`__init__()`).
|
:class:`Phaser`).
|
||||||
|
|
||||||
:param frequency: NCO frequency in Hz (passband from -400 MHz
|
:param frequency: NCO frequency in Hz (passband from -400 MHz
|
||||||
to 400 MHz, wrapping around at +- 500 MHz)
|
to 400 MHz, wrapping around at +- 500 MHz)
|
||||||
|
@ -1001,14 +999,13 @@ class PhaserChannel:
|
||||||
By default, the new NCO phase applies on completion of the SPI
|
By default, the new NCO phase applies on completion of the SPI
|
||||||
transfer. This also causes a staged NCO frequency to be applied.
|
transfer. This also causes a staged NCO frequency to be applied.
|
||||||
Different triggers for applying NCO settings may be configured through
|
Different triggers for applying NCO settings may be configured through
|
||||||
the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see
|
the ``syncsel_mixerxx`` fields in the ``dac`` configuration dictionary (see
|
||||||
`__init__()`).
|
:class:`Phaser`).
|
||||||
|
|
||||||
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
|
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
|
||||||
can be configured via the `dac` configuration dictionary (see
|
can be configured via the ``dac`` configuration dictionary.
|
||||||
`__init__()`).
|
|
||||||
|
|
||||||
:param pow: NCO phase offset word (16 bit)
|
:param pow: NCO phase offset word (16-bit)
|
||||||
"""
|
"""
|
||||||
self.phaser.dac_write(0x12 + self.index, pow)
|
self.phaser.dac_write(0x12 + self.index, pow)
|
||||||
|
|
||||||
|
@ -1019,12 +1016,11 @@ class PhaserChannel:
|
||||||
By default, the new NCO phase applies on completion of the SPI
|
By default, the new NCO phase applies on completion of the SPI
|
||||||
transfer. This also causes a staged NCO frequency to be applied.
|
transfer. This also causes a staged NCO frequency to be applied.
|
||||||
Different triggers for applying NCO settings may be configured through
|
Different triggers for applying NCO settings may be configured through
|
||||||
the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see
|
the ``syncsel_mixerxx`` fields in the ``dac`` configuration dictionary (see
|
||||||
`__init__()`).
|
:class:`Phaser`).
|
||||||
|
|
||||||
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
|
Use of the DAC-NCO requires the DAC mixer and NCO to be enabled. These
|
||||||
can be configured via the `dac` configuration dictionary (see
|
can be configured via the ``dac`` configuration dictionary.
|
||||||
`__init__()`).
|
|
||||||
|
|
||||||
:param phase: NCO phase in turns
|
:param phase: NCO phase in turns
|
||||||
"""
|
"""
|
||||||
|
@ -1035,7 +1031,7 @@ class PhaserChannel:
|
||||||
def set_att_mu(self, data):
|
def set_att_mu(self, data):
|
||||||
"""Set channel attenuation.
|
"""Set channel attenuation.
|
||||||
|
|
||||||
:param data: Attenuator data in machine units (8 bit)
|
:param data: Attenuator data in machine units (8-bit)
|
||||||
"""
|
"""
|
||||||
div = 34 # 30 ns min period
|
div = 34 # 30 ns min period
|
||||||
t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns)
|
t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns)
|
||||||
|
@ -1082,7 +1078,7 @@ class PhaserChannel:
|
||||||
def trf_write(self, data, readback=False):
|
def trf_write(self, data, readback=False):
|
||||||
"""Write 32 bits to quadrature upconverter register.
|
"""Write 32 bits to quadrature upconverter register.
|
||||||
|
|
||||||
:param data: Register data (32 bit) containing encoded address
|
:param data: Register data (32-bit) containing encoded address
|
||||||
:param readback: Whether to return the read back MISO data
|
:param readback: Whether to return the read back MISO data
|
||||||
"""
|
"""
|
||||||
div = 34 # 50 ns min period
|
div = 34 # 50 ns min period
|
||||||
|
@ -1114,7 +1110,7 @@ class PhaserChannel:
|
||||||
|
|
||||||
:param addr: Register address to read (0 to 7)
|
:param addr: Register address to read (0 to 7)
|
||||||
:param cnt_mux_sel: Report VCO counter min or max frequency
|
:param cnt_mux_sel: Report VCO counter min or max frequency
|
||||||
:return: Register data (32 bit)
|
:return: Register data (32-bit)
|
||||||
"""
|
"""
|
||||||
self.trf_write(0x80000008 | (addr << 28) | (cnt_mux_sel << 27))
|
self.trf_write(0x80000008 | (addr << 28) | (cnt_mux_sel << 27))
|
||||||
# single clk pulse with ~LE to start readback
|
# single clk pulse with ~LE to start readback
|
||||||
|
@ -1189,13 +1185,13 @@ class PhaserChannel:
|
||||||
* :math:`b_0` and :math:`b_1` are the feedforward gains for the two
|
* :math:`b_0` and :math:`b_1` are the feedforward gains for the two
|
||||||
delays
|
delays
|
||||||
|
|
||||||
.. seealso:: :meth:`set_iir`
|
See also :meth:`PhaserChannel.set_iir`.
|
||||||
|
|
||||||
:param profile: Profile to set (0 to 3)
|
:param profile: Profile to set (0 to 3)
|
||||||
:param b0: b0 filter coefficient (16 bit signed)
|
:param b0: b0 filter coefficient (16-bit signed)
|
||||||
:param b1: b1 filter coefficient (16 bit signed)
|
:param b1: b1 filter coefficient (16-bit signed)
|
||||||
:param a1: a1 filter coefficient (16 bit signed)
|
:param a1: a1 filter coefficient (16-bit signed)
|
||||||
:param offset: Output offset (16 bit signed)
|
:param offset: Output offset (16-bit signed)
|
||||||
"""
|
"""
|
||||||
if (profile < 0) or (profile > 3):
|
if (profile < 0) or (profile > 3):
|
||||||
raise ValueError("invalid profile index")
|
raise ValueError("invalid profile index")
|
||||||
|
@ -1240,7 +1236,7 @@ class PhaserChannel:
|
||||||
integrator gain limit is infinite. Same sign as ``ki``.
|
integrator gain limit is infinite. Same sign as ``ki``.
|
||||||
:param x_offset: IIR input offset. Used as the negative
|
:param x_offset: IIR input offset. Used as the negative
|
||||||
setpoint when stabilizing to a desired input setpoint. Will
|
setpoint when stabilizing to a desired input setpoint. Will
|
||||||
be converted to an equivalent output offset and added to y_offset.
|
be converted to an equivalent output offset and added to ``y_offset``.
|
||||||
:param y_offset: IIR output offset.
|
:param y_offset: IIR output offset.
|
||||||
"""
|
"""
|
||||||
NORM = 1 << SERVO_COEFF_SHIFT
|
NORM = 1 << SERVO_COEFF_SHIFT
|
||||||
|
@ -1296,7 +1292,7 @@ class PhaserOscillator:
|
||||||
def set_frequency_mu(self, ftw):
|
def set_frequency_mu(self, ftw):
|
||||||
"""Set Phaser MultiDDS frequency tuning word.
|
"""Set Phaser MultiDDS frequency tuning word.
|
||||||
|
|
||||||
:param ftw: Frequency tuning word (32 bit)
|
:param ftw: Frequency tuning word (32-bit)
|
||||||
"""
|
"""
|
||||||
rtio_output(self.base_addr, ftw)
|
rtio_output(self.base_addr, ftw)
|
||||||
|
|
||||||
|
@ -1314,8 +1310,8 @@ class PhaserOscillator:
|
||||||
def set_amplitude_phase_mu(self, asf=0x7fff, pow=0, clr=0):
|
def set_amplitude_phase_mu(self, asf=0x7fff, pow=0, clr=0):
|
||||||
"""Set Phaser MultiDDS amplitude, phase offset and accumulator clear.
|
"""Set Phaser MultiDDS amplitude, phase offset and accumulator clear.
|
||||||
|
|
||||||
:param asf: Amplitude (15 bit)
|
:param asf: Amplitude (15-bit)
|
||||||
:param pow: Phase offset word (16 bit)
|
:param pow: Phase offset word (16-bit)
|
||||||
:param clr: Clear the phase accumulator (persistent)
|
:param clr: Clear the phase accumulator (persistent)
|
||||||
"""
|
"""
|
||||||
data = (asf & 0x7fff) | ((clr & 1) << 15) | (pow << 16)
|
data = (asf & 0x7fff) | ((clr & 1) << 15) | (pow << 16)
|
||||||
|
@ -1346,38 +1342,42 @@ class Miqro:
|
||||||
|
|
||||||
**Oscillators**
|
**Oscillators**
|
||||||
|
|
||||||
* There are n_osc = 16 oscillators with oscillator IDs 0..n_osc-1.
|
* There are ``n_osc = 16`` oscillators with oscillator IDs ``0``... ``n_osc-1``.
|
||||||
* Each oscillator outputs one tone at any given time
|
* Each oscillator outputs one tone at any given time
|
||||||
|
|
||||||
* I/Q (quadrature, a.k.a. complex) 2x16 bit signed data
|
* I/Q (quadrature, a.k.a. complex) 2x16-bit signed data
|
||||||
at tau = 4 ns sample intervals, 250 MS/s, Nyquist 125 MHz, bandwidth 200 MHz
|
at tau = 4 ns sample intervals, 250 MS/s, Nyquist 125 MHz, bandwidth 200 MHz
|
||||||
(from f = -100..+100 MHz, taking into account the interpolation anti-aliasing
|
(from f = -100..+100 MHz, taking into account the interpolation anti-aliasing
|
||||||
filters in subsequent interpolators),
|
filters in subsequent interpolators),
|
||||||
* 32 bit frequency (f) resolution (~ 1/16 Hz),
|
* 32-bit frequency (f) resolution (~ 1/16 Hz),
|
||||||
* 16 bit unsigned amplitude (a) resolution
|
* 16-bit unsigned amplitude (a) resolution
|
||||||
* 16 bit phase offset (p) resolution
|
* 16-bit phase offset (p) resolution
|
||||||
|
|
||||||
* The output phase p' of each oscillator at time t (boot/reset/initialization of the
|
* The output phase ``p'`` of each oscillator at time ``t`` (boot/reset/initialization of the
|
||||||
device at t=0) is then p' = f*t + p (mod 1 turn) where f and p are the (currently
|
device at ``t=0``) is then ``p' = f*t + p (mod 1 turn)`` where ``f`` and ``p`` are the (currently
|
||||||
active) profile frequency and phase offset.
|
active) profile frequency and phase offset.
|
||||||
* Note: The terms "phase coherent" and "phase tracking" are defined to refer to this
|
|
||||||
choice of oscillator output phase p'. Note that the phase offset p is not relative to
|
.. note ::
|
||||||
(on top of previous phase/profiles/oscillator history).
|
The terms "phase coherent" and "phase tracking" are defined to refer to this
|
||||||
It is "absolute" in the sense that frequency f and phase offset p fully determine
|
choice of oscillator output phase ``p'``. Note that the phase offset ``p`` is not relative to
|
||||||
oscillator output phase p' at time t. This is unlike typical DDS behavior.
|
(on top of previous phase/profiles/oscillator history).
|
||||||
|
It is "absolute" in the sense that frequency ``f`` and phase offset ``p`` fully determine
|
||||||
|
oscillator output phase ``p'`` at time ``t``. This is unlike typical DDS behavior.
|
||||||
|
|
||||||
* Frequency, phase, and amplitude of each oscillator are configurable by selecting one of
|
* Frequency, phase, and amplitude of each oscillator are configurable by selecting one of
|
||||||
n_profile = 32 profiles 0..n_profile-1. This selection is fast and can be done for
|
``n_profiles = 32`` profiles ``0``... ``n_profile-1``. This selection is fast and can be
|
||||||
each pulse. The phase coherence defined above is guaranteed for each
|
done for each pulse. The phase coherence defined above is guaranteed for each
|
||||||
profile individually.
|
profile individually.
|
||||||
* Note: one profile per oscillator (usually profile index 0) should be reserved
|
* Note: one profile per oscillator (usually profile index 0) should be reserved
|
||||||
for the NOP (no operation, identity) profile, usually with zero amplitude.
|
for the NOP (no operation, identity) profile, usually with zero amplitude.
|
||||||
* Data for each profile for each oscillator can be configured
|
* Data for each profile for each oscillator can be configured
|
||||||
individually. Storing profile data should be considered "expensive".
|
individually. Storing profile data should be considered "expensive".
|
||||||
* Note: The annotation that some operation is "expensive" does not mean it is
|
|
||||||
impossible, just that it may take a significant amount of time and
|
.. note::
|
||||||
resources to execute such that it may be impractical when used often or
|
To refer to an operation as "expensive" does not mean it is impossible,
|
||||||
during fast pulse sequences. They are intended for use in calibration and
|
merely that it may take a significant amount of time and resources to
|
||||||
initialization.
|
execute, such that it may be impractical when used often or during fast
|
||||||
|
pulse sequences. They are intended for use in calibration and initialization.
|
||||||
|
|
||||||
**Summation**
|
**Summation**
|
||||||
|
|
||||||
|
@ -1394,18 +1394,18 @@ class Miqro:
|
||||||
the RF output.
|
the RF output.
|
||||||
* Selected profiles become active simultaneously (on the same output sample) when
|
* Selected profiles become active simultaneously (on the same output sample) when
|
||||||
triggering the shaper with the first shaper output sample.
|
triggering the shaper with the first shaper output sample.
|
||||||
* The shaper reads (replays) window samples from a memory of size n_window = 1 << 10.
|
* The shaper reads (replays) window samples from a memory of size ``n_window = 1 << 10``.
|
||||||
* The window memory can be segmented by choosing different start indices
|
* The window memory can be segmented by choosing different start indices
|
||||||
to support different windows.
|
to support different windows.
|
||||||
* Each window memory segment starts with a header determining segment
|
* Each window memory segment starts with a header determining segment
|
||||||
length and interpolation parameters.
|
length and interpolation parameters.
|
||||||
* The window samples are interpolated by a factor (rate change) between 1 and
|
* The window samples are interpolated by a factor (rate change) between 1 and
|
||||||
r = 1 << 12.
|
``r = 1 << 12``.
|
||||||
* The interpolation order is constant, linear, quadratic, or cubic. This
|
* The interpolation order is constant, linear, quadratic, or cubic. This
|
||||||
corresponds to interpolation modes from rectangular window (1st order CIC)
|
corresponds to interpolation modes from rectangular window (1st order CIC)
|
||||||
or zero order hold) to Parzen window (4th order CIC or cubic spline).
|
or zero order hold) to Parzen window (4th order CIC or cubic spline).
|
||||||
* This results in support for single shot pulse lengths (envelope support) between
|
* This results in support for single shot pulse lengths (envelope support) between
|
||||||
tau and a bit more than r * n_window * tau = (1 << 12 + 10) tau ~ 17 ms.
|
tau and a bit more than ``r * n_window * tau = (1 << 12 + 10) tau ~ 17 ms``.
|
||||||
* Windows can be configured to be head-less and/or tail-less, meaning, they
|
* Windows can be configured to be head-less and/or tail-less, meaning, they
|
||||||
do not feed zero-amplitude samples into the shaper before and after
|
do not feed zero-amplitude samples into the shaper before and after
|
||||||
each window respectively. This is used to implement pulses with arbitrary
|
each window respectively. This is used to implement pulses with arbitrary
|
||||||
|
@ -1413,18 +1413,18 @@ class Miqro:
|
||||||
|
|
||||||
**Overall properties**
|
**Overall properties**
|
||||||
|
|
||||||
* The DAC may upconvert the signal by applying a frequency offset f1 with
|
* The DAC may upconvert the signal by applying a frequency offset ``f1`` with
|
||||||
phase p1.
|
phase ``p1``.
|
||||||
* In the Upconverter Phaser variant, the analog quadrature upconverter
|
* In the Upconverter Phaser variant, the analog quadrature upconverter
|
||||||
applies another frequency of f2 and phase p2.
|
applies another frequency of ``f2`` and phase ``p2``.
|
||||||
* The resulting phase of the signal from one oscillator at the SMA output is
|
* The resulting phase of the signal from one oscillator at the SMA output is
|
||||||
(f + f1 + f2)*t + p + s(t - t0) + p1 + p2 (mod 1 turn)
|
``(f + f1 + f2)*t + p + s(t - t0) + p1 + p2 (mod 1 turn)``
|
||||||
where s(t - t0) is the phase of the interpolated
|
where ``s(t - t0)`` is the phase of the interpolated
|
||||||
shaper output, and t0 is the trigger time (fiducial of the shaper).
|
shaper output, and ``t0`` is the trigger time (fiducial of the shaper).
|
||||||
Unsurprisingly the frequency is the derivative of the phase.
|
Unsurprisingly the frequency is the derivative of the phase.
|
||||||
* Group delays between pulse parameter updates are matched across oscillators,
|
* Group delays between pulse parameter updates are matched across oscillators,
|
||||||
shapers, and channels.
|
shapers, and channels.
|
||||||
* The minimum time to change profiles and phase offsets is ~128 ns (estimate, TBC).
|
* The minimum time to change profiles and phase offsets is ``~128 ns`` (estimate, TBC).
|
||||||
This is the minimum pulse interval.
|
This is the minimum pulse interval.
|
||||||
The sustained pulse rate of the RTIO PHY/Fastlink is one pulse per Fastlink frame
|
The sustained pulse rate of the RTIO PHY/Fastlink is one pulse per Fastlink frame
|
||||||
(may be increased, TBC).
|
(may be increased, TBC).
|
||||||
|
@ -1455,9 +1455,9 @@ class Miqro:
|
||||||
|
|
||||||
:param oscillator: Oscillator index (0 to 15)
|
:param oscillator: Oscillator index (0 to 15)
|
||||||
:param profile: Profile index (0 to 31)
|
:param profile: Profile index (0 to 31)
|
||||||
:param ftw: Frequency tuning word (32 bit signed integer on a 250 MHz clock)
|
:param ftw: Frequency tuning word (32-bit signed integer on a 250 MHz clock)
|
||||||
:param asf: Amplitude scale factor (16 bit unsigned integer)
|
:param asf: Amplitude scale factor (16-bit unsigned integer)
|
||||||
:param pow_: Phase offset word (16 bit integer)
|
:param pow_: Phase offset word (16-bit integer)
|
||||||
"""
|
"""
|
||||||
if oscillator >= 16:
|
if oscillator >= 16:
|
||||||
raise ValueError("invalid oscillator index")
|
raise ValueError("invalid oscillator index")
|
||||||
|
@ -1481,7 +1481,7 @@ class Miqro:
|
||||||
:param amplitude: Amplitude in units of full scale (0. to 1.)
|
:param amplitude: Amplitude in units of full scale (0. to 1.)
|
||||||
:param phase: Phase in turns. See :class:`Miqro` for a definition of
|
:param phase: Phase in turns. See :class:`Miqro` for a definition of
|
||||||
phase in this context.
|
phase in this context.
|
||||||
:return: The quantized 32 bit frequency tuning word
|
:return: The quantized 32-bit frequency tuning word
|
||||||
"""
|
"""
|
||||||
ftw = int32(round(frequency*((1 << 30)/(62.5*MHz))))
|
ftw = int32(round(frequency*((1 << 30)/(62.5*MHz))))
|
||||||
asf = int32(round(amplitude*0xffff))
|
asf = int32(round(amplitude*0xffff))
|
||||||
|
@ -1493,7 +1493,7 @@ class Miqro:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_window_mu(self, start, iq, rate=1, shift=0, order=3, head=1, tail=1):
|
def set_window_mu(self, start, iq, rate=1, shift=0, order=3, head=1, tail=1):
|
||||||
"""Store a window segment (machine units)
|
"""Store a window segment (machine units).
|
||||||
|
|
||||||
:param start: Window start address (0 to 0x3ff)
|
:param start: Window start address (0 to 0x3ff)
|
||||||
:param iq: List of IQ window samples. Each window sample is an integer
|
:param iq: List of IQ window samples. Each window sample is an integer
|
||||||
|
@ -1540,7 +1540,7 @@ class Miqro:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_window(self, start, iq, period=4*ns, order=3, head=1, tail=1):
|
def set_window(self, start, iq, period=4*ns, order=3, head=1, tail=1):
|
||||||
"""Store a window segment
|
"""Store a window segment.
|
||||||
|
|
||||||
:param start: Window start address (0 to 0x3ff)
|
:param start: Window start address (0 to 0x3ff)
|
||||||
:param iq: List of IQ window samples. Each window sample is a pair of
|
:param iq: List of IQ window samples. Each window sample is a pair of
|
||||||
|
@ -1577,7 +1577,7 @@ class Miqro:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def encode(self, window, profiles, data):
|
def encode(self, window, profiles, data):
|
||||||
"""Encode window and profile selection
|
"""Encode window and profile selection.
|
||||||
|
|
||||||
:param window: Window start address (0 to 0x3ff)
|
:param window: Window start address (0 to 0x3ff)
|
||||||
:param profiles: List of profile indices for the oscillators. Maximum
|
:param profiles: List of profile indices for the oscillators. Maximum
|
||||||
|
|
|
@ -25,7 +25,7 @@ def rtio_input_data(channel: TInt32) -> TInt32:
|
||||||
@syscall(flags={"nowrite"})
|
@syscall(flags={"nowrite"})
|
||||||
def rtio_input_timestamped_data(timeout_mu: TInt64,
|
def rtio_input_timestamped_data(timeout_mu: TInt64,
|
||||||
channel: TInt32) -> TTuple([TInt64, TInt32]):
|
channel: TInt32) -> TTuple([TInt64, TInt32]):
|
||||||
"""Wait for an input event up to timeout_mu on the given channel, and
|
"""Wait for an input event up to ``timeout_mu`` on the given channel, and
|
||||||
return a tuple of timestamp and attached data, or (-1, 0) if the timeout is
|
return a tuple of timestamp and attached data, or (-1, 0) if the timeout is
|
||||||
reached."""
|
reached."""
|
||||||
raise NotImplementedError("syscall not simulated")
|
raise NotImplementedError("syscall not simulated")
|
||||||
|
|
|
@ -16,13 +16,13 @@ SPI_CS_PGIA = 1 # separate SPI bus, CS used as RCLK
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def adc_mu_to_volt(data, gain=0, corrected_fs=True):
|
def adc_mu_to_volt(data, gain=0, corrected_fs=True):
|
||||||
"""Convert ADC data in machine units to Volts.
|
"""Convert ADC data in machine units to volts.
|
||||||
|
|
||||||
:param data: 16 bit signed ADC word
|
:param data: 16-bit signed ADC word
|
||||||
:param gain: PGIA gain setting (0: 1, ..., 3: 1000)
|
:param gain: PGIA gain setting (0: 1, ..., 3: 1000)
|
||||||
:param corrected_fs: use corrected ADC FS reference.
|
:param corrected_fs: use corrected ADC FS reference.
|
||||||
Should be True for Samplers' revisions after v2.1. False for v2.1 and earlier.
|
Should be ``True`` for Sampler revisions after v2.1. ``False`` for v2.1 and earlier.
|
||||||
:return: Voltage in Volts
|
:return: Voltage in volts
|
||||||
"""
|
"""
|
||||||
if gain == 0:
|
if gain == 0:
|
||||||
volt_per_lsb = 20.48 / (1 << 16) if corrected_fs else 20. / (1 << 16)
|
volt_per_lsb = 20.48 / (1 << 16) if corrected_fs else 20. / (1 << 16)
|
||||||
|
@ -40,7 +40,7 @@ def adc_mu_to_volt(data, gain=0, corrected_fs=True):
|
||||||
class Sampler:
|
class Sampler:
|
||||||
"""Sampler ADC.
|
"""Sampler ADC.
|
||||||
|
|
||||||
Controls the LTC2320-16 8 channel 16 bit ADC with SPI interface and
|
Controls the LTC2320-16 8-channel 16-bit ADC with SPI interface and
|
||||||
the switchable gain instrumentation amplifiers.
|
the switchable gain instrumentation amplifiers.
|
||||||
|
|
||||||
:param spi_adc_device: ADC SPI bus device name
|
:param spi_adc_device: ADC SPI bus device name
|
||||||
|
@ -119,12 +119,12 @@ class Sampler:
|
||||||
Perform a conversion and transfer the samples.
|
Perform a conversion and transfer the samples.
|
||||||
|
|
||||||
This assumes that the input FIFO of the ADC SPI RTIO channel is deep
|
This assumes that the input FIFO of the ADC SPI RTIO channel is deep
|
||||||
enough to buffer the samples (half the length of `data` deep).
|
enough to buffer the samples (half the length of ``data`` deep).
|
||||||
If it is not, there will be RTIO input overflows.
|
If it is not, there will be RTIO input overflows.
|
||||||
|
|
||||||
:param data: List of data samples to fill. Must have even length.
|
:param data: List of data samples to fill. Must have even length.
|
||||||
Samples are always read from the last channel (channel 7) down.
|
Samples are always read from the last channel (channel 7) down.
|
||||||
The `data` list will always be filled with the last item
|
The ``data`` list will always be filled with the last item
|
||||||
holding to the sample from channel 7.
|
holding to the sample from channel 7.
|
||||||
"""
|
"""
|
||||||
self.cnv.pulse(30*ns) # t_CNVH
|
self.cnv.pulse(30*ns) # t_CNVH
|
||||||
|
@ -142,7 +142,7 @@ class Sampler:
|
||||||
def sample(self, data):
|
def sample(self, data):
|
||||||
"""Acquire a set of samples.
|
"""Acquire a set of samples.
|
||||||
|
|
||||||
.. seealso:: :meth:`sample_mu`
|
See also :meth:`Sampler.sample_mu`.
|
||||||
|
|
||||||
:param data: List of floating point data samples to fill.
|
:param data: List of floating point data samples to fill.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -16,8 +16,8 @@ def shuttler_volt_to_mu(volt):
|
||||||
class Config:
|
class Config:
|
||||||
"""Shuttler configuration registers interface.
|
"""Shuttler configuration registers interface.
|
||||||
|
|
||||||
The configuration registers control waveform phase auto-clear, and pre-DAC
|
The configuration registers control waveform phase auto-clear, pre-DAC
|
||||||
gain & offset values for calibration with ADC on the Shuttler AFE card.
|
gain and offset values for calibration with ADC on the Shuttler AFE card.
|
||||||
|
|
||||||
To find the calibrated DAC code, the Shuttler Core first multiplies the
|
To find the calibrated DAC code, the Shuttler Core first multiplies the
|
||||||
output data with pre-DAC gain, then adds the offset.
|
output data with pre-DAC gain, then adds the offset.
|
||||||
|
@ -84,8 +84,7 @@ class Config:
|
||||||
def set_offset(self, channel, offset):
|
def set_offset(self, channel, offset):
|
||||||
"""Set the 16-bits pre-DAC offset register of a Shuttler Core channel.
|
"""Set the 16-bits pre-DAC offset register of a Shuttler Core channel.
|
||||||
|
|
||||||
.. seealso::
|
See also :meth:`shuttler_volt_to_mu`.
|
||||||
:meth:`shuttler_volt_to_mu`
|
|
||||||
|
|
||||||
:param channel: Shuttler Core channel to be configured.
|
:param channel: Shuttler Core channel to be configured.
|
||||||
:param offset: Shuttler Core channel offset.
|
:param offset: Shuttler Core channel offset.
|
||||||
|
@ -114,13 +113,13 @@ class DCBias:
|
||||||
.. math::
|
.. math::
|
||||||
w(t) = a(t) + b(t) * cos(c(t))
|
w(t) = a(t) + b(t) * cos(c(t))
|
||||||
|
|
||||||
And `t` corresponds to time in seconds.
|
and `t` corresponds to time in seconds.
|
||||||
This class controls the cubic spline `a(t)`, in which
|
This class controls the cubic spline `a(t)`, in which
|
||||||
|
|
||||||
.. math::
|
.. math::
|
||||||
a(t) = p_0 + p_1t + \\frac{p_2t^2}{2} + \\frac{p_3t^3}{6}
|
a(t) = p_0 + p_1t + \\frac{p_2t^2}{2} + \\frac{p_3t^3}{6}
|
||||||
|
|
||||||
And `a(t)` is in Volt.
|
and `a(t)` is measured in volts.
|
||||||
|
|
||||||
:param channel: RTIO channel number of this DC-bias spline interface.
|
:param channel: RTIO channel number of this DC-bias spline interface.
|
||||||
:param core_device: Core device name.
|
:param core_device: Core device name.
|
||||||
|
@ -137,7 +136,7 @@ class DCBias:
|
||||||
"""Set the DC-bias spline waveform.
|
"""Set the DC-bias spline waveform.
|
||||||
|
|
||||||
Given `a(t)` as defined in :class:`DCBias`, the coefficients should be
|
Given `a(t)` as defined in :class:`DCBias`, the coefficients should be
|
||||||
configured by the following formulae.
|
configured by the following formulae:
|
||||||
|
|
||||||
.. math::
|
.. math::
|
||||||
T &= 8*10^{-9}
|
T &= 8*10^{-9}
|
||||||
|
@ -154,8 +153,10 @@ class DCBias:
|
||||||
and 48 bits in width respectively. See :meth:`shuttler_volt_to_mu` for
|
and 48 bits in width respectively. See :meth:`shuttler_volt_to_mu` for
|
||||||
machine unit conversion.
|
machine unit conversion.
|
||||||
|
|
||||||
Note: The waveform is not updated to the Shuttler Core until
|
.. note::
|
||||||
triggered. See :class:`Trigger` for the update triggering mechanism.
|
The waveform is not updated to the Shuttler Core until
|
||||||
|
triggered. See :class:`Trigger` for the update triggering
|
||||||
|
mechanism.
|
||||||
|
|
||||||
:param a0: The :math:`a_0` coefficient in machine unit.
|
:param a0: The :math:`a_0` coefficient in machine unit.
|
||||||
:param a1: The :math:`a_1` coefficient in machine unit.
|
:param a1: The :math:`a_1` coefficient in machine unit.
|
||||||
|
@ -189,7 +190,7 @@ class DDS:
|
||||||
.. math::
|
.. math::
|
||||||
w(t) = a(t) + b(t) * cos(c(t))
|
w(t) = a(t) + b(t) * cos(c(t))
|
||||||
|
|
||||||
And `t` corresponds to time in seconds.
|
and `t` corresponds to time in seconds.
|
||||||
This class controls the cubic spline `b(t)` and quadratic spline `c(t)`,
|
This class controls the cubic spline `b(t)` and quadratic spline `c(t)`,
|
||||||
in which
|
in which
|
||||||
|
|
||||||
|
@ -198,7 +199,7 @@ class DDS:
|
||||||
|
|
||||||
c(t) &= r_0 + r_1t + \\frac{r_2t^2}{2}
|
c(t) &= r_0 + r_1t + \\frac{r_2t^2}{2}
|
||||||
|
|
||||||
And `b(t)` is in Volt, `c(t)` is in number of turns. Note that `b(t)`
|
`b(t)` is in volts, `c(t)` is in number of turns. Note that `b(t)`
|
||||||
contributes to a constant gain of :math:`g=1.64676`.
|
contributes to a constant gain of :math:`g=1.64676`.
|
||||||
|
|
||||||
:param channel: RTIO channel number of this DC-bias spline interface.
|
:param channel: RTIO channel number of this DC-bias spline interface.
|
||||||
|
@ -244,13 +245,13 @@ class DDS:
|
||||||
Note: The waveform is not updated to the Shuttler Core until
|
Note: The waveform is not updated to the Shuttler Core until
|
||||||
triggered. See :class:`Trigger` for the update triggering mechanism.
|
triggered. See :class:`Trigger` for the update triggering mechanism.
|
||||||
|
|
||||||
:param b0: The :math:`b_0` coefficient in machine unit.
|
:param b0: The :math:`b_0` coefficient in machine units.
|
||||||
:param b1: The :math:`b_1` coefficient in machine unit.
|
:param b1: The :math:`b_1` coefficient in machine units.
|
||||||
:param b2: The :math:`b_2` coefficient in machine unit.
|
:param b2: The :math:`b_2` coefficient in machine units.
|
||||||
:param b3: The :math:`b_3` coefficient in machine unit.
|
:param b3: The :math:`b_3` coefficient in machine units.
|
||||||
:param c0: The :math:`c_0` coefficient in machine unit.
|
:param c0: The :math:`c_0` coefficient in machine units.
|
||||||
:param c1: The :math:`c_1` coefficient in machine unit.
|
:param c1: The :math:`c_1` coefficient in machine units.
|
||||||
:param c2: The :math:`c_2` coefficient in machine unit.
|
:param c2: The :math:`c_2` coefficient in machine units.
|
||||||
"""
|
"""
|
||||||
coef_words = [
|
coef_words = [
|
||||||
b0,
|
b0,
|
||||||
|
@ -292,8 +293,8 @@ class Trigger:
|
||||||
"""Triggers coefficient update of (a) Shuttler Core channel(s).
|
"""Triggers coefficient update of (a) Shuttler Core channel(s).
|
||||||
|
|
||||||
Each bit corresponds to a Shuttler waveform generator core. Setting
|
Each bit corresponds to a Shuttler waveform generator core. Setting
|
||||||
`trig_out` bits commits the pending coefficient update (from
|
``trig_out`` bits commits the pending coefficient update (from
|
||||||
`set_waveform` in :class:`DCBias` and :class:`DDS`) to the Shuttler Core
|
``set_waveform`` in :class:`DCBias` and :class:`DDS`) to the Shuttler Core
|
||||||
synchronously.
|
synchronously.
|
||||||
|
|
||||||
:param trig_out: Coefficient update trigger bits. The MSB corresponds
|
:param trig_out: Coefficient update trigger bits. The MSB corresponds
|
||||||
|
@ -336,8 +337,8 @@ _AD4115_REG_SETUPCON0 = 0x20
|
||||||
class Relay:
|
class Relay:
|
||||||
"""Shuttler AFE relay switches.
|
"""Shuttler AFE relay switches.
|
||||||
|
|
||||||
It controls the AFE relay switches and the LEDs. Switch on the relay to
|
This class controls the AFE relay switches and the LEDs. Switch the relay on to
|
||||||
enable AFE output; And off to disable the output. The LEDs indicates the
|
enable AFE output; off to disable the output. The LEDs indicate the
|
||||||
relay status.
|
relay status.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
@ -357,7 +358,7 @@ class Relay:
|
||||||
def init(self):
|
def init(self):
|
||||||
"""Initialize SPI device.
|
"""Initialize SPI device.
|
||||||
|
|
||||||
Configures the SPI bus to 16-bits, write-only, simultaneous relay
|
Configures the SPI bus to 16 bits, write-only, simultaneous relay
|
||||||
switches and LED control.
|
switches and LED control.
|
||||||
"""
|
"""
|
||||||
self.bus.set_config_mu(
|
self.bus.set_config_mu(
|
||||||
|
@ -365,10 +366,10 @@ class Relay:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def enable(self, en: TInt32):
|
def enable(self, en: TInt32):
|
||||||
"""Enable/Disable relay switches of corresponding channels.
|
"""Enable/disable relay switches of corresponding channels.
|
||||||
|
|
||||||
Each bit corresponds to the relay switch of a channel. Asserting a bit
|
Each bit corresponds to the relay switch of a channel. Asserting a bit
|
||||||
turns on the corresponding relay switch; Deasserting the same bit
|
turns on the corresponding relay switch; deasserting the same bit
|
||||||
turns off the switch instead.
|
turns off the switch instead.
|
||||||
|
|
||||||
:param en: Switch enable bits. The MSB corresponds to Channel 15, LSB
|
:param en: Switch enable bits. The MSB corresponds to Channel 15, LSB
|
||||||
|
@ -403,12 +404,12 @@ class ADC:
|
||||||
def reset(self):
|
def reset(self):
|
||||||
"""AD4115 reset procedure.
|
"""AD4115 reset procedure.
|
||||||
|
|
||||||
This performs a write operation of 96 serial clock cycles with DIN
|
Performs a write operation of 96 serial clock cycles with DIN
|
||||||
held at high. It resets the entire device, including the register
|
held at high. This resets the entire device, including the register
|
||||||
contents.
|
contents.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
The datasheet only requires 64 cycles, but reasserting `CS_n` right
|
The datasheet only requires 64 cycles, but reasserting ``CS_n`` right
|
||||||
after the transfer appears to interrupt the start-up sequence.
|
after the transfer appears to interrupt the start-up sequence.
|
||||||
"""
|
"""
|
||||||
self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC)
|
self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC)
|
||||||
|
@ -420,7 +421,7 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read8(self, addr: TInt32) -> TInt32:
|
def read8(self, addr: TInt32) -> TInt32:
|
||||||
"""Read from 8 bit register.
|
"""Read from 8-bit register.
|
||||||
|
|
||||||
:param addr: Register address.
|
:param addr: Register address.
|
||||||
:return: Read-back register content.
|
:return: Read-back register content.
|
||||||
|
@ -433,7 +434,7 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read16(self, addr: TInt32) -> TInt32:
|
def read16(self, addr: TInt32) -> TInt32:
|
||||||
"""Read from 16 bit register.
|
"""Read from 16-bit register.
|
||||||
|
|
||||||
:param addr: Register address.
|
:param addr: Register address.
|
||||||
:return: Read-back register content.
|
:return: Read-back register content.
|
||||||
|
@ -446,7 +447,7 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def read24(self, addr: TInt32) -> TInt32:
|
def read24(self, addr: TInt32) -> TInt32:
|
||||||
"""Read from 24 bit register.
|
"""Read from 24-bit register.
|
||||||
|
|
||||||
:param addr: Register address.
|
:param addr: Register address.
|
||||||
:return: Read-back register content.
|
:return: Read-back register content.
|
||||||
|
@ -459,7 +460,7 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write8(self, addr: TInt32, data: TInt32):
|
def write8(self, addr: TInt32, data: TInt32):
|
||||||
"""Write to 8 bit register.
|
"""Write to 8-bit register.
|
||||||
|
|
||||||
:param addr: Register address.
|
:param addr: Register address.
|
||||||
:param data: Data to be written.
|
:param data: Data to be written.
|
||||||
|
@ -470,7 +471,7 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write16(self, addr: TInt32, data: TInt32):
|
def write16(self, addr: TInt32, data: TInt32):
|
||||||
"""Write to 16 bit register.
|
"""Write to 16-bit register.
|
||||||
|
|
||||||
:param addr: Register address.
|
:param addr: Register address.
|
||||||
:param data: Data to be written.
|
:param data: Data to be written.
|
||||||
|
@ -481,7 +482,7 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def write24(self, addr: TInt32, data: TInt32):
|
def write24(self, addr: TInt32, data: TInt32):
|
||||||
"""Write to 24 bit register.
|
"""Write to 24-bit register.
|
||||||
|
|
||||||
:param addr: Register address.
|
:param addr: Register address.
|
||||||
:param data: Data to be written.
|
:param data: Data to be written.
|
||||||
|
@ -494,11 +495,11 @@ class ADC:
|
||||||
def read_ch(self, channel: TInt32) -> TFloat:
|
def read_ch(self, channel: TInt32) -> TFloat:
|
||||||
"""Sample a Shuttler channel on the AFE.
|
"""Sample a Shuttler channel on the AFE.
|
||||||
|
|
||||||
It performs a single conversion using profile 0 and setup 0, on the
|
Performs a single conversion using profile 0 and setup 0 on the
|
||||||
selected channel. The sample is then recovered and converted to volt.
|
selected channel. The sample is then recovered and converted to volts.
|
||||||
|
|
||||||
:param channel: Shuttler channel to be sampled.
|
:param channel: Shuttler channel to be sampled.
|
||||||
:return: Voltage sample in volt.
|
:return: Voltage sample in volts.
|
||||||
"""
|
"""
|
||||||
# Always configure Profile 0 for single conversion
|
# Always configure Profile 0 for single conversion
|
||||||
self.write16(_AD4115_REG_CH0, 0x8000 | ((channel * 2 + 1) << 4))
|
self.write16(_AD4115_REG_CH0, 0x8000 | ((channel * 2 + 1) << 4))
|
||||||
|
@ -519,7 +520,7 @@ class ADC:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def standby(self):
|
def standby(self):
|
||||||
"""Place the ADC in standby mode and disables power down the clock.
|
"""Place the ADC in standby mode and disable power down the clock.
|
||||||
|
|
||||||
The ADC can be returned to single conversion mode by calling
|
The ADC can be returned to single conversion mode by calling
|
||||||
:meth:`single_conversion`.
|
:meth:`single_conversion`.
|
||||||
|
@ -536,13 +537,7 @@ class ADC:
|
||||||
.. note::
|
.. note::
|
||||||
The AD4115 datasheet suggests placing the ADC in standby mode
|
The AD4115 datasheet suggests placing the ADC in standby mode
|
||||||
before power-down. This is to prevent accidental entry into the
|
before power-down. This is to prevent accidental entry into the
|
||||||
power-down mode.
|
power-down mode. See also :meth:`standby` and :meth:`power_up`.
|
||||||
|
|
||||||
.. seealso::
|
|
||||||
:meth:`standby`
|
|
||||||
|
|
||||||
:meth:`power_up`
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self.write16(_AD4115_REG_ADCMODE, 0x8030)
|
self.write16(_AD4115_REG_ADCMODE, 0x8030)
|
||||||
|
|
||||||
|
@ -552,8 +547,7 @@ class ADC:
|
||||||
|
|
||||||
The ADC should be in power-down mode before calling this method.
|
The ADC should be in power-down mode before calling this method.
|
||||||
|
|
||||||
.. seealso::
|
See also :meth:`power_down`.
|
||||||
:meth:`power_down`
|
|
||||||
"""
|
"""
|
||||||
self.reset()
|
self.reset()
|
||||||
# Although the datasheet claims 500 us reset wait time, only waiting
|
# Although the datasheet claims 500 us reset wait time, only waiting
|
||||||
|
@ -564,22 +558,18 @@ class ADC:
|
||||||
def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]):
|
def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]):
|
||||||
"""Calibrate the Shuttler waveform generator using the ADC on the AFE.
|
"""Calibrate the Shuttler waveform generator using the ADC on the AFE.
|
||||||
|
|
||||||
It finds the average slope rate and average offset by samples, and
|
Finds the average slope rate and average offset by samples, and
|
||||||
compensate by writing the pre-DAC gain and offset registers in the
|
compensates by writing the pre-DAC gain and offset registers in the
|
||||||
configuration registers.
|
configuration registers.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
If the pre-calibration slope rate < 1, the calibration procedure
|
If the pre-calibration slope rate is less than 1, the calibration
|
||||||
will introduce a pre-DAC gain compensation. However, this may
|
procedure will introduce a pre-DAC gain compensation. However, this
|
||||||
saturate the pre-DAC voltage code. (See :class:`Config` notes).
|
may saturate the pre-DAC voltage code (see :class:`Config` notes).
|
||||||
Shuttler cannot cover the entire +/- 10 V range in this case.
|
Shuttler cannot cover the entire +/- 10 V range in this case.
|
||||||
|
See also :meth:`Config.set_gain` and :meth:`Config.set_offset`.
|
||||||
|
|
||||||
.. seealso::
|
:param volts: A list of all 16 cubic DC-bias splines.
|
||||||
:meth:`Config.set_gain`
|
|
||||||
|
|
||||||
:meth:`Config.set_offset`
|
|
||||||
|
|
||||||
:param volts: A list of all 16 cubic DC-bias spline.
|
|
||||||
(See :class:`DCBias`)
|
(See :class:`DCBias`)
|
||||||
:param trigger: The Shuttler spline coefficient update trigger.
|
:param trigger: The Shuttler spline coefficient update trigger.
|
||||||
:param config: The Shuttler Core configuration registers.
|
:param config: The Shuttler Core configuration registers.
|
||||||
|
|
|
@ -4,7 +4,7 @@ Driver for generic SPI on RTIO.
|
||||||
This ARTIQ coredevice driver corresponds to the "new" MiSoC SPI core (v2).
|
This ARTIQ coredevice driver corresponds to the "new" MiSoC SPI core (v2).
|
||||||
|
|
||||||
Output event replacement is not supported and issuing commands at the same
|
Output event replacement is not supported and issuing commands at the same
|
||||||
time is an error.
|
time results in collision errors.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from artiq.language.core import syscall, kernel, portable, delay_mu
|
from artiq.language.core import syscall, kernel, portable, delay_mu
|
||||||
|
@ -51,7 +51,7 @@ class SPIMaster:
|
||||||
event (``SPI_INPUT`` set), then :meth:`read` the ``data``.
|
event (``SPI_INPUT`` set), then :meth:`read` the ``data``.
|
||||||
* If ``SPI_END`` was not set, repeat the transfer sequence.
|
* If ``SPI_END`` was not set, repeat the transfer sequence.
|
||||||
|
|
||||||
A **transaction** consists of one or more **transfers**. The chip select
|
A *transaction* consists of one or more *transfers*. The chip select
|
||||||
pattern is asserted for the entire length of the transaction. All but the
|
pattern is asserted for the entire length of the transaction. All but the
|
||||||
last transfer are submitted with ``SPI_END`` cleared in the configuration
|
last transfer are submitted with ``SPI_END`` cleared in the configuration
|
||||||
register.
|
register.
|
||||||
|
@ -138,10 +138,10 @@ class SPIMaster:
|
||||||
* :const:`SPI_LSB_FIRST`: LSB is the first bit on the wire (reset=0)
|
* :const:`SPI_LSB_FIRST`: LSB is the first bit on the wire (reset=0)
|
||||||
* :const:`SPI_HALF_DUPLEX`: 3-wire SPI, in/out on ``mosi`` (reset=0)
|
* :const:`SPI_HALF_DUPLEX`: 3-wire SPI, in/out on ``mosi`` (reset=0)
|
||||||
|
|
||||||
:param flags: A bit map of `SPI_*` flags.
|
:param flags: A bit map of :const:`SPI_*` flags.
|
||||||
:param length: Number of bits to write during the next transfer.
|
:param length: Number of bits to write during the next transfer.
|
||||||
(reset=1)
|
(reset=1)
|
||||||
:param freq: Desired SPI clock frequency. (reset=f_rtio/2)
|
:param freq: Desired SPI clock frequency. (reset= ``f_rtio/2``)
|
||||||
:param cs: Bit pattern of chip selects to assert.
|
:param cs: Bit pattern of chip selects to assert.
|
||||||
Or number of the chip select to assert if ``cs`` is decoded
|
Or number of the chip select to assert if ``cs`` is decoded
|
||||||
downstream. (reset=0)
|
downstream. (reset=0)
|
||||||
|
@ -152,16 +152,15 @@ class SPIMaster:
|
||||||
def set_config_mu(self, flags, length, div, cs):
|
def set_config_mu(self, flags, length, div, cs):
|
||||||
"""Set the ``config`` register (in SPI bus machine units).
|
"""Set the ``config`` register (in SPI bus machine units).
|
||||||
|
|
||||||
.. seealso:: :meth:`set_config`
|
See also :meth:`set_config`.
|
||||||
|
|
||||||
:param flags: A bit map of `SPI_*` flags.
|
:param flags: A bit map of `SPI_*` flags.
|
||||||
:param length: Number of bits to write during the next transfer.
|
:param length: Number of bits to write during the next transfer.
|
||||||
(reset=1)
|
(reset=1)
|
||||||
:param div: Counter load value to divide the RTIO
|
:param div: Counter load value to divide the RTIO
|
||||||
clock by to generate the SPI clock. (minimum=2, reset=2)
|
clock by to generate the SPI clock; ``f_rtio_clk/f_spi == div``.
|
||||||
``f_rtio_clk/f_spi == div``. If ``div`` is odd,
|
If ``div`` is odd, the setup phase of the SPI clock is one
|
||||||
the setup phase of the SPI clock is one coarse RTIO clock cycle
|
coarse RTIO clock cycle longer than the hold phase. (minimum=2, reset=2)
|
||||||
longer than the hold phase.
|
|
||||||
:param cs: Bit pattern of chip selects to assert.
|
:param cs: Bit pattern of chip selects to assert.
|
||||||
Or number of the chip select to assert if ``cs`` is decoded
|
Or number of the chip select to assert if ``cs`` is decoded
|
||||||
downstream. (reset=0)
|
downstream. (reset=0)
|
||||||
|
@ -188,7 +187,7 @@ class SPIMaster:
|
||||||
experiments and are known.
|
experiments and are known.
|
||||||
|
|
||||||
This method is portable and can also be called from e.g.
|
This method is portable and can also be called from e.g.
|
||||||
:meth:`__init__`.
|
``__init__``.
|
||||||
|
|
||||||
.. warning:: If this method is called while recording a DMA
|
.. warning:: If this method is called while recording a DMA
|
||||||
sequence, the playback of the sequence will not update the
|
sequence, the playback of the sequence will not update the
|
||||||
|
@ -208,7 +207,7 @@ class SPIMaster:
|
||||||
* The ``data`` register and the shift register are 32 bits wide.
|
* The ``data`` register and the shift register are 32 bits wide.
|
||||||
* Data writes take one ``ref_period`` cycle.
|
* Data writes take one ``ref_period`` cycle.
|
||||||
* A transaction consisting of a single transfer (``SPI_END``) takes
|
* A transaction consisting of a single transfer (``SPI_END``) takes
|
||||||
:attr:`xfer_duration_mu` ``=(n + 1)*div`` cycles RTIO time where
|
:attr:`xfer_duration_mu` `` = (n + 1) * div`` cycles RTIO time, where
|
||||||
``n`` is the number of bits and ``div`` is the SPI clock divider.
|
``n`` is the number of bits and ``div`` is the SPI clock divider.
|
||||||
* Transfers in a multi-transfer transaction take up to one SPI clock
|
* Transfers in a multi-transfer transaction take up to one SPI clock
|
||||||
cycle less time depending on multiple parameters. Advanced users may
|
cycle less time depending on multiple parameters. Advanced users may
|
||||||
|
|
|
@ -24,7 +24,7 @@ def y_mu_to_full_scale(y):
|
||||||
|
|
||||||
@portable
|
@portable
|
||||||
def adc_mu_to_volts(x, gain, corrected_fs=True):
|
def adc_mu_to_volts(x, gain, corrected_fs=True):
|
||||||
"""Convert servo ADC data from machine units to Volt."""
|
"""Convert servo ADC data from machine units to volts."""
|
||||||
val = (x >> 1) & 0xffff
|
val = (x >> 1) & 0xffff
|
||||||
mask = 1 << 15
|
mask = 1 << 15
|
||||||
val = -(val & mask) + (val & ~mask)
|
val = -(val & mask) + (val & ~mask)
|
||||||
|
@ -155,7 +155,7 @@ class SUServo:
|
||||||
This method advances the timeline by one servo memory access.
|
This method advances the timeline by one servo memory access.
|
||||||
It does not support RTIO event replacement.
|
It does not support RTIO event replacement.
|
||||||
|
|
||||||
:param enable (int): Enable servo operation. Enabling starts servo
|
:param int enable: Enable servo operation. Enabling starts servo
|
||||||
iterations beginning with the ADC sampling stage. The first DDS
|
iterations beginning with the ADC sampling stage. The first DDS
|
||||||
update will happen about two servo cycles (~2.3 µs) after enabling
|
update will happen about two servo cycles (~2.3 µs) after enabling
|
||||||
the servo. The delay is deterministic.
|
the servo. The delay is deterministic.
|
||||||
|
@ -198,7 +198,7 @@ class SUServo:
|
||||||
consistent and valid data, stop the servo before using this method.
|
consistent and valid data, stop the servo before using this method.
|
||||||
|
|
||||||
:param adc: ADC channel number (0-7)
|
:param adc: ADC channel number (0-7)
|
||||||
:return: 17 bit signed X0
|
:return: 17-bit signed X0
|
||||||
"""
|
"""
|
||||||
# State memory entries are 25 bits. Due to the pre-adder dynamic
|
# State memory entries are 25 bits. Due to the pre-adder dynamic
|
||||||
# range, X0/X1/OFFSET are only 24 bits. Finally, the RTIO interface
|
# range, X0/X1/OFFSET are only 24 bits. Finally, the RTIO interface
|
||||||
|
@ -288,12 +288,12 @@ class Channel:
|
||||||
def set_dds_mu(self, profile, ftw, offs, pow_=0):
|
def set_dds_mu(self, profile, ftw, offs, pow_=0):
|
||||||
"""Set profile DDS coefficients in machine units.
|
"""Set profile DDS coefficients in machine units.
|
||||||
|
|
||||||
.. seealso:: :meth:`set_amplitude`
|
See also :meth:`Channel.set_dds`.
|
||||||
|
|
||||||
:param profile: Profile number (0-31)
|
:param profile: Profile number (0-31)
|
||||||
:param ftw: Frequency tuning word (32 bit unsigned)
|
:param ftw: Frequency tuning word (32-bit unsigned)
|
||||||
:param offs: IIR offset (17 bit signed)
|
:param offs: IIR offset (17-bit signed)
|
||||||
:param pow_: Phase offset word (16 bit)
|
:param pow_: Phase offset word (16-bit)
|
||||||
"""
|
"""
|
||||||
base = (self.servo_channel << 8) | (profile << 3)
|
base = (self.servo_channel << 8) | (profile << 3)
|
||||||
self.servo.write(base + 0, ftw >> 16)
|
self.servo.write(base + 0, ftw >> 16)
|
||||||
|
@ -327,7 +327,7 @@ class Channel:
|
||||||
See :meth:`set_dds_mu` for setting the complete DDS profile.
|
See :meth:`set_dds_mu` for setting the complete DDS profile.
|
||||||
|
|
||||||
:param profile: Profile number (0-31)
|
:param profile: Profile number (0-31)
|
||||||
:param offs: IIR offset (17 bit signed)
|
:param offs: IIR offset (17-bit signed)
|
||||||
"""
|
"""
|
||||||
base = (self.servo_channel << 8) | (profile << 3)
|
base = (self.servo_channel << 8) | (profile << 3)
|
||||||
self.servo.write(base + 4, offs)
|
self.servo.write(base + 4, offs)
|
||||||
|
@ -375,15 +375,15 @@ class Channel:
|
||||||
* :math:`b_0` and :math:`b_1` are the feedforward gains for the two
|
* :math:`b_0` and :math:`b_1` are the feedforward gains for the two
|
||||||
delays
|
delays
|
||||||
|
|
||||||
.. seealso:: :meth:`set_iir`
|
See also :meth:`Channel.set_iir`.
|
||||||
|
|
||||||
:param profile: Profile number (0-31)
|
:param profile: Profile number (0-31)
|
||||||
:param adc: ADC channel to take IIR input from (0-7)
|
:param adc: ADC channel to take IIR input from (0-7)
|
||||||
:param a1: 18 bit signed A1 coefficient (Y1 coefficient,
|
:param a1: 18-bit signed A1 coefficient (Y1 coefficient,
|
||||||
feedback, integrator gain)
|
feedback, integrator gain)
|
||||||
:param b0: 18 bit signed B0 coefficient (recent,
|
:param b0: 18-bit signed B0 coefficient (recent,
|
||||||
X0 coefficient, feed forward, proportional gain)
|
X0 coefficient, feed forward, proportional gain)
|
||||||
:param b1: 18 bit signed B1 coefficient (old,
|
:param b1: 18-bit signed B1 coefficient (old,
|
||||||
X1 coefficient, feed forward, proportional gain)
|
X1 coefficient, feed forward, proportional gain)
|
||||||
:param dly: IIR update suppression time. In units of IIR cycles
|
:param dly: IIR update suppression time. In units of IIR cycles
|
||||||
(~1.2 µs, 0-255).
|
(~1.2 µs, 0-255).
|
||||||
|
@ -499,7 +499,7 @@ class Channel:
|
||||||
consistent and valid data, stop the servo before using this method.
|
consistent and valid data, stop the servo before using this method.
|
||||||
|
|
||||||
:param profile: Profile number (0-31)
|
:param profile: Profile number (0-31)
|
||||||
:return: 17 bit unsigned Y0
|
:return: 17-bit unsigned Y0
|
||||||
"""
|
"""
|
||||||
return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile)
|
return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile)
|
||||||
|
|
||||||
|
@ -535,7 +535,7 @@ class Channel:
|
||||||
This method advances the timeline by one servo memory access.
|
This method advances the timeline by one servo memory access.
|
||||||
|
|
||||||
:param profile: Profile number (0-31)
|
:param profile: Profile number (0-31)
|
||||||
:param y: 17 bit unsigned Y0
|
:param y: 17-bit unsigned Y0
|
||||||
"""
|
"""
|
||||||
# State memory is 25 bits wide and signed.
|
# State memory is 25 bits wide and signed.
|
||||||
# Reads interact with the 18 MSBs (coefficient memory width)
|
# Reads interact with the 18 MSBs (coefficient memory width)
|
||||||
|
|
|
@ -27,7 +27,7 @@ class TTLOut:
|
||||||
|
|
||||||
This should be used with output-only channels.
|
This should be used with output-only channels.
|
||||||
|
|
||||||
:param channel: channel number
|
:param channel: Channel number
|
||||||
"""
|
"""
|
||||||
kernel_invariants = {"core", "channel", "target_o"}
|
kernel_invariants = {"core", "channel", "target_o"}
|
||||||
|
|
||||||
|
@ -109,7 +109,7 @@ class TTLInOut:
|
||||||
API is active (e.g. the gate is open, or the input events have not been
|
API is active (e.g. the gate is open, or the input events have not been
|
||||||
fully read out), another API must not be used simultaneously.
|
fully read out), another API must not be used simultaneously.
|
||||||
|
|
||||||
:param channel: channel number
|
:param channel: Channel number
|
||||||
"""
|
"""
|
||||||
kernel_invariants = {"core", "channel", "gate_latency_mu",
|
kernel_invariants = {"core", "channel", "gate_latency_mu",
|
||||||
"target_o", "target_oe", "target_sens", "target_sample"}
|
"target_o", "target_oe", "target_sens", "target_sample"}
|
||||||
|
@ -145,7 +145,7 @@ class TTLInOut:
|
||||||
"""Set the direction to output at the current position of the time
|
"""Set the direction to output at the current position of the time
|
||||||
cursor.
|
cursor.
|
||||||
|
|
||||||
There must be a delay of at least one RTIO clock cycle before any
|
A delay of at least one RTIO clock cycle is necessary before any
|
||||||
other command can be issued.
|
other command can be issued.
|
||||||
|
|
||||||
This method only configures the direction at the FPGA. When using
|
This method only configures the direction at the FPGA. When using
|
||||||
|
@ -158,7 +158,7 @@ class TTLInOut:
|
||||||
"""Set the direction to input at the current position of the time
|
"""Set the direction to input at the current position of the time
|
||||||
cursor.
|
cursor.
|
||||||
|
|
||||||
There must be a delay of at least one RTIO clock cycle before any
|
A delay of at least one RTIO clock cycle is necessary before any
|
||||||
other command can be issued.
|
other command can be issued.
|
||||||
|
|
||||||
This method only configures the direction at the FPGA. When using
|
This method only configures the direction at the FPGA. When using
|
||||||
|
@ -326,17 +326,18 @@ class TTLInOut:
|
||||||
:return: The number of events before the timeout elapsed (0 if none
|
:return: The number of events before the timeout elapsed (0 if none
|
||||||
observed).
|
observed).
|
||||||
|
|
||||||
Examples:
|
**Examples:**
|
||||||
|
|
||||||
To count events on channel ``ttl_input``, up to the current timeline
|
To count events on channel ``ttl_input``, up to the current timeline
|
||||||
position::
|
position: ::
|
||||||
|
|
||||||
ttl_input.count(now_mu())
|
ttl_input.count(now_mu())
|
||||||
|
|
||||||
If other events are scheduled between the end of the input gate
|
If other events are scheduled between the end of the input gate
|
||||||
period and when the number of events is counted, using ``now_mu()``
|
period and when the number of events is counted, using
|
||||||
as timeout consumes an unnecessary amount of timeline slack. In
|
:meth:`~artiq.language.core.now_mu()` as timeout consumes an
|
||||||
such cases, it can be beneficial to pass a more precise timestamp,
|
unnecessary amount of timeline slack. In such cases, it can be
|
||||||
for example::
|
beneficial to pass a more precise timestamp, for example: ::
|
||||||
|
|
||||||
gate_end_mu = ttl_input.gate_rising(100 * us)
|
gate_end_mu = ttl_input.gate_rising(100 * us)
|
||||||
|
|
||||||
|
@ -350,7 +351,7 @@ class TTLInOut:
|
||||||
num_rising_edges = ttl_input.count(gate_end_mu)
|
num_rising_edges = ttl_input.count(gate_end_mu)
|
||||||
|
|
||||||
The ``gate_*()`` family of methods return the cursor at the end
|
The ``gate_*()`` family of methods return the cursor at the end
|
||||||
of the window, allowing this to be expressed in a compact fashion::
|
of the window, allowing this to be expressed in a compact fashion: ::
|
||||||
|
|
||||||
ttl_input.count(ttl_input.gate_rising(100 * us))
|
ttl_input.count(ttl_input.gate_rising(100 * us))
|
||||||
"""
|
"""
|
||||||
|
@ -441,7 +442,7 @@ class TTLInOut:
|
||||||
was being watched.
|
was being watched.
|
||||||
|
|
||||||
The time cursor is not modified by this function. This function
|
The time cursor is not modified by this function. This function
|
||||||
always makes the slack negative.
|
always results in negative slack.
|
||||||
"""
|
"""
|
||||||
rtio_output(self.target_sens, 0)
|
rtio_output(self.target_sens, 0)
|
||||||
success = True
|
success = True
|
||||||
|
|
|
@ -130,7 +130,7 @@ class CPLD:
|
||||||
:param spi_device: SPI bus device name
|
:param spi_device: SPI bus device name
|
||||||
:param io_update_device: IO update RTIO TTLOut channel name
|
:param io_update_device: IO update RTIO TTLOut channel name
|
||||||
:param dds_reset_device: DDS reset RTIO TTLOut channel name
|
:param dds_reset_device: DDS reset RTIO TTLOut channel name
|
||||||
:param sync_device: AD9910 SYNC_IN RTIO TTLClockGen channel name
|
:param sync_device: AD9910 ``SYNC_IN`` RTIO TTLClockGen channel name
|
||||||
:param refclk: Reference clock (SMA, MMCX or on-board 100 MHz oscillator)
|
:param refclk: Reference clock (SMA, MMCX or on-board 100 MHz oscillator)
|
||||||
frequency in Hz
|
frequency in Hz
|
||||||
:param clk_sel: Reference clock selection. For hardware revision >= 1.3
|
:param clk_sel: Reference clock selection. For hardware revision >= 1.3
|
||||||
|
@ -143,9 +143,9 @@ class CPLD:
|
||||||
1: divide-by-1; 2: divide-by-2; 3: divide-by-4.
|
1: divide-by-1; 2: divide-by-2; 3: divide-by-4.
|
||||||
On Urukul boards with CPLD gateware before v1.3.1 only the default
|
On Urukul boards with CPLD gateware before v1.3.1 only the default
|
||||||
(0, i.e. variant dependent divider) is valid.
|
(0, i.e. variant dependent divider) is valid.
|
||||||
:param sync_sel: SYNC (multi-chip synchronisation) signal source selection.
|
:param sync_sel: ``SYNC`` (multi-chip synchronisation) signal source selection.
|
||||||
0 corresponds to SYNC_IN being supplied by the FPGA via the EEM
|
0 corresponds to ``SYNC_IN`` being supplied by the FPGA via the EEM
|
||||||
connector. 1 corresponds to SYNC_OUT from DDS0 being distributed to the
|
connector. 1 corresponds to ``SYNC_OUT`` from DDS0 being distributed to the
|
||||||
other chips.
|
other chips.
|
||||||
:param rf_sw: Initial CPLD RF switch register setting (default: 0x0).
|
:param rf_sw: Initial CPLD RF switch register setting (default: 0x0).
|
||||||
Knowledge of this state is not transferred between experiments.
|
Knowledge of this state is not transferred between experiments.
|
||||||
|
@ -153,8 +153,8 @@ class CPLD:
|
||||||
0x00000000). See also :meth:`get_att_mu` which retrieves the hardware
|
0x00000000). See also :meth:`get_att_mu` which retrieves the hardware
|
||||||
state without side effects. Knowledge of this state is not transferred
|
state without side effects. Knowledge of this state is not transferred
|
||||||
between experiments.
|
between experiments.
|
||||||
:param sync_div: SYNC_IN generator divider. The ratio between the coarse
|
:param sync_div: ``SYNC_IN`` generator divider. The ratio between the coarse
|
||||||
RTIO frequency and the SYNC_IN generator frequency (default: 2 if
|
RTIO frequency and the ``SYNC_IN`` generator frequency (default: 2 if
|
||||||
`sync_device` was specified).
|
`sync_device` was specified).
|
||||||
:param core_device: Core device name
|
:param core_device: Core device name
|
||||||
|
|
||||||
|
@ -204,7 +204,7 @@ class CPLD:
|
||||||
|
|
||||||
See :func:`urukul_cfg` for possible flags.
|
See :func:`urukul_cfg` for possible flags.
|
||||||
|
|
||||||
:param cfg: 24 bit data to be written. Will be stored at
|
:param cfg: 24-bit data to be written. Will be stored at
|
||||||
:attr:`cfg_reg`.
|
:attr:`cfg_reg`.
|
||||||
"""
|
"""
|
||||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24,
|
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24,
|
||||||
|
@ -237,7 +237,7 @@ class CPLD:
|
||||||
|
|
||||||
Resets the DDS I/O interface and verifies correct CPLD gateware
|
Resets the DDS I/O interface and verifies correct CPLD gateware
|
||||||
version.
|
version.
|
||||||
Does not pulse the DDS MASTER_RESET as that confuses the AD9910.
|
Does not pulse the DDS ``MASTER_RESET`` as that confuses the AD9910.
|
||||||
|
|
||||||
:param blind: Do not attempt to verify presence and compatibility.
|
:param blind: Do not attempt to verify presence and compatibility.
|
||||||
"""
|
"""
|
||||||
|
@ -283,7 +283,7 @@ class CPLD:
|
||||||
def cfg_switches(self, state: TInt32):
|
def cfg_switches(self, state: TInt32):
|
||||||
"""Configure all four RF switches through the configuration register.
|
"""Configure all four RF switches through the configuration register.
|
||||||
|
|
||||||
:param state: RF switch state as a 4 bit integer.
|
:param state: RF switch state as a 4-bit integer.
|
||||||
"""
|
"""
|
||||||
self.cfg_write((self.cfg_reg & ~0xf) | state)
|
self.cfg_write((self.cfg_reg & ~0xf) | state)
|
||||||
|
|
||||||
|
@ -327,10 +327,9 @@ class CPLD:
|
||||||
@kernel
|
@kernel
|
||||||
def set_all_att_mu(self, att_reg: TInt32):
|
def set_all_att_mu(self, att_reg: TInt32):
|
||||||
"""Set all four digital step attenuators (in machine units).
|
"""Set all four digital step attenuators (in machine units).
|
||||||
|
See also :meth:`set_att_mu`.
|
||||||
|
|
||||||
.. seealso:: :meth:`set_att_mu`
|
:param att_reg: Attenuator setting string (32-bit)
|
||||||
|
|
||||||
:param att_reg: Attenuator setting string (32 bit)
|
|
||||||
"""
|
"""
|
||||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32,
|
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32,
|
||||||
SPIT_ATT_WR, CS_ATT)
|
SPIT_ATT_WR, CS_ATT)
|
||||||
|
@ -342,8 +341,7 @@ class CPLD:
|
||||||
"""Set digital step attenuator in SI units.
|
"""Set digital step attenuator in SI units.
|
||||||
|
|
||||||
This method will write the attenuator settings of all four channels.
|
This method will write the attenuator settings of all four channels.
|
||||||
|
See also :meth:`set_att_mu`.
|
||||||
.. seealso:: :meth:`set_att_mu`
|
|
||||||
|
|
||||||
:param channel: Attenuator channel (0-3).
|
:param channel: Attenuator channel (0-3).
|
||||||
:param att: Attenuation setting in dB. Higher value is more
|
:param att: Attenuation setting in dB. Higher value is more
|
||||||
|
@ -359,9 +357,9 @@ class CPLD:
|
||||||
The result is stored and will be used in future calls of
|
The result is stored and will be used in future calls of
|
||||||
:meth:`set_att_mu` and :meth:`set_att`.
|
:meth:`set_att_mu` and :meth:`set_att`.
|
||||||
|
|
||||||
.. seealso:: :meth:`get_channel_att_mu`
|
See also :meth:`get_channel_att_mu`.
|
||||||
|
|
||||||
:return: 32 bit attenuator settings
|
:return: 32-bit attenuator settings
|
||||||
"""
|
"""
|
||||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 32,
|
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 32,
|
||||||
SPIT_ATT_RD, CS_ATT)
|
SPIT_ATT_RD, CS_ATT)
|
||||||
|
@ -380,7 +378,7 @@ class CPLD:
|
||||||
The result is stored and will be used in future calls of
|
The result is stored and will be used in future calls of
|
||||||
:meth:`set_att_mu` and :meth:`set_att`.
|
:meth:`set_att_mu` and :meth:`set_att`.
|
||||||
|
|
||||||
.. seealso:: :meth:`get_att_mu`
|
See also :meth:`get_att_mu`.
|
||||||
|
|
||||||
:param channel: Attenuator channel (0-3).
|
:param channel: Attenuator channel (0-3).
|
||||||
:return: 8-bit digital attenuation setting:
|
:return: 8-bit digital attenuation setting:
|
||||||
|
@ -392,7 +390,7 @@ class CPLD:
|
||||||
def get_channel_att(self, channel: TInt32) -> TFloat:
|
def get_channel_att(self, channel: TInt32) -> TFloat:
|
||||||
"""Get digital step attenuator value for a channel in SI units.
|
"""Get digital step attenuator value for a channel in SI units.
|
||||||
|
|
||||||
.. seealso:: :meth:`get_channel_att_mu`
|
See also :meth:`get_channel_att_mu`.
|
||||||
|
|
||||||
:param channel: Attenuator channel (0-3).
|
:param channel: Attenuator channel (0-3).
|
||||||
:return: Attenuation setting in dB. Higher value is more
|
:return: Attenuation setting in dB. Higher value is more
|
||||||
|
@ -403,14 +401,14 @@ class CPLD:
|
||||||
|
|
||||||
@kernel
|
@kernel
|
||||||
def set_sync_div(self, div: TInt32):
|
def set_sync_div(self, div: TInt32):
|
||||||
"""Set the SYNC_IN AD9910 pulse generator frequency
|
"""Set the ``SYNC_IN`` AD9910 pulse generator frequency
|
||||||
and align it to the current RTIO timestamp.
|
and align it to the current RTIO timestamp.
|
||||||
|
|
||||||
The SYNC_IN signal is derived from the coarse RTIO clock
|
The ``SYNC_IN`` signal is derived from the coarse RTIO clock
|
||||||
and the divider must be a power of two.
|
and the divider must be a power of two.
|
||||||
Configure ``sync_sel == 0``.
|
Configure ``sync_sel == 0``.
|
||||||
|
|
||||||
:param div: SYNC_IN frequency divider. Must be a power of two.
|
:param div: ``SYNC_IN`` frequency divider. Must be a power of two.
|
||||||
Minimum division ratio is 2. Maximum division ratio is 16.
|
Minimum division ratio is 2. Maximum division ratio is 16.
|
||||||
"""
|
"""
|
||||||
ftw_max = 1 << 4
|
ftw_max = 1 << 4
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""RTIO driver for the Zotino 32-channel, 16-bit 1MSPS DAC.
|
"""RTIO driver for the Zotino 32-channel, 16-bit 1MSPS DAC.
|
||||||
|
|
||||||
Output event replacement is not supported and issuing commands at the same
|
Output event replacement is not supported and issuing commands at the same
|
||||||
time is an error.
|
time results in a collision error.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from artiq.language.core import kernel
|
from artiq.language.core import kernel
|
||||||
|
|
|
@ -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,13 +2,12 @@ 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
|
||||||
from artiq.gui.scientific_spinbox import ScientificSpinBox
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -64,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)
|
||||||
|
|
||||||
|
@ -81,8 +80,7 @@ class CreateEditDialog(QtWidgets.QDialog):
|
||||||
t = value.dtype if value is np.ndarray else type(value)
|
t = value.dtype if value is np.ndarray else type(value)
|
||||||
if scale != 1 and np.issubdtype(t, np.number):
|
if scale != 1 and np.issubdtype(t, np.number):
|
||||||
# degenerates to float type
|
# degenerates to float type
|
||||||
value_edit_string = self.value_to_edit_string(
|
value_edit_string = self.value_to_edit_string(value / scale)
|
||||||
np.float64(value / scale))
|
|
||||||
self.unit_widget.setText(metadata.get('unit', ''))
|
self.unit_widget.setText(metadata.get('unit', ''))
|
||||||
self.scale_widget.setText(str(metadata.get('scale', '')))
|
self.scale_widget.setText(str(metadata.get('scale', '')))
|
||||||
self.precision_widget.setText(str(metadata.get('precision', '')))
|
self.precision_widget.setText(str(metadata.get('precision', '')))
|
||||||
|
@ -109,11 +107,13 @@ class CreateEditDialog(QtWidgets.QDialog):
|
||||||
t = value.dtype if value is np.ndarray else type(value)
|
t = value.dtype if value is np.ndarray else type(value)
|
||||||
if scale != 1 and np.issubdtype(t, np.number):
|
if scale != 1 and np.issubdtype(t, np.number):
|
||||||
# degenerates to float type
|
# degenerates to float type
|
||||||
value = np.float64(value * scale)
|
value = float(value * scale)
|
||||||
if self.key and self.key != key:
|
if self.key and self.key != key:
|
||||||
asyncio.ensure_future(exc_to_warning(rename(self.key, key, value, metadata, persist, self.dataset_ctl)))
|
asyncio.ensure_future(exc_to_warning(rename(self.key, key, value, metadata, persist,
|
||||||
|
self.dataset_ctl)))
|
||||||
else:
|
else:
|
||||||
asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set(key, value, metadata=metadata, persist=persist)))
|
asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set(key, value, metadata=metadata,
|
||||||
|
persist=persist)))
|
||||||
self.key = key
|
self.key = key
|
||||||
QtWidgets.QDialog.accept(self)
|
QtWidgets.QDialog.accept(self)
|
||||||
|
|
||||||
|
@ -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:
|
||||||
|
@ -163,7 +163,7 @@ class CreateEditDialog(QtWidgets.QDialog):
|
||||||
|
|
||||||
|
|
||||||
class Model(DictSyncTreeSepModel):
|
class Model(DictSyncTreeSepModel):
|
||||||
def __init__(self, init):
|
def __init__(self, init):
|
||||||
DictSyncTreeSepModel.__init__(self, ".",
|
DictSyncTreeSepModel.__init__(self, ".",
|
||||||
["Dataset", "Persistent", "Value"],
|
["Dataset", "Persistent", "Value"],
|
||||||
init)
|
init)
|
||||||
|
@ -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,15 +4,14 @@ 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
|
||||||
|
|
||||||
from artiq.gui.entries import procdesc_to_entry, ScanEntry
|
from artiq.gui.entries import procdesc_to_entry, EntryTreeWidget
|
||||||
from artiq.gui.fuzzy_select import FuzzySelectWidget
|
from artiq.gui.fuzzy_select import FuzzySelectWidget
|
||||||
from artiq.gui.tools import (LayoutWidget, WheelFilter,
|
from artiq.gui.tools import (LayoutWidget, log_level_to_name, get_open_file_name)
|
||||||
log_level_to_name, get_open_file_name)
|
|
||||||
from artiq.tools import parse_devarg_override, unparse_devarg_override
|
from artiq.tools import parse_devarg_override, unparse_devarg_override
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,108 +24,32 @@ logger = logging.getLogger(__name__)
|
||||||
# 2. file:<class name>@<file name>
|
# 2. file:<class name>@<file name>
|
||||||
|
|
||||||
|
|
||||||
class _ArgumentEditor(QtWidgets.QTreeWidget):
|
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
|
||||||
|
|
||||||
QtWidgets.QTreeWidget.__init__(self)
|
EntryTreeWidget.__init__(self)
|
||||||
self.setColumnCount(3)
|
|
||||||
self.header().setStretchLastSection(False)
|
|
||||||
if hasattr(self.header(), "setSectionResizeMode"):
|
|
||||||
set_resize_mode = self.header().setSectionResizeMode
|
|
||||||
else:
|
|
||||||
set_resize_mode = self.header().setResizeMode
|
|
||||||
set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents)
|
|
||||||
set_resize_mode(1, QtWidgets.QHeaderView.Stretch)
|
|
||||||
set_resize_mode(2, QtWidgets.QHeaderView.ResizeToContents)
|
|
||||||
self.header().setVisible(False)
|
|
||||||
self.setSelectionMode(self.NoSelection)
|
|
||||||
self.setHorizontalScrollMode(self.ScrollPerPixel)
|
|
||||||
self.setVerticalScrollMode(self.ScrollPerPixel)
|
|
||||||
|
|
||||||
self.setStyleSheet("QTreeWidget {background: " +
|
|
||||||
self.palette().midlight().color().name() + " ;}")
|
|
||||||
|
|
||||||
self.viewport().installEventFilter(WheelFilter(self.viewport(), True))
|
|
||||||
|
|
||||||
self._groups = dict()
|
|
||||||
self._arg_to_widgets = dict()
|
|
||||||
|
|
||||||
arguments = self.manager.get_submission_arguments(self.expurl)
|
arguments = self.manager.get_submission_arguments(self.expurl)
|
||||||
|
|
||||||
if not arguments:
|
if not arguments:
|
||||||
self.addTopLevelItem(QtWidgets.QTreeWidgetItem(["No arguments"]))
|
self.insertTopLevelItem(0, QtWidgets.QTreeWidgetItem(["No arguments"]))
|
||||||
|
|
||||||
gradient = QtGui.QLinearGradient(
|
|
||||||
0, 0, 0, QtGui.QFontMetrics(self.font()).lineSpacing()*2.5)
|
|
||||||
gradient.setColorAt(0, self.palette().base().color())
|
|
||||||
gradient.setColorAt(1, self.palette().midlight().color())
|
|
||||||
for name, argument in arguments.items():
|
for name, argument in arguments.items():
|
||||||
widgets = dict()
|
self.set_argument(name, argument)
|
||||||
self._arg_to_widgets[name] = widgets
|
|
||||||
|
|
||||||
entry = procdesc_to_entry(argument["desc"])(argument)
|
self.quickStyleClicked.connect(dock.submit_clicked)
|
||||||
widget_item = QtWidgets.QTreeWidgetItem([name])
|
|
||||||
if argument["tooltip"]:
|
|
||||||
widget_item.setToolTip(0, argument["tooltip"])
|
|
||||||
widgets["entry"] = entry
|
|
||||||
widgets["widget_item"] = widget_item
|
|
||||||
|
|
||||||
for col in range(3):
|
|
||||||
widget_item.setBackground(col, gradient)
|
|
||||||
font = widget_item.font(0)
|
|
||||||
font.setBold(True)
|
|
||||||
widget_item.setFont(0, font)
|
|
||||||
|
|
||||||
if argument["group"] is None:
|
|
||||||
self.addTopLevelItem(widget_item)
|
|
||||||
else:
|
|
||||||
self._get_group(argument["group"]).addChild(widget_item)
|
|
||||||
fix_layout = LayoutWidget()
|
|
||||||
widgets["fix_layout"] = fix_layout
|
|
||||||
fix_layout.addWidget(entry)
|
|
||||||
self.setItemWidget(widget_item, 1, fix_layout)
|
|
||||||
recompute_argument = QtWidgets.QToolButton()
|
|
||||||
recompute_argument.setToolTip("Re-run the experiment's build "
|
|
||||||
"method and take the default value")
|
|
||||||
recompute_argument.setIcon(
|
|
||||||
QtWidgets.QApplication.style().standardIcon(
|
|
||||||
QtWidgets.QStyle.SP_BrowserReload))
|
|
||||||
recompute_argument.clicked.connect(
|
|
||||||
partial(self._recompute_argument_clicked, name))
|
|
||||||
|
|
||||||
tool_buttons = LayoutWidget()
|
|
||||||
tool_buttons.addWidget(recompute_argument, 1)
|
|
||||||
|
|
||||||
disable_other_scans = QtWidgets.QToolButton()
|
|
||||||
widgets["disable_other_scans"] = disable_other_scans
|
|
||||||
disable_other_scans.setIcon(
|
|
||||||
QtWidgets.QApplication.style().standardIcon(
|
|
||||||
QtWidgets.QStyle.SP_DialogResetButton))
|
|
||||||
disable_other_scans.setToolTip("Disable all other scans in "
|
|
||||||
"this experiment")
|
|
||||||
disable_other_scans.clicked.connect(
|
|
||||||
partial(self._disable_other_scans, name))
|
|
||||||
tool_buttons.layout.setRowStretch(0, 1)
|
|
||||||
tool_buttons.layout.setRowStretch(3, 1)
|
|
||||||
tool_buttons.addWidget(disable_other_scans, 2)
|
|
||||||
if not isinstance(entry, ScanEntry):
|
|
||||||
disable_other_scans.setVisible(False)
|
|
||||||
|
|
||||||
self.setItemWidget(widget_item, 2, tool_buttons)
|
|
||||||
|
|
||||||
widget_item = QtWidgets.QTreeWidgetItem()
|
|
||||||
self.addTopLevelItem(widget_item)
|
|
||||||
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()
|
||||||
|
@ -136,41 +59,10 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
||||||
buttons.layout.setColumnStretch(1, 0)
|
buttons.layout.setColumnStretch(1, 0)
|
||||||
buttons.layout.setColumnStretch(2, 0)
|
buttons.layout.setColumnStretch(2, 0)
|
||||||
buttons.layout.setColumnStretch(3, 1)
|
buttons.layout.setColumnStretch(3, 1)
|
||||||
self.setItemWidget(widget_item, 1, buttons)
|
self.setItemWidget(self.bottom_item, 1, buttons)
|
||||||
|
|
||||||
def _get_group(self, name):
|
def reset_entry(self, key):
|
||||||
if name in self._groups:
|
asyncio.ensure_future(self._recompute_argument(key))
|
||||||
return self._groups[name]
|
|
||||||
group = QtWidgets.QTreeWidgetItem([name])
|
|
||||||
for col in range(3):
|
|
||||||
group.setBackground(col, self.palette().mid())
|
|
||||||
group.setForeground(col, self.palette().brightText())
|
|
||||||
font = group.font(col)
|
|
||||||
font.setBold(True)
|
|
||||||
group.setFont(col, font)
|
|
||||||
self.addTopLevelItem(group)
|
|
||||||
self._groups[name] = group
|
|
||||||
return group
|
|
||||||
|
|
||||||
def update_argument(self, name, argument):
|
|
||||||
widgets = self._arg_to_widgets[name]
|
|
||||||
|
|
||||||
# Qt needs a setItemWidget() to handle layout correctly,
|
|
||||||
# simply replacing the entry inside the LayoutWidget
|
|
||||||
# results in a bug.
|
|
||||||
|
|
||||||
widgets["entry"].deleteLater()
|
|
||||||
widgets["entry"] = procdesc_to_entry(argument["desc"])(argument)
|
|
||||||
widgets["disable_other_scans"].setVisible(
|
|
||||||
isinstance(widgets["entry"], ScanEntry))
|
|
||||||
widgets["fix_layout"].deleteLater()
|
|
||||||
widgets["fix_layout"] = LayoutWidget()
|
|
||||||
widgets["fix_layout"].addWidget(widgets["entry"])
|
|
||||||
self.setItemWidget(widgets["widget_item"], 1, widgets["fix_layout"])
|
|
||||||
self.updateGeometries()
|
|
||||||
|
|
||||||
def _recompute_argument_clicked(self, name):
|
|
||||||
asyncio.ensure_future(self._recompute_argument(name))
|
|
||||||
|
|
||||||
async def _recompute_argument(self, name):
|
async def _recompute_argument(self, name):
|
||||||
try:
|
try:
|
||||||
|
@ -187,30 +79,6 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
||||||
argument["state"] = state
|
argument["state"] = state
|
||||||
self.update_argument(name, argument)
|
self.update_argument(name, argument)
|
||||||
|
|
||||||
def _disable_other_scans(self, current_name):
|
|
||||||
for name, widgets in self._arg_to_widgets.items():
|
|
||||||
if (name != current_name
|
|
||||||
and isinstance(widgets["entry"], ScanEntry)):
|
|
||||||
widgets["entry"].disable()
|
|
||||||
|
|
||||||
def save_state(self):
|
|
||||||
expanded = []
|
|
||||||
for k, v in self._groups.items():
|
|
||||||
if v.isExpanded():
|
|
||||||
expanded.append(k)
|
|
||||||
return {
|
|
||||||
"expanded": expanded,
|
|
||||||
"scroll": self.verticalScrollBar().value()
|
|
||||||
}
|
|
||||||
|
|
||||||
def restore_state(self, state):
|
|
||||||
for e in state["expanded"]:
|
|
||||||
try:
|
|
||||||
self._groups[e].setExpanded(True)
|
|
||||||
except KeyError:
|
|
||||||
pass
|
|
||||||
self.verticalScrollBar().setValue(state["scroll"])
|
|
||||||
|
|
||||||
# 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
|
||||||
# up-to-date, so no further action is required.
|
# up-to-date, so no further action is required.
|
||||||
|
@ -230,10 +98,10 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
def __init__(self, manager, expurl):
|
def __init__(self, manager, expurl):
|
||||||
QtWidgets.QMdiSubWindow.__init__(self)
|
QtWidgets.QMdiSubWindow.__init__(self)
|
||||||
qfm = QtGui.QFontMetrics(self.font())
|
qfm = QtGui.QFontMetrics(self.font())
|
||||||
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()
|
||||||
|
@ -263,17 +131,17 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
datetime.setDate(QtCore.QDate.currentDate())
|
datetime.setDate(QtCore.QDate.currentDate())
|
||||||
else:
|
else:
|
||||||
datetime.setDateTime(QtCore.QDateTime.fromMSecsSinceEpoch(
|
datetime.setDateTime(QtCore.QDateTime.fromMSecsSinceEpoch(
|
||||||
int(scheduling["due_date"]*1000)))
|
int(scheduling["due_date"] * 1000)))
|
||||||
datetime_en.setChecked(scheduling["due_date"] is not None)
|
datetime_en.setChecked(scheduling["due_date"] is not None)
|
||||||
|
|
||||||
def update_datetime(dt):
|
def update_datetime(dt):
|
||||||
scheduling["due_date"] = dt.toMSecsSinceEpoch()/1000
|
scheduling["due_date"] = dt.toMSecsSinceEpoch() / 1000
|
||||||
datetime_en.setChecked(True)
|
datetime_en.setChecked(True)
|
||||||
datetime.dateTimeChanged.connect(update_datetime)
|
datetime.dateTimeChanged.connect(update_datetime)
|
||||||
|
|
||||||
def update_datetime_en(checked):
|
def update_datetime_en(checked):
|
||||||
if checked:
|
if checked:
|
||||||
due_date = datetime.dateTime().toMSecsSinceEpoch()/1000
|
due_date = datetime.dateTime().toMSecsSinceEpoch() / 1000
|
||||||
else:
|
else:
|
||||||
due_date = None
|
due_date = None
|
||||||
scheduling["due_date"] = due_date
|
scheduling["due_date"] = due_date
|
||||||
|
@ -349,9 +217,10 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
repo_rev = QtWidgets.QLineEdit()
|
repo_rev = QtWidgets.QLineEdit()
|
||||||
repo_rev.setPlaceholderText("current")
|
repo_rev.setPlaceholderText("current")
|
||||||
repo_rev.setClearButtonEnabled(True)
|
repo_rev.setClearButtonEnabled(True)
|
||||||
repo_rev_label = QtWidgets.QLabel("Revision:")
|
repo_rev_label = QtWidgets.QLabel("Rev / ref:")
|
||||||
repo_rev_label.setToolTip("Experiment repository revision "
|
repo_rev_label.setToolTip("Experiment repository revision "
|
||||||
"(commit ID) to use")
|
"(commit ID) or reference (branch "
|
||||||
|
"or tag) to use")
|
||||||
self.layout.addWidget(repo_rev_label, 3, 2)
|
self.layout.addWidget(repo_rev_label, 3, 2)
|
||||||
self.layout.addWidget(repo_rev, 3, 3)
|
self.layout.addWidget(repo_rev, 3, 3)
|
||||||
|
|
||||||
|
@ -368,21 +237,21 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
|
|
||||||
submit = QtWidgets.QPushButton("Submit")
|
submit = QtWidgets.QPushButton("Submit")
|
||||||
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
|
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
QtWidgets.QStyle.SP_DialogOkButton))
|
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
|
||||||
submit.setToolTip("Schedule the experiment (Ctrl+Return)")
|
submit.setToolTip("Schedule the experiment (Ctrl+Return)")
|
||||||
submit.setShortcut("CTRL+RETURN")
|
submit.setShortcut("CTRL+RETURN")
|
||||||
submit.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
submit.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
|
||||||
QtWidgets.QSizePolicy.Expanding)
|
QtWidgets.QSizePolicy.Policy.Expanding)
|
||||||
self.layout.addWidget(submit, 1, 4, 2, 1)
|
self.layout.addWidget(submit, 1, 4, 2, 1)
|
||||||
submit.clicked.connect(self.submit_clicked)
|
submit.clicked.connect(self.submit_clicked)
|
||||||
|
|
||||||
reqterm = QtWidgets.QPushButton("Terminate instances")
|
reqterm = QtWidgets.QPushButton("Terminate instances")
|
||||||
reqterm.setIcon(QtWidgets.QApplication.style().standardIcon(
|
reqterm.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
QtWidgets.QStyle.SP_DialogCancelButton))
|
QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton))
|
||||||
reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)")
|
reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)")
|
||||||
reqterm.setShortcut("CTRL+BACKSPACE")
|
reqterm.setShortcut("CTRL+BACKSPACE")
|
||||||
reqterm.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
reqterm.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
|
||||||
QtWidgets.QSizePolicy.Expanding)
|
QtWidgets.QSizePolicy.Policy.Expanding)
|
||||||
self.layout.addWidget(reqterm, 3, 4)
|
self.layout.addWidget(reqterm, 3, 4)
|
||||||
reqterm.clicked.connect(self.reqterm_clicked)
|
reqterm.clicked.connect(self.reqterm_clicked)
|
||||||
|
|
||||||
|
@ -420,8 +289,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
arginfo = expdesc["arginfo"]
|
arginfo = expdesc["arginfo"]
|
||||||
for k, v in overrides.items():
|
for k, v in overrides.items():
|
||||||
# Some values (e.g. scans) may have multiple defaults in a list
|
# Some values (e.g. scans) may have multiple defaults in a list
|
||||||
if ("default" in arginfo[k][0]
|
if ("default" in arginfo[k][0] and isinstance(arginfo[k][0]["default"], list)):
|
||||||
and isinstance(arginfo[k][0]["default"], list)):
|
|
||||||
arginfo[k][0]["default"].insert(0, v)
|
arginfo[k][0]["default"].insert(0, v)
|
||||||
else:
|
else:
|
||||||
arginfo[k][0]["default"] = v
|
arginfo[k][0]["default"] = v
|
||||||
|
@ -432,13 +300,13 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
|
|
||||||
editor_class = self.manager.get_argument_editor_class(self.expurl)
|
editor_class = self.manager.get_argument_editor_class(self.expurl)
|
||||||
self.argeditor = editor_class(self.manager, self, self.expurl)
|
self.argeditor = editor_class(self.manager, self, self.expurl)
|
||||||
self.argeditor.restore_state(argeditor_state)
|
|
||||||
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
||||||
|
self.argeditor.restore_state(argeditor_state)
|
||||||
|
|
||||||
def contextMenuEvent(self, event):
|
def contextMenuEvent(self, event):
|
||||||
menu = QtWidgets.QMenu(self)
|
menu = QtWidgets.QMenu(self)
|
||||||
reset_sched = menu.addAction("Reset scheduler settings")
|
reset_sched = menu.addAction("Reset scheduler settings")
|
||||||
action = menu.exec_(self.mapToGlobal(event.pos()))
|
action = menu.exec(self.mapToGlobal(event.pos()))
|
||||||
if action == reset_sched:
|
if action == reset_sched:
|
||||||
asyncio.ensure_future(self._recompute_sched_options_task())
|
asyncio.ensure_future(self._recompute_sched_options_task())
|
||||||
|
|
||||||
|
@ -486,9 +354,9 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
||||||
unparse_devarg_override(expid["devarg_override"]))
|
unparse_devarg_override(expid["devarg_override"]))
|
||||||
self.log_level.setCurrentIndex(log_levels.index(
|
self.log_level.setCurrentIndex(log_levels.index(
|
||||||
log_level_to_name(expid["log_level"])))
|
log_level_to_name(expid["log_level"])))
|
||||||
if ("repo_rev" in expid and
|
if "repo_rev" in expid and \
|
||||||
expid["repo_rev"] != "N/A" and
|
expid["repo_rev"] != "N/A" and \
|
||||||
hasattr(self, "repo_rev")):
|
hasattr(self, "repo_rev"):
|
||||||
self.repo_rev.setText(expid["repo_rev"])
|
self.repo_rev.setText(expid["repo_rev"])
|
||||||
except:
|
except:
|
||||||
logger.error("Could not set submission options from HDF5 expid",
|
logger.error("Could not set submission options from HDF5 expid",
|
||||||
|
@ -555,7 +423,7 @@ class _QuickOpenDialog(QtWidgets.QDialog):
|
||||||
QtWidgets.QDialog.done(self, r)
|
QtWidgets.QDialog.done(self, r)
|
||||||
|
|
||||||
def _open_experiment(self, exp_name, modifiers):
|
def _open_experiment(self, exp_name, modifiers):
|
||||||
if modifiers & QtCore.Qt.ControlModifier:
|
if modifiers & QtCore.Qt.KeyboardModifier.ControlModifier:
|
||||||
try:
|
try:
|
||||||
self.manager.submit(exp_name)
|
self.manager.submit(exp_name)
|
||||||
except:
|
except:
|
||||||
|
@ -599,10 +467,10 @@ class ExperimentManager:
|
||||||
self.open_experiments = dict()
|
self.open_experiments = dict()
|
||||||
|
|
||||||
self.is_quick_open_shown = False
|
self.is_quick_open_shown = False
|
||||||
quick_open_shortcut = QtWidgets.QShortcut(
|
quick_open_shortcut = QtGui.QShortcut(
|
||||||
QtCore.Qt.CTRL + QtCore.Qt.Key_P,
|
QtGui.QKeySequence("Ctrl+P"),
|
||||||
main_window)
|
main_window)
|
||||||
quick_open_shortcut.setContext(QtCore.Qt.ApplicationShortcut)
|
quick_open_shortcut.setContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)
|
||||||
quick_open_shortcut.activated.connect(self.show_quick_open)
|
quick_open_shortcut.activated.connect(self.show_quick_open)
|
||||||
|
|
||||||
def set_dataset_model(self, model):
|
def set_dataset_model(self, model):
|
||||||
|
@ -691,7 +559,8 @@ class ExperimentManager:
|
||||||
if expurl in self.open_experiments.keys():
|
if expurl in self.open_experiments.keys():
|
||||||
self.open_experiments[expurl].argeditor.update_argument(name, argument)
|
self.open_experiments[expurl].argeditor.update_argument(name, argument)
|
||||||
except:
|
except:
|
||||||
logger.warn("Failed to set value for argument \"{}\" in experiment: {}.".format(name, expurl), exc_info=1)
|
logger.warn("Failed to set value for argument \"{}\" in experiment: {}."
|
||||||
|
.format(name, expurl), exc_info=1)
|
||||||
|
|
||||||
def get_submission_arguments(self, expurl):
|
def get_submission_arguments(self, expurl):
|
||||||
if expurl in self.submission_arguments:
|
if expurl in self.submission_arguments:
|
||||||
|
@ -701,8 +570,8 @@ class ExperimentManager:
|
||||||
raise ValueError("Submission arguments must be preinitialized "
|
raise ValueError("Submission arguments must be preinitialized "
|
||||||
"when not using repository")
|
"when not using repository")
|
||||||
class_desc = self.explist[expurl[5:]]
|
class_desc = self.explist[expurl[5:]]
|
||||||
return self.initialize_submission_arguments(expurl,
|
return self.initialize_submission_arguments(expurl, class_desc["arginfo"],
|
||||||
class_desc["arginfo"], class_desc.get("argument_ui", None))
|
class_desc.get("argument_ui", None))
|
||||||
|
|
||||||
def open_experiment(self, expurl):
|
def open_experiment(self, expurl):
|
||||||
if expurl in self.open_experiments:
|
if expurl in self.open_experiments:
|
||||||
|
@ -720,7 +589,7 @@ class ExperimentManager:
|
||||||
del self.submission_arguments[expurl]
|
del self.submission_arguments[expurl]
|
||||||
dock = _ExperimentDock(self, expurl)
|
dock = _ExperimentDock(self, expurl)
|
||||||
self.open_experiments[expurl] = dock
|
self.open_experiments[expurl] = dock
|
||||||
dock.setAttribute(QtCore.Qt.WA_DeleteOnClose)
|
dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
|
||||||
self.main_window.centralWidget().addSubWindow(dock)
|
self.main_window.centralWidget().addSubWindow(dock)
|
||||||
dock.show()
|
dock.show()
|
||||||
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
|
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
|
||||||
|
@ -739,8 +608,13 @@ class ExperimentManager:
|
||||||
del self.open_experiments[expurl]
|
del self.open_experiments[expurl]
|
||||||
|
|
||||||
async def _submit_task(self, expurl, *args):
|
async def _submit_task(self, expurl, *args):
|
||||||
rid = await self.schedule_ctl.submit(*args)
|
try:
|
||||||
logger.info("Submitted '%s', RID is %d", expurl, rid)
|
rid = await self.schedule_ctl.submit(*args)
|
||||||
|
except KeyError:
|
||||||
|
expid = args[1]
|
||||||
|
logger.error("Submission failed - revision \"%s\" was not found", expid["repo_rev"])
|
||||||
|
else:
|
||||||
|
logger.info("Submitted '%s', RID is %d", expurl, rid)
|
||||||
|
|
||||||
def submit(self, expurl):
|
def submit(self, expurl):
|
||||||
file, class_name, _ = self.resolve_expurl(expurl)
|
file, class_name, _ = self.resolve_expurl(expurl)
|
||||||
|
@ -797,9 +671,9 @@ class ExperimentManager:
|
||||||
repo_match = "repo_rev" in expid
|
repo_match = "repo_rev" in expid
|
||||||
else:
|
else:
|
||||||
repo_match = "repo_rev" not in expid
|
repo_match = "repo_rev" not in expid
|
||||||
if (repo_match and
|
if repo_match and \
|
||||||
("file" in expid and expid["file"] == file) and
|
("file" in expid and expid["file"] == file) and \
|
||||||
expid["class_name"] == class_name):
|
expid["class_name"] == class_name:
|
||||||
rids.append(rid)
|
rids.append(rid)
|
||||||
asyncio.ensure_future(self._request_term_multiple(rids))
|
asyncio.ensure_future(self._request_term_multiple(rids))
|
||||||
|
|
||||||
|
@ -819,7 +693,7 @@ class ExperimentManager:
|
||||||
for class_name, class_desc in description.items():
|
for class_name, class_desc in description.items():
|
||||||
expurl = "file:{}@{}".format(class_name, file)
|
expurl = "file:{}@{}".format(class_name, file)
|
||||||
self.initialize_submission_arguments(expurl, class_desc["arginfo"],
|
self.initialize_submission_arguments(expurl, class_desc["arginfo"],
|
||||||
class_desc.get("argument_ui", None))
|
class_desc.get("argument_ui", None))
|
||||||
if expurl in self.open_experiments:
|
if expurl in self.open_experiments:
|
||||||
self.open_experiments[expurl].close()
|
self.open_experiments[expurl].close()
|
||||||
self.open_experiment(expurl)
|
self.open_experiment(expurl)
|
||||||
|
@ -853,6 +727,7 @@ class ExperimentManager:
|
||||||
|
|
||||||
self.is_quick_open_shown = True
|
self.is_quick_open_shown = True
|
||||||
dialog = _QuickOpenDialog(self)
|
dialog = _QuickOpenDialog(self)
|
||||||
|
|
||||||
def closed():
|
def closed():
|
||||||
self.is_quick_open_shown = False
|
self.is_quick_open_shown = False
|
||||||
dialog.closed.connect(closed)
|
dialog.closed.connect(closed)
|
||||||
|
|
|
@ -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()
|
||||||
|
@ -94,7 +95,7 @@ class _OpenFileDialog(QtWidgets.QDialog):
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
self.explorer.current_directory = \
|
self.explorer.current_directory = \
|
||||||
self.explorer.current_directory[:idx+1]
|
self.explorer.current_directory[:idx + 1]
|
||||||
if self.explorer.current_directory == "/":
|
if self.explorer.current_directory == "/":
|
||||||
self.explorer.current_directory = ""
|
self.explorer.current_directory = ""
|
||||||
asyncio.ensure_future(self.refresh_view())
|
asyncio.ensure_future(self.refresh_view())
|
||||||
|
@ -103,6 +104,7 @@ class _OpenFileDialog(QtWidgets.QDialog):
|
||||||
asyncio.ensure_future(self.refresh_view())
|
asyncio.ensure_future(self.refresh_view())
|
||||||
else:
|
else:
|
||||||
file = self.explorer.current_directory + selected
|
file = self.explorer.current_directory + selected
|
||||||
|
|
||||||
async def open_task():
|
async def open_task():
|
||||||
try:
|
try:
|
||||||
await self.exp_manager.open_file(file)
|
await self.exp_manager.open_file(file)
|
||||||
|
@ -162,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)
|
||||||
|
@ -174,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()
|
||||||
|
@ -186,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(
|
||||||
|
@ -201,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(
|
||||||
|
@ -210,55 +212,56 @@ 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():
|
||||||
asyncio.ensure_future(experiment_db_ctl.scan_repository_async())
|
asyncio.ensure_future(experiment_db_ctl.scan_repository_async())
|
||||||
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,
|
||||||
|
@ -292,7 +295,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
||||||
if expname is not None:
|
if expname is not None:
|
||||||
expurl = "repo:" + expname
|
expurl = "repo:" + expname
|
||||||
self.d_shortcuts.set_shortcut(nr, expurl)
|
self.d_shortcuts.set_shortcut(nr, expurl)
|
||||||
logger.info("Set shortcut F%d to '%s'", nr+1, expurl)
|
logger.info("Set shortcut F%d to '%s'", nr + 1, expurl)
|
||||||
|
|
||||||
def update_scanning(self, scanning):
|
def update_scanning(self, scanning):
|
||||||
if scanning:
|
if scanning:
|
||||||
|
|
|
@ -0,0 +1,158 @@
|
||||||
|
import logging
|
||||||
|
import asyncio
|
||||||
|
|
||||||
|
from PyQt6 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
|
from artiq.gui.models import DictSyncModel
|
||||||
|
from artiq.gui.entries import EntryTreeWidget, procdesc_to_entry
|
||||||
|
from artiq.gui.tools import LayoutWidget
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Model(DictSyncModel):
|
||||||
|
def __init__(self, init):
|
||||||
|
DictSyncModel.__init__(self, ["RID", "Title", "Args"], init)
|
||||||
|
|
||||||
|
def convert(self, k, v, column):
|
||||||
|
if column == 0:
|
||||||
|
return k
|
||||||
|
elif column == 1:
|
||||||
|
txt = ": " + v["title"] if v["title"] != "" else ""
|
||||||
|
return str(k) + txt
|
||||||
|
elif column == 2:
|
||||||
|
return v["arglist_desc"]
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
def sort_key(self, k, v):
|
||||||
|
return k
|
||||||
|
|
||||||
|
|
||||||
|
class _InteractiveArgsRequest(EntryTreeWidget):
|
||||||
|
supplied = QtCore.pyqtSignal(int, dict)
|
||||||
|
cancelled = QtCore.pyqtSignal(int)
|
||||||
|
|
||||||
|
def __init__(self, rid, arglist_desc):
|
||||||
|
EntryTreeWidget.__init__(self)
|
||||||
|
self.rid = rid
|
||||||
|
self.arguments = dict()
|
||||||
|
for key, procdesc, group, tooltip in arglist_desc:
|
||||||
|
self.arguments[key] = {"desc": procdesc, "group": group, "tooltip": tooltip}
|
||||||
|
self.set_argument(key, self.arguments[key])
|
||||||
|
self.quickStyleClicked.connect(self.supply)
|
||||||
|
cancel_btn = QtWidgets.QPushButton("Cancel")
|
||||||
|
cancel_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton))
|
||||||
|
cancel_btn.clicked.connect(self.cancel)
|
||||||
|
supply_btn = QtWidgets.QPushButton("Supply")
|
||||||
|
supply_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
|
||||||
|
supply_btn.clicked.connect(self.supply)
|
||||||
|
buttons = LayoutWidget()
|
||||||
|
buttons.addWidget(cancel_btn, 1, 1)
|
||||||
|
buttons.addWidget(supply_btn, 1, 2)
|
||||||
|
buttons.layout.setColumnStretch(0, 1)
|
||||||
|
buttons.layout.setColumnStretch(1, 0)
|
||||||
|
buttons.layout.setColumnStretch(2, 0)
|
||||||
|
buttons.layout.setColumnStretch(3, 1)
|
||||||
|
self.setItemWidget(self.bottom_item, 1, buttons)
|
||||||
|
|
||||||
|
def supply(self):
|
||||||
|
argument_values = dict()
|
||||||
|
for key, argument in self.arguments.items():
|
||||||
|
entry_cls = procdesc_to_entry(argument["desc"])
|
||||||
|
argument_values[key] = entry_cls.state_to_value(argument["state"])
|
||||||
|
self.supplied.emit(self.rid, argument_values)
|
||||||
|
|
||||||
|
def cancel(self):
|
||||||
|
self.cancelled.emit(self.rid)
|
||||||
|
|
||||||
|
|
||||||
|
class _InteractiveArgsView(QtWidgets.QStackedWidget):
|
||||||
|
supplied = QtCore.pyqtSignal(int, dict)
|
||||||
|
cancelled = QtCore.pyqtSignal(int)
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
QtWidgets.QStackedWidget.__init__(self)
|
||||||
|
self.tabs = QtWidgets.QTabWidget()
|
||||||
|
self.default_label = QtWidgets.QLabel("No pending interactive arguments requests.")
|
||||||
|
self.default_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
||||||
|
font = QtGui.QFont(self.default_label.font())
|
||||||
|
font.setItalic(True)
|
||||||
|
self.default_label.setFont(font)
|
||||||
|
self.addWidget(self.tabs)
|
||||||
|
self.addWidget(self.default_label)
|
||||||
|
self.model = Model({})
|
||||||
|
|
||||||
|
def setModel(self, model):
|
||||||
|
self.setCurrentIndex(1)
|
||||||
|
for i in range(self.tabs.count()):
|
||||||
|
widget = self.tabs.widget(i)
|
||||||
|
self.tabs.removeTab(i)
|
||||||
|
widget.deleteLater()
|
||||||
|
self.model = model
|
||||||
|
self.model.rowsInserted.connect(self.rowsInserted)
|
||||||
|
self.model.rowsRemoved.connect(self.rowsRemoved)
|
||||||
|
for i in range(self.model.rowCount(QtCore.QModelIndex())):
|
||||||
|
self._insert_widget(i)
|
||||||
|
|
||||||
|
def _insert_widget(self, row):
|
||||||
|
rid = self.model.data(self.model.index(row, 0),
|
||||||
|
QtCore.Qt.ItemDataRole.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.supplied.connect(self.supplied)
|
||||||
|
inter_args_request.cancelled.connect(self.cancelled)
|
||||||
|
self.tabs.insertTab(row, inter_args_request, title)
|
||||||
|
|
||||||
|
def rowsInserted(self, parent, first, last):
|
||||||
|
assert first == last
|
||||||
|
self.setCurrentIndex(0)
|
||||||
|
self._insert_widget(first)
|
||||||
|
|
||||||
|
def rowsRemoved(self, parent, first, last):
|
||||||
|
assert first == last
|
||||||
|
widget = self.tabs.widget(first)
|
||||||
|
self.tabs.removeTab(first)
|
||||||
|
widget.deleteLater()
|
||||||
|
if self.tabs.count() == 0:
|
||||||
|
self.setCurrentIndex(1)
|
||||||
|
|
||||||
|
|
||||||
|
class InteractiveArgsDock(QtWidgets.QDockWidget):
|
||||||
|
def __init__(self, interactive_args_sub, interactive_args_rpc):
|
||||||
|
QtWidgets.QDockWidget.__init__(self, "Interactive Args")
|
||||||
|
self.setObjectName("Interactive Args")
|
||||||
|
self.setFeatures(
|
||||||
|
self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable)
|
||||||
|
self.interactive_args_rpc = interactive_args_rpc
|
||||||
|
self.request_view = _InteractiveArgsView()
|
||||||
|
self.request_view.supplied.connect(self.supply)
|
||||||
|
self.request_view.cancelled.connect(self.cancel)
|
||||||
|
self.setWidget(self.request_view)
|
||||||
|
interactive_args_sub.add_setmodel_callback(self.request_view.setModel)
|
||||||
|
|
||||||
|
def supply(self, rid, values):
|
||||||
|
asyncio.ensure_future(self._supply_task(rid, values))
|
||||||
|
|
||||||
|
async def _supply_task(self, rid, values):
|
||||||
|
try:
|
||||||
|
await self.interactive_args_rpc.supply(rid, values)
|
||||||
|
except Exception:
|
||||||
|
logger.error("failed to supply interactive arguments for experiment: %d",
|
||||||
|
rid, exc_info=True)
|
||||||
|
|
||||||
|
def cancel(self, rid):
|
||||||
|
asyncio.ensure_future(self._cancel_task(rid))
|
||||||
|
|
||||||
|
async def _cancel_task(self, rid):
|
||||||
|
try:
|
||||||
|
await self.interactive_args_rpc.cancel(rid)
|
||||||
|
except Exception:
|
||||||
|
logger.error("failed to cancel interactive args request for experiment: %d",
|
||||||
|
rid, exc_info=True)
|
|
@ -2,61 +2,66 @@ import asyncio
|
||||||
import logging
|
import logging
|
||||||
import textwrap
|
import textwrap
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt6 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
from sipyco.sync_struct import Subscriber
|
from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe
|
||||||
|
from artiq.coredevice.ad9912_reg import AD9912_SER_CONF
|
||||||
from artiq.coredevice.comm_moninj import *
|
from artiq.gui.tools import LayoutWidget, QDockWidgetCloseDetect, DoubleClickLineEdit
|
||||||
from artiq.coredevice.ad9910 import (
|
from artiq.gui.dndwidgets import VDragScrollArea, DragDropFlowLayoutWidget
|
||||||
_AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7,
|
from artiq.gui.models import DictSyncTreeSepModel
|
||||||
_AD9910_REG_FTW, _AD9910_REG_CFR1
|
from artiq.tools import elide
|
||||||
)
|
|
||||||
from artiq.coredevice.ad9912_reg import AD9912_POW1, AD9912_SER_CONF
|
|
||||||
from artiq.gui.tools import LayoutWidget
|
|
||||||
from artiq.gui.flowlayout import FlowLayout
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class _CancellableLineEdit(QtWidgets.QLineEdit):
|
class _CancellableLineEdit(QtWidgets.QLineEdit):
|
||||||
def escapePressedConnect(self, cb):
|
def escapePressedConnect(self, cb):
|
||||||
self.esc_cb = cb
|
self.esc_cb = cb
|
||||||
|
|
||||||
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.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()
|
||||||
|
@ -76,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)
|
||||||
|
@ -133,7 +138,7 @@ class _TTLWidget(QtWidgets.QFrame):
|
||||||
else:
|
else:
|
||||||
color = ""
|
color = ""
|
||||||
self.value.setText("<font size=\"5\"{}>{}</font>".format(
|
self.value.setText("<font size=\"5\"{}>{}</font>".format(
|
||||||
color, value_s))
|
color, value_s))
|
||||||
oe = self.cur_oe or self.force_out
|
oe = self.cur_oe or self.force_out
|
||||||
direction = "OUT" if oe else "IN"
|
direction = "OUT" if oe else "IN"
|
||||||
self.direction.setText("<font size=\"2\">" + direction + "</font>")
|
self.direction.setText("<font size=\"2\">" + direction + "</font>")
|
||||||
|
@ -148,40 +153,13 @@ class _TTLWidget(QtWidgets.QFrame):
|
||||||
self.programmatic_change = False
|
self.programmatic_change = False
|
||||||
|
|
||||||
def sort_key(self):
|
def sort_key(self):
|
||||||
return self.channel
|
return (0, self.channel, 0)
|
||||||
|
|
||||||
|
def uid(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
class _SimpleDisplayWidget(QtWidgets.QFrame):
|
def to_model_path(self):
|
||||||
def __init__(self, title):
|
return "ttl/{}".format(self.title)
|
||||||
QtWidgets.QFrame.__init__(self)
|
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
self.value = QtWidgets.QLabel()
|
|
||||||
self.value.setAlignment(QtCore.Qt.AlignCenter)
|
|
||||||
grid.addWidget(self.value, 2, 1, 6, 1)
|
|
||||||
|
|
||||||
grid.setRowStretch(1, 1)
|
|
||||||
grid.setRowStretch(2, 0)
|
|
||||||
grid.setRowStretch(3, 1)
|
|
||||||
|
|
||||||
self.refresh_display()
|
|
||||||
|
|
||||||
def refresh_display(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
def sort_key(self):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
|
|
||||||
class _DDSModel:
|
class _DDSModel:
|
||||||
|
@ -215,28 +193,18 @@ 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=0, channel=0, dds_model=None):
|
def __init__(self, dm, title, bus_channel, channel,
|
||||||
|
dds_type, ref_clk, cpld=None, pll=1, clk_div=0):
|
||||||
self.dm = dm
|
self.dm = dm
|
||||||
self.bus_channel = bus_channel
|
self.bus_channel = bus_channel
|
||||||
self.channel = channel
|
self.channel = channel
|
||||||
self.dds_name = title
|
self.dds_name = title
|
||||||
self.cur_frequency = 0
|
self.cur_frequency = 0
|
||||||
self.dds_model = dds_model
|
self.dds_model = _DDSModel(dds_type, ref_clk, cpld, pll, clk_div)
|
||||||
|
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()
|
||||||
|
@ -248,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)
|
||||||
|
@ -264,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()
|
||||||
|
@ -304,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)
|
||||||
|
@ -327,8 +295,7 @@ class _DDSWidget(QtWidgets.QFrame):
|
||||||
def set_clicked(self, set):
|
def set_clicked(self, set):
|
||||||
self.data_stack.setCurrentIndex(1)
|
self.data_stack.setCurrentIndex(1)
|
||||||
self.button_stack.setCurrentIndex(1)
|
self.button_stack.setCurrentIndex(1)
|
||||||
self.value_edit.setText("{:.7f}"
|
self.value_edit.setText("{:.7f}".format(self.cur_frequency / 1e6))
|
||||||
.format(self.cur_frequency/1e6))
|
|
||||||
self.value_edit.setFocus()
|
self.value_edit.setFocus()
|
||||||
self.value_edit.selectAll()
|
self.value_edit.selectAll()
|
||||||
|
|
||||||
|
@ -338,7 +305,7 @@ class _DDSWidget(QtWidgets.QFrame):
|
||||||
def apply_changes(self, apply):
|
def apply_changes(self, apply):
|
||||||
self.data_stack.setCurrentIndex(0)
|
self.data_stack.setCurrentIndex(0)
|
||||||
self.button_stack.setCurrentIndex(0)
|
self.button_stack.setCurrentIndex(0)
|
||||||
frequency = float(self.value_edit.text())*1e6
|
frequency = float(self.value_edit.text()) * 1e6
|
||||||
self.dm.dds_set_frequency(self.dds_name, self.dds_model, frequency)
|
self.dm.dds_set_frequency(self.dds_name, self.dds_model, frequency)
|
||||||
|
|
||||||
def cancel_changes(self, cancel):
|
def cancel_changes(self, cancel):
|
||||||
|
@ -347,28 +314,53 @@ class _DDSWidget(QtWidgets.QFrame):
|
||||||
|
|
||||||
def refresh_display(self):
|
def refresh_display(self):
|
||||||
self.cur_frequency = self.dds_model.cur_frequency
|
self.cur_frequency = self.dds_model.cur_frequency
|
||||||
self.value_label.setText("<font size=\"4\">{:.7f}</font>"
|
self.value_label.setText("<font size=\"4\">{:.7f}</font>".format(self.cur_frequency / 1e6))
|
||||||
.format(self.cur_frequency/1e6))
|
self.value_edit.setText("{:.7f}".format(self.cur_frequency / 1e6))
|
||||||
self.value_edit.setText("{:.7f}"
|
|
||||||
.format(self.cur_frequency/1e6))
|
|
||||||
|
|
||||||
def sort_key(self):
|
def sort_key(self):
|
||||||
return (self.bus_channel, self.channel)
|
return (1, self.bus_channel, self.channel)
|
||||||
|
|
||||||
|
def uid(self):
|
||||||
|
return self.title
|
||||||
|
|
||||||
|
def to_model_path(self):
|
||||||
|
return "dds/{}".format(self.title)
|
||||||
|
|
||||||
|
|
||||||
class _DACWidget(_SimpleDisplayWidget):
|
class _DACWidget(_MoninjWidget):
|
||||||
def __init__(self, dm, spi_channel, channel, title):
|
def __init__(self, dm, spi_channel, channel, title, vref, offset_dacs):
|
||||||
|
_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
|
||||||
_SimpleDisplayWidget.__init__(self, "{} ch{}".format(title, channel))
|
self.title = title
|
||||||
|
self.vref = vref
|
||||||
|
self.offset_dacs = offset_dacs
|
||||||
|
|
||||||
|
self.value = QtWidgets.QLabel()
|
||||||
|
self.value.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignTop)
|
||||||
|
self.grid.addWidget(self.value, 2, 1, 6, 1)
|
||||||
|
|
||||||
|
self.grid.setRowStretch(1, 1)
|
||||||
|
self.grid.setRowStretch(2, 1)
|
||||||
|
|
||||||
|
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 (self.spi_channel, self.channel)
|
return (2, self.spi_channel, self.channel)
|
||||||
|
|
||||||
|
def uid(self):
|
||||||
|
return (self.title, self.channel)
|
||||||
|
|
||||||
|
def to_model_path(self):
|
||||||
|
return "dac/{}_ch{}".format(self.title, self.channel)
|
||||||
|
|
||||||
|
|
||||||
_WidgetDesc = namedtuple("_WidgetDesc", "uid comment cls arguments")
|
_WidgetDesc = namedtuple("_WidgetDesc", "uid comment cls arguments")
|
||||||
|
@ -386,24 +378,19 @@ def setup_from_ddb(ddb):
|
||||||
comment = v.get("comment")
|
comment = v.get("comment")
|
||||||
if v["type"] == "local":
|
if v["type"] == "local":
|
||||||
if v["module"] == "artiq.coredevice.ttl":
|
if v["module"] == "artiq.coredevice.ttl":
|
||||||
if "ttl_urukul" in k:
|
|
||||||
continue
|
|
||||||
channel = v["arguments"]["channel"]
|
channel = v["arguments"]["channel"]
|
||||||
force_out = v["class"] == "TTLOut"
|
force_out = v["class"] == "TTLOut"
|
||||||
widget = _WidgetDesc(k, comment, _TTLWidget, (channel, force_out, k))
|
widget = _WidgetDesc(k, comment, _TTLWidget, (channel, force_out, k))
|
||||||
description.add(widget)
|
description.add(widget)
|
||||||
elif (v["module"] == "artiq.coredevice.ad9914"
|
elif (v["module"] == "artiq.coredevice.ad9914" and v["class"] == "AD9914"):
|
||||||
and v["class"] == "AD9914"):
|
|
||||||
bus_channel = v["arguments"]["bus_channel"]
|
bus_channel = v["arguments"]["bus_channel"]
|
||||||
channel = v["arguments"]["channel"]
|
channel = v["arguments"]["channel"]
|
||||||
dds_sysclk = v["arguments"]["sysclk"]
|
dds_sysclk = v["arguments"]["sysclk"]
|
||||||
model = _DDSModel(v["class"], dds_sysclk)
|
widget = _WidgetDesc(k, comment, _DDSWidget,
|
||||||
widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model))
|
(k, bus_channel, channel, v["class"], dds_sysclk))
|
||||||
description.add(widget)
|
description.add(widget)
|
||||||
elif (v["module"] == "artiq.coredevice.ad9910"
|
elif (v["module"] == "artiq.coredevice.ad9910" and v["class"] == "AD9910") or \
|
||||||
and v["class"] == "AD9910") or \
|
(v["module"] == "artiq.coredevice.ad9912" and v["class"] == "AD9912"):
|
||||||
(v["module"] == "artiq.coredevice.ad9912"
|
|
||||||
and v["class"] == "AD9912"):
|
|
||||||
channel = v["arguments"]["chip_select"] - 4
|
channel = v["arguments"]["chip_select"] - 4
|
||||||
if channel < 0:
|
if channel < 0:
|
||||||
continue
|
continue
|
||||||
|
@ -413,18 +400,28 @@ def setup_from_ddb(ddb):
|
||||||
pll = v["arguments"]["pll_n"]
|
pll = v["arguments"]["pll_n"]
|
||||||
refclk = ddb[dds_cpld]["arguments"]["refclk"]
|
refclk = ddb[dds_cpld]["arguments"]["refclk"]
|
||||||
clk_div = v["arguments"].get("clk_div", 0)
|
clk_div = v["arguments"].get("clk_div", 0)
|
||||||
model = _DDSModel( v["class"], refclk, dds_cpld, pll, clk_div)
|
widget = _WidgetDesc(k, comment, _DDSWidget,
|
||||||
widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model))
|
(k, bus_channel, channel, v["class"],
|
||||||
|
refclk, dds_cpld, pll, clk_div))
|
||||||
description.add(widget)
|
description.add(widget)
|
||||||
elif ( (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx")
|
elif (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx") or \
|
||||||
or (v["module"] == "artiq.coredevice.zotino" and v["class"] == "Zotino")):
|
(v["module"] == "artiq.coredevice.zotino" and v["class"] == "Zotino"):
|
||||||
spi_device = v["arguments"]["spi_device"]
|
spi_device = v["arguments"]["spi_device"]
|
||||||
spi_device = ddb[spi_device]
|
spi_device = ddb[spi_device]
|
||||||
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, (spi_channel, channel, k))
|
widget = _WidgetDesc((k, channel), comment, _DACWidget,
|
||||||
|
(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"]
|
||||||
|
@ -449,18 +446,15 @@ class _DeviceManager:
|
||||||
self.widgets_by_uid = dict()
|
self.widgets_by_uid = dict()
|
||||||
|
|
||||||
self.dds_sysclk = 0
|
self.dds_sysclk = 0
|
||||||
self.ttl_cb = lambda: None
|
|
||||||
self.ttl_widgets = dict()
|
self.ttl_widgets = dict()
|
||||||
self.dds_cb = lambda: None
|
|
||||||
self.dds_widgets = dict()
|
self.dds_widgets = dict()
|
||||||
self.dac_cb = lambda: None
|
|
||||||
self.dac_widgets = dict()
|
self.dac_widgets = dict()
|
||||||
|
self.channels_cb = lambda: None
|
||||||
|
|
||||||
def init_ddb(self, ddb):
|
def init_ddb(self, ddb):
|
||||||
self.ddb = ddb
|
self.ddb = ddb
|
||||||
return ddb
|
|
||||||
|
|
||||||
def notify(self, mod):
|
def notify_ddb(self, mod):
|
||||||
mi_addr, mi_port, description = setup_from_ddb(self.ddb)
|
mi_addr, mi_port, description = setup_from_ddb(self.ddb)
|
||||||
|
|
||||||
if (mi_addr, mi_port) != (self.mi_addr, self.mi_port):
|
if (mi_addr, mi_port) != (self.mi_addr, self.mi_port):
|
||||||
|
@ -471,24 +465,8 @@ class _DeviceManager:
|
||||||
for to_remove in self.description - description:
|
for to_remove in self.description - description:
|
||||||
widget = self.widgets_by_uid[to_remove.uid]
|
widget = self.widgets_by_uid[to_remove.uid]
|
||||||
del self.widgets_by_uid[to_remove.uid]
|
del self.widgets_by_uid[to_remove.uid]
|
||||||
|
self.setup_monitoring(False, widget)
|
||||||
if isinstance(widget, _TTLWidget):
|
widget.deleteLater()
|
||||||
self.setup_ttl_monitoring(False, widget.channel)
|
|
||||||
widget.deleteLater()
|
|
||||||
del self.ttl_widgets[widget.channel]
|
|
||||||
self.ttl_cb()
|
|
||||||
elif isinstance(widget, _DDSWidget):
|
|
||||||
self.setup_dds_monitoring(False, widget.bus_channel, widget.channel)
|
|
||||||
widget.deleteLater()
|
|
||||||
del self.dds_widgets[(widget.bus_channel, widget.channel)]
|
|
||||||
self.dds_cb()
|
|
||||||
elif isinstance(widget, _DACWidget):
|
|
||||||
self.setup_dac_monitoring(False, widget.spi_channel, widget.channel)
|
|
||||||
widget.deleteLater()
|
|
||||||
del self.dac_widgets[(widget.spi_channel, widget.channel)]
|
|
||||||
self.dac_cb()
|
|
||||||
else:
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
for to_add in description - self.description:
|
for to_add in description - self.description:
|
||||||
widget = to_add.cls(self, *to_add.arguments)
|
widget = to_add.cls(self, *to_add.arguments)
|
||||||
|
@ -496,26 +474,15 @@ class _DeviceManager:
|
||||||
widget.setToolTip(to_add.comment)
|
widget.setToolTip(to_add.comment)
|
||||||
self.widgets_by_uid[to_add.uid] = widget
|
self.widgets_by_uid[to_add.uid] = widget
|
||||||
|
|
||||||
if isinstance(widget, _TTLWidget):
|
if description != self.description:
|
||||||
self.ttl_widgets[widget.channel] = widget
|
self.channels_cb()
|
||||||
self.ttl_cb()
|
|
||||||
self.setup_ttl_monitoring(True, widget.channel)
|
|
||||||
elif isinstance(widget, _DDSWidget):
|
|
||||||
self.dds_widgets[(widget.bus_channel, widget.channel)] = widget
|
|
||||||
self.dds_cb()
|
|
||||||
self.setup_dds_monitoring(True, widget.bus_channel, widget.channel)
|
|
||||||
elif isinstance(widget, _DACWidget):
|
|
||||||
self.dac_widgets[(widget.spi_channel, widget.channel)] = widget
|
|
||||||
self.dac_cb()
|
|
||||||
self.setup_dac_monitoring(True, widget.spi_channel, widget.channel)
|
|
||||||
else:
|
|
||||||
raise ValueError
|
|
||||||
|
|
||||||
self.description = description
|
self.description = description
|
||||||
|
|
||||||
def ttl_set_mode(self, channel, mode):
|
def ttl_set_mode(self, channel, mode):
|
||||||
if self.mi_connection is not None:
|
if self.mi_connection is not None:
|
||||||
widget = self.ttl_widgets[channel]
|
widget_uid = self.ttl_widgets[channel]
|
||||||
|
widget = self.widgets_by_uid[widget_uid]
|
||||||
if mode == "0":
|
if mode == "0":
|
||||||
widget.cur_override = True
|
widget.cur_override = True
|
||||||
widget.cur_level = False
|
widget.cur_level = False
|
||||||
|
@ -641,7 +608,7 @@ class _DeviceManager:
|
||||||
dds_model,
|
dds_model,
|
||||||
action,
|
action,
|
||||||
"SetDDS",
|
"SetDDS",
|
||||||
"Set DDS {} {}MHz".format(dds_channel, freq/1e6))
|
"Set DDS {} {}MHz".format(dds_channel, freq / 1e6))
|
||||||
|
|
||||||
def dds_channel_toggle(self, dds_channel, dds_model, sw=True):
|
def dds_channel_toggle(self, dds_channel, dds_model, sw=True):
|
||||||
# urukul only
|
# urukul only
|
||||||
|
@ -664,6 +631,31 @@ class _DeviceManager:
|
||||||
"ToggleDDS",
|
"ToggleDDS",
|
||||||
"Toggle DDS {} {}".format(dds_channel, "on" if sw else "off"))
|
"Toggle DDS {} {}".format(dds_channel, "on" if sw else "off"))
|
||||||
|
|
||||||
|
def setup_monitoring(self, enable, widget):
|
||||||
|
if isinstance(widget, _TTLWidget):
|
||||||
|
key = widget.channel
|
||||||
|
args = (key,)
|
||||||
|
subscribers = self.ttl_widgets
|
||||||
|
subscribe_func = self.setup_ttl_monitoring
|
||||||
|
elif isinstance(widget, _DDSWidget):
|
||||||
|
key = (widget.bus_channel, widget.channel)
|
||||||
|
args = key
|
||||||
|
subscribers = self.dds_widgets
|
||||||
|
subscribe_func = self.setup_dds_monitoring
|
||||||
|
elif isinstance(widget, _DACWidget):
|
||||||
|
key = (widget.spi_channel, widget.channel)
|
||||||
|
args = key
|
||||||
|
subscribers = self.dac_widgets
|
||||||
|
subscribe_func = self.setup_dac_monitoring
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
if enable and key not in subscribers:
|
||||||
|
subscribers[key] = widget.uid()
|
||||||
|
subscribe_func(enable, *args)
|
||||||
|
elif not enable and key in subscribers:
|
||||||
|
subscribe_func(enable, *args)
|
||||||
|
del subscribers[key]
|
||||||
|
|
||||||
def setup_ttl_monitoring(self, enable, channel):
|
def setup_ttl_monitoring(self, enable, channel):
|
||||||
if self.mi_connection is not None:
|
if self.mi_connection is not None:
|
||||||
self.mi_connection.monitor_probe(enable, channel, TTLProbe.level.value)
|
self.mi_connection.monitor_probe(enable, channel, TTLProbe.level.value)
|
||||||
|
@ -683,24 +675,28 @@ class _DeviceManager:
|
||||||
|
|
||||||
def monitor_cb(self, channel, probe, value):
|
def monitor_cb(self, channel, probe, value):
|
||||||
if channel in self.ttl_widgets:
|
if channel in self.ttl_widgets:
|
||||||
widget = self.ttl_widgets[channel]
|
widget_uid = self.ttl_widgets[channel]
|
||||||
|
widget = self.widgets_by_uid[widget_uid]
|
||||||
if probe == TTLProbe.level.value:
|
if probe == TTLProbe.level.value:
|
||||||
widget.cur_level = bool(value)
|
widget.cur_level = bool(value)
|
||||||
elif probe == TTLProbe.oe.value:
|
elif probe == TTLProbe.oe.value:
|
||||||
widget.cur_oe = bool(value)
|
widget.cur_oe = bool(value)
|
||||||
widget.refresh_display()
|
widget.refresh_display()
|
||||||
elif (channel, probe) in self.dds_widgets:
|
elif (channel, probe) in self.dds_widgets:
|
||||||
widget = self.dds_widgets[(channel, probe)]
|
widget_uid = self.dds_widgets[(channel, probe)]
|
||||||
|
widget = self.widgets_by_uid[widget_uid]
|
||||||
widget.dds_model.monitor_update(probe, value)
|
widget.dds_model.monitor_update(probe, value)
|
||||||
widget.refresh_display()
|
widget.refresh_display()
|
||||||
elif (channel, probe) in self.dac_widgets:
|
elif (channel, probe) in self.dac_widgets:
|
||||||
widget = self.dac_widgets[(channel, probe)]
|
widget_uid = self.dac_widgets[(channel, probe)]
|
||||||
|
widget = self.widgets_by_uid[widget_uid]
|
||||||
widget.cur_value = value
|
widget.cur_value = value
|
||||||
widget.refresh_display()
|
widget.refresh_display()
|
||||||
|
|
||||||
def injection_status_cb(self, channel, override, value):
|
def injection_status_cb(self, channel, override, value):
|
||||||
if channel in self.ttl_widgets:
|
if channel in self.ttl_widgets:
|
||||||
widget = self.ttl_widgets[channel]
|
widget_uid = self.ttl_widgets[channel]
|
||||||
|
widget = self.widgets_by_uid[widget_uid]
|
||||||
if override == TTLOverride.en.value:
|
if override == TTLOverride.en.value:
|
||||||
widget.cur_override = bool(value)
|
widget.cur_override = bool(value)
|
||||||
if override == TTLOverride.level.value:
|
if override == TTLOverride.level.value:
|
||||||
|
@ -719,14 +715,12 @@ class _DeviceManager:
|
||||||
await self.mi_connection.close()
|
await self.mi_connection.close()
|
||||||
self.mi_connection = None
|
self.mi_connection = None
|
||||||
new_mi_connection = CommMonInj(self.monitor_cb, self.injection_status_cb,
|
new_mi_connection = CommMonInj(self.monitor_cb, self.injection_status_cb,
|
||||||
self.disconnect_cb)
|
self.disconnect_cb)
|
||||||
try:
|
try:
|
||||||
await new_mi_connection.connect(self.mi_addr, self.mi_port)
|
await new_mi_connection.connect(self.mi_addr, self.mi_port)
|
||||||
except asyncio.CancelledError:
|
except Exception:
|
||||||
logger.info("cancelled connection to moninj")
|
logger.error("failed to connect to moninj. Is aqctl_moninj_proxy running?",
|
||||||
break
|
exc_info=True)
|
||||||
except:
|
|
||||||
logger.error("failed to connect to moninj. Is aqctl_moninj_proxy running?", exc_info=True)
|
|
||||||
await asyncio.sleep(10.)
|
await asyncio.sleep(10.)
|
||||||
self.reconnect_mi.set()
|
self.reconnect_mi.set()
|
||||||
else:
|
else:
|
||||||
|
@ -736,9 +730,9 @@ class _DeviceManager:
|
||||||
for ttl_channel in self.ttl_widgets.keys():
|
for ttl_channel in self.ttl_widgets.keys():
|
||||||
self.setup_ttl_monitoring(True, ttl_channel)
|
self.setup_ttl_monitoring(True, ttl_channel)
|
||||||
for bus_channel, channel in self.dds_widgets.keys():
|
for bus_channel, channel in self.dds_widgets.keys():
|
||||||
self.setup_dds_monitoring(True, bus_channel, channel)
|
self.setup_dds_monitoring(True, bus_channel, channel)
|
||||||
for spi_channel, channel in self.dac_widgets.keys():
|
for spi_channel, channel in self.dac_widgets.keys():
|
||||||
self.setup_dac_monitoring(True, spi_channel, channel)
|
self.setup_dac_monitoring(True, spi_channel, channel)
|
||||||
|
|
||||||
async def close(self):
|
async def close(self):
|
||||||
self.mi_connector_task.cancel()
|
self.mi_connector_task.cancel()
|
||||||
|
@ -750,48 +744,237 @@ class _DeviceManager:
|
||||||
await self.mi_connection.close()
|
await self.mi_connection.close()
|
||||||
|
|
||||||
|
|
||||||
class _MonInjDock(QtWidgets.QDockWidget):
|
class Model(DictSyncTreeSepModel):
|
||||||
def __init__(self, name):
|
def __init__(self, init):
|
||||||
QtWidgets.QDockWidget.__init__(self, name)
|
DictSyncTreeSepModel.__init__(self, "/", ["Channels"], init)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
for k in self.backing_store:
|
||||||
|
self._del_item(self, k.split(self.separator))
|
||||||
|
self.backing_store.clear()
|
||||||
|
|
||||||
|
def update(self, d):
|
||||||
|
for k, v in d.items():
|
||||||
|
self[v.to_model_path()] = v
|
||||||
|
|
||||||
|
|
||||||
|
class _AddChannelDialog(QtWidgets.QDialog):
|
||||||
|
def __init__(self, parent, model):
|
||||||
|
QtWidgets.QDialog.__init__(self, parent=parent)
|
||||||
|
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||||
|
self.setWindowTitle("Add channels")
|
||||||
|
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
self._model = model
|
||||||
|
self._tree_view = QtWidgets.QTreeView()
|
||||||
|
self._tree_view.setHeaderHidden(True)
|
||||||
|
self._tree_view.setSelectionBehavior(
|
||||||
|
QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems)
|
||||||
|
self._tree_view.setSelectionMode(
|
||||||
|
QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
|
self._tree_view.setModel(self._model)
|
||||||
|
layout.addWidget(self._tree_view)
|
||||||
|
|
||||||
|
self._button_box = QtWidgets.QDialogButtonBox(
|
||||||
|
QtWidgets.QDialogButtonBox.StandardButton.Ok | \
|
||||||
|
QtWidgets.QDialogButtonBox.StandardButton.Cancel
|
||||||
|
)
|
||||||
|
self._button_box.setCenterButtons(True)
|
||||||
|
self._button_box.accepted.connect(self.add_channels)
|
||||||
|
self._button_box.rejected.connect(self.reject)
|
||||||
|
layout.addWidget(self._button_box)
|
||||||
|
|
||||||
|
def add_channels(self):
|
||||||
|
selection = self._tree_view.selectedIndexes()
|
||||||
|
channels = []
|
||||||
|
for select in selection:
|
||||||
|
key = self._model.index_to_key(select)
|
||||||
|
if key is not None:
|
||||||
|
channels.append(self._model[key].ref)
|
||||||
|
self.channels = channels
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
|
||||||
|
class _MonInjDock(QDockWidgetCloseDetect):
|
||||||
|
def __init__(self, name, manager):
|
||||||
|
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()
|
||||||
|
self.setWidget(grid)
|
||||||
|
self.manager = manager
|
||||||
|
self.widget_uids = None
|
||||||
|
|
||||||
|
newdock = QtWidgets.QToolButton()
|
||||||
|
newdock.setToolTip("Create new moninj dock")
|
||||||
|
newdock.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.StandardPixmap.SP_FileDialogNewFolder))
|
||||||
|
newdock.clicked.connect(lambda: self.manager.create_new_dock())
|
||||||
|
grid.addWidget(newdock, 0, 0)
|
||||||
|
|
||||||
|
self.channel_dialog = _AddChannelDialog(self, self.manager.channel_model)
|
||||||
|
self.channel_dialog.accepted.connect(self.add_channels)
|
||||||
|
|
||||||
|
dialog_btn = QtWidgets.QToolButton()
|
||||||
|
dialog_btn.setToolTip("Add channels")
|
||||||
|
dialog_btn.setIcon(
|
||||||
|
QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.StandardPixmap.SP_FileDialogListView))
|
||||||
|
dialog_btn.clicked.connect(self.channel_dialog.open)
|
||||||
|
grid.addWidget(dialog_btn, 0, 1)
|
||||||
|
|
||||||
|
self.label = DoubleClickLineEdit(name)
|
||||||
|
self.label.setStyleSheet("background:transparent;")
|
||||||
|
grid.addWidget(self.label, 0, 2)
|
||||||
|
|
||||||
|
scroll_area = VDragScrollArea(self)
|
||||||
|
grid.addWidget(scroll_area, 1, 0, 1, 10)
|
||||||
|
self.flow = DragDropFlowLayoutWidget()
|
||||||
|
scroll_area.setWidgetResizable(True)
|
||||||
|
scroll_area.setWidget(self.flow)
|
||||||
|
self.flow.setContextMenuPolicy(
|
||||||
|
QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
|
||||||
|
self.flow.customContextMenuRequested.connect(self.custom_context_menu)
|
||||||
|
|
||||||
|
def custom_context_menu(self, pos):
|
||||||
|
index = self.flow._get_index(pos)
|
||||||
|
if index == -1:
|
||||||
|
return
|
||||||
|
menu = QtWidgets.QMenu()
|
||||||
|
delete_action = QtGui.QAction("Delete widget", menu)
|
||||||
|
delete_action.triggered.connect(partial(self.delete_widget, index))
|
||||||
|
menu.addAction(delete_action)
|
||||||
|
menu.exec_(self.flow.mapToGlobal(pos))
|
||||||
|
|
||||||
|
def delete_all_widgets(self):
|
||||||
|
for index in reversed(range(self.flow.count())):
|
||||||
|
self.delete_widget(index, True)
|
||||||
|
|
||||||
|
|
||||||
|
def delete_widget(self, index, checked):
|
||||||
|
widget = self.flow.itemAt(index).widget()
|
||||||
|
self.manager.dm.setup_monitoring(False, widget)
|
||||||
|
self.flow.layout.takeAt(index)
|
||||||
|
widget.setParent(self.manager.main_window)
|
||||||
|
widget.hide()
|
||||||
|
|
||||||
|
def add_channels(self):
|
||||||
|
channels = self.channel_dialog.channels
|
||||||
|
self.layout_widgets(channels)
|
||||||
|
|
||||||
def layout_widgets(self, widgets):
|
def layout_widgets(self, widgets):
|
||||||
scroll_area = QtWidgets.QScrollArea()
|
|
||||||
self.setWidget(scroll_area)
|
|
||||||
|
|
||||||
grid = FlowLayout()
|
|
||||||
grid_widget = QtWidgets.QWidget()
|
|
||||||
grid_widget.setLayout(grid)
|
|
||||||
|
|
||||||
for widget in sorted(widgets, key=lambda w: w.sort_key()):
|
for widget in sorted(widgets, key=lambda w: w.sort_key()):
|
||||||
grid.addWidget(widget)
|
self.manager.dm.setup_monitoring(True, widget)
|
||||||
|
self.flow.addWidget(widget)
|
||||||
|
widget.show()
|
||||||
|
|
||||||
scroll_area.setWidgetResizable(True)
|
def restore_widgets(self):
|
||||||
scroll_area.setWidget(grid_widget)
|
if self.widget_uids is not None:
|
||||||
|
widgets_by_uid = self.manager.dm.widgets_by_uid
|
||||||
|
widgets = list()
|
||||||
|
for uid in self.widget_uids:
|
||||||
|
if uid in widgets_by_uid:
|
||||||
|
widgets.append(widgets_by_uid[uid])
|
||||||
|
else:
|
||||||
|
logger.warning("removing moninj widget {}".format(uid))
|
||||||
|
self.layout_widgets(widgets)
|
||||||
|
self.widget_uids = None
|
||||||
|
|
||||||
|
def _save_widget_uids(self):
|
||||||
|
uids = []
|
||||||
|
for i in range(self.flow.count()):
|
||||||
|
uids.append(self.flow.itemAt(i).widget().uid())
|
||||||
|
return uids
|
||||||
|
|
||||||
|
def save_state(self):
|
||||||
|
return {
|
||||||
|
"dock_label": self.label.text(),
|
||||||
|
"widget_uids": self._save_widget_uids()
|
||||||
|
}
|
||||||
|
|
||||||
|
def restore_state(self, state):
|
||||||
|
try:
|
||||||
|
label = state["dock_label"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
self.label._text = label
|
||||||
|
self.label.setText(label)
|
||||||
|
try:
|
||||||
|
self.widget_uids = state["widget_uids"]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class MonInj:
|
class MonInj:
|
||||||
def __init__(self, schedule_ctl):
|
def __init__(self, schedule_ctl, main_window):
|
||||||
self.ttl_dock = _MonInjDock("TTL")
|
self.docks = dict()
|
||||||
self.dds_dock = _MonInjDock("DDS")
|
self.main_window = main_window
|
||||||
self.dac_dock = _MonInjDock("DAC")
|
|
||||||
|
|
||||||
self.dm = _DeviceManager(schedule_ctl)
|
self.dm = _DeviceManager(schedule_ctl)
|
||||||
self.dm.ttl_cb = lambda: self.ttl_dock.layout_widgets(
|
self.dm.channels_cb = self.add_channels
|
||||||
self.dm.ttl_widgets.values())
|
self.channel_model = Model({})
|
||||||
self.dm.dds_cb = lambda: self.dds_dock.layout_widgets(
|
|
||||||
self.dm.dds_widgets.values())
|
|
||||||
self.dm.dac_cb = lambda: self.dac_dock.layout_widgets(
|
|
||||||
self.dm.dac_widgets.values())
|
|
||||||
|
|
||||||
self.subscriber = Subscriber("devices", self.dm.init_ddb, self.dm.notify)
|
def add_channels(self):
|
||||||
|
self.channel_model.clear()
|
||||||
|
self.channel_model.update(self.dm.widgets_by_uid)
|
||||||
|
for dock in self.docks.values():
|
||||||
|
dock.restore_widgets()
|
||||||
|
|
||||||
async def start(self, server, port):
|
def create_new_dock(self, add_to_area=True):
|
||||||
await self.subscriber.connect(server, port)
|
n = 0
|
||||||
|
name = "moninj0"
|
||||||
|
while name in self.docks:
|
||||||
|
n += 1
|
||||||
|
name = "moninj" + str(n)
|
||||||
|
|
||||||
|
dock = _MonInjDock(name, self)
|
||||||
|
self.docks[name] = dock
|
||||||
|
if add_to_area:
|
||||||
|
self.main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock)
|
||||||
|
dock.setFloating(True)
|
||||||
|
dock.sigClosed.connect(partial(self.on_dock_closed, name))
|
||||||
|
self.update_closable()
|
||||||
|
return dock
|
||||||
|
|
||||||
|
def on_dock_closed(self, name):
|
||||||
|
dock = self.docks[name]
|
||||||
|
del self.docks[name]
|
||||||
|
self.update_closable()
|
||||||
|
dock.delete_all_widgets()
|
||||||
|
dock.deleteLater()
|
||||||
|
|
||||||
|
def update_closable(self):
|
||||||
|
flags = (QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
|
||||||
|
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
|
||||||
|
if len(self.docks) > 1:
|
||||||
|
flags |= QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetClosable
|
||||||
|
for dock in self.docks.values():
|
||||||
|
dock.setFeatures(flags)
|
||||||
|
|
||||||
|
def first_moninj_dock(self):
|
||||||
|
if self.docks:
|
||||||
|
return None
|
||||||
|
dock = self.create_new_dock(False)
|
||||||
|
return dock
|
||||||
|
|
||||||
|
def save_state(self):
|
||||||
|
return {name: dock.save_state() for name, dock in self.docks.items()}
|
||||||
|
|
||||||
|
def restore_state(self, state):
|
||||||
|
if self.docks:
|
||||||
|
raise NotImplementedError
|
||||||
|
for name, dock_state in state.items():
|
||||||
|
dock = _MonInjDock(name, self)
|
||||||
|
self.docks[name] = dock
|
||||||
|
dock.restore_state(dock_state)
|
||||||
|
self.main_window.addDockWidget(
|
||||||
|
QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock)
|
||||||
|
dock.sigClosed.connect(partial(self.on_dock_closed, name))
|
||||||
|
self.update_closable()
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self):
|
||||||
await self.subscriber.close()
|
|
||||||
if self.dm is not None:
|
if self.dm is not None:
|
||||||
await self.dm.close()
|
await self.dm.close()
|
||||||
|
|
|
@ -3,7 +3,7 @@ import time
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
from PyQt6 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
from artiq.gui.models import DictSyncModel
|
from artiq.gui.models import DictSyncModel
|
||||||
from artiq.tools import elide
|
from artiq.tools import elide
|
||||||
|
@ -15,9 +15,8 @@ logger = logging.getLogger(__name__)
|
||||||
class Model(DictSyncModel):
|
class Model(DictSyncModel):
|
||||||
def __init__(self, init):
|
def __init__(self, init):
|
||||||
DictSyncModel.__init__(self,
|
DictSyncModel.__init__(self,
|
||||||
["RID", "Pipeline", "Status", "Prio", "Due date",
|
["RID", "Pipeline", "Status", "Prio", "Due date",
|
||||||
"Revision", "File", "Class name"],
|
"Revision", "File", "Class name"], init)
|
||||||
init)
|
|
||||||
|
|
||||||
def sort_key(self, k, v):
|
def sort_key(self, k, v):
|
||||||
# order by priority, and then by due date and RID
|
# order by priority, and then by due date and RID
|
||||||
|
@ -62,31 +61,31 @@ class ScheduleDock(QtWidgets.QDockWidget):
|
||||||
def __init__(self, schedule_ctl, schedule_sub):
|
def __init__(self, schedule_ctl, schedule_sub):
|
||||||
QtWidgets.QDockWidget.__init__(self, "Schedule")
|
QtWidgets.QDockWidget.__init__(self, "Schedule")
|
||||||
self.setObjectName("Schedule")
|
self.setObjectName("Schedule")
|
||||||
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
|
self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
|
||||||
QtWidgets.QDockWidget.DockWidgetFloatable)
|
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
|
||||||
|
|
||||||
self.schedule_ctl = schedule_ctl
|
self.schedule_ctl = schedule_ctl
|
||||||
|
|
||||||
self.table = QtWidgets.QTableView()
|
self.table = QtWidgets.QTableView()
|
||||||
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
|
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
|
||||||
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
|
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
|
||||||
self.table.verticalHeader().setSectionResizeMode(
|
self.table.verticalHeader().setSectionResizeMode(
|
||||||
QtWidgets.QHeaderView.ResizeToContents)
|
QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
|
||||||
self.table.verticalHeader().hide()
|
self.table.verticalHeader().hide()
|
||||||
self.setWidget(self.table)
|
self.setWidget(self.table)
|
||||||
|
|
||||||
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||||
request_termination_action = QtWidgets.QAction("Request termination", self.table)
|
request_termination_action = QtGui.QAction("Request termination", self.table)
|
||||||
request_termination_action.triggered.connect(partial(self.delete_clicked, True))
|
request_termination_action.triggered.connect(partial(self.delete_clicked, True))
|
||||||
request_termination_action.setShortcut("DELETE")
|
request_termination_action.setShortcut("DELETE")
|
||||||
request_termination_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
|
request_termination_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
|
||||||
self.table.addAction(request_termination_action)
|
self.table.addAction(request_termination_action)
|
||||||
delete_action = QtWidgets.QAction("Delete", self.table)
|
delete_action = QtGui.QAction("Delete", self.table)
|
||||||
delete_action.triggered.connect(partial(self.delete_clicked, False))
|
delete_action.triggered.connect(partial(self.delete_clicked, False))
|
||||||
delete_action.setShortcut("SHIFT+DELETE")
|
delete_action.setShortcut("SHIFT+DELETE")
|
||||||
delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
|
delete_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
|
||||||
self.table.addAction(delete_action)
|
self.table.addAction(delete_action)
|
||||||
terminate_pipeline = QtWidgets.QAction(
|
terminate_pipeline = QtGui.QAction(
|
||||||
"Gracefully terminate all in pipeline", self.table)
|
"Gracefully terminate all in pipeline", self.table)
|
||||||
terminate_pipeline.triggered.connect(self.terminate_pipeline_clicked)
|
terminate_pipeline.triggered.connect(self.terminate_pipeline_clicked)
|
||||||
self.table.addAction(terminate_pipeline)
|
self.table.addAction(terminate_pipeline)
|
||||||
|
@ -96,14 +95,14 @@ class ScheduleDock(QtWidgets.QDockWidget):
|
||||||
|
|
||||||
cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
|
cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
|
||||||
h = self.table.horizontalHeader()
|
h = self.table.horizontalHeader()
|
||||||
h.resizeSection(0, 7*cw)
|
h.resizeSection(0, 7 * cw)
|
||||||
h.resizeSection(1, 12*cw)
|
h.resizeSection(1, 12 * cw)
|
||||||
h.resizeSection(2, 16*cw)
|
h.resizeSection(2, 16 * cw)
|
||||||
h.resizeSection(3, 6*cw)
|
h.resizeSection(3, 6 * cw)
|
||||||
h.resizeSection(4, 16*cw)
|
h.resizeSection(4, 16 * cw)
|
||||||
h.resizeSection(5, 30*cw)
|
h.resizeSection(5, 30 * cw)
|
||||||
h.resizeSection(6, 20*cw)
|
h.resizeSection(6, 20 * cw)
|
||||||
h.resizeSection(7, 20*cw)
|
h.resizeSection(7, 20 * cw)
|
||||||
|
|
||||||
def set_model(self, model):
|
def set_model(self, model):
|
||||||
self.table_model = model
|
self.table_model = model
|
||||||
|
@ -143,7 +142,7 @@ class ScheduleDock(QtWidgets.QDockWidget):
|
||||||
selected_rid = self.table_model.row_to_key[row]
|
selected_rid = self.table_model.row_to_key[row]
|
||||||
pipeline = self.table_model.backing_store[selected_rid]["pipeline"]
|
pipeline = self.table_model.backing_store[selected_rid]["pipeline"]
|
||||||
logger.info("Requesting termination of all "
|
logger.info("Requesting termination of all "
|
||||||
"experiments in pipeline '%s'", pipeline)
|
"experiments in pipeline '%s'", pipeline)
|
||||||
|
|
||||||
rids = set()
|
rids = set()
|
||||||
for rid, info in self.table_model.backing_store.items():
|
for rid, info in self.table_model.backing_store.items():
|
||||||
|
@ -151,7 +150,6 @@ class ScheduleDock(QtWidgets.QDockWidget):
|
||||||
rids.add(rid)
|
rids.add(rid)
|
||||||
asyncio.ensure_future(self.request_term_multiple(rids))
|
asyncio.ensure_future(self.request_term_multiple(rids))
|
||||||
|
|
||||||
|
|
||||||
def save_state(self):
|
def save_state(self):
|
||||||
return bytes(self.table.horizontalHeader().saveState())
|
return bytes(self.table.horizontalHeader().saveState())
|
||||||
|
|
||||||
|
|
|
@ -1,9 +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
|
||||||
|
|
||||||
from artiq.gui.tools import LayoutWidget
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -13,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()
|
||||||
|
@ -35,28 +33,28 @@ class ShortcutsDock(QtWidgets.QDockWidget):
|
||||||
for i in range(12):
|
for i in range(12):
|
||||||
row = i + 1
|
row = i + 1
|
||||||
|
|
||||||
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))
|
||||||
|
|
||||||
|
@ -70,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):
|
||||||
|
|
|
@ -0,0 +1,926 @@
|
||||||
|
import os
|
||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
import bisect
|
||||||
|
import itertools
|
||||||
|
import math
|
||||||
|
|
||||||
|
from PyQt6 import QtCore, QtWidgets, QtGui
|
||||||
|
|
||||||
|
import pyqtgraph as pg
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from sipyco.pc_rpc import AsyncioClient
|
||||||
|
from sipyco import pyon
|
||||||
|
|
||||||
|
from artiq.tools import exc_to_warning, short_format
|
||||||
|
from artiq.coredevice import comm_analyzer
|
||||||
|
from artiq.coredevice.comm_analyzer import WaveformType
|
||||||
|
from artiq.gui.tools import LayoutWidget, get_open_file_name, get_save_file_name
|
||||||
|
from artiq.gui.models import DictSyncTreeSepModel
|
||||||
|
from artiq.gui.dndwidgets import VDragScrollArea, VDragDropSplitter
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
WAVEFORM_MIN_HEIGHT = 50
|
||||||
|
WAVEFORM_MAX_HEIGHT = 200
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyClient():
|
||||||
|
def __init__(self, receive_cb, timeout=5, timer=5, timer_backoff=1.1):
|
||||||
|
self.receive_cb = receive_cb
|
||||||
|
self.receiver = None
|
||||||
|
self.addr = None
|
||||||
|
self.port_proxy = None
|
||||||
|
self.port = None
|
||||||
|
self._reconnect_event = asyncio.Event()
|
||||||
|
self.timeout = timeout
|
||||||
|
self.timer = timer
|
||||||
|
self.timer_cur = timer
|
||||||
|
self.timer_backoff = timer_backoff
|
||||||
|
self._reconnect_task = asyncio.ensure_future(self._reconnect())
|
||||||
|
|
||||||
|
def update_address(self, addr, port, port_proxy):
|
||||||
|
self.addr = addr
|
||||||
|
self.port = port
|
||||||
|
self.port_proxy = port_proxy
|
||||||
|
self._reconnect_event.set()
|
||||||
|
|
||||||
|
async def trigger_proxy_task(self):
|
||||||
|
remote = AsyncioClient()
|
||||||
|
try:
|
||||||
|
try:
|
||||||
|
if self.addr is None:
|
||||||
|
logger.error("missing core_analyzer host in device db")
|
||||||
|
return
|
||||||
|
await remote.connect_rpc(self.addr, self.port, "coreanalyzer_proxy_control")
|
||||||
|
except:
|
||||||
|
logger.error("error connecting to analyzer proxy control", exc_info=True)
|
||||||
|
return
|
||||||
|
await remote.trigger()
|
||||||
|
except:
|
||||||
|
logger.error("analyzer proxy reported failure", exc_info=True)
|
||||||
|
finally:
|
||||||
|
remote.close_rpc()
|
||||||
|
|
||||||
|
async def _reconnect(self):
|
||||||
|
while True:
|
||||||
|
await self._reconnect_event.wait()
|
||||||
|
self._reconnect_event.clear()
|
||||||
|
if self.receiver is not None:
|
||||||
|
await self.receiver.close()
|
||||||
|
self.receiver = None
|
||||||
|
new_receiver = comm_analyzer.AnalyzerProxyReceiver(
|
||||||
|
self.receive_cb, self.disconnect_cb)
|
||||||
|
try:
|
||||||
|
if self.addr is not None:
|
||||||
|
await asyncio.wait_for(new_receiver.connect(self.addr, self.port_proxy),
|
||||||
|
self.timeout)
|
||||||
|
logger.info("ARTIQ dashboard connected to analyzer proxy (%s)", self.addr)
|
||||||
|
self.timer_cur = self.timer
|
||||||
|
self.receiver = new_receiver
|
||||||
|
continue
|
||||||
|
except Exception:
|
||||||
|
logger.error("error connecting to analyzer proxy", exc_info=True)
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(self._reconnect_event.wait(), self.timer_cur)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
self.timer_cur *= self.timer_backoff
|
||||||
|
self._reconnect_event.set()
|
||||||
|
else:
|
||||||
|
self.timer_cur = self.timer
|
||||||
|
|
||||||
|
async def close(self):
|
||||||
|
self._reconnect_task.cancel()
|
||||||
|
try:
|
||||||
|
await asyncio.wait_for(self._reconnect_task, None)
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
pass
|
||||||
|
if self.receiver is not None:
|
||||||
|
await self.receiver.close()
|
||||||
|
|
||||||
|
def disconnect_cb(self):
|
||||||
|
logger.error("lost connection to analyzer proxy")
|
||||||
|
self._reconnect_event.set()
|
||||||
|
|
||||||
|
|
||||||
|
class _BackgroundItem(pg.GraphicsWidgetAnchor, pg.GraphicsWidget):
|
||||||
|
def __init__(self, parent, rect):
|
||||||
|
pg.GraphicsWidget.__init__(self, parent)
|
||||||
|
pg.GraphicsWidgetAnchor.__init__(self)
|
||||||
|
self.item = QtWidgets.QGraphicsRectItem(rect, self)
|
||||||
|
brush = QtGui.QBrush(QtGui.QColor(10, 10, 10, 140))
|
||||||
|
self.item.setBrush(brush)
|
||||||
|
|
||||||
|
|
||||||
|
class _BaseWaveform(pg.PlotWidget):
|
||||||
|
cursorMove = QtCore.pyqtSignal(float)
|
||||||
|
|
||||||
|
def __init__(self, name, width, precision, unit,
|
||||||
|
parent=None, pen="r", stepMode="right", connect="finite"):
|
||||||
|
pg.PlotWidget.__init__(self,
|
||||||
|
parent=parent,
|
||||||
|
x=None,
|
||||||
|
y=None,
|
||||||
|
pen=pen,
|
||||||
|
stepMode=stepMode,
|
||||||
|
connect=connect)
|
||||||
|
|
||||||
|
self.setMinimumHeight(WAVEFORM_MIN_HEIGHT)
|
||||||
|
self.setMaximumHeight(WAVEFORM_MAX_HEIGHT)
|
||||||
|
self.setMenuEnabled(False)
|
||||||
|
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||||
|
|
||||||
|
self.name = name
|
||||||
|
self.width = width
|
||||||
|
self.precision = precision
|
||||||
|
self.unit = unit
|
||||||
|
|
||||||
|
self.x_data = []
|
||||||
|
self.y_data = []
|
||||||
|
|
||||||
|
self.plot_item = self.getPlotItem()
|
||||||
|
self.plot_item.hideButtons()
|
||||||
|
self.plot_item.hideAxis("top")
|
||||||
|
self.plot_item.getAxis("bottom").setStyle(showValues=False, tickLength=0)
|
||||||
|
self.plot_item.getAxis("left").setStyle(showValues=False, tickLength=0)
|
||||||
|
self.plot_item.setRange(yRange=(0, 1), padding=0.1)
|
||||||
|
self.plot_item.showGrid(x=True, y=True)
|
||||||
|
|
||||||
|
self.plot_data_item = self.plot_item.listDataItems()[0]
|
||||||
|
self.plot_data_item.setClipToView(True)
|
||||||
|
|
||||||
|
self.view_box = self.plot_item.getViewBox()
|
||||||
|
self.view_box.setMouseEnabled(x=True, y=False)
|
||||||
|
self.view_box.disableAutoRange(axis=pg.ViewBox.YAxis)
|
||||||
|
self.view_box.setLimits(xMin=0, minXRange=20)
|
||||||
|
|
||||||
|
self.title_label = pg.LabelItem(self.name, parent=self.plot_item)
|
||||||
|
self.title_label.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, 0))
|
||||||
|
self.title_label.setAttr('justify', 'left')
|
||||||
|
self.title_label.setZValue(10)
|
||||||
|
|
||||||
|
rect = self.title_label.boundingRect()
|
||||||
|
rect.setHeight(rect.height() * 2)
|
||||||
|
rect.setWidth(225)
|
||||||
|
self.label_bg = _BackgroundItem(parent=self.plot_item, rect=rect)
|
||||||
|
self.label_bg.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, 0))
|
||||||
|
|
||||||
|
self.cursor = pg.InfiniteLine()
|
||||||
|
self.cursor_y = None
|
||||||
|
self.addItem(self.cursor)
|
||||||
|
|
||||||
|
self.cursor_label = pg.LabelItem('', parent=self.plot_item)
|
||||||
|
self.cursor_label.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, 20))
|
||||||
|
self.cursor_label.setAttr('justify', 'left')
|
||||||
|
self.cursor_label.setZValue(10)
|
||||||
|
|
||||||
|
def setStoppedX(self, stopped_x):
|
||||||
|
self.stopped_x = stopped_x
|
||||||
|
self.view_box.setLimits(xMax=stopped_x)
|
||||||
|
|
||||||
|
def setData(self, data):
|
||||||
|
if len(data) == 0:
|
||||||
|
self.x_data, self.y_data = [], []
|
||||||
|
else:
|
||||||
|
self.x_data, self.y_data = zip(*data)
|
||||||
|
|
||||||
|
def onDataChange(self, data):
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
def onCursorMove(self, x):
|
||||||
|
self.cursor.setValue(x)
|
||||||
|
if len(self.x_data) < 1:
|
||||||
|
return
|
||||||
|
ind = bisect.bisect_left(self.x_data, x) - 1
|
||||||
|
dr = self.plot_data_item.dataRect()
|
||||||
|
self.cursor_y = None
|
||||||
|
if dr is not None and 0 <= ind < len(self.y_data):
|
||||||
|
self.cursor_y = self.y_data[ind]
|
||||||
|
|
||||||
|
def mouseMoveEvent(self, e):
|
||||||
|
if e.buttons() == QtCore.Qt.MouseButton.LeftButton \
|
||||||
|
and e.modifiers() == QtCore.Qt.KeyboardModifier.ShiftModifier:
|
||||||
|
drag = QtGui.QDrag(self)
|
||||||
|
mime = QtCore.QMimeData()
|
||||||
|
drag.setMimeData(mime)
|
||||||
|
pixmapi = QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.StandardPixmap.SP_FileIcon)
|
||||||
|
drag.setPixmap(pixmapi.pixmap(32))
|
||||||
|
drag.exec(QtCore.Qt.DropAction.MoveAction)
|
||||||
|
else:
|
||||||
|
super().mouseMoveEvent(e)
|
||||||
|
|
||||||
|
def wheelEvent(self, e):
|
||||||
|
if e.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
||||||
|
super().wheelEvent(e)
|
||||||
|
else:
|
||||||
|
e.ignore()
|
||||||
|
|
||||||
|
|
||||||
|
def mouseDoubleClickEvent(self, e):
|
||||||
|
pos = self.view_box.mapSceneToView(e.position())
|
||||||
|
self.cursorMove.emit(pos.x())
|
||||||
|
|
||||||
|
|
||||||
|
class BitWaveform(_BaseWaveform):
|
||||||
|
def __init__(self, name, width, precision, unit, parent=None):
|
||||||
|
_BaseWaveform.__init__(self, name, width, precision, unit, parent)
|
||||||
|
self.plot_item.showGrid(x=True, y=False)
|
||||||
|
self._arrows = []
|
||||||
|
|
||||||
|
def onDataChange(self, data):
|
||||||
|
try:
|
||||||
|
self.setData(data)
|
||||||
|
for arw in self._arrows:
|
||||||
|
self.removeItem(arw)
|
||||||
|
self._arrows = []
|
||||||
|
l = len(data)
|
||||||
|
display_y = np.empty(l)
|
||||||
|
display_x = np.empty(l)
|
||||||
|
display_map = {
|
||||||
|
"X": 0.5,
|
||||||
|
"1": 1,
|
||||||
|
"0": 0
|
||||||
|
}
|
||||||
|
previous_y = None
|
||||||
|
for i, coord in enumerate(data):
|
||||||
|
x, y = coord
|
||||||
|
dis_y = display_map[y]
|
||||||
|
if previous_y == y:
|
||||||
|
arw = pg.ArrowItem(pxMode=True, angle=90)
|
||||||
|
self.addItem(arw)
|
||||||
|
self._arrows.append(arw)
|
||||||
|
arw.setPos(x, dis_y)
|
||||||
|
display_y[i] = dis_y
|
||||||
|
display_x[i] = x
|
||||||
|
previous_y = y
|
||||||
|
self.plot_data_item.setData(x=display_x, y=display_y)
|
||||||
|
except:
|
||||||
|
logger.error("Error when displaying waveform: %s", self.name, exc_info=True)
|
||||||
|
for arw in self._arrows:
|
||||||
|
self.removeItem(arw)
|
||||||
|
self.plot_data_item.setData(x=[], y=[])
|
||||||
|
|
||||||
|
def onCursorMove(self, x):
|
||||||
|
_BaseWaveform.onCursorMove(self, x)
|
||||||
|
if self.cursor_y is not None:
|
||||||
|
self.cursor_label.setText(self.cursor_y)
|
||||||
|
else:
|
||||||
|
self.cursor_label.setText("")
|
||||||
|
|
||||||
|
|
||||||
|
class AnalogWaveform(_BaseWaveform):
|
||||||
|
def __init__(self, name, width, precision, unit, parent=None):
|
||||||
|
_BaseWaveform.__init__(self, name, width, precision, unit, parent)
|
||||||
|
|
||||||
|
def onDataChange(self, data):
|
||||||
|
try:
|
||||||
|
self.setData(data)
|
||||||
|
self.plot_data_item.setData(x=self.x_data, y=self.y_data)
|
||||||
|
if len(data) > 0:
|
||||||
|
max_y = max(self.y_data)
|
||||||
|
min_y = min(self.y_data)
|
||||||
|
self.plot_item.setRange(yRange=(min_y, max_y), padding=0.1)
|
||||||
|
except:
|
||||||
|
logger.error("Error when displaying waveform: %s", self.name, exc_info=True)
|
||||||
|
self.plot_data_item.setData(x=[], y=[])
|
||||||
|
|
||||||
|
def onCursorMove(self, x):
|
||||||
|
_BaseWaveform.onCursorMove(self, x)
|
||||||
|
if self.cursor_y is not None:
|
||||||
|
t = short_format(self.cursor_y, {"precision": self.precision, "unit": self.unit})
|
||||||
|
else:
|
||||||
|
t = ""
|
||||||
|
self.cursor_label.setText(t)
|
||||||
|
|
||||||
|
|
||||||
|
class BitVectorWaveform(_BaseWaveform):
|
||||||
|
def __init__(self, name, width, precision, unit, parent=None):
|
||||||
|
_BaseWaveform.__init__(self, name, width, precision, parent)
|
||||||
|
self._labels = []
|
||||||
|
self._format_string = "{:0=" + str(math.ceil(width / 4)) + "X}"
|
||||||
|
self.view_box.sigTransformChanged.connect(self._update_labels)
|
||||||
|
self.plot_item.showGrid(x=True, y=False)
|
||||||
|
|
||||||
|
def _update_labels(self):
|
||||||
|
for label in self._labels:
|
||||||
|
self.removeItem(label)
|
||||||
|
xmin, xmax = self.view_box.viewRange()[0]
|
||||||
|
left_label_i = bisect.bisect_left(self.x_data, xmin)
|
||||||
|
right_label_i = bisect.bisect_right(self.x_data, xmax) + 1
|
||||||
|
for i, j in itertools.pairwise(range(left_label_i, right_label_i)):
|
||||||
|
x1 = self.x_data[i]
|
||||||
|
x2 = self.x_data[j] if j < len(self.x_data) else self.stopped_x
|
||||||
|
lbl = self._labels[i]
|
||||||
|
bounds = lbl.boundingRect()
|
||||||
|
bounds_view = self.view_box.mapSceneToView(bounds)
|
||||||
|
if bounds_view.boundingRect().width() < x2 - x1:
|
||||||
|
self.addItem(lbl)
|
||||||
|
|
||||||
|
def onDataChange(self, data):
|
||||||
|
try:
|
||||||
|
self.setData(data)
|
||||||
|
for lbl in self._labels:
|
||||||
|
self.plot_item.removeItem(lbl)
|
||||||
|
self._labels = []
|
||||||
|
l = len(data)
|
||||||
|
display_x = np.empty(l * 2)
|
||||||
|
display_y = np.empty(l * 2)
|
||||||
|
for i, coord in enumerate(data):
|
||||||
|
x, y = coord
|
||||||
|
display_x[i * 2] = x
|
||||||
|
display_x[i * 2 + 1] = x
|
||||||
|
display_y[i * 2] = 0
|
||||||
|
display_y[i * 2 + 1] = int(int(y) != 0)
|
||||||
|
lbl = pg.TextItem(
|
||||||
|
self._format_string.format(int(y, 2)), anchor=(0, 0.5))
|
||||||
|
lbl.setPos(x, 0.5)
|
||||||
|
lbl.setTextWidth(100)
|
||||||
|
self._labels.append(lbl)
|
||||||
|
self.plot_data_item.setData(x=display_x, y=display_y)
|
||||||
|
except:
|
||||||
|
logger.error("Error when displaying waveform: %s", self.name, exc_info=True)
|
||||||
|
for lbl in self._labels:
|
||||||
|
self.plot_item.removeItem(lbl)
|
||||||
|
self.plot_data_item.setData(x=[], y=[])
|
||||||
|
|
||||||
|
def onCursorMove(self, x):
|
||||||
|
_BaseWaveform.onCursorMove(self, x)
|
||||||
|
if self.cursor_y is not None:
|
||||||
|
t = self._format_string.format(int(self.cursor_y, 2))
|
||||||
|
else:
|
||||||
|
t = ""
|
||||||
|
self.cursor_label.setText(t)
|
||||||
|
|
||||||
|
|
||||||
|
class LogWaveform(_BaseWaveform):
|
||||||
|
def __init__(self, name, width, precision, unit, parent=None):
|
||||||
|
_BaseWaveform.__init__(self, name, width, precision, parent)
|
||||||
|
self.plot_data_item.opts['pen'] = None
|
||||||
|
self.plot_data_item.opts['symbol'] = 'x'
|
||||||
|
self._labels = []
|
||||||
|
self.plot_item.showGrid(x=True, y=False)
|
||||||
|
|
||||||
|
def onDataChange(self, data):
|
||||||
|
try:
|
||||||
|
self.setData(data)
|
||||||
|
for lbl in self._labels:
|
||||||
|
self.plot_item.removeItem(lbl)
|
||||||
|
self._labels = []
|
||||||
|
self.plot_data_item.setData(
|
||||||
|
x=self.x_data, y=np.ones(len(self.x_data)))
|
||||||
|
if len(data) == 0:
|
||||||
|
return
|
||||||
|
old_x = data[0][0]
|
||||||
|
old_msg = data[0][1]
|
||||||
|
for x, msg in data[1:]:
|
||||||
|
if x == old_x:
|
||||||
|
old_msg += "\n" + msg
|
||||||
|
else:
|
||||||
|
lbl = pg.TextItem(old_msg)
|
||||||
|
self.addItem(lbl)
|
||||||
|
self._labels.append(lbl)
|
||||||
|
lbl.setPos(old_x, 1)
|
||||||
|
old_msg = msg
|
||||||
|
old_x = x
|
||||||
|
lbl = pg.TextItem(old_msg)
|
||||||
|
self.addItem(lbl)
|
||||||
|
self._labels.append(lbl)
|
||||||
|
lbl.setPos(old_x, 1)
|
||||||
|
except:
|
||||||
|
logger.error("Error when displaying waveform: %s", self.name, exc_info=True)
|
||||||
|
for lbl in self._labels:
|
||||||
|
self.plot_item.removeItem(lbl)
|
||||||
|
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):
|
||||||
|
cursorMove = QtCore.pyqtSignal(float)
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QtWidgets.QWidget.__init__(self, parent=parent)
|
||||||
|
|
||||||
|
self._stopped_x = None
|
||||||
|
self._timescale = 1
|
||||||
|
self._cursor_x = 0
|
||||||
|
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
layout.setSpacing(0)
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
self._ref_axis = _RefAxis()
|
||||||
|
self._ref_axis.hideAxis("bottom")
|
||||||
|
self._ref_axis.hideAxis("left")
|
||||||
|
self._ref_axis.hideButtons()
|
||||||
|
self._ref_axis.setFixedHeight(45)
|
||||||
|
self._ref_axis.setMenuEnabled(False)
|
||||||
|
self._top = pg.AxisItem("top")
|
||||||
|
self._top.setScale(1e-12)
|
||||||
|
self._top.setLabel(units="s")
|
||||||
|
self._ref_axis.setAxisItems({"top": self._top})
|
||||||
|
layout.addWidget(self._ref_axis)
|
||||||
|
|
||||||
|
self._ref_vb = self._ref_axis.getPlotItem().getViewBox()
|
||||||
|
self._ref_vb.setFixedHeight(0)
|
||||||
|
self._ref_vb.setMouseEnabled(x=True, y=False)
|
||||||
|
self._ref_vb.setLimits(xMin=0)
|
||||||
|
|
||||||
|
scroll_area = VDragScrollArea(self)
|
||||||
|
scroll_area.setWidgetResizable(True)
|
||||||
|
scroll_area.setContentsMargins(0, 0, 0, 0)
|
||||||
|
scroll_area.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
|
||||||
|
scroll_area.setVerticalScrollBarPolicy(
|
||||||
|
QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
||||||
|
layout.addWidget(scroll_area)
|
||||||
|
|
||||||
|
self._splitter = VDragDropSplitter(parent=scroll_area)
|
||||||
|
self._splitter.setHandleWidth(1)
|
||||||
|
scroll_area.setWidget(self._splitter)
|
||||||
|
|
||||||
|
self.cursorMove.connect(self.onCursorMove)
|
||||||
|
|
||||||
|
self.confirm_delete_dialog = QtWidgets.QMessageBox(self)
|
||||||
|
self.confirm_delete_dialog.setIcon(
|
||||||
|
QtWidgets.QMessageBox.Icon.Warning
|
||||||
|
)
|
||||||
|
self.confirm_delete_dialog.setText("Delete all waveforms?")
|
||||||
|
self.confirm_delete_dialog.setStandardButtons(
|
||||||
|
QtWidgets.QMessageBox.StandardButton.Ok |
|
||||||
|
QtWidgets.QMessageBox.StandardButton.Cancel
|
||||||
|
)
|
||||||
|
self.confirm_delete_dialog.setDefaultButton(
|
||||||
|
QtWidgets.QMessageBox.StandardButton.Ok
|
||||||
|
)
|
||||||
|
|
||||||
|
def setModel(self, model):
|
||||||
|
self._model = model
|
||||||
|
self._model.dataChanged.connect(self.onDataChange)
|
||||||
|
self._model.rowsInserted.connect(self.onInsert)
|
||||||
|
self._model.rowsRemoved.connect(self.onRemove)
|
||||||
|
self._model.rowsMoved.connect(self.onMove)
|
||||||
|
self._splitter.dropped.connect(self._model.move)
|
||||||
|
self.confirm_delete_dialog.accepted.connect(self._model.clear)
|
||||||
|
|
||||||
|
def setTimescale(self, timescale):
|
||||||
|
self._timescale = timescale
|
||||||
|
self._top.setScale(1e-12 * timescale)
|
||||||
|
|
||||||
|
def setStoppedX(self, stopped_x):
|
||||||
|
self._stopped_x = stopped_x
|
||||||
|
self._ref_vb.setLimits(xMax=stopped_x)
|
||||||
|
self._ref_vb.setRange(xRange=(0, stopped_x))
|
||||||
|
for i in range(self._model.rowCount()):
|
||||||
|
self._splitter.widget(i).setStoppedX(stopped_x)
|
||||||
|
|
||||||
|
def resetZoom(self):
|
||||||
|
if self._stopped_x is not None:
|
||||||
|
self._ref_vb.setRange(xRange=(0, self._stopped_x))
|
||||||
|
|
||||||
|
def onDataChange(self, top, bottom, roles):
|
||||||
|
self.cursorMove.emit(0)
|
||||||
|
first = top.row()
|
||||||
|
last = bottom.row()
|
||||||
|
data_row = self._model.headers.index("data")
|
||||||
|
for i in range(first, last + 1):
|
||||||
|
data = self._model.data(self._model.index(i, data_row))
|
||||||
|
self._splitter.widget(i).onDataChange(data)
|
||||||
|
|
||||||
|
def onInsert(self, parent, first, last):
|
||||||
|
for i in range(first, last + 1):
|
||||||
|
w = self._create_waveform(i)
|
||||||
|
self._splitter.insertWidget(i, w)
|
||||||
|
self._resize()
|
||||||
|
|
||||||
|
def onRemove(self, parent, first, last):
|
||||||
|
for i in reversed(range(first, last + 1)):
|
||||||
|
w = self._splitter.widget(i)
|
||||||
|
w.deleteLater()
|
||||||
|
self._splitter.refresh()
|
||||||
|
self._resize()
|
||||||
|
|
||||||
|
def onMove(self, src_parent, src_start, src_end, dest_parent, dest_row):
|
||||||
|
w = self._splitter.widget(src_start)
|
||||||
|
self._splitter.insertWidget(dest_row, w)
|
||||||
|
|
||||||
|
def onCursorMove(self, x):
|
||||||
|
self._cursor_x = x
|
||||||
|
for i in range(self._model.rowCount()):
|
||||||
|
self._splitter.widget(i).onCursorMove(x)
|
||||||
|
|
||||||
|
def _create_waveform(self, row):
|
||||||
|
name, ty, width, precision, unit = (
|
||||||
|
self._model.data(self._model.index(row, i)) for i in range(5))
|
||||||
|
waveform_cls = {
|
||||||
|
WaveformType.BIT: BitWaveform,
|
||||||
|
WaveformType.VECTOR: BitVectorWaveform,
|
||||||
|
WaveformType.ANALOG: AnalogWaveform,
|
||||||
|
WaveformType.LOG: LogWaveform
|
||||||
|
}[ty]
|
||||||
|
w = waveform_cls(name, width, precision, unit, parent=self._splitter)
|
||||||
|
w.setXLink(self._ref_vb)
|
||||||
|
w.setStoppedX(self._stopped_x)
|
||||||
|
w.cursorMove.connect(self.cursorMove)
|
||||||
|
w.onCursorMove(self._cursor_x)
|
||||||
|
action = QtGui.QAction("Delete waveform", w)
|
||||||
|
action.triggered.connect(lambda: self._delete_waveform(w))
|
||||||
|
w.addAction(action)
|
||||||
|
action = QtGui.QAction("Delete all waveforms", w)
|
||||||
|
action.triggered.connect(self.confirm_delete_dialog.open)
|
||||||
|
w.addAction(action)
|
||||||
|
return w
|
||||||
|
|
||||||
|
def _delete_waveform(self, waveform):
|
||||||
|
row = self._splitter.indexOf(waveform)
|
||||||
|
self._model.pop(row)
|
||||||
|
|
||||||
|
def _resize(self):
|
||||||
|
self._splitter.setFixedHeight(
|
||||||
|
int((WAVEFORM_MIN_HEIGHT + WAVEFORM_MAX_HEIGHT) * self._model.rowCount() / 2))
|
||||||
|
|
||||||
|
|
||||||
|
class _WaveformModel(QtCore.QAbstractTableModel):
|
||||||
|
def __init__(self):
|
||||||
|
self.backing_struct = []
|
||||||
|
self.headers = ["name", "type", "width", "precision", "unit", "data"]
|
||||||
|
QtCore.QAbstractTableModel.__init__(self)
|
||||||
|
|
||||||
|
def rowCount(self, parent=QtCore.QModelIndex()):
|
||||||
|
return len(self.backing_struct)
|
||||||
|
|
||||||
|
def columnCount(self, parent=QtCore.QModelIndex()):
|
||||||
|
return len(self.headers)
|
||||||
|
|
||||||
|
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
|
||||||
|
if index.isValid():
|
||||||
|
return self.backing_struct[index.row()][index.column()]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def extend(self, data):
|
||||||
|
length = len(self.backing_struct)
|
||||||
|
len_data = len(data)
|
||||||
|
self.beginInsertRows(QtCore.QModelIndex(), length, length + len_data - 1)
|
||||||
|
self.backing_struct.extend(data)
|
||||||
|
self.endInsertRows()
|
||||||
|
|
||||||
|
def pop(self, row):
|
||||||
|
self.beginRemoveRows(QtCore.QModelIndex(), row, row)
|
||||||
|
self.backing_struct.pop(row)
|
||||||
|
self.endRemoveRows()
|
||||||
|
|
||||||
|
def move(self, src, dest):
|
||||||
|
if src == dest:
|
||||||
|
return
|
||||||
|
if src < dest:
|
||||||
|
dest, src = src, dest
|
||||||
|
self.beginMoveRows(QtCore.QModelIndex(), src, src, QtCore.QModelIndex(), dest)
|
||||||
|
self.backing_struct.insert(dest, self.backing_struct.pop(src))
|
||||||
|
self.endMoveRows()
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.beginRemoveRows(QtCore.QModelIndex(), 0, len(self.backing_struct) - 1)
|
||||||
|
self.backing_struct.clear()
|
||||||
|
self.endRemoveRows()
|
||||||
|
|
||||||
|
def export_list(self):
|
||||||
|
return [[row[0], row[1].value, *row[2:5]] for row in self.backing_struct]
|
||||||
|
|
||||||
|
def import_list(self, channel_list):
|
||||||
|
self.clear()
|
||||||
|
data = [[row[0], WaveformType(row[1]), *row[2:5], []] for row in channel_list]
|
||||||
|
self.extend(data)
|
||||||
|
|
||||||
|
def update_data(self, waveform_data, top, bottom):
|
||||||
|
name_col = self.headers.index("name")
|
||||||
|
data_col = self.headers.index("data")
|
||||||
|
for i in range(top, bottom):
|
||||||
|
name = self.data(self.index(i, name_col))
|
||||||
|
self.backing_struct[i][data_col] = waveform_data.get(name, [])
|
||||||
|
self.dataChanged.emit(self.index(i, data_col),
|
||||||
|
self.index(i, data_col))
|
||||||
|
|
||||||
|
def update_all(self, waveform_data):
|
||||||
|
self.update_data(waveform_data, 0, self.rowCount())
|
||||||
|
|
||||||
|
|
||||||
|
class _CursorTimeControl(QtWidgets.QLineEdit):
|
||||||
|
submit = QtCore.pyqtSignal(float)
|
||||||
|
|
||||||
|
def __init__(self, parent):
|
||||||
|
QtWidgets.QLineEdit.__init__(self, parent=parent)
|
||||||
|
self._text = ""
|
||||||
|
self._value = 0
|
||||||
|
self._timescale = 1
|
||||||
|
self.setDisplayValue(0)
|
||||||
|
self.textChanged.connect(self._onTextChange)
|
||||||
|
self.returnPressed.connect(self._onReturnPress)
|
||||||
|
|
||||||
|
def setTimescale(self, timescale):
|
||||||
|
self._timescale = timescale
|
||||||
|
|
||||||
|
def _onTextChange(self, text):
|
||||||
|
self._text = text
|
||||||
|
|
||||||
|
def setDisplayValue(self, value):
|
||||||
|
self._value = value
|
||||||
|
self._text = pg.siFormat(value * 1e-12 * self._timescale,
|
||||||
|
suffix="s",
|
||||||
|
allowUnicode=False,
|
||||||
|
precision=15)
|
||||||
|
self.setText(self._text)
|
||||||
|
|
||||||
|
def _setValueFromText(self, text):
|
||||||
|
try:
|
||||||
|
self._value = pg.siEval(text) * (1e12 / self._timescale)
|
||||||
|
except:
|
||||||
|
logger.error("Error when parsing cursor time input", exc_info=True)
|
||||||
|
|
||||||
|
def _onReturnPress(self):
|
||||||
|
self._setValueFromText(self._text)
|
||||||
|
self.setDisplayValue(self._value)
|
||||||
|
self.submit.emit(self._value)
|
||||||
|
self.clearFocus()
|
||||||
|
|
||||||
|
|
||||||
|
class Model(DictSyncTreeSepModel):
|
||||||
|
def __init__(self, init):
|
||||||
|
DictSyncTreeSepModel.__init__(self, "/", ["Channels"], init)
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
for k in self.backing_store:
|
||||||
|
self._del_item(self, k.split(self.separator))
|
||||||
|
self.backing_store.clear()
|
||||||
|
|
||||||
|
def update(self, d):
|
||||||
|
for k, v in d.items():
|
||||||
|
self[k] = v
|
||||||
|
|
||||||
|
|
||||||
|
class _AddChannelDialog(QtWidgets.QDialog):
|
||||||
|
def __init__(self, parent, model):
|
||||||
|
QtWidgets.QDialog.__init__(self, parent=parent)
|
||||||
|
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
|
||||||
|
self.setWindowTitle("Add channels")
|
||||||
|
|
||||||
|
layout = QtWidgets.QVBoxLayout()
|
||||||
|
self.setLayout(layout)
|
||||||
|
|
||||||
|
self._model = model
|
||||||
|
self._tree_view = QtWidgets.QTreeView()
|
||||||
|
self._tree_view.setHeaderHidden(True)
|
||||||
|
self._tree_view.setSelectionBehavior(
|
||||||
|
QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems)
|
||||||
|
self._tree_view.setSelectionMode(
|
||||||
|
QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
|
||||||
|
self._tree_view.setModel(self._model)
|
||||||
|
layout.addWidget(self._tree_view)
|
||||||
|
|
||||||
|
self._button_box = QtWidgets.QDialogButtonBox(
|
||||||
|
QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
|
||||||
|
)
|
||||||
|
self._button_box.setCenterButtons(True)
|
||||||
|
self._button_box.accepted.connect(self.add_channels)
|
||||||
|
self._button_box.rejected.connect(self.reject)
|
||||||
|
layout.addWidget(self._button_box)
|
||||||
|
|
||||||
|
def add_channels(self):
|
||||||
|
selection = self._tree_view.selectedIndexes()
|
||||||
|
channels = []
|
||||||
|
for select in selection:
|
||||||
|
key = self._model.index_to_key(select)
|
||||||
|
if key is not None:
|
||||||
|
channels.append([key, *self._model[key].ref, []])
|
||||||
|
self.channels = channels
|
||||||
|
self.accept()
|
||||||
|
|
||||||
|
|
||||||
|
class WaveformDock(QtWidgets.QDockWidget):
|
||||||
|
def __init__(self, timeout, timer, timer_backoff):
|
||||||
|
QtWidgets.QDockWidget.__init__(self, "Waveform")
|
||||||
|
self.setObjectName("Waveform")
|
||||||
|
self.setFeatures(
|
||||||
|
self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable)
|
||||||
|
|
||||||
|
self._channel_model = Model({})
|
||||||
|
self._waveform_model = _WaveformModel()
|
||||||
|
|
||||||
|
self._ddb = None
|
||||||
|
self._dump = None
|
||||||
|
|
||||||
|
self._waveform_data = {
|
||||||
|
"timescale": 1,
|
||||||
|
"stopped_x": None,
|
||||||
|
"logs": dict(),
|
||||||
|
"data": dict(),
|
||||||
|
}
|
||||||
|
|
||||||
|
self._current_dir = os.getcwd()
|
||||||
|
|
||||||
|
self.proxy_client = ProxyClient(self.on_dump_receive,
|
||||||
|
timeout,
|
||||||
|
timer,
|
||||||
|
timer_backoff)
|
||||||
|
|
||||||
|
grid = LayoutWidget()
|
||||||
|
self.setWidget(grid)
|
||||||
|
|
||||||
|
self._menu_btn = QtWidgets.QPushButton()
|
||||||
|
self._menu_btn.setIcon(
|
||||||
|
QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.StandardPixmap.SP_FileDialogStart))
|
||||||
|
grid.addWidget(self._menu_btn, 0, 0)
|
||||||
|
|
||||||
|
self._request_dump_btn = QtWidgets.QToolButton()
|
||||||
|
self._request_dump_btn.setToolTip("Fetch analyzer data from device")
|
||||||
|
self._request_dump_btn.setIcon(
|
||||||
|
QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.StandardPixmap.SP_BrowserReload))
|
||||||
|
self._request_dump_btn.clicked.connect(
|
||||||
|
lambda: asyncio.ensure_future(exc_to_warning(self.proxy_client.trigger_proxy_task())))
|
||||||
|
grid.addWidget(self._request_dump_btn, 0, 1)
|
||||||
|
|
||||||
|
self._add_channel_dialog = _AddChannelDialog(self, self._channel_model)
|
||||||
|
self._add_channel_dialog.accepted.connect(self._add_channels)
|
||||||
|
|
||||||
|
self._add_btn = QtWidgets.QToolButton()
|
||||||
|
self._add_btn.setToolTip("Add channels...")
|
||||||
|
self._add_btn.setIcon(
|
||||||
|
QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.StandardPixmap.SP_FileDialogListView))
|
||||||
|
self._add_btn.clicked.connect(self._add_channel_dialog.open)
|
||||||
|
grid.addWidget(self._add_btn, 0, 2)
|
||||||
|
|
||||||
|
self._file_menu = QtWidgets.QMenu()
|
||||||
|
self._add_async_action("Open trace...", self.load_trace)
|
||||||
|
self._add_async_action("Save trace...", self.save_trace)
|
||||||
|
self._add_async_action("Save trace as VCD...", self.save_vcd)
|
||||||
|
self._add_async_action("Open channel list...", self.load_channels)
|
||||||
|
self._add_async_action("Save channel list...", self.save_channels)
|
||||||
|
self._menu_btn.setMenu(self._file_menu)
|
||||||
|
|
||||||
|
self._waveform_view = _WaveformView(self)
|
||||||
|
self._waveform_view.setModel(self._waveform_model)
|
||||||
|
grid.addWidget(self._waveform_view, 1, 0, colspan=12)
|
||||||
|
|
||||||
|
self._reset_zoom_btn = QtWidgets.QToolButton()
|
||||||
|
self._reset_zoom_btn.setToolTip("Reset zoom")
|
||||||
|
self._reset_zoom_btn.setIcon(
|
||||||
|
QtWidgets.QApplication.style().standardIcon(
|
||||||
|
QtWidgets.QStyle.StandardPixmap.SP_TitleBarMaxButton))
|
||||||
|
self._reset_zoom_btn.clicked.connect(self._waveform_view.resetZoom)
|
||||||
|
grid.addWidget(self._reset_zoom_btn, 0, 3)
|
||||||
|
|
||||||
|
self._cursor_control = _CursorTimeControl(self)
|
||||||
|
self._waveform_view.cursorMove.connect(self._cursor_control.setDisplayValue)
|
||||||
|
self._cursor_control.submit.connect(self._waveform_view.onCursorMove)
|
||||||
|
grid.addWidget(self._cursor_control, 0, 4, colspan=6)
|
||||||
|
|
||||||
|
def _add_async_action(self, label, coro):
|
||||||
|
action = QtGui.QAction(label, self)
|
||||||
|
action.triggered.connect(
|
||||||
|
lambda: asyncio.ensure_future(exc_to_warning(coro())))
|
||||||
|
self._file_menu.addAction(action)
|
||||||
|
|
||||||
|
def _add_channels(self):
|
||||||
|
channels = self._add_channel_dialog.channels
|
||||||
|
count = self._waveform_model.rowCount()
|
||||||
|
self._waveform_model.extend(channels)
|
||||||
|
self._waveform_model.update_data(self._waveform_data['data'],
|
||||||
|
count,
|
||||||
|
count + len(channels))
|
||||||
|
|
||||||
|
def on_dump_receive(self, dump):
|
||||||
|
self._dump = dump
|
||||||
|
decoded_dump = comm_analyzer.decode_dump(dump)
|
||||||
|
waveform_data = comm_analyzer.decoded_dump_to_waveform_data(self._ddb, decoded_dump)
|
||||||
|
self._waveform_data.update(waveform_data)
|
||||||
|
self._channel_model.update(self._waveform_data['logs'])
|
||||||
|
self._waveform_model.update_all(self._waveform_data['data'])
|
||||||
|
self._waveform_view.setStoppedX(self._waveform_data['stopped_x'])
|
||||||
|
self._waveform_view.setTimescale(self._waveform_data['timescale'])
|
||||||
|
self._cursor_control.setTimescale(self._waveform_data['timescale'])
|
||||||
|
|
||||||
|
async def load_trace(self):
|
||||||
|
try:
|
||||||
|
filename = await get_open_file_name(
|
||||||
|
self,
|
||||||
|
"Load Analyzer Trace",
|
||||||
|
self._current_dir,
|
||||||
|
"All files (*.*)")
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
return
|
||||||
|
self._current_dir = os.path.dirname(filename)
|
||||||
|
try:
|
||||||
|
with open(filename, 'rb') as f:
|
||||||
|
dump = f.read()
|
||||||
|
self.on_dump_receive(dump)
|
||||||
|
except:
|
||||||
|
logger.error("Failed to open analyzer trace", exc_info=True)
|
||||||
|
|
||||||
|
async def save_trace(self):
|
||||||
|
if self._dump is None:
|
||||||
|
logger.error("No analyzer trace stored in dashboard, "
|
||||||
|
"try loading from file or fetching from device")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
filename = await get_save_file_name(
|
||||||
|
self,
|
||||||
|
"Save Analyzer Trace",
|
||||||
|
self._current_dir,
|
||||||
|
"All files (*.*)")
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
return
|
||||||
|
self._current_dir = os.path.dirname(filename)
|
||||||
|
try:
|
||||||
|
with open(filename, 'wb') as f:
|
||||||
|
f.write(self._dump)
|
||||||
|
except:
|
||||||
|
logger.error("Failed to save analyzer trace", exc_info=True)
|
||||||
|
|
||||||
|
async def save_vcd(self):
|
||||||
|
if self._dump is None:
|
||||||
|
logger.error("No analyzer trace stored in dashboard, "
|
||||||
|
"try loading from file or fetching from device")
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
filename = await get_save_file_name(
|
||||||
|
self,
|
||||||
|
"Save VCD",
|
||||||
|
self._current_dir,
|
||||||
|
"All files (*.*)")
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
return
|
||||||
|
self._current_dir = os.path.dirname(filename)
|
||||||
|
try:
|
||||||
|
decoded_dump = comm_analyzer.decode_dump(self._dump)
|
||||||
|
with open(filename, 'w') as f:
|
||||||
|
comm_analyzer.decoded_dump_to_vcd(f, self._ddb, decoded_dump)
|
||||||
|
except:
|
||||||
|
logger.error("Failed to save trace as VCD", exc_info=True)
|
||||||
|
|
||||||
|
async def load_channels(self):
|
||||||
|
try:
|
||||||
|
filename = await get_open_file_name(
|
||||||
|
self,
|
||||||
|
"Open channel list",
|
||||||
|
self._current_dir,
|
||||||
|
"PYON files (*.pyon);;All files (*.*)")
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
return
|
||||||
|
self._current_dir = os.path.dirname(filename)
|
||||||
|
try:
|
||||||
|
channel_list = pyon.load_file(filename)
|
||||||
|
self._waveform_model.import_list(channel_list)
|
||||||
|
self._waveform_model.update_all(self._waveform_data['data'])
|
||||||
|
except:
|
||||||
|
logger.error("Failed to open channel list", exc_info=True)
|
||||||
|
|
||||||
|
async def save_channels(self):
|
||||||
|
try:
|
||||||
|
filename = await get_save_file_name(
|
||||||
|
self,
|
||||||
|
"Save channel list",
|
||||||
|
self._current_dir,
|
||||||
|
"PYON files (*.pyon);;All files (*.*)")
|
||||||
|
except asyncio.CancelledError:
|
||||||
|
return
|
||||||
|
self._current_dir = os.path.dirname(filename)
|
||||||
|
try:
|
||||||
|
channel_list = self._waveform_model.export_list()
|
||||||
|
pyon.store_file(filename, channel_list)
|
||||||
|
except:
|
||||||
|
logger.error("Failed to save channel list", exc_info=True)
|
||||||
|
|
||||||
|
def _process_ddb(self):
|
||||||
|
channel_list = comm_analyzer.get_channel_list(self._ddb)
|
||||||
|
self._channel_model.clear()
|
||||||
|
self._channel_model.update(channel_list)
|
||||||
|
desc = self._ddb.get("core_analyzer")
|
||||||
|
if desc is not None:
|
||||||
|
addr = desc["host"]
|
||||||
|
port_proxy = desc.get("port_proxy", 1385)
|
||||||
|
port = desc.get("port", 1386)
|
||||||
|
self.proxy_client.update_address(addr, port, port_proxy)
|
||||||
|
else:
|
||||||
|
self.proxy_client.update_address(None, None, None)
|
||||||
|
|
||||||
|
def init_ddb(self, ddb):
|
||||||
|
self._ddb = ddb
|
||||||
|
self._process_ddb()
|
||||||
|
return ddb
|
||||||
|
|
||||||
|
def notify_ddb(self, mod):
|
||||||
|
self._process_ddb()
|
||||||
|
|
||||||
|
async def stop(self):
|
||||||
|
if self.proxy_client is not None:
|
||||||
|
await self.proxy_client.close()
|
|
@ -127,7 +127,7 @@
|
||||||
"# let's connect to the master\n",
|
"# let's connect to the master\n",
|
||||||
"\n",
|
"\n",
|
||||||
"schedule, exps, datasets = [\n",
|
"schedule, exps, datasets = [\n",
|
||||||
" Client(\"::1\", 3251, \"master_\" + i) for i in\n",
|
" Client(\"::1\", 3251, i) for i in\n",
|
||||||
" \"schedule experiment_db dataset_db\".split()]\n",
|
" \"schedule experiment_db dataset_db\".split()]\n",
|
||||||
"\n",
|
"\n",
|
||||||
"print(\"current schedule\")\n",
|
"print(\"current schedule\")\n",
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"peripherals": [
|
"peripherals": [
|
||||||
{
|
{
|
||||||
"type": "shuttler",
|
"type": "shuttler",
|
||||||
|
"hw_rev": "v1.1",
|
||||||
"ports": [0]
|
"ports": [0]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
from PyQt5 import QtWidgets
|
from PyQt6 import QtWidgets
|
||||||
|
|
||||||
from artiq.applets.simple import SimpleApplet
|
from artiq.applets.simple import SimpleApplet
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
from artiq.experiment import *
|
||||||
|
|
||||||
|
|
||||||
|
class InteractiveDemo(EnvExperiment):
|
||||||
|
def build(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
print("Waiting for user input...")
|
||||||
|
with self.interactive(title="Interactive Demo") as interactive:
|
||||||
|
interactive.setattr_argument("pyon_value",
|
||||||
|
PYONValue(self.get_dataset("foo", default=42)))
|
||||||
|
interactive.setattr_argument("number", NumberValue(42e-6,
|
||||||
|
unit="us",
|
||||||
|
precision=4))
|
||||||
|
interactive.setattr_argument("integer", NumberValue(42,
|
||||||
|
step=1, precision=0))
|
||||||
|
interactive.setattr_argument("string", StringValue("Hello World"))
|
||||||
|
interactive.setattr_argument("scan", Scannable(global_max=400,
|
||||||
|
default=NoScan(325),
|
||||||
|
precision=6))
|
||||||
|
interactive.setattr_argument("boolean", BooleanValue(True), "Group")
|
||||||
|
interactive.setattr_argument("enum",
|
||||||
|
EnumerationValue(["foo", "bar", "quux"], "foo"),
|
||||||
|
"Group")
|
||||||
|
print("Done! Values:")
|
||||||
|
print(interactive.pyon_value)
|
||||||
|
print(interactive.boolean)
|
||||||
|
print(interactive.enum)
|
||||||
|
print(interactive.number, type(interactive.number))
|
||||||
|
print(interactive.integer, type(interactive.integer))
|
||||||
|
print(interactive.string)
|
||||||
|
for i in interactive.scan:
|
||||||
|
print(i)
|
|
@ -1,10 +1,50 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
# And yet, manual edits have been made. Crate yanking should be illegal.
|
||||||
|
version = 3
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "addr2line"
|
||||||
|
version = "0.16.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd"
|
||||||
|
dependencies = [
|
||||||
|
"cpp_demangle",
|
||||||
|
"fallible-iterator",
|
||||||
|
"gimli",
|
||||||
|
"object",
|
||||||
|
"rustc-demangle",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "0.2.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "adler"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ahash"
|
||||||
|
version = "0.7.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "efa60d2eadd8b12a996add391db32bd1153eac697ba4869660c0016353611426"
|
||||||
|
dependencies = [
|
||||||
|
"getrandom",
|
||||||
|
"once_cell",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
version = "0.7.18"
|
version = "0.7.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"memchr",
|
"memchr",
|
||||||
]
|
]
|
||||||
|
@ -30,9 +70,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bit_field"
|
name = "bit_field"
|
||||||
version = "0.10.1"
|
version = "0.10.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
|
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bitflags"
|
name = "bitflags"
|
||||||
|
@ -72,12 +112,30 @@ dependencies = [
|
||||||
name = "bootloader"
|
name = "bootloader"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"addr2line",
|
||||||
|
"ahash",
|
||||||
"board_misoc",
|
"board_misoc",
|
||||||
"build_misoc",
|
"build_misoc",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
|
"cc",
|
||||||
|
"compiler_builtins",
|
||||||
"crc",
|
"crc",
|
||||||
|
"dlmalloc",
|
||||||
|
"fortanix-sgx-abi",
|
||||||
|
"getopts",
|
||||||
|
"getrandom",
|
||||||
|
"hashbrown",
|
||||||
|
"hermit-abi",
|
||||||
|
"libc 0.2.99",
|
||||||
|
"miniz_oxide 0.4.0",
|
||||||
|
"object",
|
||||||
|
"once_cell",
|
||||||
"riscv",
|
"riscv",
|
||||||
|
"rustc-demangle",
|
||||||
"smoltcp",
|
"smoltcp",
|
||||||
|
"unicode-width",
|
||||||
|
"version_check",
|
||||||
|
"wasi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -98,9 +156,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.70"
|
version = "1.0.69"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
|
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
|
@ -116,9 +174,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "compiler_builtins"
|
name = "compiler_builtins"
|
||||||
version = "0.1.39"
|
version = "0.1.49"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3748f82c7d366a0b4950257d19db685d4958d2fa27c6d164a3f069fec42b748b"
|
checksum = "20b1438ef42c655665a8ab2c1c6d605a305f031d38d9be689ddfef41a20f3aa2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cpp_demangle"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crc"
|
name = "crc"
|
||||||
|
@ -129,12 +196,30 @@ dependencies = [
|
||||||
"build_const",
|
"build_const",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crc32fast"
|
||||||
|
version = "1.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cslice"
|
name = "cslice"
|
||||||
version = "0.3.0"
|
version = "0.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f8cb7306107e4b10e64994de6d3274bd08996a7c1322a27b86482392f96be0a"
|
checksum = "0f8cb7306107e4b10e64994de6d3274bd08996a7c1322a27b86482392f96be0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dlmalloc"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "332570860c2edf2d57914987bf9e24835425f75825086b6ba7d1e6a3e4f1f254"
|
||||||
|
dependencies = [
|
||||||
|
"libc 0.2.99",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dyld"
|
name = "dyld"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
@ -143,7 +228,6 @@ version = "0.0.0"
|
||||||
name = "eh"
|
name = "eh"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"compiler_builtins",
|
|
||||||
"cslice",
|
"cslice",
|
||||||
"libc 0.1.0",
|
"libc 0.1.0",
|
||||||
"unwind",
|
"unwind",
|
||||||
|
@ -166,12 +250,82 @@ dependencies = [
|
||||||
"synstructure",
|
"synstructure",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fallible-iterator"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "flate2"
|
||||||
|
version = "1.0.30"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
|
||||||
|
dependencies = [
|
||||||
|
"crc32fast",
|
||||||
|
"miniz_oxide 0.7.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fortanix-sgx-abi"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c56c422ef86062869b2d57ae87270608dc5929969dd130a6e248979cf4fb6ca6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fringe"
|
name = "fringe"
|
||||||
version = "1.2.1"
|
version = "1.2.1"
|
||||||
source = "git+https://git.m-labs.hk/M-Labs/libfringe.git?rev=3ecbe5#3ecbe53f7644b18ee46ebd5b2ca12c9cbceec43a"
|
source = "git+https://git.m-labs.hk/M-Labs/libfringe.git?rev=53a964#53a964a63d2d384b22ae1949a471a732003a30b9"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc 0.2.101",
|
"libc 0.2.99",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getopts"
|
||||||
|
version = "0.2.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getrandom"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if 0.1.10",
|
||||||
|
"libc 0.2.99",
|
||||||
|
"wasi",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "gimli"
|
||||||
|
version = "0.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
|
||||||
|
dependencies = [
|
||||||
|
"fallible-iterator",
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hashbrown"
|
||||||
|
version = "0.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "362385356d610bd1e5a408ddf8d022041774b683f345a1d2cfcb4f60f8ae2db5"
|
||||||
|
dependencies = [
|
||||||
|
"ahash",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc 0.2.99",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -212,9 +366,9 @@ version = "0.1.0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.101"
|
version = "0.2.99"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
|
@ -258,6 +412,40 @@ version = "2.4.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f"
|
||||||
|
dependencies = [
|
||||||
|
"adler 0.2.3",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "miniz_oxide"
|
||||||
|
version = "0.7.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||||
|
dependencies = [
|
||||||
|
"adler 1.0.2",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "object"
|
||||||
|
version = "0.26.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2"
|
||||||
|
dependencies = [
|
||||||
|
"flate2",
|
||||||
|
"memchr",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.8.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proto_artiq"
|
name = "proto_artiq"
|
||||||
version = "0.0.0"
|
version = "0.0.0"
|
||||||
|
@ -280,9 +468,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.5.4"
|
version = "1.7.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"aho-corasick",
|
"aho-corasick",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
@ -291,9 +479,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex-syntax"
|
name = "regex-syntax"
|
||||||
version = "0.6.25"
|
version = "0.6.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "riscv"
|
name = "riscv"
|
||||||
|
@ -342,6 +530,12 @@ dependencies = [
|
||||||
"unwind_backtrace",
|
"unwind_backtrace",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rustc-demangle"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustc_version"
|
name = "rustc_version"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
|
@ -382,6 +576,12 @@ version = "0.7.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "smallvec"
|
||||||
|
version = "1.13.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smoltcp"
|
name = "smoltcp"
|
||||||
version = "0.8.2"
|
version = "0.8.2"
|
||||||
|
@ -393,6 +593,12 @@ dependencies = [
|
||||||
"managed 0.8.0",
|
"managed 0.8.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "0.11.11"
|
version = "0.11.11"
|
||||||
|
@ -433,6 +639,12 @@ dependencies = [
|
||||||
"log",
|
"log",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.1.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-xid"
|
name = "unicode-xid"
|
||||||
version = "0.0.4"
|
version = "0.0.4"
|
||||||
|
@ -454,3 +666,15 @@ dependencies = [
|
||||||
"libc 0.1.0",
|
"libc 0.1.0",
|
||||||
"unwind",
|
"unwind",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "version_check"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "wasi"
|
||||||
|
version = "0.9.0+wasi-snapshot-preview1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||||
|
|
|
@ -13,8 +13,30 @@ path = "main.rs"
|
||||||
build_misoc = { path = "../libbuild_misoc" }
|
build_misoc = { path = "../libbuild_misoc" }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
byteorder = { version = "1.0", default-features = false }
|
byteorder = { version = "=1.4.3", default-features = false }
|
||||||
crc = { version = "1.7", default-features = false }
|
crc = { version = "1.7", default-features = false }
|
||||||
board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp"] }
|
board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp"] }
|
||||||
smoltcp = { version = "0.8.2", default-features = false, features = ["medium-ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp"] }
|
smoltcp = { version = "0.8.2", default-features = false, features = ["medium-ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp"] }
|
||||||
riscv = { version = "0.6.0", features = ["inline-asm"] }
|
riscv = { version = "0.6.0", features = ["inline-asm"] }
|
||||||
|
|
||||||
|
# insanity required by using cargo build-std over xbuild with nix
|
||||||
|
# cargo update does not work thanks to ahash 0.7 problems
|
||||||
|
[dev-dependencies]
|
||||||
|
getopts = "=0.2.21"
|
||||||
|
libc = "=0.2.99"
|
||||||
|
unicode-width = "=0.1.8"
|
||||||
|
addr2line = "=0.16.0"
|
||||||
|
hashbrown = "=0.11.0" # must be injected into lockfile manually
|
||||||
|
ahash = "=0.7.0" # must be injected into lockfile manually
|
||||||
|
miniz_oxide = "=0.4.0"
|
||||||
|
rustc-demangle = "=0.1.21"
|
||||||
|
hermit-abi = "=0.1.19"
|
||||||
|
dlmalloc = "=0.2.1"
|
||||||
|
fortanix-sgx-abi = "=0.3.3"
|
||||||
|
cc = "=1.0.69"
|
||||||
|
compiler_builtins = "=0.1.49"
|
||||||
|
version_check = "=0.9.3"
|
||||||
|
once_cell = "=1.8.0"
|
||||||
|
wasi = "=0.9.0"
|
||||||
|
getrandom = "=0.2.0"
|
||||||
|
object = "=0.26.2"
|
|
@ -3,8 +3,6 @@ include $(MISOC_DIRECTORY)/software/common.mak
|
||||||
|
|
||||||
RUSTFLAGS += -Cpanic=abort
|
RUSTFLAGS += -Cpanic=abort
|
||||||
|
|
||||||
export XBUILD_SYSROOT_PATH=$(BUILDINC_DIRECTORY)/../sysroot
|
|
||||||
|
|
||||||
all:: bootloader.bin
|
all:: bootloader.bin
|
||||||
|
|
||||||
.PHONY: $(RUSTOUT)/libbootloader.a
|
.PHONY: $(RUSTOUT)/libbootloader.a
|
||||||
|
|
|
@ -500,7 +500,7 @@ pub extern fn main() -> i32 {
|
||||||
println!(r"|_| |_|_|____/ \___/ \____|");
|
println!(r"|_| |_|_|____/ \___/ \____|");
|
||||||
println!("");
|
println!("");
|
||||||
println!("MiSoC Bootloader");
|
println!("MiSoC Bootloader");
|
||||||
println!("Copyright (c) 2017-2023 M-Labs Limited");
|
println!("Copyright (c) 2017-2024 M-Labs Limited");
|
||||||
println!("");
|
println!("");
|
||||||
|
|
||||||
#[cfg(has_ethmac)]
|
#[cfg(has_ethmac)]
|
||||||
|
|
|
@ -14,8 +14,6 @@ LDFLAGS += --eh-frame-hdr \
|
||||||
|
|
||||||
RUSTFLAGS += -Cpanic=unwind
|
RUSTFLAGS += -Cpanic=unwind
|
||||||
|
|
||||||
export XBUILD_SYSROOT_PATH=$(BUILDINC_DIRECTORY)/../sysroot
|
|
||||||
|
|
||||||
all:: ksupport.elf
|
all:: ksupport.elf
|
||||||
|
|
||||||
.PHONY: $(RUSTOUT)/libksupport.a
|
.PHONY: $(RUSTOUT)/libksupport.a
|
||||||
|
|
|
@ -105,6 +105,7 @@ static mut API: &'static [(&'static str, *const ())] = &[
|
||||||
api!(nextafter),
|
api!(nextafter),
|
||||||
api!(pow),
|
api!(pow),
|
||||||
api!(round),
|
api!(round),
|
||||||
|
api!(rint),
|
||||||
api!(sin),
|
api!(sin),
|
||||||
api!(sinh),
|
api!(sinh),
|
||||||
api!(sqrt),
|
api!(sqrt),
|
||||||
|
|
|
@ -55,12 +55,14 @@ struct ExceptionBuffer {
|
||||||
exception_count: usize,
|
exception_count: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EXCEPTION: uw::_Unwind_Exception = uw::_Unwind_Exception {
|
||||||
|
exception_class: EXCEPTION_CLASS,
|
||||||
|
exception_cleanup: cleanup,
|
||||||
|
private: [0; uw::unwinder_private_data_size],
|
||||||
|
};
|
||||||
|
|
||||||
static mut EXCEPTION_BUFFER: ExceptionBuffer = ExceptionBuffer {
|
static mut EXCEPTION_BUFFER: ExceptionBuffer = ExceptionBuffer {
|
||||||
uw_exceptions: [uw::_Unwind_Exception {
|
uw_exceptions: [EXCEPTION; MAX_INFLIGHT_EXCEPTIONS],
|
||||||
exception_class: EXCEPTION_CLASS,
|
|
||||||
exception_cleanup: cleanup,
|
|
||||||
private: [0; uw::unwinder_private_data_size],
|
|
||||||
}; MAX_INFLIGHT_EXCEPTIONS],
|
|
||||||
exceptions: [None; MAX_INFLIGHT_EXCEPTIONS + 1],
|
exceptions: [None; MAX_INFLIGHT_EXCEPTIONS + 1],
|
||||||
exception_stack: [-1; MAX_INFLIGHT_EXCEPTIONS + 1],
|
exception_stack: [-1; MAX_INFLIGHT_EXCEPTIONS + 1],
|
||||||
backtrace: [(0, 0); MAX_BACKTRACE_SIZE],
|
backtrace: [(0, 0); MAX_BACKTRACE_SIZE],
|
||||||
|
@ -74,11 +76,7 @@ static mut EXCEPTION_BUFFER: ExceptionBuffer = ExceptionBuffer {
|
||||||
};
|
};
|
||||||
|
|
||||||
pub unsafe extern fn reset_exception_buffer(payload_addr: usize) {
|
pub unsafe extern fn reset_exception_buffer(payload_addr: usize) {
|
||||||
EXCEPTION_BUFFER.uw_exceptions = [uw::_Unwind_Exception {
|
EXCEPTION_BUFFER.uw_exceptions = [EXCEPTION; MAX_INFLIGHT_EXCEPTIONS];
|
||||||
exception_class: EXCEPTION_CLASS,
|
|
||||||
exception_cleanup: cleanup,
|
|
||||||
private: [0; uw::unwinder_private_data_size],
|
|
||||||
}; MAX_INFLIGHT_EXCEPTIONS];
|
|
||||||
EXCEPTION_BUFFER.exceptions = [None; MAX_INFLIGHT_EXCEPTIONS + 1];
|
EXCEPTION_BUFFER.exceptions = [None; MAX_INFLIGHT_EXCEPTIONS + 1];
|
||||||
EXCEPTION_BUFFER.exception_stack = [-1; MAX_INFLIGHT_EXCEPTIONS + 1];
|
EXCEPTION_BUFFER.exception_stack = [-1; MAX_INFLIGHT_EXCEPTIONS + 1];
|
||||||
EXCEPTION_BUFFER.backtrace_size = 0;
|
EXCEPTION_BUFFER.backtrace_size = 0;
|
||||||
|
@ -151,8 +149,7 @@ pub extern fn personality(version: c_int,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[export_name="__artiq_raise"]
|
#[export_name="__artiq_raise"]
|
||||||
#[unwind(allowed)]
|
pub unsafe extern "C-unwind" fn raise(exception: *const Exception) -> ! {
|
||||||
pub unsafe extern fn raise(exception: *const Exception) -> ! {
|
|
||||||
let count = EXCEPTION_BUFFER.exception_count;
|
let count = EXCEPTION_BUFFER.exception_count;
|
||||||
let stack = &mut EXCEPTION_BUFFER.exception_stack;
|
let stack = &mut EXCEPTION_BUFFER.exception_stack;
|
||||||
let diff = exception as isize - EXCEPTION_BUFFER.exceptions.as_ptr() as isize;
|
let diff = exception as isize - EXCEPTION_BUFFER.exceptions.as_ptr() as isize;
|
||||||
|
@ -222,8 +219,7 @@ pub unsafe extern fn raise(exception: *const Exception) -> ! {
|
||||||
|
|
||||||
|
|
||||||
#[export_name="__artiq_resume"]
|
#[export_name="__artiq_resume"]
|
||||||
#[unwind(allowed)]
|
pub unsafe extern "C-unwind" fn resume() -> ! {
|
||||||
pub unsafe extern fn resume() -> ! {
|
|
||||||
assert!(EXCEPTION_BUFFER.exception_count != 0);
|
assert!(EXCEPTION_BUFFER.exception_count != 0);
|
||||||
let i = EXCEPTION_BUFFER.exception_stack[EXCEPTION_BUFFER.exception_count - 1];
|
let i = EXCEPTION_BUFFER.exception_stack[EXCEPTION_BUFFER.exception_count - 1];
|
||||||
assert!(i != -1);
|
assert!(i != -1);
|
||||||
|
@ -233,8 +229,7 @@ pub unsafe extern fn resume() -> ! {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[export_name="__artiq_end_catch"]
|
#[export_name="__artiq_end_catch"]
|
||||||
#[unwind(allowed)]
|
pub unsafe extern "C-unwind" fn end_catch() {
|
||||||
pub unsafe extern fn end_catch() {
|
|
||||||
let mut count = EXCEPTION_BUFFER.exception_count;
|
let mut count = EXCEPTION_BUFFER.exception_count;
|
||||||
assert!(count != 0);
|
assert!(count != 0);
|
||||||
// we remove all exceptions with SP <= current exception SP
|
// we remove all exceptions with SP <= current exception SP
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![feature(lang_items, llvm_asm, panic_unwind, libc, unwind_attributes,
|
#![feature(lang_items, asm, panic_unwind, libc,
|
||||||
panic_info_message, nll, const_in_array_repeat_expressions)]
|
panic_info_message, nll, c_unwind)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
extern crate libc;
|
extern crate libc;
|
||||||
|
@ -30,8 +30,9 @@ fn send(request: &Message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recv<R, F: FnOnce(&Message) -> R>(f: F) -> R {
|
fn recv<R, F: FnOnce(&Message) -> R>(f: F) -> R {
|
||||||
while mailbox::receive() == 0 {}
|
let mut msg_ptr = 0;
|
||||||
let result = f(unsafe { &*(mailbox::receive() as *const Message) });
|
while msg_ptr == 0 { msg_ptr = mailbox::receive(); }
|
||||||
|
let result = f(unsafe { &*(msg_ptr as *const Message) });
|
||||||
mailbox::acknowledge();
|
mailbox::acknowledge();
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@ -121,7 +122,6 @@ pub extern fn send_to_rtio_log(text: CSlice<u8>) {
|
||||||
rtio::log(text.as_ref())
|
rtio::log(text.as_ref())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(aborts)]
|
|
||||||
extern fn rpc_send(service: u32, tag: &CSlice<u8>, data: *const *const ()) {
|
extern fn rpc_send(service: u32, tag: &CSlice<u8>, data: *const *const ()) {
|
||||||
while !rpc_queue::empty() {}
|
while !rpc_queue::empty() {}
|
||||||
send(&RpcSend {
|
send(&RpcSend {
|
||||||
|
@ -132,7 +132,6 @@ extern fn rpc_send(service: u32, tag: &CSlice<u8>, data: *const *const ()) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(aborts)]
|
|
||||||
extern fn rpc_send_async(service: u32, tag: &CSlice<u8>, data: *const *const ()) {
|
extern fn rpc_send_async(service: u32, tag: &CSlice<u8>, data: *const *const ()) {
|
||||||
while rpc_queue::full() {}
|
while rpc_queue::full() {}
|
||||||
rpc_queue::enqueue(|mut slice| {
|
rpc_queue::enqueue(|mut slice| {
|
||||||
|
@ -170,8 +169,7 @@ extern fn rpc_send_async(service: u32, tag: &CSlice<u8>, data: *const *const ())
|
||||||
/// to the maximum required for any of the possible types according to the target ABI).
|
/// to the maximum required for any of the possible types according to the target ABI).
|
||||||
///
|
///
|
||||||
/// If the RPC call resulted in an exception, it is reconstructed and raised.
|
/// If the RPC call resulted in an exception, it is reconstructed and raised.
|
||||||
#[unwind(allowed)]
|
extern "C-unwind" fn rpc_recv(slot: *mut ()) -> usize {
|
||||||
extern fn rpc_recv(slot: *mut ()) -> usize {
|
|
||||||
send(&RpcRecvRequest(slot));
|
send(&RpcRecvRequest(slot));
|
||||||
recv!(&RpcRecvReply(ref result) => {
|
recv!(&RpcRecvReply(ref result) => {
|
||||||
match result {
|
match result {
|
||||||
|
@ -203,7 +201,6 @@ fn terminate(exceptions: &'static [Option<eh_artiq::Exception<'static>>],
|
||||||
loop {}
|
loop {}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(aborts)]
|
|
||||||
extern fn cache_get<'a>(key: &CSlice<u8>) -> *const CSlice<'a, i32> {
|
extern fn cache_get<'a>(key: &CSlice<u8>) -> *const CSlice<'a, i32> {
|
||||||
send(&CacheGetRequest {
|
send(&CacheGetRequest {
|
||||||
key: str::from_utf8(key.as_ref()).unwrap()
|
key: str::from_utf8(key.as_ref()).unwrap()
|
||||||
|
@ -213,8 +210,7 @@ extern fn cache_get<'a>(key: &CSlice<u8>) -> *const CSlice<'a, i32> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(allowed)]
|
extern "C-unwind" fn cache_put(key: &CSlice<u8>, list: &CSlice<i32>) {
|
||||||
extern fn cache_put(key: &CSlice<u8>, list: &CSlice<i32>) {
|
|
||||||
send(&CachePutRequest {
|
send(&CachePutRequest {
|
||||||
key: str::from_utf8(key.as_ref()).unwrap(),
|
key: str::from_utf8(key.as_ref()).unwrap(),
|
||||||
value: list.as_ref()
|
value: list.as_ref()
|
||||||
|
@ -247,8 +243,7 @@ fn dma_record_flush() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(allowed)]
|
extern "C-unwind" fn dma_record_start(name: &CSlice<u8>) {
|
||||||
extern fn dma_record_start(name: &CSlice<u8>) {
|
|
||||||
let name = str::from_utf8(name.as_ref()).unwrap();
|
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -267,8 +262,7 @@ extern fn dma_record_start(name: &CSlice<u8>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(allowed)]
|
extern "C-unwind" fn dma_record_stop(duration: i64, enable_ddma: bool) {
|
||||||
extern fn dma_record_stop(duration: i64, enable_ddma: bool) {
|
|
||||||
unsafe {
|
unsafe {
|
||||||
dma_record_flush();
|
dma_record_flush();
|
||||||
|
|
||||||
|
@ -290,7 +284,6 @@ extern fn dma_record_stop(duration: i64, enable_ddma: bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(aborts)]
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
unsafe fn dma_record_output_prepare(timestamp: i64, target: i32,
|
unsafe fn dma_record_output_prepare(timestamp: i64, target: i32,
|
||||||
words: usize) -> &'static mut [u8] {
|
words: usize) -> &'static mut [u8] {
|
||||||
|
@ -327,7 +320,6 @@ unsafe fn dma_record_output_prepare(timestamp: i64, target: i32,
|
||||||
data
|
data
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(aborts)]
|
|
||||||
extern fn dma_record_output(target: i32, word: i32) {
|
extern fn dma_record_output(target: i32, word: i32) {
|
||||||
unsafe {
|
unsafe {
|
||||||
let timestamp = ((csr::rtio::now_hi_read() as i64) << 32) | (csr::rtio::now_lo_read() as i64);
|
let timestamp = ((csr::rtio::now_hi_read() as i64) << 32) | (csr::rtio::now_lo_read() as i64);
|
||||||
|
@ -341,7 +333,6 @@ extern fn dma_record_output(target: i32, word: i32) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(aborts)]
|
|
||||||
extern fn dma_record_output_wide(target: i32, words: &CSlice<i32>) {
|
extern fn dma_record_output_wide(target: i32, words: &CSlice<i32>) {
|
||||||
assert!(words.len() <= 16); // enforce the hardware limit
|
assert!(words.len() <= 16); // enforce the hardware limit
|
||||||
|
|
||||||
|
@ -360,7 +351,6 @@ extern fn dma_record_output_wide(target: i32, words: &CSlice<i32>) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(aborts)]
|
|
||||||
extern fn dma_erase(name: &CSlice<u8>) {
|
extern fn dma_erase(name: &CSlice<u8>) {
|
||||||
let name = str::from_utf8(name.as_ref()).unwrap();
|
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||||
|
|
||||||
|
@ -374,8 +364,7 @@ struct DmaTrace {
|
||||||
uses_ddma: bool,
|
uses_ddma: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(allowed)]
|
extern "C-unwind" fn dma_retrieve(name: &CSlice<u8>) -> DmaTrace {
|
||||||
extern fn dma_retrieve(name: &CSlice<u8>) -> DmaTrace {
|
|
||||||
let name = str::from_utf8(name.as_ref()).unwrap();
|
let name = str::from_utf8(name.as_ref()).unwrap();
|
||||||
|
|
||||||
send(&DmaRetrieveRequest { name: name });
|
send(&DmaRetrieveRequest { name: name });
|
||||||
|
@ -396,8 +385,7 @@ extern fn dma_retrieve(name: &CSlice<u8>) -> DmaTrace {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(kernel_has_rtio_dma)]
|
#[cfg(kernel_has_rtio_dma)]
|
||||||
#[unwind(allowed)]
|
extern "C-unwind" fn dma_playback(timestamp: i64, ptr: i32, _uses_ddma: bool) {
|
||||||
extern fn dma_playback(timestamp: i64, ptr: i32, _uses_ddma: bool) {
|
|
||||||
assert!(ptr % 64 == 0);
|
assert!(ptr % 64 == 0);
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
@ -453,15 +441,39 @@ extern fn dma_playback(timestamp: i64, ptr: i32, _uses_ddma: bool) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(kernel_has_rtio_dma))]
|
#[cfg(all(not(kernel_has_rtio_dma), not(has_rtio_dma)))]
|
||||||
#[unwind(allowed)]
|
extern "C-unwind" fn dma_playback(_timestamp: i64, _ptr: i32, _uses_ddma: bool) {
|
||||||
extern fn dma_playback(_timestamp: i64, _ptr: i32, _uses_ddma: bool) {
|
|
||||||
unimplemented!("not(kernel_has_rtio_dma)")
|
unimplemented!("not(kernel_has_rtio_dma)")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(allowed)]
|
// for satellite (has_rtio_dma but not in kernel)
|
||||||
extern fn subkernel_load_run(id: u32, run: bool) {
|
#[cfg(all(not(kernel_has_rtio_dma), has_rtio_dma))]
|
||||||
send(&SubkernelLoadRunRequest { id: id, run: run });
|
extern "C-unwind" fn dma_playback(timestamp: i64, ptr: i32, _uses_ddma: bool) {
|
||||||
|
// DDMA is always used on satellites, so the `uses_ddma` setting is ignored
|
||||||
|
// StartRemoteRequest reused as "normal" start request
|
||||||
|
send(&DmaStartRemoteRequest { id: ptr as i32, timestamp: timestamp });
|
||||||
|
// skip awaitremoterequest - it's a given
|
||||||
|
recv!(&DmaAwaitRemoteReply { timeout, error, channel, timestamp } => {
|
||||||
|
if timeout {
|
||||||
|
raise!("DMAError",
|
||||||
|
"Error running DMA on satellite device, timed out waiting for results");
|
||||||
|
}
|
||||||
|
if error & 1 != 0 {
|
||||||
|
raise!("RTIOUnderflow",
|
||||||
|
"RTIO underflow at channel {rtio_channel_info:0}, {1} mu",
|
||||||
|
channel as i64, timestamp as i64, 0);
|
||||||
|
}
|
||||||
|
if error & 2 != 0 {
|
||||||
|
raise!("RTIODestinationUnreachable",
|
||||||
|
"RTIO destination unreachable, output, at channel {rtio_channel_info:0}, {1} mu",
|
||||||
|
channel as i64, timestamp as i64, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern "C-unwind" fn subkernel_load_run(id: u32, destination: u8, run: bool) {
|
||||||
|
send(&SubkernelLoadRunRequest { id: id, destination: destination, run: run });
|
||||||
recv!(&SubkernelLoadRunReply { succeeded } => {
|
recv!(&SubkernelLoadRunReply { succeeded } => {
|
||||||
if !succeeded {
|
if !succeeded {
|
||||||
raise!("SubkernelError",
|
raise!("SubkernelError",
|
||||||
|
@ -470,54 +482,64 @@ extern fn subkernel_load_run(id: u32, run: bool) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(allowed)]
|
extern "C-unwind" fn subkernel_await_finish(id: u32, timeout: i64) {
|
||||||
extern fn subkernel_await_finish(id: u32, timeout: u64) {
|
|
||||||
send(&SubkernelAwaitFinishRequest { id: id, timeout: timeout });
|
send(&SubkernelAwaitFinishRequest { id: id, timeout: timeout });
|
||||||
recv!(SubkernelAwaitFinishReply { status } => {
|
recv(move |request| {
|
||||||
match status {
|
if let SubkernelAwaitFinishReply = request { }
|
||||||
SubkernelStatus::NoError => (),
|
else if let SubkernelError(status) = request {
|
||||||
SubkernelStatus::IncorrectState => raise!("SubkernelError",
|
match status {
|
||||||
"Subkernel not running"),
|
SubkernelStatus::IncorrectState => raise!("SubkernelError",
|
||||||
SubkernelStatus::Timeout => raise!("SubkernelError",
|
"Subkernel not running"),
|
||||||
"Subkernel timed out"),
|
SubkernelStatus::Timeout => raise!("SubkernelError",
|
||||||
SubkernelStatus::CommLost => raise!("SubkernelError",
|
"Subkernel timed out"),
|
||||||
"Lost communication with satellite"),
|
SubkernelStatus::CommLost => raise!("SubkernelError",
|
||||||
SubkernelStatus::OtherError => raise!("SubkernelError",
|
"Lost communication with satellite"),
|
||||||
"An error occurred during subkernel operation")
|
SubkernelStatus::OtherError => raise!("SubkernelError",
|
||||||
|
"An error occurred during subkernel operation"),
|
||||||
|
SubkernelStatus::Exception(e) => unsafe { crate::eh_artiq::raise(e) },
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
send(&Log(format_args!("unexpected reply: {:?}\n", request)));
|
||||||
|
loop {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(aborts)]
|
extern fn subkernel_send_message(id: u32, is_return: bool, destination: u8,
|
||||||
extern fn subkernel_send_message(id: u32, count: u8, tag: &CSlice<u8>, data: *const *const ()) {
|
count: u8, tag: &CSlice<u8>, data: *const *const ()) {
|
||||||
send(&SubkernelMsgSend {
|
send(&SubkernelMsgSend {
|
||||||
id: id,
|
id: id,
|
||||||
|
destination: if is_return { None } else { Some(destination) },
|
||||||
count: count,
|
count: count,
|
||||||
tag: tag.as_ref(),
|
tag: tag.as_ref(),
|
||||||
data: data
|
data: data
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[unwind(allowed)]
|
extern "C-unwind" fn subkernel_await_message(id: i32, timeout: i64, tags: &CSlice<u8>, min: u8, max: u8) -> u8 {
|
||||||
extern fn subkernel_await_message(id: u32, timeout: u64, tags: &CSlice<u8>, min: u8, max: u8) -> u8 {
|
|
||||||
send(&SubkernelMsgRecvRequest { id: id, timeout: timeout, tags: tags.as_ref() });
|
send(&SubkernelMsgRecvRequest { id: id, timeout: timeout, tags: tags.as_ref() });
|
||||||
recv!(SubkernelMsgRecvReply { status, count } => {
|
recv(move |request| {
|
||||||
match status {
|
if let SubkernelMsgRecvReply { count } = request {
|
||||||
SubkernelStatus::NoError => {
|
if count < &min || count > &max {
|
||||||
if count < &min || count > &max {
|
raise!("SubkernelError",
|
||||||
raise!("SubkernelError",
|
"Received less or more arguments than expected");
|
||||||
"Received less or more arguments than expected");
|
|
||||||
}
|
|
||||||
*count
|
|
||||||
}
|
}
|
||||||
SubkernelStatus::IncorrectState => raise!("SubkernelError",
|
*count
|
||||||
"Subkernel not running"),
|
} else if let SubkernelError(status) = request {
|
||||||
SubkernelStatus::Timeout => raise!("SubkernelError",
|
match status {
|
||||||
"Subkernel timed out"),
|
SubkernelStatus::IncorrectState => raise!("SubkernelError",
|
||||||
SubkernelStatus::CommLost => raise!("SubkernelError",
|
"Subkernel not running"),
|
||||||
"Lost communication with satellite"),
|
SubkernelStatus::Timeout => raise!("SubkernelError",
|
||||||
SubkernelStatus::OtherError => raise!("SubkernelError",
|
"Subkernel timed out"),
|
||||||
"An error occurred during subkernel operation")
|
SubkernelStatus::CommLost => raise!("SubkernelError",
|
||||||
|
"Lost communication with satellite"),
|
||||||
|
SubkernelStatus::OtherError => raise!("SubkernelError",
|
||||||
|
"An error occurred during subkernel operation"),
|
||||||
|
SubkernelStatus::Exception(e) => unsafe { crate::eh_artiq::raise(e) },
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
send(&Log(format_args!("unexpected reply: {:?}\n", request)));
|
||||||
|
loop {}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// RpcRecvRequest should be called `count` times after this to receive message data
|
// RpcRecvRequest should be called `count` times after this to receive message data
|
||||||
|
@ -624,8 +646,7 @@ pub unsafe fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[unwind(allowed)]
|
pub unsafe extern "C-unwind" fn exception(_regs: *const u32) {
|
||||||
pub unsafe extern fn exception(_regs: *const u32) {
|
|
||||||
let pc = mepc::read();
|
let pc = mepc::read();
|
||||||
let cause = mcause::read().cause();
|
let cause = mcause::read().cause();
|
||||||
let mtval = mtval::read();
|
let mtval = mtval::read();
|
||||||
|
@ -643,7 +664,6 @@ pub unsafe extern fn exception(_regs: *const u32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
#[unwind(allowed)]
|
pub extern "C-unwind" fn abort() {
|
||||||
pub extern fn abort() {
|
|
||||||
panic!("aborted")
|
panic!("aborted")
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,3 +25,4 @@ proto_artiq = { path = "../libproto_artiq" }
|
||||||
[features]
|
[features]
|
||||||
uart_console = []
|
uart_console = []
|
||||||
alloc = []
|
alloc = []
|
||||||
|
calibrate_wrpll_skew = []
|
||||||
|
|
|
@ -69,9 +69,9 @@ fn receive<F, T>(linkno: u8, f: F) -> Result<Option<T>, Error<!>>
|
||||||
let linkidx = linkno as usize;
|
let linkidx = linkno as usize;
|
||||||
unsafe {
|
unsafe {
|
||||||
if (DRTIOAUX[linkidx].aux_rx_present_read)() == 1 {
|
if (DRTIOAUX[linkidx].aux_rx_present_read)() == 1 {
|
||||||
let ptr = DRTIOAUX_MEM[linkidx].base + DRTIOAUX_MEM[linkidx].size / 2;
|
let read_ptr = (DRTIOAUX[linkidx].aux_read_pointer_read)() as usize;
|
||||||
let len = (DRTIOAUX[linkidx].aux_rx_length_read)();
|
let ptr = DRTIOAUX_MEM[linkidx].base + (DRTIOAUX_MEM[linkidx].size / 2) + (read_ptr * 0x400);
|
||||||
let result = f(slice::from_raw_parts(ptr as *mut u8, len as usize));
|
let result = f(slice::from_raw_parts(ptr as *mut u8, 0x400 as usize));
|
||||||
(DRTIOAUX[linkidx].aux_rx_present_write)(1);
|
(DRTIOAUX[linkidx].aux_rx_present_write)(1);
|
||||||
Ok(Some(result?))
|
Ok(Some(result?))
|
||||||
} else {
|
} else {
|
||||||
|
@ -86,21 +86,17 @@ pub fn recv(linkno: u8) -> Result<Option<Packet>, Error<!>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
receive(linkno, |buffer| {
|
receive(linkno, |buffer| {
|
||||||
if buffer.len() < 8 {
|
|
||||||
return Err(IoError::UnexpectedEnd.into())
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut reader = Cursor::new(buffer);
|
let mut reader = Cursor::new(buffer);
|
||||||
|
|
||||||
let checksum_at = buffer.len() - 4;
|
let packet = Packet::read_from(&mut reader)?;
|
||||||
|
let padding = (12 - (reader.position() % 8)) % 8;
|
||||||
|
let checksum_at = reader.position() + padding;
|
||||||
let checksum = crc::crc32::checksum_ieee(&reader.get_ref()[0..checksum_at]);
|
let checksum = crc::crc32::checksum_ieee(&reader.get_ref()[0..checksum_at]);
|
||||||
reader.set_position(checksum_at);
|
reader.set_position(checksum_at);
|
||||||
if reader.read_u32()? != checksum {
|
if reader.read_u32()? != checksum {
|
||||||
return Err(Error::CorruptedPacket)
|
return Err(Error::CorruptedPacket)
|
||||||
}
|
}
|
||||||
reader.set_position(0);
|
Ok(packet)
|
||||||
|
|
||||||
Ok(Packet::read_from(&mut reader)?)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![feature(lang_items, never_type)]
|
#![feature(asm, lang_items, never_type)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
extern crate failure;
|
extern crate failure;
|
||||||
|
@ -24,6 +24,8 @@ pub mod rpc_queue;
|
||||||
|
|
||||||
#[cfg(has_si5324)]
|
#[cfg(has_si5324)]
|
||||||
pub mod si5324;
|
pub mod si5324;
|
||||||
|
#[cfg(has_si549)]
|
||||||
|
pub mod si549;
|
||||||
|
|
||||||
#[cfg(has_grabber)]
|
#[cfg(has_grabber)]
|
||||||
pub mod grabber;
|
pub mod grabber;
|
||||||
|
|
|
@ -6,7 +6,11 @@ static mut LAST: usize = 0;
|
||||||
|
|
||||||
pub unsafe fn send(data: usize) {
|
pub unsafe fn send(data: usize) {
|
||||||
LAST = data;
|
LAST = data;
|
||||||
write_volatile(MAILBOX, data)
|
// after Rust toolchain update to LLVM12, this empty asm! block is required
|
||||||
|
// to ensure that the compiler doesn't take any shortcuts
|
||||||
|
// otherwise, the comm CPU will read garbage data and crash
|
||||||
|
asm!("", options(preserves_flags, readonly, nostack));
|
||||||
|
write_volatile(MAILBOX, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn acknowledged() -> bool {
|
pub fn acknowledged() -> bool {
|
||||||
|
|
|
@ -0,0 +1,862 @@
|
||||||
|
use board_misoc::{clock, csr};
|
||||||
|
use log::info;
|
||||||
|
|
||||||
|
const ADDRESS: u8 = 0x67;
|
||||||
|
|
||||||
|
const ADPLL_MAX: i32 = (950.0 / 0.0001164) as i32;
|
||||||
|
|
||||||
|
pub struct DividerConfig {
|
||||||
|
pub hsdiv: u16,
|
||||||
|
pub lsdiv: u8,
|
||||||
|
pub fbdiv: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FrequencySetting {
|
||||||
|
pub main: DividerConfig,
|
||||||
|
pub helper: DividerConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
mod i2c {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum DCXO {
|
||||||
|
Main,
|
||||||
|
Helper,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn half_period() {
|
||||||
|
clock::spin_us(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sda_i(dcxo: DCXO) -> bool {
|
||||||
|
match dcxo {
|
||||||
|
DCXO::Main => unsafe { csr::wrpll::main_dcxo_sda_in_read() == 1 },
|
||||||
|
DCXO::Helper => unsafe { csr::wrpll::helper_dcxo_sda_in_read() == 1 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sda_oe(dcxo: DCXO, oe: bool) {
|
||||||
|
let val = if oe { 1 } else { 0 };
|
||||||
|
match dcxo {
|
||||||
|
DCXO::Main => unsafe { csr::wrpll::main_dcxo_sda_oe_write(val) },
|
||||||
|
DCXO::Helper => unsafe { csr::wrpll::helper_dcxo_sda_oe_write(val) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sda_o(dcxo: DCXO, o: bool) {
|
||||||
|
let val = if o { 1 } else { 0 };
|
||||||
|
match dcxo {
|
||||||
|
DCXO::Main => unsafe { csr::wrpll::main_dcxo_sda_out_write(val) },
|
||||||
|
DCXO::Helper => unsafe { csr::wrpll::helper_dcxo_sda_out_write(val) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scl_oe(dcxo: DCXO, oe: bool) {
|
||||||
|
let val = if oe { 1 } else { 0 };
|
||||||
|
match dcxo {
|
||||||
|
DCXO::Main => unsafe { csr::wrpll::main_dcxo_scl_oe_write(val) },
|
||||||
|
DCXO::Helper => unsafe { csr::wrpll::helper_dcxo_scl_oe_write(val) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scl_o(dcxo: DCXO, o: bool) {
|
||||||
|
let val = if o { 1 } else { 0 };
|
||||||
|
match dcxo {
|
||||||
|
DCXO::Main => unsafe { csr::wrpll::main_dcxo_scl_out_write(val) },
|
||||||
|
DCXO::Helper => unsafe { csr::wrpll::helper_dcxo_scl_out_write(val) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init(dcxo: DCXO) -> Result<(), &'static str> {
|
||||||
|
// Set SCL as output, and high level
|
||||||
|
scl_o(dcxo, true);
|
||||||
|
scl_oe(dcxo, true);
|
||||||
|
// Prepare a zero level on SDA so that sda_oe pulls it down
|
||||||
|
sda_o(dcxo, false);
|
||||||
|
// Release SDA
|
||||||
|
sda_oe(dcxo, false);
|
||||||
|
|
||||||
|
// Check the I2C bus is ready
|
||||||
|
half_period();
|
||||||
|
half_period();
|
||||||
|
if !sda_i(dcxo) {
|
||||||
|
// Try toggling SCL a few times
|
||||||
|
for _bit in 0..8 {
|
||||||
|
scl_o(dcxo, false);
|
||||||
|
half_period();
|
||||||
|
scl_o(dcxo, true);
|
||||||
|
half_period();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sda_i(dcxo) {
|
||||||
|
return Err("SDA is stuck low and doesn't get unstuck");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn start(dcxo: DCXO) {
|
||||||
|
// Set SCL high then SDA low
|
||||||
|
scl_o(dcxo, true);
|
||||||
|
half_period();
|
||||||
|
sda_oe(dcxo, true);
|
||||||
|
half_period();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn stop(dcxo: DCXO) {
|
||||||
|
// First, make sure SCL is low, so that the target releases the SDA line
|
||||||
|
scl_o(dcxo, false);
|
||||||
|
half_period();
|
||||||
|
// Set SCL high then SDA high
|
||||||
|
sda_oe(dcxo, true);
|
||||||
|
scl_o(dcxo, true);
|
||||||
|
half_period();
|
||||||
|
sda_oe(dcxo, false);
|
||||||
|
half_period();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(dcxo: DCXO, data: u8) -> bool {
|
||||||
|
// MSB first
|
||||||
|
for bit in (0..8).rev() {
|
||||||
|
// Set SCL low and set our bit on SDA
|
||||||
|
scl_o(dcxo, false);
|
||||||
|
sda_oe(dcxo, data & (1 << bit) == 0);
|
||||||
|
half_period();
|
||||||
|
// Set SCL high ; data is shifted on the rising edge of SCL
|
||||||
|
scl_o(dcxo, true);
|
||||||
|
half_period();
|
||||||
|
}
|
||||||
|
// Check ack
|
||||||
|
// Set SCL low, then release SDA so that the I2C target can respond
|
||||||
|
scl_o(dcxo, false);
|
||||||
|
half_period();
|
||||||
|
sda_oe(dcxo, false);
|
||||||
|
// Set SCL high and check for ack
|
||||||
|
scl_o(dcxo, true);
|
||||||
|
half_period();
|
||||||
|
// returns true if acked (I2C target pulled SDA low)
|
||||||
|
!sda_i(dcxo)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read(dcxo: DCXO, ack: bool) -> u8 {
|
||||||
|
// Set SCL low first, otherwise setting SDA as input may cause a transition
|
||||||
|
// on SDA with SCL high which will be interpreted as START/STOP condition.
|
||||||
|
scl_o(dcxo, false);
|
||||||
|
half_period(); // make sure SCL has settled low
|
||||||
|
sda_oe(dcxo, false);
|
||||||
|
|
||||||
|
let mut data: u8 = 0;
|
||||||
|
|
||||||
|
// MSB first
|
||||||
|
for bit in (0..8).rev() {
|
||||||
|
scl_o(dcxo, false);
|
||||||
|
half_period();
|
||||||
|
// Set SCL high and shift data
|
||||||
|
scl_o(dcxo, true);
|
||||||
|
half_period();
|
||||||
|
if sda_i(dcxo) {
|
||||||
|
data |= 1 << bit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Send ack
|
||||||
|
// Set SCL low and pull SDA low when acking
|
||||||
|
scl_o(dcxo, false);
|
||||||
|
if ack {
|
||||||
|
sda_oe(dcxo, true)
|
||||||
|
}
|
||||||
|
half_period();
|
||||||
|
// then set SCL high
|
||||||
|
scl_o(dcxo, true);
|
||||||
|
half_period();
|
||||||
|
|
||||||
|
data
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(dcxo: i2c::DCXO, reg: u8, val: u8) -> Result<(), &'static str> {
|
||||||
|
i2c::start(dcxo);
|
||||||
|
if !i2c::write(dcxo, ADDRESS << 1) {
|
||||||
|
return Err("Si549 failed to ack write address");
|
||||||
|
}
|
||||||
|
if !i2c::write(dcxo, reg) {
|
||||||
|
return Err("Si549 failed to ack register");
|
||||||
|
}
|
||||||
|
if !i2c::write(dcxo, val) {
|
||||||
|
return Err("Si549 failed to ack value");
|
||||||
|
}
|
||||||
|
i2c::stop(dcxo);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read(dcxo: i2c::DCXO, reg: u8) -> Result<u8, &'static str> {
|
||||||
|
i2c::start(dcxo);
|
||||||
|
if !i2c::write(dcxo, ADDRESS << 1) {
|
||||||
|
return Err("Si549 failed to ack write address");
|
||||||
|
}
|
||||||
|
if !i2c::write(dcxo, reg) {
|
||||||
|
return Err("Si549 failed to ack register");
|
||||||
|
}
|
||||||
|
i2c::stop(dcxo);
|
||||||
|
|
||||||
|
i2c::start(dcxo);
|
||||||
|
if !i2c::write(dcxo, (ADDRESS << 1) | 1) {
|
||||||
|
return Err("Si549 failed to ack read address");
|
||||||
|
}
|
||||||
|
let val = i2c::read(dcxo, false);
|
||||||
|
i2c::stop(dcxo);
|
||||||
|
Ok(val)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup(dcxo: i2c::DCXO, config: &DividerConfig) -> Result<(), &'static str> {
|
||||||
|
i2c::init(dcxo)?;
|
||||||
|
|
||||||
|
write(dcxo, 255, 0x00)?; // PAGE
|
||||||
|
write(dcxo, 69, 0x00)?; // Disable FCAL override.
|
||||||
|
write(dcxo, 17, 0x00)?; // Synchronously disable output
|
||||||
|
|
||||||
|
// The Si549 has no ID register, so we check that it responds correctly
|
||||||
|
// by writing values to a RAM-like register and reading them back.
|
||||||
|
for test_value in 0..255 {
|
||||||
|
write(dcxo, 23, test_value)?;
|
||||||
|
let readback = read(dcxo, 23)?;
|
||||||
|
if readback != test_value {
|
||||||
|
return Err("Si549 detection failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write(dcxo, 23, config.hsdiv as u8)?;
|
||||||
|
write(dcxo, 24, (config.hsdiv >> 8) as u8 | (config.lsdiv << 4))?;
|
||||||
|
write(dcxo, 26, config.fbdiv as u8)?;
|
||||||
|
write(dcxo, 27, (config.fbdiv >> 8) as u8)?;
|
||||||
|
write(dcxo, 28, (config.fbdiv >> 16) as u8)?;
|
||||||
|
write(dcxo, 29, (config.fbdiv >> 24) as u8)?;
|
||||||
|
write(dcxo, 30, (config.fbdiv >> 32) as u8)?;
|
||||||
|
write(dcxo, 31, (config.fbdiv >> 40) as u8)?;
|
||||||
|
|
||||||
|
write(dcxo, 7, 0x08)?; // Start FCAL
|
||||||
|
clock::spin_us(30_000); // Internal FCAL VCO calibration
|
||||||
|
write(dcxo, 17, 0x01)?; // Synchronously enable output
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn main_setup(settings: &FrequencySetting) -> Result<(), &'static str> {
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll::main_dcxo_bitbang_enable_write(1);
|
||||||
|
csr::wrpll::main_dcxo_i2c_address_write(ADDRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(i2c::DCXO::Main, &settings.main)?;
|
||||||
|
|
||||||
|
// Si549 maximum settling time for large frequency change.
|
||||||
|
clock::spin_us(40_000);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll::main_dcxo_bitbang_enable_write(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
info!("Main Si549 started");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn helper_setup(settings: &FrequencySetting) -> Result<(), &'static str> {
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll::helper_reset_write(1);
|
||||||
|
csr::wrpll::helper_dcxo_bitbang_enable_write(1);
|
||||||
|
csr::wrpll::helper_dcxo_i2c_address_write(ADDRESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
setup(i2c::DCXO::Helper, &settings.helper)?;
|
||||||
|
|
||||||
|
// Si549 maximum settling time for large frequency change.
|
||||||
|
clock::spin_us(40_000);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll::helper_reset_write(0);
|
||||||
|
csr::wrpll::helper_dcxo_bitbang_enable_write(0);
|
||||||
|
}
|
||||||
|
info!("Helper Si549 started");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_adpll(dcxo: i2c::DCXO, adpll: i32) -> Result<(), &'static str> {
|
||||||
|
if adpll.abs() > ADPLL_MAX {
|
||||||
|
return Err("adpll is too large");
|
||||||
|
}
|
||||||
|
|
||||||
|
match dcxo {
|
||||||
|
i2c::DCXO::Main => unsafe {
|
||||||
|
if csr::wrpll::main_dcxo_bitbang_enable_read() == 1 {
|
||||||
|
return Err("Main si549 bitbang mode is active when using gateware i2c");
|
||||||
|
}
|
||||||
|
|
||||||
|
while csr::wrpll::main_dcxo_adpll_busy_read() == 1 {}
|
||||||
|
if csr::wrpll::main_dcxo_nack_read() == 1 {
|
||||||
|
return Err("Main si549 failed to ack adpll write");
|
||||||
|
}
|
||||||
|
|
||||||
|
csr::wrpll::main_dcxo_i2c_address_write(ADDRESS);
|
||||||
|
csr::wrpll::main_dcxo_adpll_write(adpll as u32);
|
||||||
|
|
||||||
|
csr::wrpll::main_dcxo_adpll_stb_write(1);
|
||||||
|
},
|
||||||
|
i2c::DCXO::Helper => unsafe {
|
||||||
|
if csr::wrpll::helper_dcxo_bitbang_enable_read() == 1 {
|
||||||
|
return Err("Helper si549 bitbang mode is active when using gateware i2c");
|
||||||
|
}
|
||||||
|
|
||||||
|
while csr::wrpll::helper_dcxo_adpll_busy_read() == 1 {}
|
||||||
|
if csr::wrpll::helper_dcxo_nack_read() == 1 {
|
||||||
|
return Err("Helper si549 failed to ack adpll write");
|
||||||
|
}
|
||||||
|
|
||||||
|
csr::wrpll::helper_dcxo_i2c_address_write(ADDRESS);
|
||||||
|
csr::wrpll::helper_dcxo_adpll_write(adpll as u32);
|
||||||
|
|
||||||
|
csr::wrpll::helper_dcxo_adpll_stb_write(1);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(has_wrpll)]
|
||||||
|
pub mod wrpll {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
const BEATING_PERIOD: i32 = 0x8000;
|
||||||
|
const BEATING_HALFPERIOD: i32 = 0x4000;
|
||||||
|
const COUNTER_WIDTH: u32 = 24;
|
||||||
|
const DIV_WIDTH: u32 = 2;
|
||||||
|
|
||||||
|
// y[n] = b0*x[n] + b1*x[n-1] + b2*x[n-2] - a1*y[n-1] - a2*y[n-2]
|
||||||
|
struct FilterParameters {
|
||||||
|
pub b0: i64,
|
||||||
|
pub b1: i64,
|
||||||
|
pub b2: i64,
|
||||||
|
pub a1: i64,
|
||||||
|
pub a2: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(rtio_frequency = "100.0")]
|
||||||
|
const LPF: FilterParameters = FilterParameters {
|
||||||
|
b0: 10905723400, // 0.03967479060647884 * 1 << 38
|
||||||
|
b1: 21811446800, // 0.07934958121295768 * 1 << 38
|
||||||
|
b2: 10905723400, // 0.03967479060647884 * 1 << 38
|
||||||
|
a1: -381134538612, // -1.3865593741228928 * 1 << 38
|
||||||
|
a2: 149879525269, // 0.5452585365488082 * 1 << 38
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(rtio_frequency = "125.0")]
|
||||||
|
const LPF: FilterParameters = FilterParameters {
|
||||||
|
b0: 19816511911, // 0.07209205036273991 * 1 << 38
|
||||||
|
b1: 39633023822, // 0.14418410072547982 * 1 << 38
|
||||||
|
b2: 19816511911, // 0.07209205036273991 * 1 << 38
|
||||||
|
a1: -168062510414, // -0.6114078511562919 * 1 << 38
|
||||||
|
a2: -27549348884, // -0.10022394739274834 * 1 << 38
|
||||||
|
};
|
||||||
|
|
||||||
|
static mut H_ADPLL1: i32 = 0;
|
||||||
|
static mut H_ADPLL2: i32 = 0;
|
||||||
|
static mut PERIOD_ERR1: i32 = 0;
|
||||||
|
static mut PERIOD_ERR2: i32 = 0;
|
||||||
|
|
||||||
|
static mut M_ADPLL1: i32 = 0;
|
||||||
|
static mut M_ADPLL2: i32 = 0;
|
||||||
|
static mut PHASE_ERR1: i32 = 0;
|
||||||
|
static mut PHASE_ERR2: i32 = 0;
|
||||||
|
|
||||||
|
static mut BASE_ADPLL: i32 = 0;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub enum ISR {
|
||||||
|
RefTag,
|
||||||
|
MainTag,
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tag_collector {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[cfg(wrpll_ref_clk = "GT_CDR")]
|
||||||
|
static mut TAG_OFFSET: u32 = 23890;
|
||||||
|
#[cfg(wrpll_ref_clk = "SMA_CLKIN")]
|
||||||
|
static mut TAG_OFFSET: u32 = 0;
|
||||||
|
static mut REF_TAG: u32 = 0;
|
||||||
|
static mut REF_TAG_READY: bool = false;
|
||||||
|
static mut MAIN_TAG: u32 = 0;
|
||||||
|
static mut MAIN_TAG_READY: bool = false;
|
||||||
|
|
||||||
|
pub fn reset() {
|
||||||
|
clear_phase_diff_ready();
|
||||||
|
unsafe {
|
||||||
|
REF_TAG = 0;
|
||||||
|
MAIN_TAG = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_phase_diff_ready() {
|
||||||
|
unsafe {
|
||||||
|
REF_TAG_READY = false;
|
||||||
|
MAIN_TAG_READY = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn collect_tags(interrupt: ISR) {
|
||||||
|
match interrupt {
|
||||||
|
ISR::RefTag => unsafe {
|
||||||
|
REF_TAG = csr::wrpll::ref_tag_read();
|
||||||
|
REF_TAG_READY = true;
|
||||||
|
},
|
||||||
|
ISR::MainTag => unsafe {
|
||||||
|
MAIN_TAG = csr::wrpll::main_tag_read();
|
||||||
|
MAIN_TAG_READY = true;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn phase_diff_ready() -> bool {
|
||||||
|
unsafe { REF_TAG_READY && MAIN_TAG_READY }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "calibrate_wrpll_skew")]
|
||||||
|
pub fn set_tag_offset(offset: u32) {
|
||||||
|
unsafe {
|
||||||
|
TAG_OFFSET = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "calibrate_wrpll_skew")]
|
||||||
|
pub fn get_tag_offset() -> u32 {
|
||||||
|
unsafe { TAG_OFFSET }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_period_error() -> i32 {
|
||||||
|
// n * BEATING_PERIOD - REF_TAG(n) mod BEATING_PERIOD
|
||||||
|
let mut period_error = unsafe {
|
||||||
|
REF_TAG
|
||||||
|
.overflowing_neg()
|
||||||
|
.0
|
||||||
|
.rem_euclid(BEATING_PERIOD as u32) as i32
|
||||||
|
};
|
||||||
|
// mapping tags from [0, 2π] -> [-π, π]
|
||||||
|
if period_error > BEATING_HALFPERIOD {
|
||||||
|
period_error -= BEATING_PERIOD
|
||||||
|
}
|
||||||
|
period_error
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_phase_error() -> i32 {
|
||||||
|
// MAIN_TAG(n) - REF_TAG(n) - TAG_OFFSET mod BEATING_PERIOD
|
||||||
|
let mut phase_error = unsafe {
|
||||||
|
MAIN_TAG
|
||||||
|
.overflowing_sub(REF_TAG + TAG_OFFSET)
|
||||||
|
.0
|
||||||
|
.rem_euclid(BEATING_PERIOD as u32) as i32
|
||||||
|
};
|
||||||
|
|
||||||
|
// mapping tags from [0, 2π] -> [-π, π]
|
||||||
|
if phase_error > BEATING_HALFPERIOD {
|
||||||
|
phase_error -= BEATING_PERIOD
|
||||||
|
}
|
||||||
|
phase_error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_isr(en: bool) {
|
||||||
|
let val = if en { 1 } else { 0 };
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll::ref_tag_ev_enable_write(val);
|
||||||
|
csr::wrpll::main_tag_ev_enable_write(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_base_adpll() -> Result<(), &'static str> {
|
||||||
|
let count2adpll = |error: i32| {
|
||||||
|
((error as f64 * 1e6) / (0.0001164 * (1 << (COUNTER_WIDTH - DIV_WIDTH)) as f64)) as i32
|
||||||
|
};
|
||||||
|
|
||||||
|
let (ref_count, main_count) = get_freq_counts();
|
||||||
|
unsafe {
|
||||||
|
BASE_ADPLL = count2adpll(ref_count as i32 - main_count as i32);
|
||||||
|
set_adpll(i2c::DCXO::Main, BASE_ADPLL)?;
|
||||||
|
set_adpll(i2c::DCXO::Helper, BASE_ADPLL)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_freq_counts() -> (u32, u32) {
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll::frequency_counter_update_write(1);
|
||||||
|
while csr::wrpll::frequency_counter_busy_read() == 1 {}
|
||||||
|
#[cfg(wrpll_ref_clk = "GT_CDR")]
|
||||||
|
let ref_count = csr::wrpll::frequency_counter_counter_rtio_rx0_read();
|
||||||
|
#[cfg(wrpll_ref_clk = "SMA_CLKIN")]
|
||||||
|
let ref_count = csr::wrpll::frequency_counter_counter_ref_read();
|
||||||
|
let main_count = csr::wrpll::frequency_counter_counter_sys_read();
|
||||||
|
|
||||||
|
(ref_count, main_count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_plls() -> Result<(), &'static str> {
|
||||||
|
unsafe {
|
||||||
|
H_ADPLL1 = 0;
|
||||||
|
H_ADPLL2 = 0;
|
||||||
|
PERIOD_ERR1 = 0;
|
||||||
|
PERIOD_ERR2 = 0;
|
||||||
|
M_ADPLL1 = 0;
|
||||||
|
M_ADPLL2 = 0;
|
||||||
|
PHASE_ERR1 = 0;
|
||||||
|
PHASE_ERR2 = 0;
|
||||||
|
}
|
||||||
|
set_adpll(i2c::DCXO::Main, 0)?;
|
||||||
|
set_adpll(i2c::DCXO::Helper, 0)?;
|
||||||
|
// wait for adpll to transfer and DCXO to settle
|
||||||
|
clock::spin_us(200);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn clear_pending(interrupt: ISR) {
|
||||||
|
match interrupt {
|
||||||
|
ISR::RefTag => unsafe { csr::wrpll::ref_tag_ev_pending_write(1) },
|
||||||
|
ISR::MainTag => unsafe { csr::wrpll::main_tag_ev_pending_write(1) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_pending(interrupt: ISR) -> bool {
|
||||||
|
match interrupt {
|
||||||
|
ISR::RefTag => unsafe { csr::wrpll::ref_tag_ev_pending_read() == 1 },
|
||||||
|
ISR::MainTag => unsafe { csr::wrpll::main_tag_ev_pending_read() == 1 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interrupt_handler() {
|
||||||
|
if is_pending(ISR::RefTag) {
|
||||||
|
tag_collector::collect_tags(ISR::RefTag);
|
||||||
|
clear_pending(ISR::RefTag);
|
||||||
|
helper_pll().expect("failed to run helper DCXO PLL");
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_pending(ISR::MainTag) {
|
||||||
|
tag_collector::collect_tags(ISR::MainTag);
|
||||||
|
clear_pending(ISR::MainTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
if tag_collector::phase_diff_ready() {
|
||||||
|
main_pll().expect("failed to run main DCXO PLL");
|
||||||
|
tag_collector::clear_phase_diff_ready();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn helper_pll() -> Result<(), &'static str> {
|
||||||
|
let period_err = tag_collector::get_period_error();
|
||||||
|
unsafe {
|
||||||
|
let adpll = (((LPF.b0 * period_err as i64)
|
||||||
|
+ (LPF.b1 * PERIOD_ERR1 as i64)
|
||||||
|
+ (LPF.b2 * PERIOD_ERR2 as i64)
|
||||||
|
- (LPF.a1 * H_ADPLL1 as i64)
|
||||||
|
- (LPF.a2 * H_ADPLL2 as i64))
|
||||||
|
>> 38) as i32;
|
||||||
|
set_adpll(i2c::DCXO::Helper, BASE_ADPLL + adpll)?;
|
||||||
|
H_ADPLL2 = H_ADPLL1;
|
||||||
|
PERIOD_ERR2 = PERIOD_ERR1;
|
||||||
|
H_ADPLL1 = adpll;
|
||||||
|
PERIOD_ERR1 = period_err;
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn main_pll() -> Result<(), &'static str> {
|
||||||
|
let phase_err = tag_collector::get_phase_error();
|
||||||
|
unsafe {
|
||||||
|
let adpll = (((LPF.b0 * phase_err as i64)
|
||||||
|
+ (LPF.b1 * PHASE_ERR1 as i64)
|
||||||
|
+ (LPF.b2 * PHASE_ERR2 as i64)
|
||||||
|
- (LPF.a1 * M_ADPLL1 as i64)
|
||||||
|
- (LPF.a2 * M_ADPLL2 as i64))
|
||||||
|
>> 38) as i32;
|
||||||
|
set_adpll(i2c::DCXO::Main, BASE_ADPLL + adpll)?;
|
||||||
|
M_ADPLL2 = M_ADPLL1;
|
||||||
|
PHASE_ERR2 = PHASE_ERR1;
|
||||||
|
M_ADPLL1 = adpll;
|
||||||
|
PHASE_ERR1 = phase_err;
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(wrpll_ref_clk = "GT_CDR")]
|
||||||
|
fn test_skew() -> Result<(), &'static str> {
|
||||||
|
// wait for PLL to stabilize
|
||||||
|
clock::spin_us(20_000);
|
||||||
|
|
||||||
|
info!("testing the skew of SYS CLK...");
|
||||||
|
if has_timing_error() {
|
||||||
|
return Err("the skew cannot satisfy setup/hold time constraint of RX synchronizer");
|
||||||
|
}
|
||||||
|
info!("the skew of SYS CLK met the timing constraint");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(wrpll_ref_clk = "GT_CDR")]
|
||||||
|
fn has_timing_error() -> bool {
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll_skewtester::error_write(1);
|
||||||
|
}
|
||||||
|
clock::spin_us(5_000);
|
||||||
|
unsafe { csr::wrpll_skewtester::error_read() == 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "calibrate_wrpll_skew")]
|
||||||
|
fn find_edge(target: bool) -> Result<u32, &'static str> {
|
||||||
|
const STEP: u32 = 8;
|
||||||
|
const STABLE_THRESHOLD: u32 = 10;
|
||||||
|
|
||||||
|
enum FSM {
|
||||||
|
Init,
|
||||||
|
WaitEdge,
|
||||||
|
GotEdge,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut state: FSM = FSM::Init;
|
||||||
|
let mut offset: u32 = tag_collector::get_tag_offset();
|
||||||
|
let mut median_edge: u32 = 0;
|
||||||
|
let mut stable_counter: u32 = 0;
|
||||||
|
|
||||||
|
for _ in 0..(BEATING_PERIOD as u32 / STEP) as usize {
|
||||||
|
tag_collector::set_tag_offset(offset);
|
||||||
|
offset += STEP;
|
||||||
|
// wait for PLL to stabilize
|
||||||
|
clock::spin_us(20_000);
|
||||||
|
|
||||||
|
let error = has_timing_error();
|
||||||
|
// A median edge deglitcher
|
||||||
|
match state {
|
||||||
|
FSM::Init => {
|
||||||
|
if error != target {
|
||||||
|
stable_counter += 1;
|
||||||
|
} else {
|
||||||
|
stable_counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if stable_counter >= STABLE_THRESHOLD {
|
||||||
|
state = FSM::WaitEdge;
|
||||||
|
stable_counter = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FSM::WaitEdge => {
|
||||||
|
if error == target {
|
||||||
|
state = FSM::GotEdge;
|
||||||
|
median_edge = offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FSM::GotEdge => {
|
||||||
|
if error != target {
|
||||||
|
median_edge += STEP;
|
||||||
|
stable_counter = 0;
|
||||||
|
} else {
|
||||||
|
stable_counter += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if stable_counter >= STABLE_THRESHOLD {
|
||||||
|
return Ok(median_edge);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Err("failed to find timing error edge");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "calibrate_wrpll_skew")]
|
||||||
|
fn calibrate_skew() -> Result<(), &'static str> {
|
||||||
|
info!("calibrating skew to meet timing constraint...");
|
||||||
|
|
||||||
|
// clear calibrated value
|
||||||
|
tag_collector::set_tag_offset(0);
|
||||||
|
let rising = find_edge(true)? as i32;
|
||||||
|
let falling = find_edge(false)? as i32;
|
||||||
|
|
||||||
|
let width = BEATING_PERIOD - (falling - rising);
|
||||||
|
let result = falling + width / 2;
|
||||||
|
tag_collector::set_tag_offset(result as u32);
|
||||||
|
|
||||||
|
info!(
|
||||||
|
"calibration successful, error zone: {} -> {}, width: {} ({}deg), middle of working region: {}",
|
||||||
|
rising,
|
||||||
|
falling,
|
||||||
|
width,
|
||||||
|
360 * width / BEATING_PERIOD,
|
||||||
|
result,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn select_recovered_clock(rc: bool) {
|
||||||
|
set_isr(false);
|
||||||
|
|
||||||
|
if rc {
|
||||||
|
tag_collector::reset();
|
||||||
|
reset_plls().expect("failed to reset main and helper PLL");
|
||||||
|
|
||||||
|
// get within capture range
|
||||||
|
set_base_adpll().expect("failed to set base adpll");
|
||||||
|
|
||||||
|
// clear gateware pending flag
|
||||||
|
clear_pending(ISR::RefTag);
|
||||||
|
clear_pending(ISR::MainTag);
|
||||||
|
|
||||||
|
// use nFIQ to avoid IRQ being disabled by mutex lock and mess up PLL
|
||||||
|
set_isr(true);
|
||||||
|
info!("WRPLL interrupt enabled");
|
||||||
|
|
||||||
|
#[cfg(feature = "calibrate_wrpll_skew")]
|
||||||
|
calibrate_skew().expect("failed to set the correct skew");
|
||||||
|
|
||||||
|
#[cfg(wrpll_ref_clk = "GT_CDR")]
|
||||||
|
test_skew().expect("skew test failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(has_wrpll_refclk)]
|
||||||
|
pub mod wrpll_refclk {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
pub struct MmcmSetting {
|
||||||
|
pub clkout0_reg1: u16, //0x08
|
||||||
|
pub clkout0_reg2: u16, //0x09
|
||||||
|
pub clkfbout_reg1: u16, //0x14
|
||||||
|
pub clkfbout_reg2: u16, //0x15
|
||||||
|
pub div_reg: u16, //0x16
|
||||||
|
pub lock_reg1: u16, //0x18
|
||||||
|
pub lock_reg2: u16, //0x19
|
||||||
|
pub lock_reg3: u16, //0x1A
|
||||||
|
pub power_reg: u16, //0x28
|
||||||
|
pub filt_reg1: u16, //0x4E
|
||||||
|
pub filt_reg2: u16, //0x4F
|
||||||
|
}
|
||||||
|
|
||||||
|
fn one_clock_cycle() {
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll_refclk::mmcm_dclk_write(1);
|
||||||
|
csr::wrpll_refclk::mmcm_dclk_write(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_addr(address: u8) {
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll_refclk::mmcm_daddr_write(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_data(value: u16) {
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll_refclk::mmcm_din_write(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_enable(en: bool) {
|
||||||
|
let val = if en { 1 } else { 0 };
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll_refclk::mmcm_den_write(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_write_enable(en: bool) {
|
||||||
|
let val = if en { 1 } else { 0 };
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll_refclk::mmcm_dwen_write(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_data() -> u16 {
|
||||||
|
unsafe { csr::wrpll_refclk::mmcm_dout_read() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drp_ready() -> bool {
|
||||||
|
unsafe { csr::wrpll_refclk::mmcm_dready_read() == 1 }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
fn read(address: u8) -> u16 {
|
||||||
|
set_addr(address);
|
||||||
|
set_enable(true);
|
||||||
|
// Set DADDR on the mmcm and assert DEN for one clock cycle
|
||||||
|
one_clock_cycle();
|
||||||
|
|
||||||
|
set_enable(false);
|
||||||
|
while !drp_ready() {
|
||||||
|
// keep the clock signal until data is ready
|
||||||
|
one_clock_cycle();
|
||||||
|
}
|
||||||
|
get_data()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(address: u8, value: u16) {
|
||||||
|
set_addr(address);
|
||||||
|
set_data(value);
|
||||||
|
set_write_enable(true);
|
||||||
|
set_enable(true);
|
||||||
|
// Set DADDR, DI on the mmcm and assert DWE, DEN for one clock cycle
|
||||||
|
one_clock_cycle();
|
||||||
|
|
||||||
|
set_write_enable(false);
|
||||||
|
set_enable(false);
|
||||||
|
while !drp_ready() {
|
||||||
|
// keep the clock signal until write is finished
|
||||||
|
one_clock_cycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(rst: bool) {
|
||||||
|
let val = if rst { 1 } else { 0 };
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll_refclk::mmcm_reset_write(val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn setup(settings: MmcmSetting, mmcm_bypass: bool) -> Result<(), &'static str> {
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll_refclk::refclk_reset_write(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if mmcm_bypass {
|
||||||
|
info!("Bypassing mmcm");
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll_refclk::mmcm_bypass_write(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Based on "DRP State Machine" from XAPP888
|
||||||
|
// hold reset HIGH during mmcm config
|
||||||
|
reset(true);
|
||||||
|
write(0x08, settings.clkout0_reg1);
|
||||||
|
write(0x09, settings.clkout0_reg2);
|
||||||
|
write(0x14, settings.clkfbout_reg1);
|
||||||
|
write(0x15, settings.clkfbout_reg2);
|
||||||
|
write(0x16, settings.div_reg);
|
||||||
|
write(0x18, settings.lock_reg1);
|
||||||
|
write(0x19, settings.lock_reg2);
|
||||||
|
write(0x1A, settings.lock_reg3);
|
||||||
|
write(0x28, settings.power_reg);
|
||||||
|
write(0x4E, settings.filt_reg1);
|
||||||
|
write(0x4F, settings.filt_reg2);
|
||||||
|
reset(false);
|
||||||
|
|
||||||
|
// wait for the mmcm to lock
|
||||||
|
clock::spin_us(100);
|
||||||
|
|
||||||
|
let locked = unsafe { csr::wrpll_refclk::mmcm_locked_read() == 1 };
|
||||||
|
if !locked {
|
||||||
|
return Err("mmcm failed to generate 125MHz ref clock from SMA CLKIN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
csr::wrpll_refclk::refclk_reset_write(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,6 +27,26 @@ impl IoExpander {
|
||||||
const VIRTUAL_LED_MAPPING0: [(u8, u8, u8); 2] = [(0, 0, 6), (1, 1, 6)];
|
const VIRTUAL_LED_MAPPING0: [(u8, u8, u8); 2] = [(0, 0, 6), (1, 1, 6)];
|
||||||
const VIRTUAL_LED_MAPPING1: [(u8, u8, u8); 2] = [(2, 0, 6), (3, 1, 6)];
|
const VIRTUAL_LED_MAPPING1: [(u8, u8, u8); 2] = [(2, 0, 6), (3, 1, 6)];
|
||||||
|
|
||||||
|
#[cfg(has_si549)]
|
||||||
|
const IODIR_CLK_SEL: u8 = 0x80; // out
|
||||||
|
#[cfg(has_si5324)]
|
||||||
|
const IODIR_CLK_SEL: u8 = 0x00; // in
|
||||||
|
|
||||||
|
#[cfg(has_si549)]
|
||||||
|
const CLK_SEL_OUT: u8 = 1 << 7;
|
||||||
|
#[cfg(has_si5324)]
|
||||||
|
const CLK_SEL_OUT: u8 = 0;
|
||||||
|
|
||||||
|
const IODIR0 : [u8; 2] = [
|
||||||
|
0xFF,
|
||||||
|
0xFF & !IODIR_CLK_SEL
|
||||||
|
];
|
||||||
|
|
||||||
|
const OUT_TAR0 : [u8; 2] = [
|
||||||
|
0,
|
||||||
|
CLK_SEL_OUT
|
||||||
|
];
|
||||||
|
|
||||||
// Both expanders on SHARED I2C bus
|
// Both expanders on SHARED I2C bus
|
||||||
let mut io_expander = match index {
|
let mut io_expander = match index {
|
||||||
0 => IoExpander {
|
0 => IoExpander {
|
||||||
|
@ -34,9 +54,9 @@ impl IoExpander {
|
||||||
port: 11,
|
port: 11,
|
||||||
address: 0x40,
|
address: 0x40,
|
||||||
virtual_led_mapping: &VIRTUAL_LED_MAPPING0,
|
virtual_led_mapping: &VIRTUAL_LED_MAPPING0,
|
||||||
iodir: [0xff; 2],
|
iodir: IODIR0,
|
||||||
out_current: [0; 2],
|
out_current: [0; 2],
|
||||||
out_target: [0; 2],
|
out_target: OUT_TAR0,
|
||||||
registers: Registers {
|
registers: Registers {
|
||||||
iodira: 0x00,
|
iodira: 0x00,
|
||||||
iodirb: 0x01,
|
iodirb: 0x01,
|
||||||
|
@ -153,10 +173,10 @@ impl IoExpander {
|
||||||
}
|
}
|
||||||
self.update_iodir()?;
|
self.update_iodir()?;
|
||||||
|
|
||||||
self.out_current[0] = 0x00;
|
self.write(self.registers.gpioa, self.out_target[0])?;
|
||||||
self.write(self.registers.gpioa, 0x00)?;
|
self.out_current[0] = self.out_target[0];
|
||||||
self.out_current[1] = 0x00;
|
self.write(self.registers.gpiob, self.out_target[1])?;
|
||||||
self.write(self.registers.gpiob, 0x00)?;
|
self.out_current[1] = self.out_target[1];
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(llvm_asm)]
|
#![feature(asm)]
|
||||||
|
|
||||||
extern crate byteorder;
|
extern crate byteorder;
|
||||||
#[cfg(feature = "log")]
|
#[cfg(feature = "log")]
|
||||||
|
|
|
@ -2,29 +2,26 @@ use super::{cache, pmp};
|
||||||
use riscv::register::*;
|
use riscv::register::*;
|
||||||
|
|
||||||
pub unsafe fn reset() -> ! {
|
pub unsafe fn reset() -> ! {
|
||||||
llvm_asm!(r#"
|
asm!("j _reset_handler",
|
||||||
j _reset_handler
|
"nop",
|
||||||
nop
|
options(nomem, nostack, noreturn)
|
||||||
"# : : : : "volatile");
|
);
|
||||||
loop {}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn jump(addr: usize) -> ! {
|
pub unsafe fn jump(addr: usize) -> ! {
|
||||||
cache::flush_cpu_icache();
|
cache::flush_cpu_icache();
|
||||||
llvm_asm!(r#"
|
asm!("jalr x0, 0({0})",
|
||||||
jalr x0, 0($0)
|
"nop",
|
||||||
nop
|
in(reg) addr,
|
||||||
"# : : "r"(addr) : : "volatile");
|
options(nomem, nostack, noreturn)
|
||||||
loop {}
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub unsafe fn start_user(addr: usize) -> ! {
|
pub unsafe fn start_user(addr: usize) -> ! {
|
||||||
pmp::enable_user_memory();
|
pmp::enable_user_memory();
|
||||||
mstatus::set_mpp(mstatus::MPP::User);
|
mstatus::set_mpp(mstatus::MPP::User);
|
||||||
mepc::write(addr);
|
mepc::write(addr);
|
||||||
llvm_asm!(
|
asm!("mret",
|
||||||
"mret"
|
options(nomem, nostack, noreturn)
|
||||||
: : : : "volatile"
|
|
||||||
);
|
);
|
||||||
unreachable!()
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,20 +7,24 @@ use mem;
|
||||||
|
|
||||||
pub fn flush_cpu_icache() {
|
pub fn flush_cpu_icache() {
|
||||||
unsafe {
|
unsafe {
|
||||||
llvm_asm!(r#"
|
asm!(
|
||||||
fence.i
|
"fence.i",
|
||||||
nop
|
"nop",
|
||||||
nop
|
"nop",
|
||||||
nop
|
"nop",
|
||||||
nop
|
"nop",
|
||||||
nop
|
"nop",
|
||||||
"# : : : : "volatile");
|
options(preserves_flags, nostack)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn flush_cpu_dcache() {
|
pub fn flush_cpu_dcache() {
|
||||||
unsafe {
|
unsafe {
|
||||||
llvm_asm!(".word(0x500F)" : : : : "volatile");
|
asm!(
|
||||||
|
".word(0x500F)",
|
||||||
|
options(preserves_flags)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
use riscv::register::{mie, mstatus};
|
||||||
|
|
||||||
|
fn vmim_write(val: usize) {
|
||||||
|
unsafe {
|
||||||
|
asm!("csrw {csr}, {rs}", rs = in(reg) val, csr = const 0xBC0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vmim_read() -> usize {
|
||||||
|
let r: usize;
|
||||||
|
unsafe {
|
||||||
|
asm!("csrr {rd}, {csr}", rd = out(reg) r, csr = const 0xBC0);
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
fn vmip_read() -> usize {
|
||||||
|
let r: usize;
|
||||||
|
unsafe {
|
||||||
|
asm!("csrr {rd}, {csr}", rd = out(reg) r, csr = const 0xFC0);
|
||||||
|
}
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_interrupts() {
|
||||||
|
unsafe {
|
||||||
|
mstatus::set_mie();
|
||||||
|
mie::set_mext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_interrupts() {
|
||||||
|
unsafe {
|
||||||
|
mstatus::clear_mie();
|
||||||
|
mie::clear_mext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable(id: u32) {
|
||||||
|
vmim_write(vmim_read() | (1 << id));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable(id: u32) {
|
||||||
|
vmim_write(vmim_read() & !(1 << id));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_pending(id: u32) -> bool {
|
||||||
|
(vmip_read() >> id) & 1 == 1
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod boot;
|
pub mod boot;
|
||||||
|
pub mod irq;
|
||||||
pub mod pmp;
|
pub mod pmp;
|
||||||
|
|
|
@ -11,4 +11,3 @@ path = "lib.rs"
|
||||||
cslice = { version = "0.3" }
|
cslice = { version = "0.3" }
|
||||||
libc = { path = "../libc" }
|
libc = { path = "../libc" }
|
||||||
unwind = { path = "../libunwind" }
|
unwind = { path = "../libunwind" }
|
||||||
compiler_builtins = "=0.1.39" # A dependency of alloc, libeh doesn't need it
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
#![feature(lang_items, panic_unwind, libc, unwind_attributes, int_bits_const)]
|
#![feature(lang_items, panic_unwind, libc)]
|
||||||
#![no_std]
|
#![no_std]
|
||||||
|
|
||||||
extern crate cslice;
|
extern crate cslice;
|
||||||
|
|
|
@ -16,9 +16,9 @@ impl<T> From<IoError<T>> for Error<T> {
|
||||||
|
|
||||||
// maximum size of arbitrary payloads
|
// maximum size of arbitrary payloads
|
||||||
// used by satellite -> master analyzer, subkernel exceptions
|
// used by satellite -> master analyzer, subkernel exceptions
|
||||||
pub const SAT_PAYLOAD_MAX_SIZE: usize = /*max size*/512 - /*CRC*/4 - /*packet ID*/1 - /*last*/1 - /*length*/2;
|
pub const SAT_PAYLOAD_MAX_SIZE: usize = /*max size*/1024 - /*CRC*/4 - /*packet ID*/1 - /*last*/1 - /*length*/2;
|
||||||
// used by DDMA, subkernel program data (need to provide extra ID and destination)
|
// used by DDMA, subkernel program data (need to provide extra ID and destination)
|
||||||
pub const MASTER_PAYLOAD_MAX_SIZE: usize = SAT_PAYLOAD_MAX_SIZE - /*destination*/1 - /*ID*/4;
|
pub const MASTER_PAYLOAD_MAX_SIZE: usize = SAT_PAYLOAD_MAX_SIZE - /*source*/1 - /*destination*/1 - /*ID*/4;
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Copy, Debug)]
|
#[derive(PartialEq, Clone, Copy, Debug)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
|
@ -106,22 +106,26 @@ pub enum Packet {
|
||||||
AnalyzerDataRequest { destination: u8 },
|
AnalyzerDataRequest { destination: u8 },
|
||||||
AnalyzerData { last: bool, length: u16, data: [u8; SAT_PAYLOAD_MAX_SIZE]},
|
AnalyzerData { last: bool, length: u16, data: [u8; SAT_PAYLOAD_MAX_SIZE]},
|
||||||
|
|
||||||
DmaAddTraceRequest { destination: u8, id: u32, status: PayloadStatus, length: u16, trace: [u8; MASTER_PAYLOAD_MAX_SIZE] },
|
DmaAddTraceRequest {
|
||||||
DmaAddTraceReply { succeeded: bool },
|
source: u8, destination: u8,
|
||||||
DmaRemoveTraceRequest { destination: u8, id: u32 },
|
id: u32, status: PayloadStatus,
|
||||||
DmaRemoveTraceReply { succeeded: bool },
|
length: u16, trace: [u8; MASTER_PAYLOAD_MAX_SIZE]
|
||||||
DmaPlaybackRequest { destination: u8, id: u32, timestamp: u64 },
|
},
|
||||||
DmaPlaybackReply { succeeded: bool },
|
DmaAddTraceReply { source: u8, destination: u8, id: u32, succeeded: bool },
|
||||||
DmaPlaybackStatus { destination: u8, id: u32, error: u8, channel: u32, timestamp: u64 },
|
DmaRemoveTraceRequest { source: u8, destination: u8, id: u32 },
|
||||||
|
DmaRemoveTraceReply { destination: u8, succeeded: bool },
|
||||||
|
DmaPlaybackRequest { source: u8, destination: u8, id: u32, timestamp: u64 },
|
||||||
|
DmaPlaybackReply { destination: u8, succeeded: bool },
|
||||||
|
DmaPlaybackStatus { source: u8, destination: u8, id: u32, error: u8, channel: u32, timestamp: u64 },
|
||||||
|
|
||||||
SubkernelAddDataRequest { destination: u8, id: u32, status: PayloadStatus, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
|
SubkernelAddDataRequest { destination: u8, id: u32, status: PayloadStatus, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
|
||||||
SubkernelAddDataReply { succeeded: bool },
|
SubkernelAddDataReply { succeeded: bool },
|
||||||
SubkernelLoadRunRequest { destination: u8, id: u32, run: bool },
|
SubkernelLoadRunRequest { source: u8, destination: u8, id: u32, run: bool },
|
||||||
SubkernelLoadRunReply { succeeded: bool },
|
SubkernelLoadRunReply { destination: u8, succeeded: bool },
|
||||||
SubkernelFinished { id: u32, with_exception: bool },
|
SubkernelFinished { destination: u8, id: u32, with_exception: bool, exception_src: u8 },
|
||||||
SubkernelExceptionRequest { destination: u8 },
|
SubkernelExceptionRequest { source: u8, destination: u8 },
|
||||||
SubkernelException { last: bool, length: u16, data: [u8; SAT_PAYLOAD_MAX_SIZE] },
|
SubkernelException { destination: u8, last: bool, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
|
||||||
SubkernelMessage { 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 },
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,6 +282,7 @@ impl Packet {
|
||||||
},
|
},
|
||||||
|
|
||||||
0xb0 => {
|
0xb0 => {
|
||||||
|
let source = reader.read_u8()?;
|
||||||
let destination = reader.read_u8()?;
|
let destination = reader.read_u8()?;
|
||||||
let id = reader.read_u32()?;
|
let id = reader.read_u32()?;
|
||||||
let status = reader.read_u8()?;
|
let status = reader.read_u8()?;
|
||||||
|
@ -285,6 +290,7 @@ impl Packet {
|
||||||
let mut trace: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
let mut trace: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
||||||
reader.read_exact(&mut trace[0..length as usize])?;
|
reader.read_exact(&mut trace[0..length as usize])?;
|
||||||
Packet::DmaAddTraceRequest {
|
Packet::DmaAddTraceRequest {
|
||||||
|
source: source,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
id: id,
|
id: id,
|
||||||
status: PayloadStatus::from(status),
|
status: PayloadStatus::from(status),
|
||||||
|
@ -293,24 +299,32 @@ impl Packet {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
0xb1 => Packet::DmaAddTraceReply {
|
0xb1 => Packet::DmaAddTraceReply {
|
||||||
|
source: reader.read_u8()?,
|
||||||
|
destination: reader.read_u8()?,
|
||||||
|
id: reader.read_u32()?,
|
||||||
succeeded: reader.read_bool()?
|
succeeded: reader.read_bool()?
|
||||||
},
|
},
|
||||||
0xb2 => Packet::DmaRemoveTraceRequest {
|
0xb2 => Packet::DmaRemoveTraceRequest {
|
||||||
|
source: reader.read_u8()?,
|
||||||
destination: reader.read_u8()?,
|
destination: reader.read_u8()?,
|
||||||
id: reader.read_u32()?
|
id: reader.read_u32()?
|
||||||
},
|
},
|
||||||
0xb3 => Packet::DmaRemoveTraceReply {
|
0xb3 => Packet::DmaRemoveTraceReply {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
succeeded: reader.read_bool()?
|
succeeded: reader.read_bool()?
|
||||||
},
|
},
|
||||||
0xb4 => Packet::DmaPlaybackRequest {
|
0xb4 => Packet::DmaPlaybackRequest {
|
||||||
|
source: reader.read_u8()?,
|
||||||
destination: reader.read_u8()?,
|
destination: reader.read_u8()?,
|
||||||
id: reader.read_u32()?,
|
id: reader.read_u32()?,
|
||||||
timestamp: reader.read_u64()?
|
timestamp: reader.read_u64()?
|
||||||
},
|
},
|
||||||
0xb5 => Packet::DmaPlaybackReply {
|
0xb5 => Packet::DmaPlaybackReply {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
succeeded: reader.read_bool()?
|
succeeded: reader.read_bool()?
|
||||||
},
|
},
|
||||||
0xb6 => Packet::DmaPlaybackStatus {
|
0xb6 => Packet::DmaPlaybackStatus {
|
||||||
|
source: reader.read_u8()?,
|
||||||
destination: reader.read_u8()?,
|
destination: reader.read_u8()?,
|
||||||
id: reader.read_u32()?,
|
id: reader.read_u32()?,
|
||||||
error: reader.read_u8()?,
|
error: reader.read_u8()?,
|
||||||
|
@ -337,32 +351,40 @@ impl Packet {
|
||||||
succeeded: reader.read_bool()?
|
succeeded: reader.read_bool()?
|
||||||
},
|
},
|
||||||
0xc4 => Packet::SubkernelLoadRunRequest {
|
0xc4 => Packet::SubkernelLoadRunRequest {
|
||||||
|
source: reader.read_u8()?,
|
||||||
destination: reader.read_u8()?,
|
destination: reader.read_u8()?,
|
||||||
id: reader.read_u32()?,
|
id: reader.read_u32()?,
|
||||||
run: reader.read_bool()?
|
run: reader.read_bool()?
|
||||||
},
|
},
|
||||||
0xc5 => Packet::SubkernelLoadRunReply {
|
0xc5 => Packet::SubkernelLoadRunReply {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
succeeded: reader.read_bool()?
|
succeeded: reader.read_bool()?
|
||||||
},
|
},
|
||||||
0xc8 => Packet::SubkernelFinished {
|
0xc8 => Packet::SubkernelFinished {
|
||||||
|
destination: reader.read_u8()?,
|
||||||
id: reader.read_u32()?,
|
id: reader.read_u32()?,
|
||||||
with_exception: reader.read_bool()?,
|
with_exception: reader.read_bool()?,
|
||||||
|
exception_src: reader.read_u8()?
|
||||||
},
|
},
|
||||||
0xc9 => Packet::SubkernelExceptionRequest {
|
0xc9 => Packet::SubkernelExceptionRequest {
|
||||||
|
source: reader.read_u8()?,
|
||||||
destination: reader.read_u8()?
|
destination: reader.read_u8()?
|
||||||
},
|
},
|
||||||
0xca => {
|
0xca => {
|
||||||
|
let destination = reader.read_u8()?;
|
||||||
let last = reader.read_bool()?;
|
let last = reader.read_bool()?;
|
||||||
let length = reader.read_u16()?;
|
let length = reader.read_u16()?;
|
||||||
let mut data: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
|
let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
||||||
reader.read_exact(&mut data[0..length as usize])?;
|
reader.read_exact(&mut data[0..length as usize])?;
|
||||||
Packet::SubkernelException {
|
Packet::SubkernelException {
|
||||||
|
destination: destination,
|
||||||
last: last,
|
last: last,
|
||||||
length: length,
|
length: length,
|
||||||
data: data
|
data: data
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
0xcb => {
|
0xcb => {
|
||||||
|
let source = reader.read_u8()?;
|
||||||
let destination = reader.read_u8()?;
|
let destination = reader.read_u8()?;
|
||||||
let id = reader.read_u32()?;
|
let id = reader.read_u32()?;
|
||||||
let status = reader.read_u8()?;
|
let status = reader.read_u8()?;
|
||||||
|
@ -370,6 +392,7 @@ impl Packet {
|
||||||
let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
|
||||||
reader.read_exact(&mut data[0..length as usize])?;
|
reader.read_exact(&mut data[0..length as usize])?;
|
||||||
Packet::SubkernelMessage {
|
Packet::SubkernelMessage {
|
||||||
|
source: source,
|
||||||
destination: destination,
|
destination: destination,
|
||||||
id: id,
|
id: id,
|
||||||
status: PayloadStatus::from(status),
|
status: PayloadStatus::from(status),
|
||||||
|
@ -561,8 +584,9 @@ impl Packet {
|
||||||
writer.write_all(&data[0..length as usize])?;
|
writer.write_all(&data[0..length as usize])?;
|
||||||
},
|
},
|
||||||
|
|
||||||
Packet::DmaAddTraceRequest { destination, id, status, trace, length } => {
|
Packet::DmaAddTraceRequest { source, destination, id, status, trace, length } => {
|
||||||
writer.write_u8(0xb0)?;
|
writer.write_u8(0xb0)?;
|
||||||
|
writer.write_u8(source)?;
|
||||||
writer.write_u8(destination)?;
|
writer.write_u8(destination)?;
|
||||||
writer.write_u32(id)?;
|
writer.write_u32(id)?;
|
||||||
writer.write_u8(status as u8)?;
|
writer.write_u8(status as u8)?;
|
||||||
|
@ -571,31 +595,39 @@ impl Packet {
|
||||||
writer.write_u16(length)?;
|
writer.write_u16(length)?;
|
||||||
writer.write_all(&trace[0..length as usize])?;
|
writer.write_all(&trace[0..length as usize])?;
|
||||||
},
|
},
|
||||||
Packet::DmaAddTraceReply { succeeded } => {
|
Packet::DmaAddTraceReply { source, destination, id, succeeded } => {
|
||||||
writer.write_u8(0xb1)?;
|
writer.write_u8(0xb1)?;
|
||||||
|
writer.write_u8(source)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
|
writer.write_u32(id)?;
|
||||||
writer.write_bool(succeeded)?;
|
writer.write_bool(succeeded)?;
|
||||||
},
|
},
|
||||||
Packet::DmaRemoveTraceRequest { destination, id } => {
|
Packet::DmaRemoveTraceRequest { source, destination, id } => {
|
||||||
writer.write_u8(0xb2)?;
|
writer.write_u8(0xb2)?;
|
||||||
|
writer.write_u8(source)?;
|
||||||
writer.write_u8(destination)?;
|
writer.write_u8(destination)?;
|
||||||
writer.write_u32(id)?;
|
writer.write_u32(id)?;
|
||||||
},
|
},
|
||||||
Packet::DmaRemoveTraceReply { succeeded } => {
|
Packet::DmaRemoveTraceReply { destination, succeeded } => {
|
||||||
writer.write_u8(0xb3)?;
|
writer.write_u8(0xb3)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
writer.write_bool(succeeded)?;
|
writer.write_bool(succeeded)?;
|
||||||
},
|
},
|
||||||
Packet::DmaPlaybackRequest { destination, id, timestamp } => {
|
Packet::DmaPlaybackRequest { source, destination, id, timestamp } => {
|
||||||
writer.write_u8(0xb4)?;
|
writer.write_u8(0xb4)?;
|
||||||
|
writer.write_u8(source)?;
|
||||||
writer.write_u8(destination)?;
|
writer.write_u8(destination)?;
|
||||||
writer.write_u32(id)?;
|
writer.write_u32(id)?;
|
||||||
writer.write_u64(timestamp)?;
|
writer.write_u64(timestamp)?;
|
||||||
},
|
},
|
||||||
Packet::DmaPlaybackReply { succeeded } => {
|
Packet::DmaPlaybackReply { destination, succeeded } => {
|
||||||
writer.write_u8(0xb5)?;
|
writer.write_u8(0xb5)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
writer.write_bool(succeeded)?;
|
writer.write_bool(succeeded)?;
|
||||||
},
|
},
|
||||||
Packet::DmaPlaybackStatus { destination, id, error, channel, timestamp } => {
|
Packet::DmaPlaybackStatus { source, destination, id, error, channel, timestamp } => {
|
||||||
writer.write_u8(0xb6)?;
|
writer.write_u8(0xb6)?;
|
||||||
|
writer.write_u8(source)?;
|
||||||
writer.write_u8(destination)?;
|
writer.write_u8(destination)?;
|
||||||
writer.write_u32(id)?;
|
writer.write_u32(id)?;
|
||||||
writer.write_u8(error)?;
|
writer.write_u8(error)?;
|
||||||
|
@ -615,33 +647,40 @@ impl Packet {
|
||||||
writer.write_u8(0xc1)?;
|
writer.write_u8(0xc1)?;
|
||||||
writer.write_bool(succeeded)?;
|
writer.write_bool(succeeded)?;
|
||||||
},
|
},
|
||||||
Packet::SubkernelLoadRunRequest { destination, id, run } => {
|
Packet::SubkernelLoadRunRequest { source, destination, id, run } => {
|
||||||
writer.write_u8(0xc4)?;
|
writer.write_u8(0xc4)?;
|
||||||
|
writer.write_u8(source)?;
|
||||||
writer.write_u8(destination)?;
|
writer.write_u8(destination)?;
|
||||||
writer.write_u32(id)?;
|
writer.write_u32(id)?;
|
||||||
writer.write_bool(run)?;
|
writer.write_bool(run)?;
|
||||||
},
|
},
|
||||||
Packet::SubkernelLoadRunReply { succeeded } => {
|
Packet::SubkernelLoadRunReply { destination, succeeded } => {
|
||||||
writer.write_u8(0xc5)?;
|
writer.write_u8(0xc5)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
writer.write_bool(succeeded)?;
|
writer.write_bool(succeeded)?;
|
||||||
},
|
},
|
||||||
Packet::SubkernelFinished { id, with_exception } => {
|
Packet::SubkernelFinished { destination, id, with_exception, exception_src } => {
|
||||||
writer.write_u8(0xc8)?;
|
writer.write_u8(0xc8)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
writer.write_u32(id)?;
|
writer.write_u32(id)?;
|
||||||
writer.write_bool(with_exception)?;
|
writer.write_bool(with_exception)?;
|
||||||
|
writer.write_u8(exception_src)?;
|
||||||
},
|
},
|
||||||
Packet::SubkernelExceptionRequest { destination } => {
|
Packet::SubkernelExceptionRequest { source, destination } => {
|
||||||
writer.write_u8(0xc9)?;
|
writer.write_u8(0xc9)?;
|
||||||
|
writer.write_u8(source)?;
|
||||||
writer.write_u8(destination)?;
|
writer.write_u8(destination)?;
|
||||||
},
|
},
|
||||||
Packet::SubkernelException { last, length, data } => {
|
Packet::SubkernelException { destination, last, length, data } => {
|
||||||
writer.write_u8(0xca)?;
|
writer.write_u8(0xca)?;
|
||||||
|
writer.write_u8(destination)?;
|
||||||
writer.write_bool(last)?;
|
writer.write_bool(last)?;
|
||||||
writer.write_u16(length)?;
|
writer.write_u16(length)?;
|
||||||
writer.write_all(&data[0..length as usize])?;
|
writer.write_all(&data[0..length as usize])?;
|
||||||
},
|
},
|
||||||
Packet::SubkernelMessage { destination, id, status, data, length } => {
|
Packet::SubkernelMessage { source, destination, id, status, data, length } => {
|
||||||
writer.write_u8(0xcb)?;
|
writer.write_u8(0xcb)?;
|
||||||
|
writer.write_u8(source)?;
|
||||||
writer.write_u8(destination)?;
|
writer.write_u8(destination)?;
|
||||||
writer.write_u32(id)?;
|
writer.write_u32(id)?;
|
||||||
writer.write_u8(status as u8)?;
|
writer.write_u8(status as u8)?;
|
||||||
|
@ -655,4 +694,38 @@ impl Packet {
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn routable_destination(&self) -> Option<u8> {
|
||||||
|
// only for packets that could be re-routed, not only forwarded
|
||||||
|
match self {
|
||||||
|
Packet::DmaAddTraceRequest { destination, .. } => Some(*destination),
|
||||||
|
Packet::DmaAddTraceReply { destination, .. } => Some(*destination),
|
||||||
|
Packet::DmaRemoveTraceRequest { destination, .. } => Some(*destination),
|
||||||
|
Packet::DmaRemoveTraceReply { destination, .. } => Some(*destination),
|
||||||
|
Packet::DmaPlaybackRequest { destination, .. } => Some(*destination),
|
||||||
|
Packet::DmaPlaybackReply { destination, .. } => Some(*destination),
|
||||||
|
Packet::SubkernelLoadRunRequest { destination, .. } => Some(*destination),
|
||||||
|
Packet::SubkernelLoadRunReply { destination, .. } => Some(*destination),
|
||||||
|
Packet::SubkernelMessage { destination, .. } => Some(*destination),
|
||||||
|
Packet::SubkernelMessageAck { destination, .. } => Some(*destination),
|
||||||
|
Packet::SubkernelExceptionRequest { destination, .. } => Some(*destination),
|
||||||
|
Packet::SubkernelException { destination, .. } => Some(*destination),
|
||||||
|
Packet::DmaPlaybackStatus { destination, .. } => Some(*destination),
|
||||||
|
Packet::SubkernelFinished { destination, .. } => Some(*destination),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn expects_response(&self) -> bool {
|
||||||
|
// returns true if the routable packet should elicit a response
|
||||||
|
// e.g. reply, ACK packets end a conversation,
|
||||||
|
// and firmware should not wait for response
|
||||||
|
match self {
|
||||||
|
Packet::DmaAddTraceReply { .. } | Packet::DmaRemoveTraceReply { .. } |
|
||||||
|
Packet::DmaPlaybackReply { .. } | Packet::SubkernelLoadRunReply { .. } |
|
||||||
|
Packet::SubkernelMessageAck { .. } | Packet::DmaPlaybackStatus { .. } |
|
||||||
|
Packet::SubkernelFinished { .. } => false,
|
||||||
|
_ => true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,12 @@ pub const KERNELCPU_LAST_ADDRESS: usize = 0x4fffffff;
|
||||||
pub const KSUPPORT_HEADER_SIZE: usize = 0x74;
|
pub const KSUPPORT_HEADER_SIZE: usize = 0x74;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum SubkernelStatus {
|
pub enum SubkernelStatus<'a> {
|
||||||
NoError,
|
|
||||||
Timeout,
|
Timeout,
|
||||||
IncorrectState,
|
IncorrectState,
|
||||||
CommLost,
|
CommLost,
|
||||||
OtherError
|
Exception(eh::eh_artiq::Exception<'a>),
|
||||||
|
OtherError,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -103,13 +103,14 @@ pub enum Message<'a> {
|
||||||
SpiReadReply { succeeded: bool, data: u32 },
|
SpiReadReply { succeeded: bool, data: u32 },
|
||||||
SpiBasicReply { succeeded: bool },
|
SpiBasicReply { succeeded: bool },
|
||||||
|
|
||||||
SubkernelLoadRunRequest { id: u32, run: bool },
|
SubkernelLoadRunRequest { id: u32, destination: u8, run: bool },
|
||||||
SubkernelLoadRunReply { succeeded: bool },
|
SubkernelLoadRunReply { succeeded: bool },
|
||||||
SubkernelAwaitFinishRequest { id: u32, timeout: u64 },
|
SubkernelAwaitFinishRequest { id: u32, timeout: i64 },
|
||||||
SubkernelAwaitFinishReply { status: SubkernelStatus },
|
SubkernelAwaitFinishReply,
|
||||||
SubkernelMsgSend { id: u32, count: u8, tag: &'a [u8], data: *const *const () },
|
SubkernelMsgSend { id: u32, destination: Option<u8>, count: u8, tag: &'a [u8], data: *const *const () },
|
||||||
SubkernelMsgRecvRequest { id: u32, timeout: u64, tags: &'a [u8] },
|
SubkernelMsgRecvRequest { id: i32, timeout: i64, tags: &'a [u8] },
|
||||||
SubkernelMsgRecvReply { status: SubkernelStatus, count: u8 },
|
SubkernelMsgRecvReply { count: u8 },
|
||||||
|
SubkernelError(SubkernelStatus<'a>),
|
||||||
|
|
||||||
Log(fmt::Arguments<'a>),
|
Log(fmt::Arguments<'a>),
|
||||||
LogSlice(&'a str)
|
LogSlice(&'a str)
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
#![feature(link_cfg)]
|
#![feature(link_cfg)]
|
||||||
#![feature(nll)]
|
#![feature(nll)]
|
||||||
#![feature(staged_api)]
|
#![feature(staged_api)]
|
||||||
#![feature(unwind_attributes)]
|
|
||||||
#![feature(static_nobundle)]
|
#![feature(static_nobundle)]
|
||||||
|
#![feature(c_unwind)]
|
||||||
#![cfg_attr(not(target_env = "msvc"), feature(libc))]
|
#![cfg_attr(not(target_env = "msvc"), feature(libc))]
|
||||||
|
|
||||||
mod libunwind;
|
mod libunwind;
|
||||||
|
|
|
@ -81,9 +81,14 @@ pub type _Unwind_Exception_Cleanup_Fn =
|
||||||
all(feature = "llvm-libunwind", any(target_os = "fuchsia", target_os = "linux")),
|
all(feature = "llvm-libunwind", any(target_os = "fuchsia", target_os = "linux")),
|
||||||
link(name = "unwind", kind = "static")
|
link(name = "unwind", kind = "static")
|
||||||
)]
|
)]
|
||||||
extern "C" {
|
extern "C-unwind" {
|
||||||
#[unwind(allowed)]
|
|
||||||
pub fn _Unwind_Resume(exception: *mut _Unwind_Exception) -> !;
|
pub fn _Unwind_Resume(exception: *mut _Unwind_Exception) -> !;
|
||||||
|
}
|
||||||
|
#[cfg_attr(
|
||||||
|
all(feature = "llvm-libunwind", any(target_os = "fuchsia", target_os = "linux")),
|
||||||
|
link(name = "unwind", kind = "static")
|
||||||
|
)]
|
||||||
|
extern "C" {
|
||||||
pub fn _Unwind_DeleteException(exception: *mut _Unwind_Exception);
|
pub fn _Unwind_DeleteException(exception: *mut _Unwind_Exception);
|
||||||
pub fn _Unwind_GetLanguageSpecificData(ctx: *mut _Unwind_Context) -> *mut c_void;
|
pub fn _Unwind_GetLanguageSpecificData(ctx: *mut _Unwind_Context) -> *mut c_void;
|
||||||
pub fn _Unwind_GetRegionStart(ctx: *mut _Unwind_Context) -> _Unwind_Ptr;
|
pub fn _Unwind_GetRegionStart(ctx: *mut _Unwind_Context) -> _Unwind_Ptr;
|
||||||
|
@ -230,9 +235,13 @@ if #[cfg(not(all(target_os = "ios", target_arch = "arm")))] {
|
||||||
#[cfg_attr(all(feature = "llvm-libunwind",
|
#[cfg_attr(all(feature = "llvm-libunwind",
|
||||||
any(target_os = "fuchsia", target_os = "linux")),
|
any(target_os = "fuchsia", target_os = "linux")),
|
||||||
link(name = "unwind", kind = "static"))]
|
link(name = "unwind", kind = "static"))]
|
||||||
extern "C" {
|
extern "C-unwind" {
|
||||||
#[unwind(allowed)]
|
|
||||||
pub fn _Unwind_RaiseException(exception: *mut _Unwind_Exception) -> _Unwind_Reason_Code;
|
pub fn _Unwind_RaiseException(exception: *mut _Unwind_Exception) -> _Unwind_Reason_Code;
|
||||||
|
}
|
||||||
|
#[cfg_attr(all(feature = "llvm-libunwind",
|
||||||
|
any(target_os = "fuchsia", target_os = "linux")),
|
||||||
|
link(name = "unwind", kind = "static"))]
|
||||||
|
extern "C" {
|
||||||
pub fn _Unwind_Backtrace(trace: _Unwind_Trace_Fn,
|
pub fn _Unwind_Backtrace(trace: _Unwind_Trace_Fn,
|
||||||
trace_argument: *mut c_void)
|
trace_argument: *mut c_void)
|
||||||
-> _Unwind_Reason_Code;
|
-> _Unwind_Reason_Code;
|
||||||
|
@ -242,8 +251,7 @@ if #[cfg(not(all(target_os = "ios", target_arch = "arm")))] {
|
||||||
#[cfg_attr(all(feature = "llvm-libunwind",
|
#[cfg_attr(all(feature = "llvm-libunwind",
|
||||||
any(target_os = "fuchsia", target_os = "linux")),
|
any(target_os = "fuchsia", target_os = "linux")),
|
||||||
link(name = "unwind", kind = "static"))]
|
link(name = "unwind", kind = "static"))]
|
||||||
extern "C" {
|
extern "C-unwind" {
|
||||||
#[unwind(allowed)]
|
|
||||||
pub fn _Unwind_SjLj_RaiseException(e: *mut _Unwind_Exception) -> _Unwind_Reason_Code;
|
pub fn _Unwind_SjLj_RaiseException(e: *mut _Unwind_Exception) -> _Unwind_Reason_Code;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -21,20 +21,6 @@
|
||||||
},
|
},
|
||||||
"relro-level": "full",
|
"relro-level": "full",
|
||||||
"target-family": "unix",
|
"target-family": "unix",
|
||||||
"target-pointer-width": "32",
|
"target-pointer-width": "32"
|
||||||
"unsupported-abis": [
|
|
||||||
"cdecl",
|
|
||||||
"stdcall",
|
|
||||||
"fastcall",
|
|
||||||
"vectorcall",
|
|
||||||
"thiscall",
|
|
||||||
"aapcs",
|
|
||||||
"win64",
|
|
||||||
"sysv64",
|
|
||||||
"ptx-kernel",
|
|
||||||
"msp430-interrupt",
|
|
||||||
"x86-interrupt",
|
|
||||||
"amdgpu-kernel"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,19 +13,5 @@
|
||||||
"max-atomic-width": 32,
|
"max-atomic-width": 32,
|
||||||
"panic-strategy": "unwind",
|
"panic-strategy": "unwind",
|
||||||
"relocation-model": "static",
|
"relocation-model": "static",
|
||||||
"target-pointer-width": "32",
|
"target-pointer-width": "32"
|
||||||
"unsupported-abis": [
|
|
||||||
"cdecl",
|
|
||||||
"stdcall",
|
|
||||||
"fastcall",
|
|
||||||
"vectorcall",
|
|
||||||
"thiscall",
|
|
||||||
"aapcs",
|
|
||||||
"win64",
|
|
||||||
"sysv64",
|
|
||||||
"ptx-kernel",
|
|
||||||
"msp430-interrupt",
|
|
||||||
"x86-interrupt",
|
|
||||||
"amdgpu-kernel"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ 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 }
|
||||||
cslice = { version = "0.3" }
|
cslice = { version = "0.3" }
|
||||||
log = { version = "0.4", 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"] }
|
||||||
dyld = { path = "../libdyld" }
|
dyld = { path = "../libdyld" }
|
||||||
eh = { path = "../libeh" }
|
eh = { path = "../libeh" }
|
||||||
|
@ -37,7 +37,7 @@ features = ["alloc", "medium-ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp"
|
||||||
|
|
||||||
[dependencies.fringe]
|
[dependencies.fringe]
|
||||||
git = "https://git.m-labs.hk/M-Labs/libfringe.git"
|
git = "https://git.m-labs.hk/M-Labs/libfringe.git"
|
||||||
rev = "3ecbe5"
|
rev = "53a964"
|
||||||
default-features = false
|
default-features = false
|
||||||
features = ["alloc"]
|
features = ["alloc"]
|
||||||
|
|
||||||
|
|
|
@ -10,8 +10,6 @@ LDFLAGS += \
|
||||||
|
|
||||||
RUSTFLAGS += -Cpanic=unwind
|
RUSTFLAGS += -Cpanic=unwind
|
||||||
|
|
||||||
export XBUILD_SYSROOT_PATH=$(BUILDINC_DIRECTORY)/../sysroot
|
|
||||||
|
|
||||||
all:: runtime.bin runtime.fbi
|
all:: runtime.bin runtime.fbi
|
||||||
|
|
||||||
.PHONY: $(RUSTOUT)/libruntime.a
|
.PHONY: $(RUSTOUT)/libruntime.a
|
||||||
|
|
|
@ -52,7 +52,7 @@ pub mod remote_analyzer {
|
||||||
pub data: Vec<u8>
|
pub data: Vec<u8>
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_data(io: &Io, aux_mutex: &Mutex, routing_table: &drtio_routing::RoutingTable,
|
pub fn get_data(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex, routing_table: &drtio_routing::RoutingTable,
|
||||||
up_destinations: &Urc<RefCell<[bool; drtio_routing::DEST_COUNT]>>
|
up_destinations: &Urc<RefCell<[bool; drtio_routing::DEST_COUNT]>>
|
||||||
) -> Result<RemoteBuffer, drtio::Error> {
|
) -> Result<RemoteBuffer, drtio::Error> {
|
||||||
// gets data from satellites and returns consolidated data
|
// gets data from satellites and returns consolidated data
|
||||||
|
@ -62,7 +62,7 @@ pub mod remote_analyzer {
|
||||||
let mut remote_total_bytes = 0;
|
let mut remote_total_bytes = 0;
|
||||||
|
|
||||||
let data_vec = drtio::analyzer_query(
|
let data_vec = drtio::analyzer_query(
|
||||||
io, aux_mutex, routing_table, up_destinations
|
io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, up_destinations
|
||||||
)?;
|
)?;
|
||||||
for data in data_vec {
|
for data in data_vec {
|
||||||
remote_total_bytes += data.total_byte_count;
|
remote_total_bytes += data.total_byte_count;
|
||||||
|
@ -83,6 +83,7 @@ pub mod remote_analyzer {
|
||||||
|
|
||||||
|
|
||||||
fn worker(stream: &mut TcpStream, _io: &Io, _aux_mutex: &Mutex,
|
fn worker(stream: &mut TcpStream, _io: &Io, _aux_mutex: &Mutex,
|
||||||
|
_ddma_mutex: &Mutex, _subkernel_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]>>
|
||||||
) -> Result<(), IoError<SchedError>> {
|
) -> Result<(), IoError<SchedError>> {
|
||||||
|
@ -96,7 +97,7 @@ fn worker(stream: &mut TcpStream, _io: &Io, _aux_mutex: &Mutex,
|
||||||
|
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
let remote = remote_analyzer::get_data(
|
let remote = remote_analyzer::get_data(
|
||||||
_io, _aux_mutex, _routing_table, _up_destinations);
|
_io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, _up_destinations);
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
let (header, remote_data) = match remote {
|
let (header, remote_data) = match remote {
|
||||||
Ok(remote) => (Header {
|
Ok(remote) => (Header {
|
||||||
|
@ -143,7 +144,7 @@ fn worker(stream: &mut TcpStream, _io: &Io, _aux_mutex: &Mutex,
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn thread(io: Io, aux_mutex: &Mutex,
|
pub fn thread(io: Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_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]>>) {
|
||||||
let listener = TcpListener::new(&io, 65535);
|
let listener = TcpListener::new(&io, 65535);
|
||||||
|
@ -158,7 +159,7 @@ pub fn thread(io: Io, aux_mutex: &Mutex,
|
||||||
disarm();
|
disarm();
|
||||||
|
|
||||||
let routing_table = routing_table.borrow();
|
let routing_table = routing_table.borrow();
|
||||||
match worker(&mut stream, &io, aux_mutex, &routing_table, up_destinations) {
|
match worker(&mut stream, &io, aux_mutex, ddma_mutex, subkernel_mutex, &routing_table, up_destinations) {
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
Err(err) => error!("analyzer aborted: {}", err)
|
Err(err) => error!("analyzer aborted: {}", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,15 @@ use board_artiq::spi as local_spi;
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
mod remote_i2c {
|
mod remote_i2c {
|
||||||
use drtioaux;
|
use drtioaux;
|
||||||
|
use drtio_routing;
|
||||||
use rtio_mgt::drtio;
|
use rtio_mgt::drtio;
|
||||||
use sched::{Io, Mutex};
|
use sched::{Io, Mutex};
|
||||||
|
|
||||||
pub fn start(io: &Io, aux_mutex: &Mutex,
|
pub fn start(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8
|
linkno: u8, destination: u8, busno: u8
|
||||||
) -> Result<(), &'static str> {
|
) -> Result<(), &'static str> {
|
||||||
let reply = drtio::aux_transact(io, aux_mutex, linkno,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cStartRequest {
|
&drtioaux::Packet::I2cStartRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
busno: busno
|
busno: busno
|
||||||
|
@ -37,10 +39,11 @@ mod remote_i2c {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn restart(io: &Io, aux_mutex: &Mutex,
|
pub fn restart(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8
|
linkno: u8, destination: u8, busno: u8
|
||||||
) -> Result<(), &'static str> {
|
) -> Result<(), &'static str> {
|
||||||
let reply = drtio::aux_transact(io, aux_mutex, linkno,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cRestartRequest {
|
&drtioaux::Packet::I2cRestartRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
busno: busno
|
busno: busno
|
||||||
|
@ -60,10 +63,11 @@ mod remote_i2c {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stop(io: &Io, aux_mutex: &Mutex,
|
pub fn stop(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8
|
linkno: u8, destination: u8, busno: u8
|
||||||
) -> Result<(), &'static str> {
|
) -> Result<(), &'static str> {
|
||||||
let reply = drtio::aux_transact(io, aux_mutex, linkno,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cStopRequest {
|
&drtioaux::Packet::I2cStopRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
busno: busno
|
busno: busno
|
||||||
|
@ -83,10 +87,11 @@ mod remote_i2c {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(io: &Io, aux_mutex: &Mutex,
|
pub fn write(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8, data: u8
|
linkno: u8, destination: u8, busno: u8, data: u8
|
||||||
) -> Result<bool, &'static str> {
|
) -> Result<bool, &'static str> {
|
||||||
let reply = drtio::aux_transact(io, aux_mutex, linkno,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cWriteRequest {
|
&drtioaux::Packet::I2cWriteRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
busno: busno,
|
busno: busno,
|
||||||
|
@ -107,10 +112,11 @@ mod remote_i2c {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(io: &Io, aux_mutex: &Mutex,
|
pub fn read(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8, ack: bool
|
linkno: u8, destination: u8, busno: u8, ack: bool
|
||||||
) -> Result<u8, &'static str> {
|
) -> Result<u8, &'static str> {
|
||||||
let reply = drtio::aux_transact(io, aux_mutex, linkno,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cReadRequest {
|
&drtioaux::Packet::I2cReadRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
busno: busno,
|
busno: busno,
|
||||||
|
@ -131,10 +137,11 @@ mod remote_i2c {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn switch_select(io: &Io, aux_mutex: &Mutex,
|
pub fn switch_select(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8, address: u8, mask: u8
|
linkno: u8, destination: u8, busno: u8, address: u8, mask: u8
|
||||||
) -> Result<(), &'static str> {
|
) -> Result<(), &'static str> {
|
||||||
let reply = drtio::aux_transact(io, aux_mutex, linkno,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::I2cSwitchSelectRequest {
|
&drtioaux::Packet::I2cSwitchSelectRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
busno: busno,
|
busno: busno,
|
||||||
|
@ -160,13 +167,15 @@ mod remote_i2c {
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
mod remote_spi {
|
mod remote_spi {
|
||||||
use drtioaux;
|
use drtioaux;
|
||||||
|
use drtio_routing;
|
||||||
use rtio_mgt::drtio;
|
use rtio_mgt::drtio;
|
||||||
use sched::{Io, Mutex};
|
use sched::{Io, Mutex};
|
||||||
|
|
||||||
pub fn set_config(io: &Io, aux_mutex: &Mutex,
|
pub fn set_config(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8
|
linkno: u8, destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
let reply = drtio::aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::SpiSetConfigRequest {
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, &drtioaux::Packet::SpiSetConfigRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
busno: busno,
|
busno: busno,
|
||||||
flags: flags,
|
flags: flags,
|
||||||
|
@ -189,10 +198,11 @@ mod remote_spi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn write(io: &Io, aux_mutex: &Mutex,
|
pub fn write(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &drtio_routing::RoutingTable,
|
||||||
linkno: u8, destination: u8, busno: u8, data: u32
|
linkno: u8, destination: u8, busno: u8, data: u32
|
||||||
) -> Result<(), ()> {
|
) -> Result<(), ()> {
|
||||||
let reply = drtio::aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::SpiWriteRequest {
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno, &drtioaux::Packet::SpiWriteRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
busno: busno,
|
busno: busno,
|
||||||
data: data
|
data: data
|
||||||
|
@ -212,9 +222,10 @@ mod remote_spi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read(io: &Io, aux_mutex: &Mutex, linkno: u8, destination: u8, busno: u8
|
pub fn read(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
|
routing_table: &drtio_routing::RoutingTable, linkno: u8, destination: u8, busno: u8
|
||||||
) -> Result<u32, ()> {
|
) -> Result<u32, ()> {
|
||||||
let reply = drtio::aux_transact(io, aux_mutex, linkno,
|
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
|
||||||
&drtioaux::Packet::SpiReadRequest {
|
&drtioaux::Packet::SpiReadRequest {
|
||||||
destination: destination,
|
destination: destination,
|
||||||
busno: busno
|
busno: busno
|
||||||
|
@ -238,7 +249,7 @@ mod remote_spi {
|
||||||
|
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
macro_rules! dispatch {
|
macro_rules! dispatch {
|
||||||
($io:ident, $aux_mutex:ident, $mod_local:ident, $mod_remote:ident, $routing_table:ident, $busno:expr, $func:ident $(, $param:expr)*) => {{
|
($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $subkernel_mutex:ident, $mod_local:ident, $mod_remote:ident, $routing_table:ident, $busno:expr, $func:ident $(, $param:expr)*) => {{
|
||||||
let destination = ($busno >> 16) as u8;
|
let destination = ($busno >> 16) as u8;
|
||||||
let busno = $busno as u8;
|
let busno = $busno as u8;
|
||||||
let hop = $routing_table.0[destination as usize][0];
|
let hop = $routing_table.0[destination as usize][0];
|
||||||
|
@ -246,27 +257,27 @@ macro_rules! dispatch {
|
||||||
$mod_local::$func(busno, $($param, )*)
|
$mod_local::$func(busno, $($param, )*)
|
||||||
} else {
|
} else {
|
||||||
let linkno = hop - 1;
|
let linkno = hop - 1;
|
||||||
$mod_remote::$func($io, $aux_mutex, linkno, destination, busno, $($param, )*)
|
$mod_remote::$func($io, $aux_mutex, $ddma_mutex, $subkernel_mutex, $routing_table, linkno, destination, busno, $($param, )*)
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(has_drtio))]
|
#[cfg(not(has_drtio))]
|
||||||
macro_rules! dispatch {
|
macro_rules! dispatch {
|
||||||
($io:ident, $aux_mutex:ident, $mod_local:ident, $mod_remote:ident, $routing_table:ident, $busno:expr, $func:ident $(, $param:expr)*) => {{
|
($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $subkernel_mutex:ident, $mod_local:ident, $mod_remote:ident, $routing_table:ident, $busno:expr, $func:ident $(, $param:expr)*) => {{
|
||||||
let busno = $busno as u8;
|
let busno = $busno as u8;
|
||||||
$mod_local::$func(busno, $($param, )*)
|
$mod_local::$func(busno, $($param, )*)
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn process_kern_hwreq(io: &Io, aux_mutex: &Mutex,
|
pub fn process_kern_hwreq(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_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]>>,
|
||||||
request: &kern::Message) -> Result<bool, Error<SchedError>> {
|
request: &kern::Message) -> Result<bool, Error<SchedError>> {
|
||||||
match request {
|
match request {
|
||||||
&kern::RtioInitRequest => {
|
&kern::RtioInitRequest => {
|
||||||
info!("resetting RTIO");
|
info!("resetting RTIO");
|
||||||
rtio_mgt::reset(io, aux_mutex);
|
rtio_mgt::reset(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table);
|
||||||
kern_acknowledge()
|
kern_acknowledge()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -282,47 +293,47 @@ pub fn process_kern_hwreq(io: &Io, aux_mutex: &Mutex,
|
||||||
}
|
}
|
||||||
|
|
||||||
&kern::I2cStartRequest { busno } => {
|
&kern::I2cStartRequest { busno } => {
|
||||||
let succeeded = dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno, start).is_ok();
|
let succeeded = dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_i2c, remote_i2c, routing_table, busno, start).is_ok();
|
||||||
kern_send(io, &kern::I2cBasicReply { succeeded: succeeded })
|
kern_send(io, &kern::I2cBasicReply { succeeded: succeeded })
|
||||||
}
|
}
|
||||||
&kern::I2cRestartRequest { busno } => {
|
&kern::I2cRestartRequest { busno } => {
|
||||||
let succeeded = dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno, restart).is_ok();
|
let succeeded = dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_i2c, remote_i2c, routing_table, busno, restart).is_ok();
|
||||||
kern_send(io, &kern::I2cBasicReply { succeeded: succeeded })
|
kern_send(io, &kern::I2cBasicReply { succeeded: succeeded })
|
||||||
}
|
}
|
||||||
&kern::I2cStopRequest { busno } => {
|
&kern::I2cStopRequest { busno } => {
|
||||||
let succeeded = dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno, stop).is_ok();
|
let succeeded = dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_i2c, remote_i2c, routing_table, busno, stop).is_ok();
|
||||||
kern_send(io, &kern::I2cBasicReply { succeeded: succeeded })
|
kern_send(io, &kern::I2cBasicReply { succeeded: succeeded })
|
||||||
}
|
}
|
||||||
&kern::I2cWriteRequest { busno, data } => {
|
&kern::I2cWriteRequest { busno, data } => {
|
||||||
match dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno, write, data) {
|
match dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_i2c, remote_i2c, routing_table, busno, write, data) {
|
||||||
Ok(ack) => kern_send(io, &kern::I2cWriteReply { succeeded: true, ack: ack }),
|
Ok(ack) => kern_send(io, &kern::I2cWriteReply { succeeded: true, ack: ack }),
|
||||||
Err(_) => kern_send(io, &kern::I2cWriteReply { succeeded: false, ack: false })
|
Err(_) => kern_send(io, &kern::I2cWriteReply { succeeded: false, ack: false })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&kern::I2cReadRequest { busno, ack } => {
|
&kern::I2cReadRequest { busno, ack } => {
|
||||||
match dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno, read, ack) {
|
match dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_i2c, remote_i2c, routing_table, busno, read, ack) {
|
||||||
Ok(data) => kern_send(io, &kern::I2cReadReply { succeeded: true, data: data }),
|
Ok(data) => kern_send(io, &kern::I2cReadReply { succeeded: true, data: data }),
|
||||||
Err(_) => kern_send(io, &kern::I2cReadReply { succeeded: false, data: 0xff })
|
Err(_) => kern_send(io, &kern::I2cReadReply { succeeded: false, data: 0xff })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
&kern::I2cSwitchSelectRequest { busno, address, mask } => {
|
&kern::I2cSwitchSelectRequest { busno, address, mask } => {
|
||||||
let succeeded = dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno,
|
let succeeded = dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_i2c, remote_i2c, routing_table, busno,
|
||||||
switch_select, address, mask).is_ok();
|
switch_select, address, mask).is_ok();
|
||||||
kern_send(io, &kern::I2cBasicReply { succeeded: succeeded })
|
kern_send(io, &kern::I2cBasicReply { succeeded: succeeded })
|
||||||
}
|
}
|
||||||
|
|
||||||
&kern::SpiSetConfigRequest { busno, flags, length, div, cs } => {
|
&kern::SpiSetConfigRequest { busno, flags, length, div, cs } => {
|
||||||
let succeeded = dispatch!(io, aux_mutex, local_spi, remote_spi, _routing_table, busno,
|
let succeeded = dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_spi, remote_spi, routing_table, busno,
|
||||||
set_config, flags, length, div, cs).is_ok();
|
set_config, flags, length, div, cs).is_ok();
|
||||||
kern_send(io, &kern::SpiBasicReply { succeeded: succeeded })
|
kern_send(io, &kern::SpiBasicReply { succeeded: succeeded })
|
||||||
},
|
},
|
||||||
&kern::SpiWriteRequest { busno, data } => {
|
&kern::SpiWriteRequest { busno, data } => {
|
||||||
let succeeded = dispatch!(io, aux_mutex, local_spi, remote_spi, _routing_table, busno,
|
let succeeded = dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_spi, remote_spi, routing_table, busno,
|
||||||
write, data).is_ok();
|
write, data).is_ok();
|
||||||
kern_send(io, &kern::SpiBasicReply { succeeded: succeeded })
|
kern_send(io, &kern::SpiBasicReply { succeeded: succeeded })
|
||||||
}
|
}
|
||||||
&kern::SpiReadRequest { busno } => {
|
&kern::SpiReadRequest { busno } => {
|
||||||
match dispatch!(io, aux_mutex, local_spi, remote_spi, _routing_table, busno, read) {
|
match dispatch!(io, aux_mutex, ddma_mutex, subkernel_mutex, local_spi, remote_spi, routing_table, busno, read) {
|
||||||
Ok(data) => kern_send(io, &kern::SpiReadReply { succeeded: true, data: data }),
|
Ok(data) => kern_send(io, &kern::SpiReadReply { succeeded: true, data: data }),
|
||||||
Err(_) => kern_send(io, &kern::SpiReadReply { succeeded: false, data: 0 })
|
Err(_) => kern_send(io, &kern::SpiReadReply { succeeded: false, data: 0 })
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,7 +95,9 @@ pub mod subkernel {
|
||||||
use board_artiq::drtio_routing::RoutingTable;
|
use board_artiq::drtio_routing::RoutingTable;
|
||||||
use board_misoc::clock;
|
use board_misoc::clock;
|
||||||
use proto_artiq::{drtioaux_proto::{PayloadStatus, MASTER_PAYLOAD_MAX_SIZE}, rpc_proto as rpc};
|
use proto_artiq::{drtioaux_proto::{PayloadStatus, MASTER_PAYLOAD_MAX_SIZE}, rpc_proto as rpc};
|
||||||
use io::Cursor;
|
use io::{Cursor, ProtoRead};
|
||||||
|
use eh::eh_artiq::Exception;
|
||||||
|
use cslice::CSlice;
|
||||||
use rtio_mgt::drtio;
|
use rtio_mgt::drtio;
|
||||||
use sched::{Io, Mutex, Error as SchedError};
|
use sched::{Io, Mutex, Error as SchedError};
|
||||||
|
|
||||||
|
@ -103,7 +105,7 @@ pub mod subkernel {
|
||||||
pub enum FinishStatus {
|
pub enum FinishStatus {
|
||||||
Ok,
|
Ok,
|
||||||
CommLost,
|
CommLost,
|
||||||
Exception
|
Exception(u8) // exception source
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
@ -181,17 +183,17 @@ pub mod subkernel {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn upload(io: &Io, aux_mutex: &Mutex, subkernel_mutex: &Mutex,
|
pub fn upload(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &RoutingTable, id: u32) -> Result<(), Error> {
|
routing_table: &RoutingTable, id: u32) -> Result<(), Error> {
|
||||||
let _lock = subkernel_mutex.lock(io)?;
|
let _lock = subkernel_mutex.lock(io)?;
|
||||||
let subkernel = unsafe { SUBKERNELS.get_mut(&id).unwrap() };
|
let subkernel = unsafe { SUBKERNELS.get_mut(&id).unwrap() };
|
||||||
drtio::subkernel_upload(io, aux_mutex, routing_table, id,
|
drtio::subkernel_upload(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, id,
|
||||||
subkernel.destination, &subkernel.data)?;
|
subkernel.destination, &subkernel.data)?;
|
||||||
subkernel.state = SubkernelState::Uploaded;
|
subkernel.state = SubkernelState::Uploaded;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn load(io: &Io, aux_mutex: &Mutex, subkernel_mutex: &Mutex, routing_table: &RoutingTable,
|
pub fn load(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex, routing_table: &RoutingTable,
|
||||||
id: u32, run: bool) -> Result<(), Error> {
|
id: u32, run: bool) -> Result<(), Error> {
|
||||||
let _lock = subkernel_mutex.lock(io)?;
|
let _lock = subkernel_mutex.lock(io)?;
|
||||||
let subkernel = unsafe { SUBKERNELS.get_mut(&id).unwrap() };
|
let subkernel = unsafe { SUBKERNELS.get_mut(&id).unwrap() };
|
||||||
|
@ -199,7 +201,7 @@ pub mod subkernel {
|
||||||
error!("for id: {} expected Uploaded, got: {:?}", id, subkernel.state);
|
error!("for id: {} expected Uploaded, got: {:?}", id, subkernel.state);
|
||||||
return Err(Error::IncorrectState);
|
return Err(Error::IncorrectState);
|
||||||
}
|
}
|
||||||
drtio::subkernel_load(io, aux_mutex, routing_table, id, subkernel.destination, run)?;
|
drtio::subkernel_load(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, id, subkernel.destination, run)?;
|
||||||
if run {
|
if run {
|
||||||
subkernel.state = SubkernelState::Running;
|
subkernel.state = SubkernelState::Running;
|
||||||
}
|
}
|
||||||
|
@ -216,7 +218,7 @@ pub mod subkernel {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn subkernel_finished(io: &Io, subkernel_mutex: &Mutex, id: u32, with_exception: bool) {
|
pub fn subkernel_finished(io: &Io, subkernel_mutex: &Mutex, id: u32, with_exception: bool, exception_src: u8) {
|
||||||
// called upon receiving DRTIO SubkernelRunDone
|
// called upon receiving DRTIO SubkernelRunDone
|
||||||
let _lock = subkernel_mutex.lock(io).unwrap();
|
let _lock = subkernel_mutex.lock(io).unwrap();
|
||||||
let subkernel = unsafe { SUBKERNELS.get_mut(&id) };
|
let subkernel = unsafe { SUBKERNELS.get_mut(&id) };
|
||||||
|
@ -226,22 +228,22 @@ pub mod subkernel {
|
||||||
if subkernel.state == SubkernelState::Running {
|
if subkernel.state == SubkernelState::Running {
|
||||||
subkernel.state = SubkernelState::Finished {
|
subkernel.state = SubkernelState::Finished {
|
||||||
status: match with_exception {
|
status: match with_exception {
|
||||||
true => FinishStatus::Exception,
|
true => FinishStatus::Exception(exception_src),
|
||||||
false => FinishStatus::Ok,
|
false => FinishStatus::Ok,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn destination_changed(io: &Io, aux_mutex: &Mutex, subkernel_mutex: &Mutex,
|
pub fn destination_changed(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &RoutingTable, destination: u8, up: bool) {
|
routing_table: &RoutingTable, destination: u8, up: bool) {
|
||||||
let _lock = subkernel_mutex.lock(io).unwrap();
|
let _lock = subkernel_mutex.lock(io).unwrap();
|
||||||
let subkernels_iter = unsafe { SUBKERNELS.iter_mut() };
|
let subkernels_iter = unsafe { SUBKERNELS.iter_mut() };
|
||||||
for (id, subkernel) in subkernels_iter {
|
for (id, subkernel) in subkernels_iter {
|
||||||
if subkernel.destination == destination {
|
if subkernel.destination == destination {
|
||||||
if up {
|
if up {
|
||||||
match drtio::subkernel_upload(io, aux_mutex, routing_table, *id, destination, &subkernel.data)
|
match drtio::subkernel_upload(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, *id, destination, &subkernel.data)
|
||||||
{
|
{
|
||||||
Ok(_) => subkernel.state = SubkernelState::Uploaded,
|
Ok(_) => subkernel.state = SubkernelState::Uploaded,
|
||||||
Err(e) => error!("Error adding subkernel on destination {}: {}", destination, e)
|
Err(e) => error!("Error adding subkernel on destination {}: {}", destination, e)
|
||||||
|
@ -256,7 +258,50 @@ pub mod subkernel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retrieve_finish_status(io: &Io, aux_mutex: &Mutex, subkernel_mutex: &Mutex,
|
fn read_exception_string<'a>(reader: &mut Cursor<&[u8]>) -> Result<CSlice<'a, u8>, Error> {
|
||||||
|
let len = reader.read_u32()? as usize;
|
||||||
|
if len == usize::MAX {
|
||||||
|
let data = reader.read_u32()?;
|
||||||
|
Ok(unsafe { CSlice::new(data as *const u8, len) })
|
||||||
|
} else {
|
||||||
|
let pos = reader.position();
|
||||||
|
let slice = unsafe {
|
||||||
|
let ptr = reader.get_ref().as_ptr().offset(pos as isize);
|
||||||
|
CSlice::new(ptr, len)
|
||||||
|
};
|
||||||
|
reader.set_position(pos + len);
|
||||||
|
Ok(slice)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_exception(buffer: &[u8]) -> Result<Exception, Error>
|
||||||
|
{
|
||||||
|
let mut reader = Cursor::new(buffer);
|
||||||
|
|
||||||
|
let mut byte = reader.read_u8()?;
|
||||||
|
// to sync
|
||||||
|
while byte != 0x5a {
|
||||||
|
byte = reader.read_u8()?;
|
||||||
|
}
|
||||||
|
// skip sync bytes, 0x09 indicates exception
|
||||||
|
while byte != 0x09 {
|
||||||
|
byte = reader.read_u8()?;
|
||||||
|
}
|
||||||
|
let _len = reader.read_u32()?;
|
||||||
|
// ignore the remaining exceptions, stack traces etc. - unwinding from another device would be unwise anyway
|
||||||
|
Ok(Exception {
|
||||||
|
id: reader.read_u32()?,
|
||||||
|
message: read_exception_string(&mut reader)?,
|
||||||
|
param: [reader.read_u64()? as i64, reader.read_u64()? as i64, reader.read_u64()? as i64],
|
||||||
|
file: read_exception_string(&mut reader)?,
|
||||||
|
line: reader.read_u32()?,
|
||||||
|
column: reader.read_u32()?,
|
||||||
|
function: read_exception_string(&mut reader)?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub fn retrieve_finish_status(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &RoutingTable, id: u32) -> Result<SubkernelFinished, Error> {
|
routing_table: &RoutingTable, id: u32) -> Result<SubkernelFinished, Error> {
|
||||||
let _lock = subkernel_mutex.lock(io)?;
|
let _lock = subkernel_mutex.lock(io)?;
|
||||||
let mut subkernel = unsafe { SUBKERNELS.get_mut(&id).unwrap() };
|
let mut subkernel = unsafe { SUBKERNELS.get_mut(&id).unwrap() };
|
||||||
|
@ -266,9 +311,9 @@ pub mod subkernel {
|
||||||
Ok(SubkernelFinished {
|
Ok(SubkernelFinished {
|
||||||
id: id,
|
id: id,
|
||||||
comm_lost: status == FinishStatus::CommLost,
|
comm_lost: status == FinishStatus::CommLost,
|
||||||
exception: if status == FinishStatus::Exception {
|
exception: if let FinishStatus::Exception(dest) = status {
|
||||||
Some(drtio::subkernel_retrieve_exception(io, aux_mutex,
|
Some(drtio::subkernel_retrieve_exception(io, aux_mutex, ddma_mutex, subkernel_mutex,
|
||||||
routing_table, subkernel.destination)?)
|
routing_table, dest)?)
|
||||||
} else { None }
|
} else { None }
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
@ -278,8 +323,8 @@ pub mod subkernel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn await_finish(io: &Io, aux_mutex: &Mutex, subkernel_mutex: &Mutex,
|
pub fn await_finish(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &RoutingTable, id: u32, timeout: u64) -> Result<SubkernelFinished, Error> {
|
routing_table: &RoutingTable, id: u32, timeout: i64) -> Result<SubkernelFinished, Error> {
|
||||||
{
|
{
|
||||||
let _lock = subkernel_mutex.lock(io)?;
|
let _lock = subkernel_mutex.lock(io)?;
|
||||||
match unsafe { SUBKERNELS.get(&id).unwrap().state } {
|
match unsafe { SUBKERNELS.get(&id).unwrap().state } {
|
||||||
|
@ -291,7 +336,7 @@ pub mod subkernel {
|
||||||
}
|
}
|
||||||
let max_time = clock::get_ms() + timeout as u64;
|
let max_time = clock::get_ms() + timeout as u64;
|
||||||
let _res = io.until(|| {
|
let _res = io.until(|| {
|
||||||
if clock::get_ms() > max_time {
|
if timeout > 0 && clock::get_ms() > max_time {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if subkernel_mutex.test_lock() {
|
if subkernel_mutex.test_lock() {
|
||||||
|
@ -305,11 +350,11 @@ pub mod subkernel {
|
||||||
_ => false
|
_ => false
|
||||||
}
|
}
|
||||||
})?;
|
})?;
|
||||||
if clock::get_ms() > max_time {
|
if timeout > 0 && clock::get_ms() > max_time {
|
||||||
error!("Remote subkernel finish await timed out");
|
error!("Remote subkernel finish await timed out");
|
||||||
return Err(Error::Timeout);
|
return Err(Error::Timeout);
|
||||||
}
|
}
|
||||||
retrieve_finish_status(io, aux_mutex, subkernel_mutex, routing_table, id)
|
retrieve_finish_status(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Message {
|
pub struct Message {
|
||||||
|
@ -332,8 +377,9 @@ pub mod subkernel {
|
||||||
Err(_) => return,
|
Err(_) => return,
|
||||||
};
|
};
|
||||||
let subkernel = unsafe { SUBKERNELS.get(&id) };
|
let subkernel = unsafe { SUBKERNELS.get(&id) };
|
||||||
if subkernel.is_none() || subkernel.unwrap().state != SubkernelState::Running {
|
if subkernel.is_some() && subkernel.unwrap().state != SubkernelState::Running {
|
||||||
// do not add messages for non-existing, non-running or deleted subkernels
|
warn!("received a message for a non-running subkernel #{}", id);
|
||||||
|
// do not add messages for non-running or deleted subkernels
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if status.is_first() {
|
if status.is_first() {
|
||||||
|
@ -359,19 +405,26 @@ pub mod subkernel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn message_await(io: &Io, subkernel_mutex: &Mutex, id: u32, timeout: u64
|
pub fn message_await(io: &Io, subkernel_mutex: &Mutex, id: u32, timeout: i64
|
||||||
) -> Result<Message, Error> {
|
) -> Result<Message, Error> {
|
||||||
{
|
let is_subkernel = {
|
||||||
let _lock = subkernel_mutex.lock(io)?;
|
let _lock = subkernel_mutex.lock(io)?;
|
||||||
|
let is_subkernel = unsafe { SUBKERNELS.get(&id).is_some() };
|
||||||
|
if is_subkernel {
|
||||||
match unsafe { SUBKERNELS.get(&id).unwrap().state } {
|
match unsafe { SUBKERNELS.get(&id).unwrap().state } {
|
||||||
SubkernelState::Finished { .. } => return Err(Error::SubkernelFinished),
|
SubkernelState::Finished { status: FinishStatus::Ok } |
|
||||||
SubkernelState::Running => (),
|
SubkernelState::Running => (),
|
||||||
|
SubkernelState::Finished {
|
||||||
|
status: FinishStatus::CommLost,
|
||||||
|
} => return Err(Error::SubkernelFinished),
|
||||||
_ => return Err(Error::IncorrectState)
|
_ => return Err(Error::IncorrectState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
is_subkernel
|
||||||
|
};
|
||||||
let max_time = clock::get_ms() + timeout as u64;
|
let max_time = clock::get_ms() + timeout as u64;
|
||||||
let message = io.until_ok(|| {
|
let message = io.until_ok(|| {
|
||||||
if clock::get_ms() > max_time {
|
if timeout > 0 && clock::get_ms() > max_time {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
if subkernel_mutex.test_lock() {
|
if subkernel_mutex.test_lock() {
|
||||||
|
@ -384,9 +437,12 @@ pub mod subkernel {
|
||||||
return Ok(Some(unsafe { MESSAGE_QUEUE.remove(i) }));
|
return Ok(Some(unsafe { MESSAGE_QUEUE.remove(i) }));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if is_subkernel {
|
||||||
match unsafe { SUBKERNELS.get(&id).unwrap().state } {
|
match unsafe { SUBKERNELS.get(&id).unwrap().state } {
|
||||||
SubkernelState::Finished { .. } => return Ok(None),
|
SubkernelState::Finished { status: FinishStatus::CommLost } |
|
||||||
|
SubkernelState::Finished { status: FinishStatus::Exception(_) } => return Ok(None),
|
||||||
_ => ()
|
_ => ()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(())
|
Err(())
|
||||||
});
|
});
|
||||||
|
@ -407,20 +463,22 @@ pub mod subkernel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn message_send<'a>(io: &Io, aux_mutex: &Mutex, subkernel_mutex: &Mutex,
|
pub fn message_send<'a>(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
|
||||||
routing_table: &RoutingTable, id: u32, count: u8, tag: &'a [u8], message: *const *const ()
|
routing_table: &RoutingTable, id: u32, destination: Option<u8>, count: u8, tag: &'a [u8], message: *const *const ()
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut writer = Cursor::new(Vec::new());
|
let mut writer = Cursor::new(Vec::new());
|
||||||
let _lock = subkernel_mutex.lock(io)?;
|
|
||||||
let destination = unsafe { SUBKERNELS.get(&id).unwrap().destination };
|
|
||||||
|
|
||||||
// reuse rpc code for sending arbitrary data
|
// reuse rpc code for sending arbitrary data
|
||||||
rpc::send_args(&mut writer, 0, tag, message, false)?;
|
rpc::send_args(&mut writer, 0, tag, message, false)?;
|
||||||
// skip service tag, but overwrite first byte with tag count
|
// skip service tag, but overwrite first byte with tag count
|
||||||
|
let destination = destination.unwrap_or_else(|| {
|
||||||
|
let _lock = subkernel_mutex.lock(io).unwrap();
|
||||||
|
unsafe { SUBKERNELS.get(&id).unwrap().destination }
|
||||||
|
}
|
||||||
|
);
|
||||||
let data = &mut writer.into_inner()[3..];
|
let data = &mut writer.into_inner()[3..];
|
||||||
data[0] = count;
|
data[0] = count;
|
||||||
Ok(drtio::subkernel_send_message(
|
Ok(drtio::subkernel_send_message(
|
||||||
io, aux_mutex, routing_table, id, destination, data
|
io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, id, destination, data
|
||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -29,19 +29,24 @@ 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)]
|
||||||
use board_misoc::ethmac;
|
use board_misoc::ethmac;
|
||||||
|
#[cfg(soc_platform = "kasli")]
|
||||||
|
use board_misoc::irq;
|
||||||
use board_misoc::net_settings::{Ipv4AddrConfig};
|
use board_misoc::net_settings::{Ipv4AddrConfig};
|
||||||
#[cfg(has_drtio)]
|
#[cfg(has_drtio)]
|
||||||
use board_artiq::drtioaux;
|
use board_artiq::drtioaux;
|
||||||
use board_artiq::drtio_routing;
|
use board_artiq::drtio_routing;
|
||||||
use board_artiq::{mailbox, rpc_queue};
|
use board_artiq::{mailbox, rpc_queue};
|
||||||
use proto_artiq::{mgmt_proto, moninj_proto, rpc_proto, session_proto, kernel_proto};
|
use proto_artiq::{mgmt_proto, moninj_proto, rpc_proto, session_proto, kernel_proto};
|
||||||
|
#[cfg(has_wrpll)]
|
||||||
|
use board_artiq::si549;
|
||||||
#[cfg(has_drtio_eem)]
|
#[cfg(has_drtio_eem)]
|
||||||
use board_artiq::drtio_eem;
|
use board_artiq::drtio_eem;
|
||||||
#[cfg(has_rtio_analyzer)]
|
#[cfg(has_rtio_analyzer)]
|
||||||
|
@ -192,6 +197,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();
|
||||||
|
@ -201,28 +207,35 @@ 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();
|
||||||
|
io.spawn(4096, move |io| { mgmt::thread(io, &restart_idle) });
|
||||||
|
}
|
||||||
{
|
{
|
||||||
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))]
|
||||||
{
|
{
|
||||||
let aux_mutex = aux_mutex.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();
|
let drtio_routing_table = drtio_routing_table.clone();
|
||||||
io.spawn(4096, move |io| { moninj::thread(io, &aux_mutex, &drtio_routing_table) });
|
io.spawn(4096, move |io| { moninj::thread(io, &aux_mutex, &ddma_mutex, &subkernel_mutex, &drtio_routing_table) });
|
||||||
}
|
}
|
||||||
#[cfg(has_rtio_analyzer)]
|
#[cfg(has_rtio_analyzer)]
|
||||||
{
|
{
|
||||||
let aux_mutex = aux_mutex.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();
|
let drtio_routing_table = drtio_routing_table.clone();
|
||||||
let up_destinations = up_destinations.clone();
|
let up_destinations = up_destinations.clone();
|
||||||
io.spawn(8192, move |io| { analyzer::thread(io, &aux_mutex, &drtio_routing_table, &up_destinations) });
|
io.spawn(8192, move |io| { analyzer::thread(io, &aux_mutex, &ddma_mutex, &subkernel_mutex, &drtio_routing_table, &up_destinations) });
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(has_grabber)]
|
#[cfg(has_grabber)]
|
||||||
|
@ -261,6 +274,11 @@ pub extern fn main() -> i32 {
|
||||||
|
|
||||||
pmp::init_stack_guard(&_sstack_guard as *const u8 as usize);
|
pmp::init_stack_guard(&_sstack_guard as *const u8 as usize);
|
||||||
|
|
||||||
|
#[cfg(soc_platform = "kasli")]
|
||||||
|
irq::enable_interrupts();
|
||||||
|
#[cfg(has_wrpll)]
|
||||||
|
irq::enable(csr::WRPLL_INTERRUPT);
|
||||||
|
|
||||||
logger_artiq::BufferLogger::new(&mut LOG_BUFFER[..]).register(||
|
logger_artiq::BufferLogger::new(&mut LOG_BUFFER[..]).register(||
|
||||||
boot::start_user(startup as usize)
|
boot::start_user(startup as usize)
|
||||||
);
|
);
|
||||||
|
@ -295,8 +313,11 @@ pub extern fn exception(regs: *const TrapFrame) {
|
||||||
let pc = mepc::read();
|
let pc = mepc::read();
|
||||||
let cause = mcause::read().cause();
|
let cause = mcause::read().cause();
|
||||||
match cause {
|
match cause {
|
||||||
mcause::Trap::Interrupt(source) => {
|
mcause::Trap::Interrupt(_source) => {
|
||||||
info!("Called interrupt with {:?}", source);
|
#[cfg(has_wrpll)]
|
||||||
|
if irq::is_pending(csr::WRPLL_INTERRUPT) {
|
||||||
|
si549::wrpll::interrupt_handler();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mcause::Trap::Exception(mcause::Exception::UserEnvCall) => {
|
mcause::Trap::Exception(mcause::Exception::UserEnvCall) => {
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
use log::{self, LevelFilter};
|
use log::{self, LevelFilter};
|
||||||
|
use core::cell::Cell;
|
||||||
|
|
||||||
use io::{Write, ProtoWrite, Error as IoError};
|
use io::{Write, ProtoWrite, Error as IoError};
|
||||||
use board_misoc::{config, spiflash};
|
use board_misoc::{config, spiflash};
|
||||||
use logger_artiq::BufferLogger;
|
use logger_artiq::BufferLogger;
|
||||||
use mgmt_proto::*;
|
use mgmt_proto::*;
|
||||||
use sched::{Io, TcpListener, TcpStream, Error as SchedError};
|
use sched::{Io, 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,7 +14,7 @@ impl From<SchedError> for Error<SchedError> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn worker(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
fn worker(io: &Io, stream: &mut TcpStream, restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
|
||||||
read_magic(stream)?;
|
read_magic(stream)?;
|
||||||
Write::write_all(stream, "e".as_bytes())?;
|
Write::write_all(stream, "e".as_bytes())?;
|
||||||
info!("new connection from {}", stream.remote_endpoint());
|
info!("new connection from {}", stream.remote_endpoint());
|
||||||
|
@ -84,7 +86,13 @@ fn worker(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
}
|
}
|
||||||
Request::ConfigWrite { ref key, ref value } => {
|
Request::ConfigWrite { ref key, ref value } => {
|
||||||
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)
|
||||||
}?;
|
}?;
|
||||||
}
|
}
|
||||||
|
@ -117,16 +125,17 @@ fn worker(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn thread(io: Io) {
|
pub fn thread(io: Io, restart_idle: &Urc<Cell<bool>>) {
|
||||||
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 stream = listener.accept().expect("mgmt: cannot accept").into_handle();
|
let stream = listener.accept().expect("mgmt: cannot accept").into_handle();
|
||||||
|
let restart_idle = restart_idle.clone();
|
||||||
io.spawn(4096, move |io| {
|
io.spawn(4096, move |io| {
|
||||||
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) {
|
||||||
Ok(()) => (),
|
Ok(()) => (),
|
||||||
Err(Error::Io(IoError::UnexpectedEnd)) => (),
|
Err(Error::Io(IoError::UnexpectedEnd)) => (),
|
||||||
Err(err) => error!("aborted: {}", err)
|
Err(err) => error!("aborted: {}", err)
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue