forked from M-Labs/artiq
1
0
Fork 0

Compare commits

...

903 Commits

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

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

This commit is purposefully restricted to the mechanical changes,
no reformatting/… is done to keep the diff easy to validate.
2024-07-27 21:37:19 +08:00
Florian Agbuya 378d962edb afws_client: fix JSON parsing in read_json method 2024-07-26 10:55:40 +08:00
Simon Renblad 11c5f537bb RELEASE_NOTES: added fastino monitor 2024-07-23 10:52:24 +08:00
Simon Renblad ee3d93ce6a moninj: add fastino support 2024-07-23 10:52:24 +08:00
Simon Renblad bd3bcbce64 fastino: monitor probe dacs 2024-07-23 10:52:24 +08:00
architeuthidae 0952c47934
ad9910: Add bitsizes to docstrings (#2505)
Co-authored-by: architeuthidae <am@m-labs.hk>
2024-07-22 18:37:43 +08:00
architeuthidae e67bcfb36e doc: Add links for ddb_template and afws_client 2024-07-22 18:36:10 +08:00
architeuthidae 52b0f30216 artiq_dashboard: Replace --port-notify 2024-07-22 16:57:38 +08:00
architeuthidae b0d2705c38 doc: link fixes 2024-07-19 18:35:21 +08:00
architeuthidae 7e6a94c6b0 doc: Trailing spaces 2024-07-19 18:17:40 +08:00
architeuthidae 5aee0df9f0
doc: Split installing page (#2495)
Co-authored-by: architeuthidae <am@m-labs.hk>
2024-07-19 17:46:05 +08:00
Simon Renblad 8a6a6042fd comm_analyzer: avoid unnecessary copy 2024-07-19 16:15:37 +08:00
architeuthidae 7b13ffd9f3 doc: Fix Sphinx error in AFWS argparser 2024-07-19 11:21:36 +08:00
Simon Renblad 8a775bc61b moninj: revert zotino square bracket notation 2024-07-18 17:55:21 +08:00
architeuthidae 499eb42c3e
doc: Edit of manual reference pages (#2466)
Co-authored-by: architeuthis <am@m-labs.hk>
2024-07-18 17:53:34 +08:00
Florian Agbuya c05b109d5f afws_client: extract get_argparser() for modular arg parsing 2024-07-18 12:45:49 +08:00
architeuthidae 5dbf870e68 doc: Reword heading 2024-07-17 12:12:47 +08:00
architeuthidae 4cd9b8a8cf doc: rename release_notes -> releases 2024-07-17 12:12:47 +08:00
architeuthidae c9a4f3b9ee doc: Refactor manual index, make sections 2024-07-17 12:12:47 +08:00
architeuthidae bff46fcf1e doc: Add explanation of event spreading 2024-07-17 12:05:41 +08:00
architeuthidae a5327e383c doc: Reference link fixes 2024-07-17 11:54:50 +08:00
architeuthidae 088ea36d53 doc: Sphinx configuration nitpicky 2024-07-17 11:54:50 +08:00
architeuthidae a4bfb0d5dd doc: Formatting and link fixes in docstrings 2024-07-17 11:39:08 +08:00
architeuthidae 0e1b29c5d9 doc: Assorted typos and dead links 2024-07-17 11:36:59 +08:00
architeuthis 81106f3567 doc: Rewrite and overhaul of 'Compiler' page 2024-07-15 19:24:48 +08:00
mwojcik 9087c8698d ad9912: use pll doubler for refclk <11mhz 2024-07-15 19:21:58 +08:00
architeuthidae c2e323662b doc: Put back telnet exit hint 2024-07-15 10:54:38 +08:00
architeuthidae 26483d5daf doc: Environment, suggested changes 2024-07-12 10:53:04 +02:00
architeuthidae d5f11387e8 doc: Mention artiq_ddb_template in Environment manual page 2024-07-12 10:53:04 +02:00
architeuthis 158789b2e5 doc: Improve description of environment in tutorials 2024-07-12 10:53:04 +02:00
architeuthis e0e96fb08b doc: Update + expansion of Environment manual page 2024-07-12 10:53:04 +02:00
architeuthidae 96e4949c37 doc: NDSP page edit, fixes 2024-07-12 09:09:59 +02:00
architeuthis 8cfae86634 doc: Mention ping() in NDSP tutorial 2024-07-12 09:09:59 +02:00
architeuthis b2955f2bbe doc: Edit of 'Developing an NDSP' page 2024-07-12 09:09:59 +02:00
architeuthidae 1032b9accf doc: resynchronize introduction to readme 2024-07-11 10:00:41 +02:00
architeuthidae 708262615d readme: add intl website, formatting 2024-07-11 10:00:41 +02:00
Harry Ying 530f67f4cd test_scheduler: remove the exact ordering assertion
Currently the exact ordering is compared in the `pending_priorities`
test which is not necessary and is non-deterministic. This patch only
 asserts the middle priority experiment is ran before the high priority
 experiment scheduled in the future.

Signed-off-by: Kanyang Ying <lexugeyky@outlook.com>
2024-07-10 18:04:59 +01:00
Egor Savkin 4a88693c32 Update llvmlite to 0.43 and llvm to 15, as in MSYS2
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-07-10 11:06:08 +02:00
Simon Renblad e9f1b9d4ff browser: fix ctrl scroll type error 2024-07-10 10:40:23 +02:00
mwojcik 5ab2602802 tests: fix sed lane distributor test, force enable spread 2024-07-10 14:36:41 +08:00
mwojcik 86c6d11ed4 repeater: handle async packets on forwarding 2024-07-09 17:03:44 +02:00
Sebastien Bourdeauducq 7a50afd9a9 flake: update dependencies 2024-07-09 10:33:45 +02:00
mwojcik af99a06919 allow toggling SED spread with flash config key 2024-07-09 10:32:01 +02:00
architeuthidae 8a178a628a doc: Change references to rtio_clock/now to _mu 2024-07-08 23:10:27 +02:00
architeuthis 810ab20425 doc: Add warning on sequence error nondeterminism, for now 2024-07-08 23:10:27 +02:00
architeuthis a5437dd4f5 doc: RTIO manual page edit, suggested changes 2024-07-08 23:10:27 +02:00
architeuthis de5cd99787 docs: RTIO manual page edit 2024-07-08 23:10:27 +02:00
Florian Agbuya c785c763fe afws_client: asyncio fixes 2024-07-08 22:55:02 +02:00
architeuthidae 8f0147f029 doc: Fix sphinx 'duplicated reference' warnings 2024-07-08 22:42:05 +02:00
Simon Renblad 9c1482aa69 moninj: adjust DAC widget label alignment 2024-07-08 22:40:49 +02:00
Simon Renblad 6044d810ca moninj: fixed size widgets 2024-07-08 22:40:49 +02:00
abdul124 dcc5307771 firmware/ksupport: add rint api 2024-07-05 08:52:53 +02:00
Sebastien Bourdeauducq d8cf91bfb2 RELEASE_NOTES: update 2024-07-05 08:42:34 +02:00
Simon Renblad 24133ca04d moninj: display voltage in DACWidget 2024-07-05 08:41:32 +02:00
mwojcik 868e4defda moninj: restore urukul TTL control 2024-07-04 13:43:43 +02:00
mwojcik e7faca81fc repeater: clear buffer after ping 2024-07-04 13:38:14 +02:00
architeuthidae b8c12976db math_fns: Delete duplicated definition 2024-07-04 09:00:40 +02:00
Sebastien Bourdeauducq 91a4315386 RELEASE_NOTES: update 2024-07-03 09:15:43 +02:00
Florian Agbuya 688f3d9225 client: integrate asyncio with happy eyeballs support 2024-07-03 09:12:52 +02:00
Charles Baynham 6f3322ea35 worker_impl - try/catch for exceptions in notify_run_end to avoid data loss 2024-07-02 19:55:20 +08:00
Simon Renblad fdb0668c8a moninj: set parent to main window on widget delete 2024-07-02 14:21:50 +08:00
Simon Renblad f1e8b8772a moninj: fix visual bug 2024-07-02 14:21:50 +08:00
architeuthis 9386a7a16f doc: Fix heading levels in Releases page 2024-06-27 15:52:48 +08:00
architeuthis bcc760a3fb doc: Add description of release model to manual 2024-06-27 14:43:10 +08:00
Egor Savkin a804be1a45 Update dead links, partially remove unavailable resources
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-06-27 13:08:21 +08:00
Egor Savkin 3cd6d50ad9 doc: mention zadig being installed with offline installer
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-06-26 12:41:57 +08:00
Florian Agbuya bd7daa5247 flake: use upstream nixpkgs openocd
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-06-26 12:38:02 +08:00
architeuthis 48cdf42016 docs: Add details of shallow 'with parallel' to manual 2024-06-25 17:47:33 +08:00
Egor Savkin c568109f3f doc: add reference to artiq-zynq for developers
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-06-25 17:36:19 +08:00
architeuthis 81cda2380d doc: Management system overhaul, further corrections 2024-06-25 17:30:31 +08:00
architeuthis ea22f67c4c doc: Clearer+more accurate overview of mgmt system 2024-06-25 17:30:31 +08:00
architeuthis aa21b78681 doc: Management system update, corrections 2024-06-25 17:30:31 +08:00
architeuthis de1df88dcd doc: Split running/management tool reference into separate page 2024-06-25 17:30:31 +08:00
architeuthis 0dc727c1fd doc: 'Management system' manual page copy+clarity edit 2024-06-25 17:30:31 +08:00
architeuthis 45ef4d18d7 doc: Elaboration on interactive args & controllers 2024-06-25 17:30:31 +08:00
architeuthis edfa5aa957 doc: 'Getting started with management system' manual page overhaul 2024-06-25 17:30:31 +08:00
Harry Ying a9a74398ab fix: nix build
Fix the nix build by rematching the revision of `rustc`

Signed-off-by: Kanyang Ying <lexugeyky@outlook.com>
2024-06-20 19:03:06 +08:00
Sébastien Bourdeauducq e09fde6f51 doc: minor correction 2024-06-20 17:34:58 +08:00
architeuthis 02c1d2514f doc: Add DRTIO-over-EEM to manual + phrasing fixes 2024-06-20 17:34:08 +08:00
architeuthis 1ca7a3c6a3 doc: Add "Using Drtio" and overhaul DRTIO page 2024-06-20 17:34:08 +08:00
Harry Ying 102506d603 chore: use rust-overlay
- use rust-overlay, a flake-native and preferred alternative to mozilla-overlay

Signed-off-by: Kanyang Ying <lexugeyky@outlook.com>
2024-06-20 16:15:20 +08:00
Egor Savkin 19132ae0e3 Fix missing jquery in docs - fixes broken search
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-06-20 12:51:15 +08:00
Sebastien Bourdeauducq 85545a8447 flake: update dependencies 2024-06-19 12:43:36 +08:00
morgan 77580b5bf6 kasli: raise error when enabling WRPLL with v1.x 2024-06-19 12:40:52 +08:00
morgan 84b97976c0 kasli: fix v1.0 & v1.1 compilation error 2024-06-19 12:40:26 +08:00
Florian Agbuya ff79854c46 plot_xy: fix missing x values handling
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-06-13 12:19:05 +08:00
architeuthis a167cc6043 doc: fix formatting in Installing page 2024-06-13 12:14:53 +08:00
architeuthis 1ee3988188 doc: Refactor manual table of contents 2024-06-12 15:48:28 +08:00
architeuthidae 2c945f260e
doc: 'Getting started with core device' manual page edit (#2431) 2024-06-12 15:43:53 +08:00
architeuthidae f432529014
doc: 'Core device' manual page overhaul (#2430) 2024-06-12 14:50:48 +08:00
Florian Agbuya bfeac30c44 test_embedding: add int boundary test from 25168422a
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-06-07 14:11:05 +08:00
Florian Agbuya a901ab74b5 compiler: fix int boundary checks
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-06-07 14:11:05 +08:00
architeuthis 8b64315ecf Deleted reference to board packages 2024-06-06 13:44:04 +08:00
architeuthis 4509ad86f8 Remove outdated references to examples/master, fix labels 2024-06-06 13:44:04 +08:00
architeuthis 59302da71c docs: 'Installing ARTIQ' manual page overhaul 2024-06-06 13:44:04 +08:00
Sebastien Bourdeauducq ebc1e3fb76 bump major version number 2024-06-06 09:45:54 +08:00
Sebastien Bourdeauducq 927bb3b6b4 flake: update dependencies 2024-06-06 09:35:05 +08:00
Simon Renblad 1bb3c503d9 moninj: sub on display, unsub on hide 2024-06-05 17:59:36 +08:00
Simon Renblad 5e73245cef moninj: delete widget action 2024-06-05 17:59:36 +08:00
Simon Renblad b74beac6b9 moninj: add drag drop 2024-06-05 17:59:36 +08:00
Simon Renblad c256d113de dndwidgets: add DragDropFlowLayoutWidget 2024-06-05 17:59:36 +08:00
Simon Renblad 33d3688bfc moninj: state management 2024-06-05 17:59:36 +08:00
Simon Renblad 88903fb38c artiq_dashboard: connect Devices sub after loading state 2024-06-05 17:59:36 +08:00
Simon Renblad 7fae395b88 moninj: add dock label 2024-06-05 17:59:36 +08:00
Simon Renblad d3d50d790a gui.tools: add DoubleClickLineEdit 2024-06-05 17:59:36 +08:00
Simon Renblad 154f186f18 moninj: add _AddChannelDialog 2024-06-05 17:59:36 +08:00
Simon Renblad ad170b469c moninj: multiple docks 2024-06-05 17:59:36 +08:00
Simon Renblad 9fc4cdea6b moninj: dock add layoutwidget 2024-06-05 17:59:36 +08:00
Simon Renblad 2fde21152a moninj: merge docks 2024-06-05 17:59:36 +08:00
Simon Renblad 51837ce1a2 moninj: consistent sort keys 2024-06-05 17:59:36 +08:00
Simon Renblad 5cd21c7a6d moninj: add uid method to widgets 2024-06-05 17:59:36 +08:00
Simon Renblad e742dc9503 moninj: refactor _DACWidget 2024-06-05 17:59:36 +08:00
Sébastien Bourdeauducq 9a770c15c5 RELEASE_NOTES: update 2024-06-05 16:51:23 +08:00
Sébastien Bourdeauducq d252b12cf6 README: remove outdated and unmaintainable Sinara crate count 2024-06-03 16:16:32 +08:00
mwojcik f1e1e54940 sinara_tester: simplify almazny tests 2024-06-03 15:15:32 +08:00
mwojcik 20c67aca23 sinara_tester: break apart legacyalmazy, add almazny tests 2024-06-03 15:15:32 +08:00
mwojcik 793f8a3c8c legacy almazny: fix missing units, remove dead code 2024-06-03 15:15:32 +08:00
Sebastien Bourdeauducq f496e6da7c flake: update dependencies 2024-06-01 11:28:22 +08:00
architeuthis d609ed4a58 docs: Minor manual fix 2024-06-01 11:15:37 +08:00
mwojcik 48f3071ee8 adf5356: sync before setting muxout 2024-05-31 08:33:03 +08:00
morgan 49e402780b Firmware: runtime WRPLL
runtime: enable WRPLL interrupt
runtime: add WRPLL interrupt handler
rtio_clocking: add main si549 setup
rtio_clocking: add 125Mhz wrpll refclk & helper si549 setup
2024-05-29 16:52:08 +08:00
morgan 44cfacf2c4 Firmware: frequency multipler for WRPLL
si549: add bit bang mmcm dynamic configuration
si549: add 125Mhz wrpll refclk setup
2024-05-29 16:52:08 +08:00
morgan c5147d7744 Gateware: kasli runtime WRPLL setup
kasli: use enable_wrpll from json to switch from si5324 to si549
kasli: add wrpll
kasli: add wrpll interrupt
kasli: add clk_synth_se
kasli: add wrpll_refclk for runtime
kasli: add WRPLL_REF_CLK config for firmware
2024-05-29 16:52:08 +08:00
morgan d5b1f04dcc Gateware: frequency multiplier for WRPLL
wrpll: add mmcm with DRP to generate 125Mhz refclk
2024-05-29 16:52:08 +08:00
morgan dad62c1aec io_expander: fix efc shuttler compilation error 2024-05-29 14:24:42 +08:00
morgan 77c50324ef flake: update libfringe hash 2024-05-29 14:24:42 +08:00
Sebastien Bourdeauducq eefc07b495 flake: move to nixos 24.05 2024-05-28 17:30:23 +08:00
morgan a10dd0520c Firmware: satman WRPLL
satman: enable WRPLL interrupt
satman: add WRPLL interrupt handler
satman: add main & helper si549 setup
satman: add WRPLL select_recovered_clock
2024-05-28 17:27:49 +08:00
morgan 0ac0e08170 Firmware: WRPLL
wrpll: add tag collector to process gtx & main tags
wrpll: add frequency counter to set BASE_ADPLL
wrpll: add TAG_OFFSET and calibration for Satman
wrpll: add 100MHz & 125MHz fixed point low pass filter
wrpll: add main & helper PLL
2024-05-28 17:27:49 +08:00
morgan 5d9bc930fe Firmware: si549
si549: add bit bang i2c
si549: add si549 programming
si549: add main & helper setup
2024-05-28 17:27:49 +08:00
morgan 5971d9e958 Firmware: set CLK_SEL in io_expander init
io_expander init: set initial out_target instead of 0x00
io_expander0: gate CLK_SEL direction & output
2024-05-28 17:27:49 +08:00
morgan 0d78e65f7a Gateware: kasli satellite WRPLL setup
kasli: use enable_wrpll from json to switch from si5324 to si549
kasli: add wrpll
kasli: add wrpll interrupt
kasli: add clk_synth_se
kasli: add skewtester
kasli: add WRPLL_REF_CLK config for firmware
2024-05-28 17:27:49 +08:00
morgan 1b0586e6a8 Gateware: si549 & WRPLL
ddmtd: add DDMTD and deglitcher
wrpll: add helper clockdomain
wrpll: add frequency counter
wrpll: add skewtester
wrpll: add gtx & main tag collection
wrpll: add gtx & main tag eventmanager for interrupt
si549: add i2c and adpll programmer
2024-05-28 17:27:49 +08:00
morgan 57780e36be cargo: update libfringe 2024-05-28 17:27:49 +08:00
morgan 14a618b48d kasli: enable interrupts 2024-05-28 17:23:29 +08:00
morgan 13830a27af riscv: add IRQ control 2024-05-28 17:23:29 +08:00
mwojcik 51c15ac777 update hw_rev in shuttler json example 2024-05-28 13:51:11 +08:00
mwojcik 0a044cf424 schema: add efc hardware version 2024-05-28 13:51:11 +08:00
Egor Savkin 2b31d38084 docs: update MSYS2 to include offline installer and openocd included by default
docs: update MSYS2 mingw -> clang

Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-05-23 17:20:43 +08:00
mwojcik c2d645ed0a enable spread in satellite, use high watermark 2024-05-23 17:18:16 +08:00
linuswck 4de3273e7a sinara_tester: add Shuttler test 2024-05-23 16:19:36 +08:00
linuswck 531640fa91 kasli: Add Shuttler Support on Satellite 2024-05-21 18:21:56 +08:00
mwojcik c5d656ba32 drtio: increase maximum payload size 2024-05-17 15:36:56 +08:00
mwojcik 688e643078 firmware: update rust to 2021-09-01 nightly 2024-05-17 15:36:56 +08:00
mwojcik c33c1df07f remove cargo-xbuild, fix nix build 2024-05-16 16:32:13 +08:00
Sebastien Bourdeauducq 6d0821ecaf flake: update dependencies 2024-05-14 08:49:29 +08:00
Mikołaj Sowiński 16e4b616ca Updated EEM FMC Carrier support for v1.1
Signed-off-by: Mikołaj Sowiński <mikolaj.sowinski@rd.technosystem.pl>
2024-05-14 08:46:35 +08:00
Simon Renblad 7dff78e849 moninj: move _DDSModel constructor
Signed-off-by: Simon Renblad <srenblad@m-labs.hk>
2024-05-07 18:01:01 +08:00
Florian Agbuya a8157cd5c9 enable dynamic address configuration in Kasli I2C EEPROM
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-05-07 12:07:37 +08:00
Sebastien Bourdeauducq 193962f31e flake: update to nixpkgs unstable (soon to be 24.05) 2024-05-06 12:44:45 +08:00
Simon Renblad 5fe47129ed fix missing get_dataset_metadata 2024-04-30 19:48:35 +08:00
Sebastien Bourdeauducq 24fe885b5c flake: update dependencies 2024-04-26 23:33:43 +08:00
mwojcik 7204feae1f master: aux_transact support for async messages 2024-04-25 10:46:57 +08:00
mwojcik acebc3d691 satellite: send async packets directly 2024-04-25 10:46:57 +08:00
mwojcik a49ba3e350 master: support unsolicited async messages 2024-04-25 10:46:57 +08:00
mwojcik b1c305fd11 drtioaux: adjust firmware for multiple buffers 2024-04-25 10:46:57 +08:00
mwojcik b6ac052e9f aux_controller: multiple receiver buffers 2024-04-25 10:46:57 +08:00
mwojcik 76d704ac33 drtio: revert async flag message 2024-04-25 10:46:57 +08:00
Norman Krackow baa58343ac
urukul: fix `tune_sync_delay()` (#2374) 2024-04-12 09:03:17 +08:00
Sébastien Bourdeauducq 1bcbee988d update copyright year 2024-04-11 16:35:44 +08:00
Sébastien Bourdeauducq ab206ac154 worker: import host_only from the right place 2024-04-11 16:34:08 +08:00
Simon Renblad 4a2352c2df browser: disable quickstyle 2024-04-11 16:06:33 +08:00
Simon Renblad f9a447e8e0 entries: fix EnumerationEntry disable_scroll_wheel 2024-04-11 16:05:39 +08:00
Simon Renblad c4892cf285 shortcuts: style (NFC) 2024-04-11 10:33:03 +08:00
Simon Renblad c1e6ae2193 schedule: style (NFC) 2024-04-11 10:33:03 +08:00
Simon Renblad 4f302ee675 explorer: style (NFC) 2024-04-11 10:33:03 +08:00
Simon Renblad 3ecd115252 experiments: style (NFC) 2024-04-11 10:33:03 +08:00
Simon Renblad 400c1644b0 datasets: style (NFC) 2024-04-11 10:33:03 +08:00
Simon Renblad 1b2a18c9c8 applets_ccb: style (NFC) 2024-04-11 10:33:03 +08:00
Simon Renblad 7d9199a2ee artiq_dashboard: style (NFC) 2024-04-11 10:33:03 +08:00
Simon Renblad 43edffc67e waveform: clean up imports 2024-04-10 12:05:32 +08:00
Simon Renblad 49930a2df2 datasets: clean imports 2024-04-10 12:05:32 +08:00
Simon Renblad 9d3509d7b0 shortcuts: clean imports 2024-04-10 12:05:32 +08:00
Simon Renblad b555f08ed8 artiq_dashboard: clean imports 2024-04-10 12:05:32 +08:00
Simon Renblad 65005ed45a moninj: flake8 style fixes (NFC) 2024-04-10 11:10:52 +08:00
Simon Renblad 6ac532a00e moninj: clean up imports 2024-04-10 10:56:28 +08:00
Simon Renblad 856e43fd61 interactive_args: add default message 2024-04-08 16:54:37 +08:00
Simon Renblad af11dc6b74 interactive_args: use bottom_item for supply, cancel 2024-04-05 18:53:06 +08:00
Sebastien Bourdeauducq 0fb31ddbb1 flake: update dependencies 2024-04-02 17:01:19 +08:00
Simon Renblad 9bf5695ab2 interactive_args: add quickstyle 2024-04-02 16:31:42 +08:00
Sébastien Bourdeauducq 5f49e582c8 master: fix race condition in interactive args supply
Closes #2375
2024-04-02 16:10:02 +08:00
Simon Renblad fddff13842 docs: mock interactive_args 2024-04-02 15:47:48 +08:00
Simon Renblad 915d3613f1 artiq_dashboard: add InteractiveArgsDock 2024-04-02 15:47:48 +08:00
Simon Renblad d463ccb218 interactive_args: add InteractiveArgsDock 2024-04-02 15:47:48 +08:00
Simon Renblad b4d070fa1b docs: add quickstyle param 2024-04-02 15:46:57 +08:00
Simon Renblad 9934c756b2 RELEASE_NOTES: quickstyle EnumerationValue 2024-04-02 15:46:57 +08:00
Simon Renblad 47716badef add quickstyle option to EnumerationValue 2024-04-02 15:46:57 +08:00
Simon Renblad 8e68501081 applets: EntryArea return processed values 2024-04-02 15:45:38 +08:00
Simon Renblad 19b652d4c0 fix interactive args cancellation 2024-04-02 15:45:12 +08:00
Florian Agbuya dc0b803b19 use nixpkgs sphinxcontrib-wavedrom
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-03-27 18:02:49 +08:00
Florian Agbuya bc8bc952d7 use nixpkgs outputcheck
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-03-27 18:02:13 +08:00
Simon Renblad aea5f04d74 dashboard, browser: fix missing recompute arguments 2024-03-26 16:46:53 +08:00
Sébastien Bourdeauducq d0f893c01c flake: export openocd-bscanspi-f 2024-03-26 15:57:44 +08:00
Simon Renblad 7fa770fba9 artiq_client: cancel interactive arguments 2024-03-26 15:36:23 +08:00
Simon Renblad 5a8bc17e4d example: expand interactive 2024-03-26 15:35:58 +08:00
Sébastien Bourdeauducq 329e7189cc example: add interactive args 2024-03-25 14:55:17 +08:00
Simon Renblad 13a36bf911 browser, dashboard: fix restore scrollbar state 2024-03-20 10:39:35 +08:00
Simon Renblad 88438e2d76 browser: subclass _ArgumentEditor as EntryTreeWidget 2024-03-19 18:59:21 +08:00
Simon Renblad 1a41b16fb6 dashboard: subclass _ArgumentEditor as EntryTreeWidget 2024-03-19 18:59:21 +08:00
Simon Renblad 6978101b1f applets: subclass EntryArea as EntryTreeWidget 2024-03-19 18:59:21 +08:00
Simon Renblad 244c73a592 entries: add EntryTreeWidget 2024-03-19 18:59:21 +08:00
Simon Renblad c4323e1179 interactive args: add title param 2024-03-13 12:13:55 +08:00
morgan 609684664a coredevice schema: add enable_wrpll option to json 2024-03-11 16:42:20 +08:00
Simon Renblad 7e6ed1655f artiq_client: fix deprecated wait usage 2024-03-11 13:10:10 +08:00
Simon Renblad 332c9c0fcd waveform: consistent log messages 2024-03-07 12:11:28 +08:00
Simon Renblad 27178c1478 moninj: remove CancelledError workaround 2024-03-07 12:10:05 +08:00
Simon Renblad e56331248e dashboard: fix device subscriber connections 2024-03-06 18:09:12 +08:00
Sébastien Bourdeauducq 692572a3b9 style (NFC) 2024-02-28 12:48:31 +08:00
Sébastien Bourdeauducq 18f55bb196 master: fix asyncio exception handling
Follow Python 3.8.
2024-02-28 12:48:11 +08:00
Sébastien Bourdeauducq 3e8a853e53 artiq_client: implement interactive arguments 2024-02-28 11:51:30 +08:00
Sébastien Bourdeauducq de29db0b35 master: implement interactive arguments
Interaction with experiment termination (forceful and requested) still
needs some work.
2024-02-28 11:49:33 +08:00
mwojcik 42d3c3b4b2 session: workaround for stream.close interrupted 2024-02-27 18:06:58 +08:00
Sébastien Bourdeauducq 450fe91e93 artiq_client: handle Ctrl-C gracefully 2024-02-27 15:46:23 +08:00
Simon Renblad 002325be17 applets: rename params 2024-02-27 15:26:37 +08:00
Sébastien Bourdeauducq 92eb3947a4 master: shorten RPC target names 2024-02-27 15:24:43 +08:00
Florian Agbuya 3609f95207 flake: add new lmdb mock module for artiq-manual
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-02-27 12:00:19 +08:00
Sébastien Bourdeauducq 5e01661443 master.databases: style (NFC) 2024-02-27 11:25:56 +08:00
Simon Renblad a21805598a dashboard fix moninj, analyzer clients 2024-02-27 11:25:43 +08:00
Simon Renblad c151f0c3ce waveform: remove unused setTimescale 2024-02-27 11:24:33 +08:00
Simon Renblad c794e51c1c waveform: fix log msg display 2024-02-27 11:24:07 +08:00
Sébastien Bourdeauducq bafa69098a style/doc fixes (NFC) 2024-02-27 11:10:32 +08:00
Sébastien Bourdeauducq b2ba087acd dashboard: do not use float64. Closes #2347 2024-02-27 11:09:37 +08:00
Sébastien Bourdeauducq a8a5fc213b worker_impl: style fixes (NFC) 2024-02-27 10:39:37 +08:00
Sébastien Bourdeauducq 7688f380b1 environment, artiq_run: introduce interactive arguments 2024-02-26 19:30:31 +08:00
Sébastien Bourdeauducq a0450555e2 RELEASE_NOTES: units in datasets 2024-02-26 19:29:31 +08:00
Sébastien Bourdeauducq b142428607 doc: remove incorrect and unneeded code comment 2024-02-26 17:26:23 +08:00
Sébastien Bourdeauducq 750fdf89b3 doc: get rid of confusing 'Extensions' wording 2024-02-26 17:18:28 +08:00
Simon Renblad 0a24d72b9f dashboard: change analyzer proxy client 2024-02-26 16:55:55 +08:00
Sébastien Bourdeauducq 7c1274f254 RELEASE_NOTES: Python 3.10 -> 3.11 2024-02-26 16:30:22 +08:00
Sébastien Bourdeauducq 716d0f556d grabber: timeout fixes 2024-02-26 11:03:59 +08:00
Charles Baynham 20d7604f87 grabber: Add optional timeout for getting grabber data
Signed-off-by: Charles Baynham <c.baynham@imperial.ac.uk>
2024-02-26 11:02:02 +08:00
Simon Renblad 4c142ec3f1 waveform: add reset zoom button 2024-02-23 10:56:37 +08:00
Simon Renblad c49600a2fc docs: fix sampler, waveform 2024-02-23 10:56:19 +08:00
Simon Renblad cda758ef53 docs: fix waveform imports 2024-02-22 17:04:37 +08:00
Simon Renblad bd9e8b3977 waveform: simplify AddChannelDialog 2024-02-20 16:30:26 +08:00
Simon Renblad 779b7704ed waveform, comm_analyzer add cursor label unit 2024-02-20 15:53:01 +08:00
Simon Renblad edd23977f8 waveform: delete all waveforms confirm dialog 2024-02-19 17:37:08 +08:00
Simon Renblad f460af3a6a waveform: remove vertical grids 2024-02-19 17:37:08 +08:00
Simon Renblad 1b0fd2e2d3 comm_analyzer: remove interval, timestamp 2024-02-19 17:37:08 +08:00
Simon Renblad 652bcc22c6 waveform: remove empty waveform error msg 2024-02-19 17:37:08 +08:00
Simon Renblad de539a4d33 waveform: remove None cursor label 2024-02-19 17:37:08 +08:00
Simon Renblad 1749fa661f waveform: reset cursor onDataChange 2024-02-16 15:54:25 +08:00
Simon Renblad 6ed6fb0bce waveform: fix log messages 2024-02-16 15:24:39 +08:00
morgan fc282d4e17 artiq_ddb_template: fix clk_div config
remove clk_div default in jsonschema
set CLK IN divided by 1 as default when bypassing PLL
2024-02-16 15:23:55 +08:00
Simon Renblad 795b8ae4c6 add analyzer proxy magic 2024-02-16 14:48:49 +08:00
Simon Renblad 21b77567f2 waveform: add timeout to reconnect_cr 2024-02-16 14:48:49 +08:00
Simon Renblad d085c1e4a4 waveform, analyzer proxy fix connect errors 2024-02-16 14:48:49 +08:00
Simon Renblad 720cbb4490 comm_analyzer, waveform add ndecimals 2024-02-15 15:46:12 +08:00
Simon Renblad efb8aaf9f9 comm_analyzer: fix stopped_x 2024-02-15 15:32:00 +08:00
Sebastien Bourdeauducq 7c583b9c04 flake: update dependencies 2024-02-09 14:04:16 +08:00
Simon Renblad 7f43c5c31a waveform: add cursor 2024-02-08 12:59:22 +08:00
Simon Renblad 40cea30285 waveform: add open, save channel list 2024-02-02 17:23:52 +08:00
Simon Renblad 8b503c3b4f waveform: add remove, clear waveform actions 2024-02-02 11:18:03 +08:00
Simon Renblad 1e9070a2af testing: add coreanalyzer_proxy smoke test 2024-02-02 10:21:22 +08:00
Simon Renblad dcf1bba8c6 waveform: implement _create_waveform 2024-02-01 19:02:09 +08:00
Simon Renblad a7b045a478 waveform: misc fixes 2024-02-01 19:02:09 +08:00
Sebastien Bourdeauducq 3aaa7e04f2 flake: update dependencies 2024-02-01 18:58:27 +08:00
mwojcik b648a2930b docs: elaborate on subkernel message names 2024-02-01 18:56:07 +08:00
mwojcik b64c75fd71 subkernel: warn on kernel finish w/ pending msgs 2024-02-01 18:56:07 +08:00
mwojcik 392533f8ee subkernel lit tests: fix timeouts to no-timeouts 2024-02-01 18:56:07 +08:00
mwojcik 7fee68ede0 subkernel messages: check for send/recv pairs 2024-02-01 18:56:07 +08:00
mwojcik 849b77fbf2 compiler: fix send_message after stackrestore 2024-02-01 18:56:07 +08:00
mwojcik 502204cab2 subkernel: fix DMA return control to wrong master 2024-02-01 18:56:07 +08:00
mwojcik d1ee0ffb83 subkernel: fix passing arguments 2024-02-01 18:56:07 +08:00
Simon Renblad cbe7ac1cfd waveform: add AnalogWaveform 2024-01-31 17:26:22 +08:00
Simon Renblad 2d8de3ed93 waveform: add BitVectorWaveform 2024-01-31 16:59:12 +08:00
Simon Renblad 5f3126f393 waveform: add BitWaveform 2024-01-31 15:51:34 +08:00
mwojcik 09462442f7 subkernel: allow negative timeouts for no timeout 2024-01-31 11:46:07 +08:00
mwojcik 726cb092ca tests: add message passing tests 2024-01-31 11:46:07 +08:00
mwojcik fbbc8d3dd1 docs: add a section for subkernel message passing 2024-01-31 11:46:07 +08:00
mwojcik 0ba0330b53 compiler: support free subkernel message passing 2024-01-31 11:46:07 +08:00
mwojcik 7d3bcc7cac satman: support free subkernel message passing 2024-01-31 11:46:07 +08:00
mwojcik 171c7a6e11 runtime: use the destination passed by kernel 2024-01-31 11:46:07 +08:00
Simon Renblad c087a47e45 waveform: add _BaseWaveform 2024-01-31 11:41:35 +08:00
Simon Renblad 28dfe1f9c6 waveform: add _WaveformView 2024-01-30 09:45:29 +08:00
Simon Renblad 3861d58749 dndwidgets: change splitter to use signal 2024-01-30 09:45:29 +08:00
Simon Renblad 6c9f1cbf7c waveform: add save_vcd 2024-01-25 15:06:21 +08:00
Simon Renblad 06b908fd18 waveform: fix in _AddChannelDialog 2024-01-25 14:49:55 +08:00
Simon Renblad e72f37eb4e waveform: add _WaveformModel 2024-01-25 14:49:55 +08:00
Simon Renblad 847b4ee2a3 waveform: add save_trace 2024-01-25 14:49:16 +08:00
Simon Renblad 863daca2da waveform: remove punctuation in log msgs 2024-01-25 14:01:25 +08:00
Simon Renblad fcaf4a8af0 gui.tools: add get_save_file_name helper 2024-01-25 12:32:06 +08:00
Simon Renblad 466d865e58 waveform: add _AddChannelDialog 2024-01-22 15:39:46 +08:00
Simon Renblad 5036230ff3 waveform: change log channel update 2024-01-22 15:39:11 +08:00
Simon Renblad 12a44fad3c comm_analyzer: change usage of logs field 2024-01-22 15:39:11 +08:00
Simon Renblad 096664c1ba dndwidgets: add drag drop helper widgets 2024-01-22 15:37:57 +08:00
Simon Renblad 8a9b6a449b artiq_dashboard: start proxy clients, device_sub 2024-01-22 15:37:10 +08:00
Simon Renblad 73be2257d3 waveform: add proxy clients 2024-01-22 15:37:10 +08:00
Simon Renblad 9088ffa2ca artiq_dashboard: add WaveformDock 2024-01-22 13:59:31 +08:00
Simon Renblad d44f55c6d9 waveform: add WaveformDock 2024-01-22 13:59:31 +08:00
Simon Renblad e393b3ab37 comm_analyzer: add set_end_time call 2024-01-15 13:39:06 +08:00
Simon Renblad 3af4c9d517 comm_analyzer: add get_channel_list 2024-01-13 00:07:42 +08:00
Simon Renblad 64567bc26f comm_analyzer: add AnalyzerProxyReceiver 2024-01-13 00:06:14 +08:00
Sebastien Bourdeauducq da15e94c22 flake: update dependencies 2024-01-11 12:30:37 +08:00
Charles Baynham 669edf17c5
scheduler: resolve git references into revisions on submission (#2296)
Signed-off-by: Charles Baynham <c.baynham@imperial.ac.uk>
2024-01-10 16:05:26 +08:00
Simon Renblad b215df2d25 comm_analyzer: add WaveformManager, WaveformChannel 2024-01-10 16:02:04 +08:00
mwojcik 6c0ff9a912 satman: fix targets without drtio routing 2024-01-09 10:41:22 +08:00
mwojcik c9e3771cd5 subkernels: add support for (d)dma 2024-01-09 08:44:45 +08:00
mwojcik c876acd5a5 docs: subkernels can call other subkernels now 2024-01-09 08:44:45 +08:00
mwojcik 4363cdf9fa master: make use of the async message ready flag 2024-01-09 08:44:45 +08:00
mwojcik 95b92a178b satman: make use of the async flag 2024-01-09 08:44:45 +08:00
mwojcik 1cc7398bc0 drtio: add sat -> mst async notif packet 2024-01-09 08:44:45 +08:00
mwojcik 4956fac861 satman: allow subkernels start subkernels 2024-01-09 08:44:45 +08:00
mwojcik 9bc66e5c14 support routing packets between satellites and master 2024-01-09 08:44:45 +08:00
mwojcik 4495f6035e master: support source parameters 2024-01-09 08:44:45 +08:00
mwojcik e556c29b40 drtioaux: add source to relevant drtio packets 2024-01-09 08:44:45 +08:00
Simon Renblad 76fba538b1 artiq_ddb_template: fixed missing separator 2023-12-18 13:23:39 +08:00
Sebastien Bourdeauducq 8dd8cfa6b0 master: implement devarg_override 2023-12-18 12:11:40 +08:00
Sebastien Bourdeauducq 5df0721811 dashboard,client: add device argument overrides to expid 2023-12-17 19:43:41 +08:00
Sebastien Bourdeauducq 6326051052 flake: forward cmdline arguments in devshell wrappers 2023-12-17 19:42:56 +08:00
Sebastien Bourdeauducq 44a95b5dda dashboard: add repository revision clear button 2023-12-17 16:37:02 +08:00
Sebastien Bourdeauducq 645b9b8c5f flake: add executable wrappers for frontends to devshell 2023-12-17 13:41:49 +08:00
Sebastien Bourdeauducq 858f0479ba aqctl_coreanalyzer_proxy: permissions and shebang 2023-12-17 13:27:38 +08:00
Sebastien Bourdeauducq 133b26b6ce flake: add ARTIQ sources to PYTHONPATH in devshell 2023-12-17 13:05:16 +08:00
Sebastien Bourdeauducq d96213dbbc flake: update dependencies 2023-12-17 12:55:36 +08:00
Sebastien Bourdeauducq 413d33c3d1 core: document analyzer proxy options 2023-12-13 14:29:33 +08:00
Sebastien Bourdeauducq c2b53ecb43 core: add option to trigger analyzer proxy at run end 2023-12-13 14:27:48 +08:00
Sebastien Bourdeauducq ede0b37c6e devices: introduce notify_run_end API 2023-12-13 14:27:04 +08:00
Sebastien Bourdeauducq 795c4372fa DeviceManager: fix close exception error message 2023-12-13 14:06:53 +08:00
Sebastien Bourdeauducq 402a5d3376 core: connect lazily to analyzer proxy
Otherwise artiq_compile and other uses of Core that does not access hardware/network may fail.
2023-12-13 13:46:47 +08:00
Sebastien Bourdeauducq 85850ad9e8 wavesynth: remove 2023-12-13 13:36:21 +08:00
Sebastien Bourdeauducq 7a863b4f5e core: add trigger_analyzer_proxy API 2023-12-13 13:08:54 +08:00
Sebastien Bourdeauducq a26cee6ca7 coreanalyzer_proxy: cleanups/renames 2023-12-13 13:07:35 +08:00
Sebastien Bourdeauducq be08862606 logo: text to path 2023-12-08 19:34:47 +08:00
Sebastien Bourdeauducq 05a9422e67 aqctl_coreanalyzer_proxy: cleanup 2023-12-08 18:56:10 +08:00
Simon Renblad b09a39c82e
add aqctl_coreanalyzer_proxy 2023-12-08 18:55:07 +08:00
mwojcik 49267671f9 core: fix precompile 2023-12-04 12:10:11 +08:00
Sebastien Bourdeauducq 8ca75a3fb9 firmware: deal with rust nonsense
Fixes
"error: edition 2021 is unstable and only available with -Z unstable-options.
error: could not compile `alloc`"
2023-12-03 11:20:18 +08:00
Florian Agbuya 8381b34a79 flake: add new booktabs dependency for artiq-manual-pdf 2023-12-03 11:18:59 +08:00
Sebastien Bourdeauducq d458fc27bf switch to new nixpkgs release 2023-12-03 11:18:25 +08:00
mwojcik 9f4b8db2de repeater: fix setting tsc 2023-12-01 16:43:48 +08:00
Florian Agbuya 1108cebd75 flake: fix ncurses on vivado
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2023-11-28 17:36:36 +08:00
Florian Agbuya cf7cbd0c3b flake: update nixpkgs
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2023-11-28 17:36:36 +08:00
mwojcik 1a28069aa2 support for pre-compiling subkernels 2023-11-23 16:49:02 +08:00
Sebastien Bourdeauducq 56418e342e take into account VERSIONEER_REV in artiq._version.get_rev 2023-11-22 20:51:02 +08:00
Sebastien Bourdeauducq 77c6553725 always provide artiq._version.get_rev 2023-11-14 14:14:47 +08:00
Sebastien Bourdeauducq e81e8f28cf gateware: merge kasli_generic into kasli. Closes #2279 2023-11-14 14:01:17 +08:00
mwojcik de10e584f6 support .tar flashed idle/startup kernels 2023-11-13 18:14:35 +08:00
Florian Agbuya 875666f3ec doc: add section on new nix flakes config (closes #2232)
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2023-11-10 16:47:56 +08:00
Sebastien Bourdeauducq 3ad3fac828 update ARTIQ-8 release notes 2023-11-08 11:17:17 +08:00
Simon Renblad 49afa116b3 RELEASE_NOTES: artiq_ddb_template needs gateware 2023-11-08 10:51:39 +08:00
Simon Renblad 363afb5fc9 artiq_ddb_template: add support for user LEDs
Add support for additional user LEDs.
2023-11-08 10:51:39 +08:00
Simon Renblad e7af219505 kasli_generic: add support for user LEDs
Add additional LED RTIO devices.
2023-11-08 10:51:39 +08:00
linuswck ec2b86b08d kc705: fix gtx clock path durnig init 2023-11-07 18:36:48 +08:00
linuswck 8f7d138dbd gtx: Always enable IBUFDS_GTE2, add clk_path_ready
- Set clk_path_ready to High to start Initialization of GTP TX and RX
2023-11-07 18:36:48 +08:00
Sebastien Bourdeauducq bbe6ff8cac flake: update dependencies 2023-11-07 18:36:11 +08:00
Sebastien Bourdeauducq c0a6252e77 afws_client: improve compatibility with older versions of prettytable. Closes #2264 2023-11-07 14:06:31 +08:00
mwojcik 6640bf0e82 drtioaux/subkernel/ddma: introduce proper errors, more robust 2023-11-07 13:42:04 +08:00
mwojcik b3c0d084d4 drtio: better control state of bigger payloads 2023-11-07 13:42:04 +08:00
linuswck bb0b8a6c00 kasli: Correct the GTP TX clock path during init
- TXOUT must be fed back into TXUSRCLK during initialization
- Now, MMCM Clock Input is switched before GTP TX Init is started instead of after GTP TX Init is done
- Reset in Sys Clock domain is kept asserted when clock is switched and GTP TX Init is NOT done
2023-11-07 13:40:32 +08:00
Sebastien Bourdeauducq ce80bf5717 flake: update dependencies 2023-11-07 13:40:17 +08:00
Florian Agbuya 378dd0e5ca flake: fix and upgrade wavedrom (closes #2266)
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2023-10-30 08:09:00 +01:00
jfniedermeyer 9c68451cae Add hotkeys to organize experiments in dashboard
Signed-off-by: jfniedermeyer <justin.niedermeyer@colorado.edu>
2023-10-27 21:47:30 +02:00
linuswck 93c9d8bcdf artiq_ddb_template:set default Shuttler drtio_dest
- remove default Shuttler "drtio_destination" value in jsonschema
- set the default Shuttler "drtio_destination" value according to
    board "target" and "hw_rev"
2023-10-27 21:46:02 +02:00
mwojcik e480bbe8d8 artiq_ddb_template: move satellite_cpu_target to core 2023-10-27 21:45:12 +02:00
mwojcik b168f0bb4b subkernel: separate tags and data 2023-10-17 12:18:03 +02:00
Sebastien Bourdeauducq 6705c9fbfb flake: update dependencies 2023-10-17 15:37:06 +08:00
mwojcik 5f445f6b92 ad53xx: fix `load()` references in documentation 2023-10-16 13:54:38 +08:00
occheung 363f7327f1 io_expander: initialize before service 2023-10-15 07:45:20 +08:00
Sebastien Bourdeauducq f7abc156cb flake: update dependencies 2023-10-11 16:41:34 +08:00
linuswck de41bd6655 eem_7series: pass through kwargs for shuttler 2023-10-11 12:15:06 +08:00
Simon Renblad 96941d7c04 big_number: fix metadata scaling, add unit label 2023-10-09 15:35:14 +08:00
mwojcik f3c79e71e1 firmware: merge runtime and satman linker scripts 2023-10-09 15:33:29 +08:00
Simon Renblad 333b81f789 set_argument_value warning in browser 2023-10-09 10:38:17 +08:00
Sebastien Bourdeauducq d070826911 flake: update dependencies 2023-10-09 10:13:58 +08:00
Sebastien Bourdeauducq 9c90f923d2 test: check return value of subprocesses in test_compile 2023-10-09 10:07:04 +08:00
Sebastien Bourdeauducq e23e4d39d7 artiq_compile: ignore subkernel_arg_types 2023-10-09 10:03:43 +08:00
David Nadlinger 08eea09d44 compiler: Catch escaping numpy.{array, full, transpose}() results
Function calls in general can still be used to hide escaping
allocations from the compiler (issue #1497), but these calls in
particular always allocate, so we can easily and accurately handle
them.
2023-10-09 09:00:26 +08:00
mwojcik 7ab52af603 docs: subkernel support 2023-10-08 17:12:06 +08:00
mwojcik 973fd88b27 core: compile and upload subkernels 2023-10-08 17:11:51 +08:00
mwojcik 8d7194941e tests: add lit tests for subkernels 2023-10-08 17:11:51 +08:00
mwojcik 0a750c77e8 compiler: support subkernels 2023-10-08 17:11:51 +08:00
mwojcik 1a0fc317df satman: support subkernels 2023-10-08 17:11:32 +08:00
mwojcik e05be2f8e4 runtime: support subkernels 2023-10-08 17:11:32 +08:00
mwojcik 6f4b8c641e drtioaux_proto: use better payload names 2023-10-08 17:11:32 +08:00
mwojcik b42816582e ksupport: support subkernels 2023-10-08 17:11:32 +08:00
Hartmann Michael (IFAG PSS SIS SCE QSE) 76f1318bc0 doc: Extend documentation
Extend the paragraph "Pitfalls" in the documentation of "Compiler" by
problems caused by returning values from the stack.
2023-10-07 07:20:33 +08:00
Sebastien Bourdeauducq 0131a8bef2 shuttler: cleanup 2023-10-06 14:55:51 +08:00
mwojcik e63e2a2897 artiq_ddb_template: better satellite formatting 2023-10-06 13:01:57 +08:00
Simon Renblad 47fc640f75 applets: rename 'ctl' attribute to 'req' 2023-10-05 12:32:01 +08:00
Simon Renblad bb7caacb5f RELEASE_NOTES: applet API extensions 2023-10-05 12:32:01 +08:00
Simon Renblad da9f7cb58a applet extensions documentation 2023-10-05 12:32:01 +08:00
occheung 43926574da shuttler: remove sdm constants 2023-10-05 07:40:00 +08:00
Simon Renblad 4f3e58db52 gui.applets: add EntryArea 2023-10-04 15:35:52 +08:00
Simon Renblad 13271cea64
gui: remove copies of _WheelFilter and refactor with parameter 2023-10-04 13:35:01 +08:00
occheung 0e8fa8933f shuttler: init sigma-delta modulator 2023-09-30 11:51:43 +08:00
David Nadlinger 2eb89cb168 dashboard: Fix occasional "unexpected action" applet errors on startup
This turned out to be a race between the dashboard's dataset db
subscriber being initialised and the applet "embed" request, with
artiq.applet.simple not being able to handle the unexpected "mod"
message. We were only handling the other ordering outcome of this
race before.
2023-09-30 00:27:25 +01:00
occheung a772dee1cc shuttler: change 0th order accumulator width
It now truncates the LSBs instead of the MSBs.
2023-09-29 10:09:39 +08:00
Simon Renblad bafb85a274 custom_applet: change constructor, data_changed signatures 2023-09-28 10:35:14 +01:00
mwojcik 0e8aa33979 core: separate master target from compilation 2023-09-28 10:41:55 +08:00
mwojcik fcf6c90ba2 ddb_template: support different satellite targets 2023-09-28 10:41:55 +08:00
linuswck 0c1b572872 Shuttler: Correct spelling and grammar in docs 2023-09-27 17:29:16 +08:00
linuswck ab0d4c41c3 Shuttler: pdq, efc->shuttler pdq_words->coef_words 2023-09-27 17:29:16 +08:00
Jonathan Coates 6eb81494c5 Allow using Python types in type annotations
This maps basic Python types (float, str, bool, np.int32, np.int64) as well as
some generics (list, tuple) to ARTIQ's own type instances.

Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2023-09-26 23:46:43 +01:00
Jonathan Coates 586d97c6cb Fix type annotations with mixed tuples
The type checker/inferer visits every node in an AST tree, including
function return annotations. This means for a function definition like

    def f() -> TTuple([TInt32, TBool]):
      ...

We attempt to type check the list [TInt32, TBool], which generates the
unification constraint builtins.TBool ~ builtins.TInt. This causes an
internal error due to compiler weirdness.

We can avoid this by just nulling-out the return annotation in the
embedding stage. The return type isn't actually used anywhere (it's
extracted via the inspect module instead), so this is entirely safe.

Arguments aren't affected by this, as we already nulled out the
annotation (see visit_arg in embedding.py).

Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2023-09-26 23:43:01 +01:00
David Nadlinger 892b0eaca2 compiler: Fix crash on multiple types with the same name
The original fix in 21574bdfa9
was incomplete, as it only addressed the TInstance types, but
not their linked (typ.constructor) TConstructor instances.

This would (potentially among other issues) cause assertion
errors in llvm_ir_generator due to the wrong associated globals
being referenced; see added test case for an example that
previously caused such a crash.

Also modified the name collision detection from O(len(type_map))
(so quadratic overall in the number of custom types) to cache
names in sets for O(1) lookup.
2023-09-26 23:31:21 +01:00
linuswck eedac7cf71 Shuttler: Patch ddb entries in the example code 2023-09-26 12:20:26 +08:00
linuswck a61bbf5618 Shuttler: Replace ddb with json for the example 2023-09-26 12:20:26 +08:00
occheung b7b8f0efa2
Generate coredevice entries for Shuttler (#2216)
* ddb: generate shuttler coredevice entries

* ddb: split-off all DRTIO-over-EEM peripherals

Only EFC uses DRTIO-over-EEM at this moment. It will be relevant to phaser-DRTIO in the future.

* ddb: generalize efc processing into drtio-over-eem peripherals

* ddb: check DRTIO role validity before processing
2023-09-26 09:44:21 +08:00
occheung b52f253dbd
Simplify OOB reset by clock division (#2217)
* oob: simply logic by dividing into clk100

* replace clk100 clk ctrl with clk200 async reset

* fix comment (singular/plural)

* oob reset: invoke platform commands locally

* cleanup

* oob reset: add async reset import

* fix duplicated comment
2023-09-26 08:02:49 +08:00
occheung 73ab71f443
shuttler: add documentation 2023-09-25 17:47:47 +08:00
linuswck ab8247b3d7 Shuttler: Add coredevice example code for Shuttler
This example code:
    - Demonstrates the init flow for Shuttler
    - Blinks LED L0, L1
    - Demonstrates the real-time control of relay
    - Includes example fns for configuring the PDQ Output Channel in mu
2023-09-25 14:56:47 +08:00
mwojcik 36b3678853 satman: fix ddma reporting wrong destination 2023-09-22 10:25:37 +08:00
mwojcik af77885dfc rtio_mgt: fix drtio reset on standalone 2023-09-22 09:46:40 +08:00
mwojcik eb57b3b393 drtio: async messages become synchronous
They are now a reply for DestinationStatusRequest.
This prevents gateware errors and lost packets if the receiver is busy.
2023-09-21 16:30:00 +08:00
Simon Renblad 40ac2e03ab set_argument_value in applets 2023-09-21 16:26:11 +08:00
occheung a2fbcb8bfd pre-dac gain/offsets: detect overflow & underflow
And output maximum / minimum DAC code when over/underflow
2023-09-19 18:49:20 +08:00
occheung 5c64eac8d2 relay: fix naming 2023-09-19 18:49:20 +08:00
occheung 477a7b693c remove debug for converter 2023-09-19 18:49:20 +08:00
occheung f2694f25eb re-impl ADC using general access methods 2023-09-19 18:49:20 +08:00
occheung 9e1447d104 adc: implement standby & power-down/up 2023-09-19 18:49:20 +08:00
occheung 870020bc9f adc: use a generous upper bound 2023-09-19 18:49:20 +08:00
occheung c2d136f669 shuttler: reorg SPI constants 2023-09-19 18:49:20 +08:00
occheung 06426e0ed9 shuttler: impl general reg access 2023-09-19 18:49:20 +08:00
occheung e443e06e62 shuttler: remove adc calibrate debug lines 2023-09-19 18:49:20 +08:00
occheung 55150ebdbb shuttler: fix calibration channel target 2023-09-19 18:49:20 +08:00
occheung eb08c55abe shuttler: add AFE drivers 2023-09-19 18:49:20 +08:00
occheung 67b6588d95 shuttler: implement gain & offset register access 2023-09-19 18:49:20 +08:00
occheung 1bb7e9ceef shuttler: support pre-DAC gain & offset 2023-09-19 18:49:20 +08:00
Florian Agbuya c02a14ba37 compiler: fix lit tests numpy.transpose error (#2190) 2023-09-18 22:11:46 +08:00
Simon Renblad 1f3b2ef645 dashboard.datasets: fix numpy objects in CreateEditDialog 2023-09-18 14:07:26 +08:00
linuswck 372008cb66 Firmware: AD9117 Add check presence of clk comment 2023-09-18 13:04:51 +08:00
linuswck 85abb1da2c Firmware: Set DACs RETIMER-CLK to Phase 1 Shuttler
- Intend to maintain the same pipeline latency across all DACs on Shuttler
- Force the RETIMER-CLK to be PHASE 1 on all DACs
- See Issue #2200 for details
2023-09-18 12:52:21 +08:00
David Nadlinger 9e5b62a6b1 gateware/targets/kasli: Only set DRTIO_ROLE in *Base classes [nfc]
kasli_generic uses the drtio_role setting to select the particular
*Generic class to use anyway.
2023-09-17 10:24:51 +08:00
David Nadlinger 22ab62324c gateware/targets/kasli: Set DRTIO_ROLE in {Master, Satellite}Base
These were introduced in 82bd913f63, and for Kasli only set from
the JSON description in the *Generic subclasses. Not all firmware
is built through that API, however, e.g. the CI system at the
University of Oxford. The missing attribute breaks artiq.build_soc.
2023-09-17 00:48:42 +01:00
David Nadlinger fc74b78a45 dashboard: Make Ctrl-Alt-W close non-docked applets only
I had introduced this in f11aef74b as a means of quickly cleaning up
after e.g. an exploratory session where a lot of transient applets were
opened from ndscan, or for a dashboard that has been running for a while
with CCBs enabled but without anybody actually working there.

It turns out that one usually wants the few docked applets to stay open,
as they were necessarily arranged manually at some prior point. And as a
corollary to the latter, if one did want to close them as well, doing so
manually would not be too onerous either.
2023-09-16 23:47:23 +01:00
Simon Renblad f01e654b9c gui.entries: fix RangeScan SpinBox size layouts 2023-09-16 16:06:45 +08:00
David Nadlinger e45dc948e9 setup.py: Add lmdb dependency
This has actually been a required dependency since
e710d4badd.
2023-09-15 17:25:45 +01:00
David Mak 460cbf4499 docs: Add section on untrusted substituters in Nix
Signed-off-by: David Mak <david.18.19.21@gmail.com>
2023-09-14 11:55:45 +08:00
Florian Agbuya 6df85478e4 scan: fix deprecated shuffle parameter in python 3.11 2023-09-13 12:24:44 +08:00
Jonathan Coates 5c85cef0c2
Allow indexing tuples in kernel code
This only allows for indexing with a constant value (e.g. x[0]).

While slices would be possible to implement, it's not clear how to
preserve type inference here. The current typing rule is:

  Γ ⊢ x : τ  Γ ⊢ a : Int  Γ ⊢ b : Int
  ------------------------------------
             Γ ⊢ x[a:b] : τ

However, tuples would require a different typing rule, and so we'd need
to defer type inference if τ is a tyvar. I'm not confident that this
won't change behaviour, so we leave as-is for now.

Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2023-09-12 14:43:38 +01:00
linuswck ccb140a929 Firmware: Add AD9117 DAC Startup Seq for shuttler 2023-09-11 15:07:47 +08:00
linuswck 7c8073c1ce Shuttler: Add DAC Data Interface Gateware
- Add Parallel DDR Data Interface for DAC
- Add MMCM to generate phase shifted DDR Clk(45 degree phase shift by default)
- Connect dac_interface to Shuttler Module
2023-09-11 11:37:13 +08:00
Florian Agbuya 2f3329181c flake: fix deprecated 'U' mode in outputcheck for python 3.11 2023-09-06 19:02:41 +08:00
Sebastien Bourdeauducq 1ec1ab0502 flake: update dependencies 2023-09-06 18:28:08 +08:00
linuswck b49fb841ce Firmware: EFC enables error led when going panic 2023-09-06 15:54:35 +08:00
Florian Agbuya a619c9f3c2 almazny: fix minor doc formatting 2023-09-06 14:12:09 +08:00
Florian Agbuya 0188f31f3a i2c: fix doc formatting 2023-09-05 17:00:27 +08:00
Florian Agbuya 4e770509db almazny: fix doc formatting 2023-09-05 17:00:27 +08:00
occheung 7f63bb322d disable DRTIO-over-EEM OSERDES until clock is stable
This asserts OOB reset on EFC.
2023-09-05 16:59:01 +08:00
occheung 5e5d671f4c kasli: add invoke order comments 2023-09-04 12:05:45 +08:00
occheung 98904ef4c3 kasli: construct DRTIO-EEM modules before adding RTIO 2023-09-04 12:05:45 +08:00
Sebastien Bourdeauducq 73ac414912 flake: update dependencies 2023-09-03 10:59:52 +08:00
occheung 838cc80922
EFC: Implement OOB reset 2023-09-03 10:25:08 +08:00
Simon Renblad 904afe1632 tools: remove trim param 2023-09-01 20:06:19 +08:00
Simon Renblad 01d777c977
dashboard/datasets: fix CreateEditDialog datatype cast (#2176) 2023-09-01 13:59:17 +08:00
Sebastien Bourdeauducq 9556ca53de flake: update dependencies 2023-08-31 17:43:28 +08:00
occheung df99450faa
shuttler: add pdq-based waveform generator 2023-08-30 23:38:39 +08:00
Sebastien Bourdeauducq 1f58cd505c flake: update dependencies 2023-08-30 15:39:46 +08:00
linuswck ddb2b5e3a1 efc: add shuttler DAC parallel data interface pads 2023-08-30 10:25:39 +08:00
linuswck b56f7e429a
drtio: rename drtio_transceiver to gt_drtio 2023-08-28 04:50:46 +00:00
Sebastien Bourdeauducq 3452d0c423 efc: use variant (expected everywhere else) 2023-08-25 15:52:40 +08:00
Sebastien Bourdeauducq 2139456f80 firmware: skip clock switch for efc 2023-08-25 15:06:42 +08:00
Sebastien Bourdeauducq a2a780a3f2 firmware: fix compilation warning 2023-08-25 15:06:02 +08:00
Sebastien Bourdeauducq 3620358f12 flake: build efc firmware 2023-08-25 13:34:56 +08:00
Sebastien Bourdeauducq 72b0a17542 flake: register firmware outputs as hydra build products 2023-08-25 13:25:22 +08:00
Sebastien Bourdeauducq f5cbca9c29 kasli: implement DRTIO-over-EEM 2023-08-25 12:47:33 +08:00
linuswck 737ff79ae7 eem: add efc 2023-08-25 12:01:17 +08:00
linuswck dc97d3aee6 drtio-eem: CONFIG_EEM_TRANSCEIVERS -> CONFIG_EEM_DRTIO_COUNT 2023-08-25 11:49:39 +08:00
Sebastien Bourdeauducq 5d38db19d0 drtio-eem: remove unnecessary rtio_rx clock domain 2023-08-25 11:32:28 +08:00
Sebastien Bourdeauducq 9bee4b9697 flake: update dependencies 2023-08-25 11:13:33 +08:00
linuswck cd22e42cb4
efc: add DRTIO virtual LEDs
- EFC Gateware: Add virtual_leds to rtio
- EFC Firmware: io_expander is kept being serviced to update
  virtual_leds after init
2023-08-23 06:21:14 +00:00
linuswck b7bac8c9d8 EFC: Add SPI Gateware for Shuttler DAC
- Verified by a functional test reading back the rev register
2023-08-23 09:04:16 +08:00
mwojcik e8818c812c satman: fix non-eem satellites failing to build 2023-08-22 16:32:59 +08:00
occheung 68dd0e029f targets: add efc target 2023-08-10 00:02:01 +00:00
occheung 64d3f867a0
add DRTIO-over-EEM PHY
for EFC and perhaps Phaser
2023-08-09 23:59:40 +00:00
Sebastien Bourdeauducq df662c4262 flake: update llvmlite 2023-08-07 23:02:23 +08:00
Sebastien Bourdeauducq d2ac6aceb3 flake: update to Clang 14 2023-08-07 18:45:13 +08:00
Sebastien Bourdeauducq 9b94a09477 flake: update to LLVM 14 2023-08-07 18:28:44 +08:00
David Nadlinger efbae51f9d runtime: Validate ksupport ELF against hard-coded address ranges
This would have caught the reduction in header padding with LLD 14.
In theory, we could just get rid of the hard-coded kernel CPU address
ranges altogether and use ksupport.elf as the one source of truth; the
code already exists in dyld. The actual base address of the file would
still need to be forwarded to the kernel-side libunwind glue, though,
as there doesn't seem to be a clean way to get the equivalent of
KSUPPORT_HEADER_SIZE through the linker script. I have left this as-is
with the hard-coded KERNELCPU_… constants for now.
2023-08-07 10:10:38 +00:00
David Nadlinger 8acfa82586 ksupport: Remove unused sections from linker script [nfc]
We no longer build ksupport.ld in a position-independent fashion, and
the reference to the ld.bfd _GLOBAL_OFFSET_TABLE issue was just a
distraction
2023-08-07 10:10:38 +00:00
David Nadlinger 4d636ea593 Upgrade to LLD 14
Previous linker versions had inserted some zero padding bytes
between the ELF headers and the first section, but LLD 14 does
not anymore.

Hard-coding the offset of the first section in ksupport.elf
manually isn't ideal; we should probably parse the ELF program
headers instead when first setting up the kernel CPU.
2023-08-07 10:10:38 +00:00
Sebastien Bourdeauducq 3ed7e0ed06 flake: update dependencies 2023-08-07 17:52:42 +08:00
Simon Renblad c4259dab18
applets.simple: add kwargs to AppletControlRPC (#2155)
Co-authored-by: Simon Renblad <srenblad@m-labs.hk>
2023-08-05 11:38:07 +08:00
mwojcik c46ac6f87d spi2: update set_config_mu doc 2023-08-04 09:22:57 +00:00
linuswck 758b97426a Bootloader: SDRAM patch for EFC
- Modification of the CFG flag ensure EFC to initialize DDRPHY correctly
Note that Kasli and EFC share the same model of SDRAM
2023-08-02 02:18:45 +00:00
linuswck c206e92f29 Bootloader: Remove kusddrphy support for SDRAM
- Delete all the kusddrphy cfg flags and related code
2023-08-02 02:18:20 +00:00
linuswck cb547c8a46
efc: turn on power of FMC peripheral
- Add efc's io expander method
- Enable VADJ, P3V3_FMC in satman main during startup
2023-08-01 00:29:45 +00:00
linuswck 72a5231493
artiq_flash: add EEM FMC Carrier Board Support
- The code is derived from PR #2134 936f24f6bd
2023-07-25 11:14:19 +08:00
Denis Ovchinnikov 07714be8a7
jsonschema: add kasli_soc HW revision v1.1 2023-07-24 16:32:13 +08:00
Simon Renblad 361088ae72 tools: add trim argument to format funcs 2023-07-21 08:38:49 +00:00
Simon Renblad a384df17a4 docs: add unit and precision explainer 2023-07-21 08:15:39 +00:00
Simon Renblad 6592b6ea1d artiq_client: change set_dataset with units 2023-07-21 08:15:39 +00:00
Simon Renblad 2fb085f1a2 datasets: change dataset value entry with units 2023-07-21 08:15:39 +00:00
Simon Renblad a7569a0b2d tools: add scale_from_metadata helper func 2023-07-21 08:15:39 +00:00
Simon Renblad 4fbff1648c scientific_spinbox: rename precision to sig_figs 2023-07-19 07:01:24 +00:00
Simon Renblad 8f4c8387f9 entries: rename setPrecision to setSigFigs 2023-07-19 07:01:24 +00:00
Simon Renblad a2d62e6006 RELEASE_NOTES: deprecated ndecimals 2023-07-18 08:02:42 +00:00
Simon Renblad 3d0feef614 docs: rename ndecimals to precision 2023-07-18 08:02:42 +00:00
Simon Renblad 59ad873831 examples: rename ndecimals to precision 2023-07-18 08:02:42 +00:00
Simon Renblad 8589da0723 test_arguments: rename ndecimals to precision 2023-07-18 08:02:42 +00:00
Simon Renblad 94e076e976 scan: rename ndecimals to precison 2023-07-18 08:02:42 +00:00
Simon Renblad a0094aafbb entries: rename ndecimals to precision 2023-07-18 08:02:42 +00:00
Simon Renblad 0befadee96 environment: rename ndecimals to precision 2023-07-18 08:02:42 +00:00
sven-oxionics b3dc199e6a Fix panic when receiving empty strings in rpc calls
Receiving an empty string in an RPC call currently panics.

When `length` is zero, a call to the `alloc` function (as implemented in `artiq/firmware/runtime/session.rs`) returns a null pointer. Constructing a `CMutSlice` from a null pointer panics.
A `CMutSlice` consists of a pointer and the length. Rust's documentation of the `core::ptr` module states: "The canonical way to obtain a pointer that is valid for zero-sized accesses is `NonNull::dangling`."
This commits adds a check for the length of a string received in an RPC call. Only for lengths greater than zero a memory allocation is performed. For zero-length strings, a dangling pointer is used.

Test plan:
Invoke the following experiment, which returns an empty string over RPC:
```
class ReturnEmptyString(artiq.experiment.EnvExperiment):
    def build(self):
        self.core: Core = self.get_device("core")

    @kernel
    def run(self):
        x = self.do_rpc()
        print(x)

    @rpc
    def do_rpc(self) -> TStr:
        return ""
```

Signed-off-by: Sven Over (Oxford Ionics) <sven.over@oxionics.com>
2023-07-18 04:00:32 +00:00
Florian Agbuya d73889fb27 gui/experiments: cast Qt timestamp to int preventing float type error 2023-07-14 08:33:27 +00:00
Simon Renblad 9f8bb6445f RELEASE_NOTES: add breaking change data_changed signature 2023-07-12 08:28:28 +00:00
Simon Renblad 068a2d1663 progress_bar: refactor data_changed 2023-07-12 08:28:28 +00:00
Simon Renblad 6c588b83d7 plot_xy_hist: refactor data_changed 2023-07-12 08:28:28 +00:00
Simon Renblad c17f69a51b plot_xy: refactor data_changed 2023-07-12 08:28:28 +00:00
Simon Renblad ac504069d2 plot_hist: refactor data_changed 2023-07-12 08:28:28 +00:00
Simon Renblad b6a83904b5 image: refactor data_changed 2023-07-12 08:28:28 +00:00
Simon Renblad 25959d0cd6 big_number: refactor data_changed 2023-07-12 08:28:28 +00:00
Simon Renblad 5695e9f77e simple: refactor TitleApplet data_changed signature 2023-07-12 08:28:28 +00:00
Simon Renblad fe0f6d8a2c simple: refactor SimpleApplet data_changed signature 2023-07-12 08:28:28 +00:00
Simon Renblad d1f2727126 simple: refactor RPC client set_dataset 2023-07-12 08:28:28 +00:00
Simon Renblad 16a3ce274f applets: add metadata param to set_dataset 2023-07-12 08:28:28 +00:00
Simon Renblad af7622d7ab simple: refactor IPC set_dataset 2023-07-12 08:28:28 +00:00
Jonathan Coates 9a84575649
eem_7series: fix typo in 77293d5
Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2023-07-11 23:09:15 +00:00
Simon Renblad faf85e815a datasets: add metadata to CreateEditDialog 2023-07-10 06:50:41 +00:00
Simon Renblad 3663a6b8e8 artiq_client: refactor set_dataset, show_datasets 2023-07-10 04:50:54 +00:00
Simon Renblad 91442e2914 browser: refactor upload_clicked for dataset metadata 2023-07-10 04:26:08 +00:00
Simon Renblad 50a6dac178 files: read dataset metadata from HDF5 2023-07-10 04:26:08 +00:00
Simon Renblad 5292a8de82 browser: add metadata param to short_format 2023-07-10 04:26:08 +00:00
Sebastien Bourdeauducq 7791f85a1a flake: update dependencies 2023-07-10 11:29:59 +08:00
Sebastien Bourdeauducq 48bc8a2ecc gtx_7series_init: GTH -> GTX (NFC) 2023-07-10 11:26:07 +08:00
Denis Ovchinnikov 93882eb3ce kasli-soc: fix of SYS CLK switch failure
Change initialization behaviour of GTX transceivers
--
Modify the config parms CPLL of GTX transceiver for PLL to lock correctly
Modify the enabling requirement of GTX input clock buffer IBUFDS_GTE2 so
    that it depends on GTX PLL locked signal instead of TX Init Done
Modify the GTX Init FSM so that BruteForceClock Aligner can reset GTX
    transceiver without resetting the GTX transceiver PLL

kasli-soc: fix of SYS CLK switch failure
Changed initialization of GTX transceivers.
Successful SYS CLK switching requires IBUFDS_GTE2 to be properly enabled and not disabled during GTX transceiver initialization.
For this reason, CPLL is not reset during GTX initialization and clock alignment.

kasli-soc: refractor fix of SYS CLK switch failure
Remove gtXxreset & cpllreset assertion and deassertion
The removed code does not affect the fix
2023-07-10 03:24:28 +00:00
Simon Renblad 7ca02a119d RELEASE_NOTES: update lmdb migrate script 2023-07-10 02:33:59 +00:00
Simon Renblad 373fe3dbe7 test_datasets: add metadata tests 2023-07-10 02:33:59 +00:00
Simon Renblad 1af98727b7 test_scheduler: refactor dataset metadata support 2023-07-10 02:33:59 +00:00
Simon Renblad 376f36c965 datasets: add metadata format param 2023-07-10 02:33:59 +00:00
Simon Renblad e710d4badd databases: read and save metadata in lmdb 2023-07-10 02:33:59 +00:00
Simon Renblad bfbe13e51b worker_db: write hdf5 dataset metadata 2023-07-10 02:33:59 +00:00
Simon Renblad bf38fc8b0f tools: refactor short_format with metadata 2023-07-10 02:33:59 +00:00
Simon Renblad 337273acb6 environment: add get_dataset_metadata 2023-07-10 02:33:59 +00:00
Simon Renblad 748707e157 environment: add unit feature 2023-07-10 02:33:59 +00:00
Leon Riesebos 833fd8760e artiq_ddb_template: use the clk_div field
this field exists in the json schema but was not used.

Signed-off-by: Leon Riesebos <28567817+lriesebos@users.noreply.github.com>
2023-06-29 03:29:18 +00:00
Florian Agbuya 454597915a RELEASE_NOTES: update 2023-06-17 05:01:02 +00:00
Sebastien Bourdeauducq 77293d53e3 json: use schema defaults when applicable 2023-06-16 16:59:08 +08:00
Sebastien Bourdeauducq a792bc5456 json: factor handling of deprecated 'base' 2023-06-16 16:32:42 +08:00
Sebastien Bourdeauducq 20d4712815 json: base -> drtio_role 2023-06-16 16:17:31 +08:00
Spaqin 82bd913f63
satellites: add kernel cpu 2023-06-16 15:44:31 +08:00
Sebastien Bourdeauducq 115415d120 Revert "flake: update to LLVM 14 and llvmlite 0.40.0+master"
This reverts commit c25c0bd55a.
2023-06-14 18:54:33 +08:00
Florian Agbuya d140c960bb
applets: implement dataset modification feature in big number applet 2023-06-12 17:52:46 +08:00
Egor Savkin c25c0bd55a
flake: update to LLVM 14 and llvmlite 0.40.0+master 2023-06-09 13:25:08 +08:00
Egor Savkin 30ef8d8cb4
compiler: skip demangling list of empty names 2023-06-09 13:24:10 +08:00
Florian Agbuya 7ad32d903a browser: add update method to dataset controller 2023-06-06 11:07:08 +00:00
Florian Agbuya bf46ce4a92 applets.simple: add mutate_dataset feature 2023-06-05 12:30:14 +00:00
den512is 1f306a2859
flake: add packaging dependency
Needed for building Kasli firmware
2023-06-05 13:17:47 +08:00
Florian Agbuya 150d325fc1 applets.simple: add append_to_dataset feature 2023-06-02 14:56:00 +00:00
Florian Agbuya c298ec4c2e applets: add update_dataset for dataset mods 2023-06-02 14:56:00 +00:00
Sebastien Bourdeauducq 69bf2dfb81 flake: sleep longer before running HITL tests to allow for clock switch and reboot 2023-06-02 17:41:15 +08:00
mwojcik 29cb7e785d fix missing DIFF_TERM for Sampler and Mirny inputs 2023-06-02 17:21:00 +08:00
Sebastien Bourdeauducq b97f6a9e44 bootloader: fix compilation warning without Ethernet 2023-06-02 10:48:55 +08:00
Sebastien Bourdeauducq e0ebc1b21d applets: fix some asyncio problems 2023-05-31 22:56:48 +08:00
Sebastien Bourdeauducq c6ddd3af17 applets: add controller and set_dataset API 2023-05-31 22:51:48 +08:00
Florian Agbuya e12219e803
gui: add handler for applet set_dataset 2023-05-31 14:08:14 +00:00
Sebastien Bourdeauducq ff11b5df71 flake: add qtsvg 2023-05-31 22:07:05 +08:00
Sebastien Bourdeauducq c8dc2cbf09 browser: decouple dataset controller from dataset dock 2023-05-31 21:57:54 +08:00
Sebastien Bourdeauducq c6b29b30fb Revert "flake: update to LLVM 14 and llvmlite 40"
This reverts commit 748969c21e.
2023-05-31 19:36:43 +08:00
Sebastien Bourdeauducq b20d09aad5 Revert "flake: export llvmlite-new"
This reverts commit fabe88065b.
2023-05-31 19:36:41 +08:00
Sebastien Bourdeauducq 6276182c96 Revert "flake: fix clang version in boards shell"
This reverts commit 9a6bc6dc7b.
2023-05-31 19:36:40 +08:00
Sebastien Bourdeauducq d103cbea31 libboard_misoc: fix clang STB_WEAK warning 2023-05-31 18:59:51 +08:00
Sebastien Bourdeauducq 9a6bc6dc7b flake: fix clang version in boards shell 2023-05-31 18:59:39 +08:00
Sebastien Bourdeauducq fabe88065b flake: export llvmlite-new 2023-05-30 16:54:59 +08:00
Egor Savkin 748969c21e
flake: update to LLVM 14 and llvmlite 40
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-05-30 16:47:59 +08:00
Sebastien Bourdeauducq 75f6bdb6a1 flake: add boards dev shell 2023-05-30 16:21:06 +08:00
Sebastien Bourdeauducq 41caec797e flake: do not install ARTIQ itself in dev shell, only its dependencies
Otherwise, test runs take a long time when entering the shell, and failing tests stop entering the shell which is not what we want.
Also make jsonschema a regular dependency of ARTIQ, since users can now retrieve JSONs via AFWS.
2023-05-30 16:20:57 +08:00
Sebastien Bourdeauducq 953a8a9555 master: merge master_config and master_terminate 2023-05-30 15:55:19 +08:00
Sebastien Bourdeauducq 444bab2186 gui: datasets_sub -> dataset_sub (nfc) 2023-05-30 15:44:30 +08:00
Sebastien Bourdeauducq 0941d3a29a flake: update dependencies 2023-05-30 11:50:30 +08:00
Denis Ovchinnikov 22e2514ce6 update configuration of IBUFDS_GTE2
Input clock is terminated internally with 50 Ohm on each leg and to 4/5 MGTAVCC.
2023-05-30 11:42:51 +08:00
mwojcik a4895b591a analyzer: fix satellite behavior 2023-05-29 13:13:24 +08:00
Sebastien Bourdeauducq ef2cc2cc12 flake: buildFHSUserEnv -> buildFHSEnv 2023-05-27 18:03:18 +08:00
Sebastien Bourdeauducq 779810163f flake: fix rustPlatform deprecation warnings 2023-05-27 17:40:36 +08:00
Sebastien Bourdeauducq b9c7905b20 nixpkgs 23.05 2023-05-27 17:17:36 +08:00
Charles Baynham c2b0c97640 worker: Wait until datasets are written before quitting
Avoids a race condition in worker_impl.py where HDF5 dataset saving was
cut off before it finished for large datasets.
2023-05-23 21:48:56 +01:00
Sebastien Bourdeauducq 58cc3b8d0a kasli_generic: fix LooseVersion deprecation warning 2023-05-23 19:36:06 +08:00
Sebastien Bourdeauducq 598c7b1d25 flake: update qasync 2023-05-23 11:26:30 +08:00
Jonathan Coates ea9fe9b4e1
dma: fix off-by-one error in RawSlicer (#2090)
Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2023-05-23 11:15:39 +08:00
mwojcik c1d6fd4bbe satman analyzer: remove forgotten comment 2023-05-19 11:39:14 +08:00
mwojcik ab52748cac analyzer sat: disarm on drop 2023-05-19 11:39:14 +08:00
mwojcik ddfe51e7ac analyzer: use transactions for data transmission 2023-05-19 11:39:14 +08:00
mwojcik 6c96033d41 analyzer: implement querying up satellites for data 2023-05-19 11:39:14 +08:00
mwojcik 0b03126038 satman: support analyzer packets 2023-05-19 11:39:14 +08:00
mwojcik fdca1ab7fc drtioaux: add analyzer related messages 2023-05-19 11:39:14 +08:00
mwojcik c36b6b3b65 master: only local rtio events in analyzer 2023-05-19 11:39:14 +08:00
mwojcik c0ca27e6cf satellite: add rtio_analyzer, only for local rtio 2023-05-19 11:39:14 +08:00
Jonathan Coates 3ca47537b8 Fix mismatched signatures for the wide interface
Lists are passed by-reference from python code, and so should be
&CSlice<_> not CSlice<_>.

Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2023-05-19 10:18:06 +08:00
Hartmann Michael (IFAG PSS SIS SCE QSE) df15f53ee9 doc: conda installation notes 2023-05-12 17:44:38 +08:00
Sebastien Bourdeauducq e015483e48 RELEASE_NOTES: add LMDB migration script (#1743) 2023-05-09 14:56:43 +08:00
Sebastien Bourdeauducq c53d333d46 almazny: fix parameter 2023-05-09 14:27:37 +08:00
Sebastien Bourdeauducq 5b94ce82e4 artiq_ddb_template: fix almazny 2023-05-09 14:27:15 +08:00
Sebastien Bourdeauducq 45cd438fb8 Almazny v1.2 support
Based on PR #2060 by Robert Jördens.
2023-05-09 12:54:48 +08:00
Sebastien Bourdeauducq 0e7e30d46e test: fix hardware testbench trying to write to ARTIQ_ROOT 2023-04-30 17:16:36 +08:00
Sebastien Bourdeauducq d5a7755584 test: improve tmpdir names 2023-04-30 17:15:34 +08:00
Sebastien Bourdeauducq 3ff0be6540 PEP440 compliant version numbers 2023-04-30 16:55:49 +08:00
Sebastien Bourdeauducq 8409a6bb94 update gitignore 2023-04-30 16:53:49 +08:00
Sebastien Bourdeauducq 2c1438c4b9 coredevice: add missing pattern to sampler_hw_rev 2023-04-30 16:07:56 +08:00
Egor Savkin 5199bea353
master: emit warning if datasets will not be stored 2023-04-30 15:22:21 +08:00
mwojcik a533f2a0cd rtio: SED, InputCollector use rio clock domain 2023-04-28 17:49:12 +08:00
Jonathan Coates 0bf57f4ebd Fix ADF3536 having RTIO channel names
The channel in this device refers to a channel on the mirny, not an RTIO
channel.
2023-04-24 20:05:14 +08:00
Sebastien Bourdeauducq 4417acd13b flake: update dependencies 2023-04-24 17:36:13 +08:00
Sebastien Bourdeauducq 4056168875 master: store datasets in LMDB (#1743) 2023-04-24 17:34:30 +08:00
Egor Savkin 9331911139
add tests for client submit functionality 2023-04-24 11:43:24 +08:00
Spaqin 2f35869eb1
satman: fix PMP and L2 flush 2023-04-20 15:45:15 +08:00
Egor Savkin aed47d79ff
master: add terminate API 2023-04-18 15:03:06 +08:00
mwojcik 918d30b900 dma: pass "uses_ddma" for non-remote recordings 2023-04-18 12:35:37 +08:00
Egor Savkin b5d9062ba9 Fix AD9914 channel map
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-04-17 09:23:30 +08:00
Egor Savkin 8984f5104a Move RTIO errors formatting to the session_proto
This would be closer to the artiq-zynq implementation

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-04-17 09:23:30 +08:00
Egor Savkin d0b8818688 Add 125 MHz from 80 MHz reference option to rtio clocking
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-04-13 14:57:24 +08:00
Sebastien Bourdeauducq 757c00b0fe afws_client: improve UX of common build errors 2023-04-08 16:50:15 +08:00
Sebastien Bourdeauducq c1474c134a remove obsolete AFWS certificate 2023-04-07 16:09:47 +08:00
Sebastien Bourdeauducq dc3db8bb66 afws_client: WebSocket, system certificates 2023-04-07 16:03:33 +08:00
Sebastien Bourdeauducq 97161a3df2 firmware: improve RTIO map error reporting 2023-04-04 11:27:31 +08:00
Ikko Eltociear Ashimine 7ba06bfe61 fix typo in comm_analyzer.py
error_occured -> error_occurred
occured -> occurred
2023-04-02 09:17:37 +08:00
Spaqin b225717ddb
DDMA: documentation 2023-03-29 13:46:33 +08:00
mwojcik 696bda5c03 handle playback status in aux_transact 2023-03-28 14:18:29 +08:00
mwojcik 9150230ea7 dma: gate ddma features behind cfg(has_drtio) 2023-03-28 14:18:29 +08:00
Spaqin e9a153b985
runtime: implement distributed DMA 2023-03-22 11:16:25 +08:00
David Nadlinger 8b1f38b015 worker_impl: Remove misleading update() from ExamineDatasetMgr [nfc]
`update(mod)` would be on the DatasetDB, not the manager. Rather,
modifications currently just fail due to e.g. `set(…)` not being
defined.
2023-03-20 13:20:40 +08:00
Egor Savkin bbf80875fb
firmware: assume empty config records as removed (#2064)
This will return `KeyNotFound` for empty values, which are produced by `remove` operation

Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-03-13 18:18:26 +08:00
Egor Savkin 1ca09b9484
Add support for default route (IPv4 and IPv6) (#2059)
Based on code by Michael Birtwell <michael.birtwell@oxionics.com>
2023-03-13 17:29:10 +08:00
Spaqin 84e7515721
satman: distributed DMA support 2023-03-11 18:36:36 +08:00
Ikko Eltociear Ashimine 15c18bdc81 fix typo in developing_a_ndsp.rst
occurence -> occurrence
2023-03-11 18:32:14 +08:00
Sebastien Bourdeauducq a9360823b1 libproto: remove obsolete Jdac packets 2023-03-02 20:29:09 +08:00
Egor Savkin 1ec0abbfcf Add Urukul PLL bypass option to the JSON
Signed-off-by: Egor Savkin <es@m-labs.hk>
2023-03-01 19:05:16 +08:00
mwojcik 90a6fe1c35 satellite: add dma to gateware 2023-02-23 17:33:23 +08:00
mwojcik d0437f5672 rtio core: fix minimum_coarse_timestamp 2023-02-22 10:44:25 +08:00
Michael Hartmann 07d684a35d doc: Add jsonschema to nix package list
Add jsonschema to the nix package list as it is required by
artiq_ddb_template.
2023-01-31 18:24:15 +08:00
Michael Hartmann 2371c825f5 doc: Add remark about FTDI drivers
Add a remark that on Windows you might need to install the FTDI drivers
first before you can connect to the serial port.
2023-01-26 21:05:47 +08:00
Egor Savkin 394138f00f
firmware: block session on startup kernel to be finished (#2046) 2023-01-19 16:46:53 +08:00
Sebastien Bourdeauducq 3f5cc4aa10 RELEASE_NOTES: fix formatting 2023-01-19 16:42:52 +08:00
Sebastien Bourdeauducq e9c65abebe manual: fix Nix flakes installation. Closes #2036 2023-01-15 13:03:15 +08:00
Sebastien Bourdeauducq 20e8f17b3d artiq_ddb_template: fix mistake in 18524911 2023-01-15 12:27:13 +08:00
Sebastien Bourdeauducq 57e87c9717 sampler: fix mistake in c591e7e3 2023-01-15 12:27:10 +08:00
Sebastien Bourdeauducq 248cd69673 flake: use nixpkgs cargo-xbuild 2023-01-12 18:03:46 +08:00
Sebastien Bourdeauducq b8968262d7 Merge branch 'syncrtio' 2023-01-12 16:44:54 +08:00
Sebastien Bourdeauducq babbbfadb3 update release notes 2023-01-12 13:12:05 +08:00
Sebastien Bourdeauducq 514ac953ce remove obsolete SI5324_AS_SYNTHESIZER config option 2023-01-12 13:01:08 +08:00
Sebastien Bourdeauducq 0a37a1a4c1 Merge branch 'syncrtio' 2023-01-12 12:58:19 +08:00
Sebastien Bourdeauducq 6d37d9d52c gui/state: fix asyncio loop management 2023-01-12 12:41:08 +08:00
Sebastien Bourdeauducq 5f77d4f5fa applets: fix asyncio loop management 2023-01-12 12:35:02 +08:00
Sebastien Bourdeauducq 2f289c552f remove unused import 2023-01-12 12:18:17 +08:00
Sebastien Bourdeauducq 9e8bb3c701 browser,dashboard: fix asyncio loop management 2023-01-12 12:17:16 +08:00
Sebastien Bourdeauducq d872c3ab4d aqctl_moninj_proxy: fix asyncio loop management 2023-01-12 12:16:53 +08:00
Sebastien Bourdeauducq f8d93813e9 aqctl_corelog: fix asyncio loop management 2023-01-12 10:52:26 +08:00
Sebastien Bourdeauducq 628b671433 update copyright year 2023-01-12 10:41:10 +08:00
Sebastien Bourdeauducq daad3d263a master: commit missing part of 7fd6dead8 2023-01-12 10:39:53 +08:00
Sebastien Bourdeauducq 80f261437a flake: update dependencies 2023-01-11 18:47:30 +08:00
Sebastien Bourdeauducq 7fd6dead8f master: fix asyncio loop management 2023-01-11 18:46:54 +08:00
Sebastien Bourdeauducq 73a4ef89ec scheduler: make asyncio loop a keyword-only argument, like in other asyncio APIs 2023-01-11 18:45:35 +08:00
mwojcik 70edc9c5c6 test_write_underflow: decrease underflow delay 2023-01-11 12:02:51 +08:00
mwojcik 9042426872 echo test: add two more yields 2023-01-11 12:02:51 +08:00
mwojcik cd860beda2 test_full_stack: restore missing check_ttls 2023-01-11 12:02:51 +08:00
mwojcik 627504b60e test_dma: remove redundant clock 2023-01-11 12:02:51 +08:00
Sebastien Bourdeauducq c8ab6c1b2b test_worker: fix asyncio event loop management 2023-01-10 12:36:33 +08:00
Sebastien Bourdeauducq a96bbd8508 test_scheduler: fix asyncio event loop management 2023-01-10 12:30:08 +08:00
Sebastien Bourdeauducq 6cfd1480a7 scheduler: support passing event loop 2023-01-10 12:26:24 +08:00
Sebastien Bourdeauducq c401559ed5 flake: update dependencies 2023-01-10 12:26:00 +08:00
Sebastien Bourdeauducq ea21f474a7 gateware: remove SAWG simulations 2023-01-09 18:37:19 +08:00
Sebastien Bourdeauducq cee9f3f44e flake: run gateware simulations 2023-01-09 18:36:55 +08:00
Sebastien Bourdeauducq b9bfe090f4 flake: cleanup 2023-01-09 18:23:36 +08:00
mwojcik eb3742fb08 kc705: do not reset si5324 during clock switch 2023-01-09 18:18:21 +08:00
Egor Savkin 070fed755b
firmware: unify RTIO error message format 2023-01-09 16:13:05 +08:00
Sebastien Bourdeauducq 63f1a6d197 drtio: partially fix tests 2023-01-06 18:33:13 +08:00
Sebastien Bourdeauducq 7dafdfe2f7 artiq_flash: fix bit2bin 2023-01-06 18:24:00 +08:00
Sebastien Bourdeauducq ec893222a4 rtio: remove support for async mode 2023-01-06 18:22:05 +08:00
Sebastien Bourdeauducq 573a895c1e remove RTIOClockMultiplier 2023-01-06 17:59:18 +08:00
Sebastien Bourdeauducq cf2a4972f7 remove WRPLL 2023-01-06 17:53:11 +08:00
Sebastien Bourdeauducq 668997a451 flake: update dependencies 2023-01-06 17:49:13 +08:00
Sebastien Bourdeauducq 5da9794895 remove Sayma and Metlino support 2023-01-06 17:41:12 +08:00
Spaqin 3838dfc1d1
DRTIO: RTIO/SYS clock merge, KC705 2023-01-06 07:13:38 +08:00
Sebastien Bourdeauducq 1be7e2a2e1 doc: duplicate nixConfig 2023-01-04 15:13:55 +08:00
Sebastien Bourdeauducq 1bf7188dec gui: update version in logo 2023-01-04 15:07:56 +08:00
mwojcik bdae594c79 suservo experimentals: fix rtio ch name changes 2023-01-04 14:56:18 +08:00
mwojcik 8dc6902c23 AD9912: Add PLL bypass option (pll_en) like AD9910 2022-12-21 13:34:31 +08:00
Norman Krackow dbb77b5356
artiq_sinara_tester: change mirny frequencies 2022-12-21 09:47:47 +08:00
Sebastien Bourdeauducq 1fc127c770 fix default version 2022-12-20 12:56:43 +08:00
David Nadlinger 88684dbd2a test_embedding: Fix up spelling in FIXME comment [nfc] 2022-12-19 01:02:51 +00:00
David Nadlinger b9f13d48aa firmware: Fix object references in tuples
Sine 8740ec3dd, the alignment() information from
"run-time type information" (i.e. the Tag type) is also
used when sending tuples to the host.
2022-12-19 00:57:46 +00:00
David Nadlinger 4bb2a3b9e0 RELEASE_NOTES: Two typo/formatting fixes 2022-12-18 17:26:58 +00:00
David Nadlinger f5c408d8d9 RELEASE_NOTES: Fix up punctuation 2022-12-18 17:11:36 +00:00
Sebastien Bourdeauducq 4be7f302e4 flake: vivado 2022.2 2022-12-18 16:51:02 +08:00
Spaqin 17efc28dbe
DRTIO: RTIO/SYS clock merge 2022-12-17 15:39:54 +08:00
David Nadlinger 1e0102379b firmware: Rename si5324 crystal_{ref -> as_ckin2} [nfc]
This would have made the issue in the pre-740543d4e code
much more obvious (the config option by itself does not
have any effect on the choice of active reference input).
2022-12-17 02:17:12 +00:00
David Nadlinger ceabeb8d84 firmware: Fix Si5324 initialisation for satellites
Commit 740543d4e2 had unintentionally broken DRTIO
satellites, as si5324::setup is also used there. This
imports setup_si5324_as_synthesizer() from artiq-zynq,
where the input selection was already explicitly done.

GitHub: Fixes #2028.
2022-12-17 02:17:06 +00:00
SingularitySurfer 8e476dd502 implement pca9539 and runtime io-expander chip selection
better comments and address translation

fix spurious };

unwrap init in runtime and return err instead of panic

propagate error

del unnecessary use

Signed-off-by: SingularitySurfer <Norman_Krackow@gmx.de>
2022-12-14 22:46:38 +08:00
David Nadlinger 874d298ceb master/scheduler: Unbreak submitting from repository
This is a fix-up to commit 2a58981822.
2022-12-13 14:58:23 +00:00
Egor Savkin d75ade7be6 Fix rtiomap failure on device aliases
Signed-off-by: Egor Savkin <es@m-labs.hk>
2022-12-13 17:21:10 +08:00
Egor Savkin 2a58981822 Scheduler: replace relative path to absolute
Signed-off-by: Egor Savkin <es@m-labs.hk>
2022-12-09 21:43:36 +08:00
Egor Savkin e80442811e
worker_impl: do not write results without rid (#2020) 2022-12-09 16:18:28 +08:00
Egor Savkin 12649720f1 browser: read artiq_version from HDF5 as string
Signed-off-by: Egor Savkin <es@m-labs.hk>
2022-12-07 16:39:19 +08:00
Egor Savkin 454ae39c5d
firmware: fix crash on exception with host message (#2017) 2022-12-07 10:41:43 +08:00
David Nadlinger 3c7a394eff runtime/rtio_clocking: Deduplicate/document input selection [nfc] 2022-12-04 04:21:44 +00:00
David Nadlinger 740543d4e2 firmware: Fix Kasli v2 runtime rtio_clock selection
SI5324_EXT_REF now only controls the (deprecated) fallbacks
for when the rtio_clock option is not set.
2022-12-04 02:23:38 +00:00
Egor Savkin b2b559e73b
browser: tolerate missing HDF5 metadata 2022-12-02 16:30:58 +08:00
Egor Savkin 1852491102
add channel names to RTIO errors 2022-12-02 16:27:03 +08:00
Egor Savkin c591e7e305
sampler: fix reference voltage of recent hardware 2022-12-02 10:45:40 +08:00
David Nadlinger 261dc6b933 firmware/runtime: Fix Ext0_Synth0_*to125 log messages 2022-12-02 01:37:56 +00:00
David Nadlinger 1abedba6dc coredevice/fastino: Fix stray punctuation [nfc] 2022-12-01 12:11:35 +00:00
Egor Savkin aa2febca53
browser: fix dummy device creation failure on analyze 2022-12-01 17:45:02 +08:00
Egor Savkin d60a96a715 Fix deprecated warnings on nix develop
Signed-off-by: Egor Savkin <es@m-labs.hk>
2022-12-01 17:33:18 +08:00
wlph17 3f93f16955
manual: add msys2 openocd instructions (#2014) 2022-12-01 17:23:51 +08:00
Sebastien Bourdeauducq 3735b7ea9d Revert "flake: update cargo-xbuild"
This reverts commit 195d2aea6a.
2022-11-30 22:19:27 +08:00
Sebastien Bourdeauducq 195d2aea6a flake: update cargo-xbuild 2022-11-30 21:48:25 +08:00
Sebastien Bourdeauducq 6d179b2bf5 flake: nixos 22.11 2022-11-30 21:36:36 +08:00
Sebastien Bourdeauducq 275b00bfc2 flake: fix libcrypt.so.1 not found by vivado 2022-11-30 11:26:23 +08:00
Jonathan Coates b8b6ce14cc Update smoltcp to 0.8.2
This fixes an issue where TCP issues are not retransmitted when only
some packets in a burst are acknowledged. This causes smoltcp to never
make progress and hang.

Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2022-11-28 22:10:23 +08:00
Nico Pulido 88c5109627 language: check_unprocessed_arguments after constructing experiment
Signed-off-by: Nico Pulido-Mateo <pulido@iqo.uni-hannover.de>
2022-11-27 02:29:57 +00:00
David Nadlinger dee154b35b compiler: Add missing sections to kernel linker script
This caused sporadic LoadFaults with LLD 14 and above, as they
happened to lay out the (not otherwise mentioned) GOT/PLT such
that they would overlap with the stack guard page.

LLD does support the --orphan-handling=error option, which
would be useful to avoid similar problems in the future, but
then we'd need to mention all the other misc sections
(symbol table, comments) in the linker script as well.

GitHub: Fixes #1975.
2022-11-24 16:57:31 +00:00
David Nadlinger 950b9ac4d6 firmware: More explicit panic message if stack guard is tripped
This should give even only mildly technical users a
chance to figure out what's going on, which empirically
is not the case for a plain Exception(LoadFault) without
further context.
2022-11-24 16:54:49 +00:00
Egor Savkin 6c47aac760
dashboard: merge create dataset and edit dataset features 2022-11-23 18:22:53 +08:00
mwojcik f2c1e663a7 regenerate suservo_coherent patch with var_urukul base 2022-11-23 17:22:26 +08:00
Egor Savkin f7f027001e
compiler: insert new lines into long synthesized code (#1986) 2022-11-23 12:10:32 +08:00
David Nadlinger 0b3c232819 language: Clarify error message for unprocessed arguments
"Unexpected argument(s)" would be another less ambiguous,
shorter phrasing.
2022-11-22 11:26:07 +00:00
Etienne Wodey d45f9b6950 ddb_template: propagate fastino log2_width setting
Signed-off-by: Etienne Wodey <etienne.wodey@aqt.eu>
2022-11-17 10:54:37 +08:00
Sebastien Bourdeauducq 2fe02cee6f doc: MSYS2 packages 2022-11-15 19:32:06 +08:00
Sebastien Bourdeauducq 404f24af6b compiler: set lld emulation explicitly 2022-11-15 11:20:06 +08:00
David Nadlinger 3d25092cbd firmware/rpc_proto: Remove unnecessary cast [nfc] 2022-11-14 22:50:38 +00:00
David Nadlinger dbbe8e8ed4 firmware/rpc_proto: Fix typo breaking receiving of arrays
This was introduced in 8740ec3dd5.
2022-11-14 22:49:45 +00:00
David Nadlinger 8740ec3dd5 firmware/rpc_proto: Fix size/alignment calculation for structs with tail padding
Also factors out duplicate code for (de)serializing
elements of lists and ndarrays, and replaces the rounding
calculations by the well-known, much faster power-of-two-only
bit-twiddling version.

GitHub: Fixes #1934.
2022-11-14 11:37:45 +08:00
David Nadlinger 6caa779c74 firmware/ksupport: Include .gcc_except_table (LSDA)
For whatever reason, no language-specific unwind data
was generated for ksupport code so far, but rustc does
emit it for an upcoming refactoring.
2022-11-14 11:37:45 +08:00
David Nadlinger 4819016a3c firmware/ksupport: Document rpc_recv alignment requirements [nfc] 2022-11-14 11:37:45 +08:00
David Nadlinger 00a27b105a compiler: Extract maximum alignment from target data layout
In particular, i64/double are actually supposed to be aligned
to their size on RISC-V (at least according to the ELF psABI),
though it is unclear to me whether this actually caused any
issues.
2022-11-14 11:37:45 +08:00
David Nadlinger beff15de5e compiler/targets: Fix refactoring leftover for native (host) target
It's unclear whether this actually caused any issues, or why this
wasn't done before (instead just setting the now-removed endianness
flag).
2022-11-14 11:37:45 +08:00
火焚 富良 defc69d9c3
compiler: fix const str/bytes handling (#1990) 2022-11-11 13:15:50 +08:00
火焚 富良 e2178f6c86
Fix GUI log issues introduced by #1950 2022-11-09 16:55:17 +08:00
Sebastien Bourdeauducq f3f068036a use maintained fork of python-Levenshtein 2022-11-03 21:24:49 +08:00
mwojcik ad000609ce simplify tsc with no rtio/sys clk distinction 2022-11-01 08:12:54 +08:00
mwojcik af0b94bb34 rtio_clock: remove 150MHz support 2022-11-01 08:12:54 +08:00
mwojcik 5cd57e8688 rtio_clocking: switch clocks and reboot 2022-11-01 08:12:54 +08:00
mwojcik f8eb695c0f dma test: no more rsys or rtio domains 2022-11-01 08:12:54 +08:00
mwojcik 458bd8a927 kasli_generic: remove rtio clockdomain reference 2022-11-01 08:12:54 +08:00
mwojcik a6856a5e4a rtio: remove rtio clock, use sys instead 2022-11-01 08:12:54 +08:00
mwojcik 1eb87164be kasli: remove rtiocrg, use rtio/sys merge 2022-11-01 08:12:54 +08:00
Sebastien Bourdeauducq f75ddf78b0 dashboard: restore connection/version message 2022-10-21 19:17:00 +08:00
Sebastien Bourdeauducq e0b1098bc0 dashboard: remove incorrect moninj proxy message 2022-10-21 19:13:47 +08:00
Robert Jördens e5c621751f
Merge pull request #1962 from quartiq/miqro
Support MIQRO mode for Phaser
2022-10-19 16:56:02 +02:00
Robert Jördens 07db770423 phaser: fix tester 2022-10-19 16:54:00 +02:00
Robert Jördens eb7a0714b3 literal copy paste error 2022-10-19 16:44:44 +02:00
Robert Jördens e15b5b50d8 phaser: tweak docs, relax slack 2022-10-19 16:42:03 +02:00
Robert Jördens 1820e1f715 phaser: cleanup 2022-10-19 16:25:33 +02:00
Robert Jördens 118b7aca1d
Merge pull request #1980 from FabianSchwartau/fix_phaser_init_delays
Fixed two too low delay values in Phaser init
2022-10-19 15:55:11 +02:00
Fabian Schwartau d5e267fadf Fixed two too low delay values in Phaser init
Signed-off-by: Fabian Schwartau <fabian@opencode.eu>
2022-10-19 15:45:45 +02:00
Sebastien Bourdeauducq 286f151d9a flake: switch to upstream llvmlite 2022-10-19 13:05:51 +08:00
Sebastien Bourdeauducq 19b8d28a2e flake: update dependencies 2022-10-10 17:58:20 +08:00
Sebastien Bourdeauducq 3ffbc5681e flake: update dependencies, enable misoc tests 2022-10-08 13:31:52 +08:00
Sebastien Bourdeauducq 192cab887f afws_client: update 2022-10-07 11:39:36 +08:00
wlph17 9846ee653c
flake: set Nix Qt environment variables in development shell
allows applets to run standalone via ``python -m ...`` without requiring the Nix Qt wrapper
2022-10-07 11:31:43 +08:00
fanmingyu212 56e6b1428c llvm: change addr2line to symbolizer
`llvm-addr2line` is not included as part of the llvm binary package for Windows. This causes ARTIQ python compilations issues when conda is not used (so the `llvm-tools` conda package is not installed, which provides `llvm-addr2line` currently).
2022-10-04 09:35:56 +08:00
Michael Birtwell b895846322 Improve exception reports when exception can't be reconstructed
Artiq assumes that all exceptions raised by the kernel can be constructed with
a single string argument. This isn't always the case. Especially for
exceptions that originated in python and were propagated to the kernel over
rpc.

With out this change a mosek solver failure looks like:
```
ERROR    root:logging_tools.py:41 Terminating with exception (TypeError: __init__() missing 1 required positional argument: 'msg')
Traceback (most recent call last):
  File "/home/mb/.cache/pypoetry/virtualenvs/ion-transport-1-b41LI0-py3.8/lib/python3.8/site-packages/artiq/master/worker_impl.py", line 540, in main
    exp_inst.run()
  File "/home/mb/.cache/pypoetry/virtualenvs/ion-transport-1-b41LI0-py3.8/lib/python3.8/site-packages/artiq/test_tools/experiment.py", line 82, in wrapper
    meth()
  File "/home/mb/.cache/pypoetry/virtualenvs/ion-transport-1-b41LI0-py3.8/lib/python3.8/site-packages/artiq/language/core.py", line 54, in run_on_core
    return getattr(self, arg).run(run_on_core, ((self,) + k_args), k_kwargs)
  File "/home/mb/.cache/pypoetry/virtualenvs/ion-transport-1-b41LI0-py3.8/lib/python3.8/site-packages/artiq/coredevice/core.py", line 152, in run
    self.comm.serve(embedding_map, symbolizer, demangler)
  File "/home/mb/.cache/pypoetry/virtualenvs/ion-transport-1-b41LI0-py3.8/lib/python3.8/site-packages/artiq/coredevice/comm_kernel.py", line 720, in serve
    self._serve_exception(embedding_map, symbolizer, demangler)
  File "/home/mb/.cache/pypoetry/virtualenvs/ion-transport-1-b41LI0-py3.8/lib/python3.8/site-packages/artiq/coredevice/comm_kernel.py", line 699, in _serve_exception
    python_exn = python_exn_type(
TypeError: __init__() missing 1 required positional argument: 'msg'
```

With this change we get:
```
ERROR    root:logging_tools.py:41 Terminating with exception (RuntimeError: Exception type=<class 'mosek.Error'>, which couldn't be reconstructed (__init__() missing 1 required positional argument: 'msg'))
Core Device Traceback:
Traceback (most recent call first):
  File "/home/mb/oxionics/ion-transport/tests/test_end_to_end.py", line 280, in get_transport
    return self.seq.solve()
  File "/home/mb/oxionics/ion-transport/tests/test_end_to_end.py", line 288, in artiq_worker_test_end_to_end.TransportTestScan.run(..., ...) (RA=+0x2e4)
    self.seq.record(self.get_transport(1e-6 + 1e-7 * x))
mosek.Error(27): rescode.err_license_expired(1001): The license has expired.

End of Core Device Traceback
Traceback (most recent call last):
  File "/home/mb/oxionics/artiq/artiq/master/worker_impl.py", line 540, in main
    exp_inst.run()
  File "/home/mb/oxionics/artiq/artiq/test_tools/experiment.py", line 82, in wrapper
    meth()
  File "/home/mb/oxionics/artiq/artiq/language/core.py", line 54, in run_on_core
    return getattr(self, arg).run(run_on_core, ((self,) + k_args), k_kwargs)
  File "/home/mb/oxionics/artiq/artiq/coredevice/core.py", line 152, in run
    self.comm.serve(embedding_map, symbolizer, demangler)
  File "/home/mb/oxionics/artiq/artiq/coredevice/comm_kernel.py", line 732, in serve
    self._serve_exception(embedding_map, symbolizer, demangler)
  File "/home/mb/oxionics/artiq/artiq/coredevice/comm_kernel.py", line 714, in _serve_exception
    raise python_exn
RuntimeError: Exception type=<class 'mosek.Error'>, which couldn't be reconstructed (__init__() missing 1 required positional argument: 'msg')
```

Signed-off-by: Michael Birtwell <michael.birtwell@oxionics.com>
2022-09-26 20:25:13 +08:00
Robert Jördens a1a4545ed4 docs: fix syntax 2022-09-23 16:22:21 +02:00
Robert Jördens a0053f7a2b add release note 2022-09-23 15:57:43 +02:00
Robert Jördens 740f3d220b refine/fixes 2022-09-23 13:39:49 +00:00
Robert Jördens 513f9f00f3 miqro: document coredevice driver 2022-09-23 12:59:21 +00:00
Robert Jördens 5cfa8d9a42 add tester support, refactor gateware mode 2022-09-23 11:54:40 +00:00
Robert Jördens 0e4a87826c return pulse support 2022-09-20 14:35:06 +00:00
Sebastien Bourdeauducq 1709cf9717 afws_client: update 2022-09-19 16:58:41 +08:00
Sebastien Bourdeauducq 4266beeb9c experimental-features: rename patches to be compatible with AFWS server sanitize() 2022-09-19 16:57:53 +08:00
mwojcik c955ac15ed dashboard moninj: add tooltip for off button 2022-09-19 10:19:54 +08:00
mwojcik 81ef484864 dashboard moninj: check if ad9910 was init 2022-09-19 10:19:54 +08:00
mwojcik f2c3f95040 moninj: fix ad9914 behavior, comment cleanup 2022-09-19 10:19:54 +08:00
mwojcik 616ed3dcc2 moninj: dds inj: extract shared code
detect urukul already init in more than one way
detect ad9912 channel already init
2022-09-19 10:19:54 +08:00
Robert Jördens aedcf205c7 miqro: docs 2022-09-16 12:15:13 +00:00
Robert Jördens 14ab1d4bbc miqro format change: encode len, not end 2022-09-15 11:02:59 +00:00
Sebastien Bourdeauducq a028b5c9f7 afws_client: update 2022-09-15 09:15:38 +08:00
Sebastien Bourdeauducq 6085fe3319 experimental-features: add SU Servo coherent phase tracking mode (PR #1467) 2022-09-13 09:37:26 +08:00
Robert Jördens af28bf3550 simplify dt reset 2022-09-08 08:39:48 +02:00
Robert Jördens 4df880faf6 clean up docs 2022-09-08 08:38:26 +02:00
Robert Jördens 857fb4ecec spelling 2022-09-06 20:44:47 +00:00
Robert Jördens a91836e5fe easier fix for dt 2022-09-06 20:26:50 +00:00
Robert Jördens c5c5c30617 add set_window(), clean up api 2022-09-06 16:05:10 +00:00
Robert Jördens 27e3c044ed fix dt computation 2022-09-06 14:32:57 +00:00
Robert Jördens c26fa5eb90 err out on tune_fifo_offset 2022-09-05 20:48:15 +00:00
Sebastien Bourdeauducq 411afbdc23 experimental-features: add SU Servo extension for variable number of Urukuls (PR #1782) 2022-09-05 11:53:09 +08:00
Sebastien Bourdeauducq b4287ac9f4 flake: add experimental feature support 2022-09-05 11:48:43 +08:00
Robert Jördens 1cc57e2345 fix len 2022-09-04 21:00:24 +00:00
Robert Jördens 263c2751b3 add profile_mu 2022-09-04 20:43:28 +00:00
Robert Jördens 876f26ee30 add some docs 2022-09-04 19:56:52 +00:00
Robert Jördens fa3678f8a3 mem auto increment 2022-09-04 12:03:44 +00:00
Robert Jördens f4d325112c reset and elaborate, si functions 2022-09-04 11:19:38 +00:00
Robert Jördens b6586cd7e4 add window data delay 2022-09-02 20:45:13 +00:00
Robert Jördens 3809ac5470 fix type, clean clear 2022-09-02 19:47:06 +00:00
Robert Jördens b9727fdfce refactor for 32 bit mem access 2022-09-02 16:38:53 +00:00
Robert Jördens d6d0c2c866 miqro: name register constants 2022-09-02 15:55:28 +00:00
Robert Jördens 0df2cadcd3 fixes 2022-09-02 15:29:36 +00:00
Robert Jördens 25c0dc4688 whitespace 2022-09-02 14:54:18 +00:00
Robert Jördens cf48232a90 fixes 2022-09-02 14:38:38 +00:00
Robert Jördens a20087848d differentiate phaser modes 2022-09-02 11:03:23 +00:00
Robert Jördens 31663556b8 phaser: add miqro mode 2022-09-02 09:32:06 +00:00
Robert Jördens 47f90a58cc add miqro phy 2022-09-02 09:32:06 +00:00
Mikołaj Sowiński 3c7ab498d1 Added DDS selection for Kasli tester variant
Signed-off-by: Mikołaj Sowiński <msowinski@technosystem.com.pl>
2022-09-02 17:14:23 +08:00
Deepskyhunter 7c306d5609
GUI log: Apply level and text filter to existing log messages (#1950) 2022-08-29 15:20:44 +08:00
mwojcik b705862ecd afws_client: fix argument order 2022-08-25 13:17:41 +08:00
fanmingyu212 20cb99061e doc: updates artiq_flash syntax in developing.rst 2022-08-25 07:03:26 +08:00
Sebastien Bourdeauducq 5ef94d30dd versioneer: fix default 2022-08-18 14:35:58 +08:00
kk1050 3c72b8d646
dashboard: use break_realtime instead of reset for Urukul set freq (#1940) 2022-08-16 14:02:01 +08:00
Sebastien Bourdeauducq 27397625ba dashboard: improve moninj logging 2022-08-12 13:41:05 +08:00
cc78078 3535d0f1ae
kasli: relocate the SatelliteBase Error LED code (#1955) 2022-08-12 12:41:50 +08:00
cc78078 185c91f522
kasli: add Error LED to MasterBase and SatelliteBase 2022-08-11 15:06:58 +08:00
Deepskyhunter f31279411e
dashboard/moninj: make arguments a dict for DDS setters 2022-08-02 17:09:56 +08:00
Alex Wong Tat Hang a3ae82502c
gtx_7series: fix IBUFGS_GTE2 buffer parameters
Co-authored-by: topquark12 <aw@m-labs.hk>
2022-08-01 10:21:28 +08:00
Deepskyhunter 0cdb06fdf5
language/environment: support no argument manager
unbreak tests
2022-07-28 17:55:25 +08:00
Deepskyhunter 2a7a72b27a
language.environment: error out if unknown arguments are passed (#1927)
Closes #1641
2022-07-26 10:42:03 +08:00
kk1050 748e28be38
artiq_flash: bail out if scan chain is wrong
Due to OpenOCD limitations, there currently doesn't seem to be a better way of doing it. Upstream patch may be coming.
2022-07-26 09:49:48 +08:00
Sebastien Bourdeauducq 4b1715c80b typo 2022-07-21 11:58:25 +08:00
Robert Jördens 5985595845
Merge pull request #1933 from quartiq/nk/phaser-servo
Nk/phaser servo
2022-07-11 14:36:25 +02:00
Robert Jördens a8f498b478
Merge branch 'master' into nk/phaser-servo 2022-07-11 14:35:25 +02:00
Sebastien Bourdeauducq db4bccda7e flake: bump major version 2022-07-08 18:49:40 +08:00
Robert Jördens 4ea11f4609 RELEASE_NOTES: update servo note 2022-07-07 16:03:35 +02:00
SingularitySurfer 57ac6ec003 add release note 2022-07-07 15:57:08 +02:00
Robert Jördens d2dacc6433 Merge branch 'master' into nk/phaser-servo-clean
* master: (25 commits)
  flake: update rpi-1 host key
  aqctl_moninj_proxy: clear listeners on disconnect
  Add method to check if termination is requested (#811, #1932)
  moninj: fix underflows by order of operation fix channel toggle
  moninj: fix underflows for urukul freq set
  Urukul monitoring (#1142, #1921)
  moninj: make receive_task private again
  moninj,corelog: fix/cleanup exception handling (#1897)
  aqctl_corelog: enable keepalive, terminate on connection failure
  Modify log for matching the style
  Add log message when dashboard connected to proxy
  Public receive_task for the use in proxy
  applets.simple: Actually forward dataset_prefixes when using IPC
  master: Fixup 32db6ff978 (argument_ui support)
  Revert "add pull.yml (#1918)"
  add pull.yml (#1918)
  Allow experiments to specify a custom argument editor UI (#1916)
  dashboard: Add submit/close hooks for custom argument editors
  dashboard: Plumb through datasets client to ExperimentManager
  dashboard: Add cmdline option to load plugins on startup
  ...
2022-07-07 15:56:30 +02:00
SingularitySurfer 9c8ffa54b2 reverse to servo enable. hopefully adapted all comments etc. 2022-07-06 14:33:46 +00:00
SingularitySurfer 953dd899fd refine docu 2022-06-23 15:46:15 +00:00
SingularitySurfer 689a2ef8ba refine note 2022-06-23 15:23:00 +00:00
SingularitySurfer d8cfe22501 add note about setpoint resolution 2022-06-23 15:18:55 +00:00
SingularitySurfer 2e834cf406 unflip logic.. 2022-06-23 10:20:38 +00:00
SingularitySurfer 3f8a221c76 flip logic of enable bit to bypass bit and update some comments 2022-06-23 10:08:34 +00:00
SingularitySurfer ab097b8ef9 add offset to coefficients as data 2022-06-23 09:37:37 +00:00
SingularitySurfer 24b4ec46bd more documentation 2022-06-23 08:48:28 +00:00
Norman Krackow 56c59e38f0
Update artiq/coredevice/phaser.py
Co-authored-by: Robert Jördens <rj@quartiq.de>
2022-06-23 09:15:50 +02:00
SingularitySurfer c0581178d6 impl offsets. to be tested 2022-06-22 16:20:59 +00:00
SingularitySurfer 43c94577ce impl set_iir. untested 2022-06-22 15:35:49 +00:00
SingularitySurfer ce4055db3b force hold on bypass and use names in set_servo() in init 2022-06-21 10:11:49 +00:00
SingularitySurfer b67a70392d rename to coeff base and shorter write16 2022-06-21 09:59:40 +00:00
SingularitySurfer 57176fedb2 add servo docu 2022-06-21 09:29:42 +00:00
SingularitySurfer 8bea821f93 just &1 to stay in field 2022-06-21 08:43:55 +00:00
SingularitySurfer 0388161754 disable servo in init 2022-06-21 07:49:29 +00:00
SingularitySurfer 751af3144e fix old line that I forgot 2022-06-21 07:43:28 +00:00
SingularitySurfer 5df766e6da fix ors 2022-06-21 07:36:59 +00:00
SingularitySurfer d09153411f adress some review comments 2022-06-17 13:03:21 +00:00
Norman Krackow dc49372d57
Update artiq/coredevice/phaser.py
Co-authored-by: Robert Jördens <rj@quartiq.de>
2022-06-17 14:40:07 +02:00
Norman Krackow 2044dc3ae5
Update artiq/coredevice/phaser.py
Co-authored-by: Robert Jördens <rj@quartiq.de>
2022-06-17 14:39:37 +02:00
SingularitySurfer ae3f1c1c71 adapt servo functions. Todo: docu 2022-06-17 11:47:45 +00:00
SingularitySurfer 1bddadc6e2 cleanup and comments 2022-06-15 17:32:11 +00:00
SingularitySurfer b0f9fd9c4c implement main driver functions 2022-06-15 12:40:21 +00:00
382 changed files with 28433 additions and 20223 deletions

View File

@ -51,7 +51,7 @@ Closes #XXX
### 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

7
.gitignore vendored
View File

@ -11,6 +11,7 @@ __pycache__/
.ipynb_checkpoints
/doc/manual/_build
/build
/result
/dist
/*.egg-info
/.coverage
@ -23,12 +24,14 @@ __pycache__/
/artiq/test/results
/artiq/examples/*/results
/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:
/repository/
/results
/last_rid.pyon
/dataset_db.pyon
/dataset_db.mdb
/dataset_db.mdb-lock
/device_db*.py
/test*

View File

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

View File

@ -5,31 +5,27 @@
: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.
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 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 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 and its dependencies are available in the form of Nix packages (for Linux) and Conda packages (for Windows and Linux). 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>`_.
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>`_.
ARTIQ is supported by M-Labs and developed openly.
Components, features, fixes, improvements, and extensions are often `funded <https://m-labs.hk/experiment-control/funding/>`_ by and developed for the partnering research groups.
ARTIQ is supported by M-Labs and developed openly. Components, features, fixes, improvements, and extensions are often `funded <https://m-labs.hk/experiment-control/funding/>`_ by and developed for the partnering research groups.
Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt5 <https://www.qt.io/>`_.
Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt6 <https://www.qt.io/>`_.
Website: https://m-labs.hk/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``.
License
=======
Copyright (C) 2014-2022 M-Labs Limited.
Copyright (C) 2014-2024 M-Labs Limited.
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

View File

@ -3,6 +3,126 @@
Release notes
=============
ARTIQ-9 (Unreleased)
--------------------
* GUI state files are now automatically backed up upon successful loading.
* Zotino monitoring in the dashboard now displays the values in volts.
* 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:
* New hardware support:
- Support for Shuttler, a 16-channel 125MSPS DAC card intended for ion transport.
Waveform generator and user API are similar to the NIST PDQ.
- Implemented Phaser-servo. This requires recent gateware on Phaser.
- Almazny v1.2 with finer RF switch control.
- Metlino and Sayma support has been dropped due to complications with synchronous RTIO clocking.
- More user LEDs are exposed to RTIO on Kasli.
- Implemented Phaser-MIQRO support. This requires the proprietary Phaser MIQRO gateware
variant from QUARTIQ.
- Sampler: fixed ADC MU to Volt conversion factor for Sampler v2.2+.
For earlier hardware versions, specify the hardware version in the device
database file (e.g. ``"hw_rev": "v2.1"``) to use the correct conversion factor.
* Support for distributed DMA, where DMA is run directly on satellites for corresponding
RTIO events, increasing bandwidth in scenarios with heavy satellite usage.
* Support for subkernels, where kernels are run on satellite device CPUs to offload some
of the processing and RTIO operations.
* CPU (on softcore platforms) and AXI bus (on Zynq) are now clocked synchronously with the RTIO
clock, to facilitate implementation of local processing on DRTIO satellites, and to slightly
reduce RTIO latency.
* 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.
* The RTIO analyzer is now proxied by ``aqctl_coreanalyzer_proxy`` typically running on the master
machine, similarly to ``aqctl_moninj_proxy``.
* 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
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.
- 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
carefully arranged default workspace.
- Hotkeys now organize experiment windows in the order they were last interacted with:
+ CTRL+SHIFT+T tiles 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.
* Python's built-in types (such as ``float``, or ``List[...]``) can now be used in type annotations on
kernel functions.
* MSYS2 packaging for Windows, which replaces Conda. Conda packages are still available to
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:
* ``SimpleApplet`` now calls widget constructors with an additional ``ctl`` parameter for control
operations, which includes dataset operations. It can be ignored if not needed. For an example usage,
refer to the ``big_number.py`` applet.
* ``SimpleApplet`` and ``TitleApplet`` now call ``data_changed`` with additional parameters. Derived applets
should change the function signature as below:
::
# SimpleApplet
def data_changed(self, value, metadata, persist, mods)
# SimpleApplet (old version)
def data_changed(self, data, mods)
# TitleApplet
def data_changed(self, value, metadata, persist, mods, title)
# TitleApplet (old version)
def data_changed(self, data, mods, title)
Accesses to the data argument should be replaced as below:
::
data[key][0] ==> persist[key]
data[key][1] ==> value[key]
* The ``ndecimals`` parameter in ``NumberValue`` and ``Scannable`` has been renamed to ``precision``.
Parameters after and including ``scale`` in both constructors are now keyword-only.
Refer to the updated ``no_hardware/arguments_demo.py`` example for current usage.
* Almazny v1.2 is incompatible with the legacy versions and is the default.
To use legacy versions, specify ``almazny_hw_rev`` in the JSON description.
* kasli_generic.py has been merged into kasli.py, and the demonstration designs without JSON descriptions
have been removed. The base classes remain present in kasli.py to support third-party flows without
JSON descriptions.
* Legacy PYON databases should be converted to LMDB with the script below:
::
from sipyco import pyon
import lmdb
old = pyon.load_file("dataset_db.pyon")
new = lmdb.open("dataset_db.mdb", subdir=False, map_size=2**30)
with new.begin(write=True) as txn:
for key, value in old.items():
txn.put(key.encode(), pyon.encode((value, {})).encode())
new.close()
* ``artiq.wavesynth`` has been removed.
ARTIQ-7
-------
@ -17,7 +137,7 @@ Highlights:
- Almazny mezzanine board for Mirny
- Phaser: improved documentation, exposed the DAC coarse mixer and ``sif_sync``, exposed upconverter calibration
and enabling/disabling of upconverter LO & RF outputs, added helpers to align Phaser updates to the
RTIO timeline (``get_next_frame_mu()``
RTIO timeline (``get_next_frame_mu()``).
- Urukul: ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912.
* Softcore targets now use the RISC-V architecture (VexRiscv) instead of OR1K (mor1kx).
* Gateware FPU is supported on KC705 and Kasli 2.0.
@ -67,9 +187,9 @@ Breaking changes:
generated for some configurations.
* Phaser: fixed coarse mixer frequency configuration
* Mirny: Added extra delays in ``ADF5356.sync()``. This avoids the need of an extra delay before
calling `ADF5356.init()`.
calling ``ADF5356.init()``.
* The deprecated ``set_dataset(..., save=...)`` is no longer supported.
* The ``PCA9548`` I2C switch class was renamed to ``I2CSwitch``, to accomodate support for PCA9547,
* The ``PCA9548`` I2C switch class was renamed to ``I2CSwitch``, to accommodate support for PCA9547,
and possibly other switches in future. Readback has been removed, and now only one channel per
switch is supported.

View File

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

View File

@ -1,23 +0,0 @@
-----BEGIN CERTIFICATE-----
MIID0zCCArugAwIBAgIUPkNfEUx/uau3z8SD4mgMbCK/DEgwDQYJKoZIhvcNAQEL
BQAweTELMAkGA1UEBhMCSEsxEzARBgNVBAgMClNvbWUtU3RhdGUxFzAVBgNVBAoM
Dk0tTGFicyBMaW1pdGVkMRkwFwYDVQQDDBBuaXhibGQubS1sYWJzLmhrMSEwHwYJ
KoZIhvcNAQkBFhJoZWxwZGVza0BtLWxhYnMuaGswHhcNMjIwMjA2MTA1ODQ0WhcN
MjUwMjA1MTA1ODQ0WjB5MQswCQYDVQQGEwJISzETMBEGA1UECAwKU29tZS1TdGF0
ZTEXMBUGA1UECgwOTS1MYWJzIExpbWl0ZWQxGTAXBgNVBAMMEG5peGJsZC5tLWxh
YnMuaGsxITAfBgkqhkiG9w0BCQEWEmhlbHBkZXNrQG0tbGFicy5oazCCASIwDQYJ
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAPWetZhoggPR2ae7waGzv1AQ8NQO3noW
8DofVjusNpX5i/YB0waAr1bm1tALLJoHV2r/gTxujlXCe/L/WG1DLseCf6NO9sHg
t0FLhDpF9kPMWBgauVVLepd2Y2yU1G8eFuEVGnsiQSu0IzsZP5FQBJSyxvxJ+V/L
EW9ox91VGOP9VZR9jqdlYjGhcwClHA/nHe0q1fZq42+9rG466I5yIlNSoa7ilhTU
2C2doxy6Sr6VJYnLEMQqoIF65t3MkKi9iaqN7az0OCrj6XR0P5iKBzUhIgMUd2qs
7Id0XUdbQvaoaRI67vhGkNr+f4rdAUNCDGcbbokuBnmE7/gva6BAABUCAwEAAaNT
MFEwHQYDVR0OBBYEFM2e2FmcytXhKyfC1KEjVJ2mPSy3MB8GA1UdIwQYMBaAFM2e
2FmcytXhKyfC1KEjVJ2mPSy3MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
BQADggEBAKH0z5vlbfTghjYWwd2yEEFBbZx5XxaLHboFQpFpxu9sZoidVs047tco
MOr1py9juiNGGM8G35sw9306f+thDFwqlQfSExUwp5pRQNq+mxglMSF05HWDqBwb
wnItKi/WXpkMQXgpQJFVeflz4B4ZFNlH1UQl5bwacXOM9NM9zO7duCjVXmGE0yxi
VQyApfPQYu9whCSowDYYaA0toJeikMzGfWxhlAH79/2Qmit8KcSCbX1fK/QoRZLa
5NeUi/OlJbBpkgTrfzfMLphmsPWPAVMeUKzqd/vXfG6ZBOZZm6e6sl8RBycBezII
15WekikTE5+T54/E0xiu+zIW/Xhhk14=
-----END CERTIFICATE-----

View File

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

View File

@ -1,22 +1,96 @@
#!/usr/bin/env python3
from PyQt5 import QtWidgets
from PyQt6 import QtWidgets, QtCore, QtGui
from artiq.applets.simple import SimpleApplet
from artiq.tools import scale_from_metadata
from artiq.gui.tools import LayoutWidget
class NumberWidget(QtWidgets.QLCDNumber):
def __init__(self, args):
QtWidgets.QLCDNumber.__init__(self)
self.setDigitCount(args.digit_count)
class QResponsiveLCDNumber(QtWidgets.QLCDNumber):
doubleClicked = QtCore.pyqtSignal()
def mouseDoubleClickEvent(self, event):
self.doubleClicked.emit()
class QCancellableLineEdit(QtWidgets.QLineEdit):
editCancelled = QtCore.pyqtSignal()
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key.Key_Escape:
self.editCancelled.emit()
else:
super().keyPressEvent(event)
class NumberWidget(LayoutWidget):
def __init__(self, args, req):
LayoutWidget.__init__(self)
self.dataset_name = args.dataset
self.req = req
self.metadata = dict()
def data_changed(self, data, mods):
self.number_area = QtWidgets.QStackedWidget()
self.addWidget(self.number_area, 0, 0)
self.unit_area = QtWidgets.QLabel()
self.unit_area.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignTop)
self.addWidget(self.unit_area, 0, 1)
self.lcd_widget = QResponsiveLCDNumber()
self.lcd_widget.setDigitCount(args.digit_count)
self.lcd_widget.doubleClicked.connect(self.start_edit)
self.number_area.addWidget(self.lcd_widget)
self.edit_widget = QCancellableLineEdit()
self.edit_widget.setValidator(QtGui.QDoubleValidator())
self.edit_widget.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
self.edit_widget.editCancelled.connect(self.cancel_edit)
self.edit_widget.returnPressed.connect(self.confirm_edit)
self.number_area.addWidget(self.edit_widget)
font = QtGui.QFont()
font.setPointSize(60)
self.edit_widget.setFont(font)
unit_font = QtGui.QFont()
unit_font.setPointSize(20)
self.unit_area.setFont(unit_font)
self.number_area.setCurrentWidget(self.lcd_widget)
def start_edit(self):
# QLCDNumber value property contains the value of zero
# if the displayed value is not a number.
self.edit_widget.setText(str(self.lcd_widget.value()))
self.edit_widget.selectAll()
self.edit_widget.setFocus()
self.number_area.setCurrentWidget(self.edit_widget)
def confirm_edit(self):
scale = scale_from_metadata(self.metadata)
val = float(self.edit_widget.text())
val *= scale
self.req.set_dataset(self.dataset_name, val, **self.metadata)
self.number_area.setCurrentWidget(self.lcd_widget)
def cancel_edit(self):
self.number_area.setCurrentWidget(self.lcd_widget)
def data_changed(self, value, metadata, persist, mods):
try:
n = float(data[self.dataset_name][1])
self.metadata = metadata[self.dataset_name]
# This applet will degenerate other scalar types to native float on edit
# Use the dashboard ChangeEditDialog for consistent type casting
val = float(value[self.dataset_name])
scale = scale_from_metadata(self.metadata)
val /= scale
except (KeyError, ValueError, TypeError):
n = "---"
self.display(n)
val = "---"
unit = self.metadata.get("unit", "")
self.unit_area.setText(unit)
self.lcd_widget.display(val)
def main():

View File

@ -1,19 +1,19 @@
#!/usr/bin/env python3
import PyQt5 # make sure pyqtgraph imports Qt5
import PyQt6 # make sure pyqtgraph imports Qt6
import pyqtgraph
from artiq.applets.simple import SimpleApplet
class Image(pyqtgraph.ImageView):
def __init__(self, args):
def __init__(self, args, req):
pyqtgraph.ImageView.__init__(self)
self.args = args
def data_changed(self, data, mods):
def data_changed(self, value, metadata, persist, mods):
try:
img = data[self.args.img][1]
img = value[self.args.img]
except KeyError:
return
self.setImage(img)

View File

@ -1,27 +1,27 @@
#!/usr/bin/env python3
import PyQt5 # make sure pyqtgraph imports Qt5
from PyQt5.QtCore import QTimer
import PyQt6 # make sure pyqtgraph imports Qt6
from PyQt6.QtCore import QTimer
import pyqtgraph
from artiq.applets.simple import TitleApplet
class HistogramPlot(pyqtgraph.PlotWidget):
def __init__(self, args):
def __init__(self, args, req):
pyqtgraph.PlotWidget.__init__(self)
self.args = args
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.length_warning)
def data_changed(self, data, mods, title):
def data_changed(self, value, metadata, persist, mods, title):
try:
y = data[self.args.y][1]
y = value[self.args.y]
if self.args.x is None:
x = None
else:
x = data[self.args.x][1]
x = value[self.args.x]
except KeyError:
return
if x is None:

View File

@ -1,15 +1,15 @@
#!/usr/bin/env python3
import numpy as np
import PyQt5 # make sure pyqtgraph imports Qt5
from PyQt5.QtCore import QTimer
import PyQt6 # make sure pyqtgraph imports Qt6
from PyQt6.QtCore import QTimer
import pyqtgraph
from artiq.applets.simple import TitleApplet
class XYPlot(pyqtgraph.PlotWidget):
def __init__(self, args):
def __init__(self, args, req):
pyqtgraph.PlotWidget.__init__(self)
self.args = args
self.timer = QTimer()
@ -19,16 +19,16 @@ class XYPlot(pyqtgraph.PlotWidget):
'Error bars': False,
'Fit values': False}
def data_changed(self, data, mods, title):
def data_changed(self, value, metadata, persist, mods, title):
try:
y = data[self.args.y][1]
y = value[self.args.y]
except KeyError:
return
x = data.get(self.args.x, (False, None))[1]
x = value.get(self.args.x)
if x is None:
x = np.arange(len(y))
error = data.get(self.args.error, (False, None))[1]
fit = data.get(self.args.fit, (False, None))[1]
error = value.get(self.args.error)
fit = value.get(self.args.fit)
if not len(y) or len(y) != len(x):
self.mismatch['X values'] = True

View File

@ -1,8 +1,8 @@
#!/usr/bin/env python3
import numpy as np
from PyQt5 import QtWidgets
from PyQt5.QtCore import QTimer
from PyQt6 import QtWidgets
from PyQt6.QtCore import QTimer
import pyqtgraph
from artiq.applets.simple import SimpleApplet
@ -22,7 +22,7 @@ def _compute_ys(histogram_bins, histograms_counts):
# pyqtgraph.GraphicsWindow fails to behave like a regular Qt widget
# and breaks embedding. Do not use as top widget.
class XYHistPlot(QtWidgets.QSplitter):
def __init__(self, args):
def __init__(self, args, req):
QtWidgets.QSplitter.__init__(self)
self.resize(1000, 600)
self.setWindowTitle("XY/Histogram")
@ -124,11 +124,11 @@ class XYHistPlot(QtWidgets.QSplitter):
return False
return True
def data_changed(self, data, mods):
def data_changed(self, value, metadata, persist, mods):
try:
xs = data[self.args.xs][1]
histogram_bins = data[self.args.histogram_bins][1]
histograms_counts = data[self.args.histograms_counts][1]
xs = value[self.args.xs]
histogram_bins = value[self.args.histogram_bins]
histograms_counts = value[self.args.histograms_counts]
except KeyError:
return
if len(xs) != histograms_counts.shape[0]:

View File

@ -1,23 +1,23 @@
#!/usr/bin/env python3
from PyQt5 import QtWidgets
from PyQt6 import QtWidgets
from artiq.applets.simple import SimpleApplet
class ProgressWidget(QtWidgets.QProgressBar):
def __init__(self, args):
def __init__(self, args, req):
QtWidgets.QProgressBar.__init__(self)
self.setMinimum(args.min)
self.setMaximum(args.max)
self.dataset_value = args.value
def data_changed(self, data, mods):
def data_changed(self, value, metadata, persist, mods):
try:
value = round(data[self.dataset_value][1])
val = round(value[self.dataset_value])
except (KeyError, ValueError, TypeError):
value = 0
self.setValue(value)
val = 0
self.setValue(val)

View File

@ -7,13 +7,113 @@ import string
from qasync import QEventLoop, QtWidgets, QtCore
from sipyco.sync_struct import Subscriber, process_mod
from sipyco.pc_rpc import AsyncioClient as RPCClient
from sipyco import pyon
from sipyco.pipe_ipc import AsyncioChildComm
from artiq.language.scan import ScanObject
logger = logging.getLogger(__name__)
class _AppletRequestInterface:
def __init__(self):
raise NotImplementedError
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
"""
Set a dataset.
See documentation of :meth:`~artiq.language.environment.HasEnvironment.set_dataset`.
"""
raise NotImplementedError
def mutate_dataset(self, key, index, value):
"""
Mutate a dataset.
See documentation of :meth:`~artiq.language.environment.HasEnvironment.mutate_dataset`.
"""
raise NotImplementedError
def append_to_dataset(self, key, value):
"""
Append to a dataset.
See documentation of :meth:`~artiq.language.environment.HasEnvironment.append_to_dataset`.
"""
raise NotImplementedError
def set_argument_value(self, expurl, key, value):
"""
Temporarily set the value of an argument in a experiment in the dashboard.
The value resets to default value when recomputing the argument.
:param expurl: Experiment URL identifying the experiment in the dashboard. Example: 'repo:ArgumentsDemo'.
:param key: Name of the argument in the experiment.
:param value: Object representing the new temporary value of the argument. For :class:`~artiq.language.scan.Scannable` arguments,
this parameter should be a :class:`~artiq.language.scan.ScanObject`. The type of the :class:`~artiq.language.scan.ScanObject`
will be set as the selected type when this function is called.
"""
raise NotImplementedError
class AppletRequestIPC(_AppletRequestInterface):
def __init__(self, ipc):
self.ipc = ipc
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
metadata = {}
if unit is not None:
metadata["unit"] = unit
if scale is not None:
metadata["scale"] = scale
if precision is not None:
metadata["precision"] = precision
self.ipc.set_dataset(key, value, metadata, persist)
def mutate_dataset(self, key, index, value):
mod = {"action": "setitem", "path": [key, 1], "key": index, "value": value}
self.ipc.update_dataset(mod)
def append_to_dataset(self, key, value):
mod = {"action": "append", "path": [key, 1], "x": value}
self.ipc.update_dataset(mod)
def set_argument_value(self, expurl, key, value):
if isinstance(value, ScanObject):
value = value.describe()
self.ipc.set_argument_value(expurl, key, value)
class AppletRequestRPC(_AppletRequestInterface):
def __init__(self, loop, dataset_ctl):
self.loop = loop
self.dataset_ctl = dataset_ctl
self.background_tasks = set()
def _background(self, coro, *args, **kwargs):
task = self.loop.create_task(coro(*args, **kwargs))
self.background_tasks.add(task)
task.add_done_callback(self.background_tasks.discard)
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
metadata = {}
if unit is not None:
metadata["unit"] = unit
if scale is not None:
metadata["scale"] = scale
if precision is not None:
metadata["precision"] = precision
self._background(self.dataset_ctl.set, key, value, metadata=metadata, persist=persist)
def mutate_dataset(self, key, index, value):
mod = {"action": "setitem", "path": [key, 1], "key": index, "value": value}
self._background(self.dataset_ctl.update, mod)
def append_to_dataset(self, key, value):
mod = {"action": "append", "path": [key, 1], "x": value}
self._background(self.dataset_ctl.update, mod)
class AppletIPCClient(AsyncioChildComm):
def set_close_cb(self, close_cb):
self.close_cb = close_cb
@ -37,9 +137,8 @@ class AppletIPCClient(AsyncioChildComm):
logger.error("unexpected action reply to embed request: %s",
reply["action"])
self.close_cb()
def fix_initial_size(self):
self.write_pyon({"action": "fix_initial_size"})
else:
return reply["size_w"], reply["size_h"]
async def listen(self):
data = None
@ -64,13 +163,30 @@ class AppletIPCClient(AsyncioChildComm):
exc_info=True)
self.close_cb()
def subscribe(self, datasets, init_cb, mod_cb, dataset_prefixes=[]):
def subscribe(self, datasets, init_cb, mod_cb, dataset_prefixes=[], *, loop):
self.write_pyon({"action": "subscribe",
"datasets": datasets,
"dataset_prefixes": dataset_prefixes})
self.init_cb = init_cb
self.mod_cb = mod_cb
asyncio.ensure_future(self.listen())
self.listen_task = loop.create_task(self.listen())
def set_dataset(self, key, value, metadata, persist=None):
self.write_pyon({"action": "set_dataset",
"key": key,
"value": value,
"metadata": metadata,
"persist": persist})
def update_dataset(self, mod):
self.write_pyon({"action": "update_dataset",
"mod": mod})
def set_argument_value(self, expurl, key, value):
self.write_pyon({"action": "set_argument_value",
"expurl": expurl,
"key": key,
"value": value})
class SimpleApplet:
@ -92,8 +208,11 @@ class SimpleApplet:
"for dataset notifications "
"(ignored in embedded mode)")
group.add_argument(
"--port", default=3250, type=int,
help="TCP port to connect to")
"--port-notify", default=3250, type=int,
help="TCP port to connect to for notifications (ignored in embedded mode)")
group.add_argument(
"--port-control", default=3251, type=int,
help="TCP port to connect to for control (ignored in embedded mode)")
self._arggroup_datasets = self.argparser.add_argument_group("datasets")
@ -132,15 +251,28 @@ class SimpleApplet:
if self.embed is not None:
self.ipc.close()
def req_init(self):
if self.embed is None:
dataset_ctl = RPCClient()
self.loop.run_until_complete(dataset_ctl.connect_rpc(
self.args.server, self.args.port_control, "dataset_db"))
self.req = AppletRequestRPC(self.loop, dataset_ctl)
else:
self.req = AppletRequestIPC(self.ipc)
def req_close(self):
if self.embed is None:
self.req.dataset_ctl.close_rpc()
def create_main_widget(self):
self.main_widget = self.main_widget_class(self.args)
self.main_widget = self.main_widget_class(self.args, self.req)
if self.embed is not None:
self.ipc.set_close_cb(self.main_widget.close)
if os.name == "nt":
# HACK: if the window has a frame, there will be garbage
# (usually white) displayed at its right and bottom borders
# after it is embedded.
self.main_widget.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.main_widget.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint)
self.main_widget.show()
win_id = int(self.main_widget.winId())
self.loop.run_until_complete(self.ipc.embed(win_id))
@ -153,12 +285,13 @@ class SimpleApplet:
# 2. applet creates native window without showing it, and
# gets its ID
# 3. applet sends the ID to host, host embeds the widget
# 4. applet shows the widget
# 5. parent resizes the widget
# and returns embedded size
# 4. applet is resized to that given size
# 5. applet shows the widget
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.ipc.fix_initial_size()
else:
self.main_widget.show()
@ -189,7 +322,12 @@ class SimpleApplet:
return False
def emit_data_changed(self, data, mod_buffer):
self.main_widget.data_changed(data, mod_buffer)
persist = dict()
value = dict()
metadata = dict()
for k, d in data.items():
persist[k], value[k], metadata[k] = d
self.main_widget.data_changed(value, metadata, persist, mod_buffer)
def flush_mod_buffer(self):
self.emit_data_changed(self.data, self.mod_buffer)
@ -204,7 +342,7 @@ class SimpleApplet:
self.mod_buffer.append(mod)
else:
self.mod_buffer = [mod]
asyncio.get_event_loop().call_later(self.args.update_delay,
self.loop.call_later(self.args.update_delay,
self.flush_mod_buffer)
else:
self.emit_data_changed(self.data, [mod])
@ -214,10 +352,11 @@ class SimpleApplet:
self.subscriber = Subscriber("datasets",
self.sub_init, self.sub_mod)
self.loop.run_until_complete(self.subscriber.connect(
self.args.server, self.args.port))
self.args.server, self.args.port_notify))
else:
self.ipc.subscribe(self.datasets, self.sub_init, self.sub_mod,
dataset_prefixes=self.dataset_prefixes)
dataset_prefixes=self.dataset_prefixes,
loop=self.loop)
def unsubscribe(self):
if self.embed is None:
@ -228,6 +367,8 @@ class SimpleApplet:
self.qasync_init()
try:
self.ipc_init()
try:
self.req_init()
try:
self.create_main_widget()
self.subscribe()
@ -235,6 +376,8 @@ class SimpleApplet:
self.loop.run_forever()
finally:
self.unsubscribe()
finally:
self.req_close()
finally:
self.ipc_close()
finally:
@ -273,4 +416,9 @@ class TitleApplet(SimpleApplet):
title = self.args.title
else:
title = None
self.main_widget.data_changed(data, mod_buffer, title)
persist = dict()
value = dict()
metadata = dict()
for k, d in data.items():
persist[k], value[k], metadata[k] = d
self.main_widget.data_changed(value, metadata, persist, mod_buffer, title)

View File

@ -1,12 +1,12 @@
import logging
import asyncio
from PyQt5 import QtCore, QtWidgets
from PyQt6 import QtCore, QtGui, QtWidgets
from sipyco.pc_rpc import AsyncioClient as RPCClient
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
# reduced read-only version of artiq.dashboard.datasets
@ -20,15 +20,50 @@ class Model(DictSyncTreeSepModel):
DictSyncTreeSepModel.__init__(self, ".", ["Dataset", "Value"], init)
def convert(self, k, v, column):
return short_format(v[1])
return short_format(v[1], v[2])
class DatasetCtl:
def __init__(self, master_host, master_port):
self.master_host = master_host
self.master_port = master_port
async def _execute_rpc(self, op_name, key_or_mod, value=None, persist=None, metadata=None):
logger.info("Starting %s operation on %s", op_name, key_or_mod)
try:
remote = RPCClient()
await remote.connect_rpc(self.master_host, self.master_port,
"dataset_db")
try:
if op_name == "set":
await remote.set(key_or_mod, value, persist, metadata)
elif op_name == "update":
await remote.update(key_or_mod)
else:
logger.error("Invalid operation: %s", op_name)
return
finally:
remote.close_rpc()
except:
logger.error("Failed %s operation on %s", op_name,
key_or_mod, exc_info=True)
else:
logger.info("Finished %s operation on %s", op_name,
key_or_mod)
async def set(self, key, value, persist=None, metadata=None):
await self._execute_rpc("set", key, value, persist, metadata)
async def update(self, mod):
await self._execute_rpc("update", mod)
class DatasetsDock(QtWidgets.QDockWidget):
def __init__(self, datasets_sub, master_host, master_port):
def __init__(self, dataset_sub, dataset_ctl):
QtWidgets.QDockWidget.__init__(self, "Datasets")
self.setObjectName("Datasets")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable)
self.setFeatures(self.DockWidgetFeature.DockWidgetMovable |
self.DockWidgetFeature.DockWidgetFloatable)
grid = LayoutWidget()
self.setWidget(grid)
@ -39,9 +74,9 @@ class DatasetsDock(QtWidgets.QDockWidget):
grid.addWidget(self.search, 0, 0)
self.table = QtWidgets.QTreeView()
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self.table.setSelectionMode(
QtWidgets.QAbstractItemView.SingleSelection)
QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
grid.addWidget(self.table, 1, 0)
metadata_grid = LayoutWidget()
@ -50,22 +85,21 @@ class DatasetsDock(QtWidgets.QDockWidget):
"rid start_time".split()):
metadata_grid.addWidget(QtWidgets.QLabel(label), i, 0)
v = QtWidgets.QLabel()
v.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
v.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse)
metadata_grid.addWidget(v, i, 1)
self.metadata[label] = v
grid.addWidget(metadata_grid, 2, 0)
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
upload_action = QtWidgets.QAction("Upload dataset to master",
self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
upload_action = QtGui.QAction("Upload dataset to master",
self.table)
upload_action.triggered.connect(self.upload_clicked)
self.table.addAction(upload_action)
self.set_model(Model(dict()))
datasets_sub.add_setmodel_callback(self.set_model)
dataset_sub.add_setmodel_callback(self.set_model)
self.master_host = master_host
self.master_port = master_port
self.dataset_ctl = dataset_ctl
def _search_datasets(self):
if hasattr(self, "table_model_filter"):
@ -78,34 +112,19 @@ class DatasetsDock(QtWidgets.QDockWidget):
def set_model(self, 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.setModel(self.table_model_filter)
async def _upload_dataset(self, name, value,):
logger.info("Uploading dataset '%s' to master...", name)
try:
remote = RPCClient()
await remote.connect_rpc(self.master_host, self.master_port,
"master_dataset_db")
try:
await remote.set(name, value)
finally:
remote.close_rpc()
except:
logger.error("Failed uploading dataset '%s'",
name, exc_info=True)
else:
logger.info("Finished uploading dataset '%s'", name)
def upload_clicked(self):
idx = self.table.selectedIndexes()
if idx:
idx = self.table_model_filter.mapToSource(idx[0])
key = self.table_model.index_to_key(idx)
if key is not None:
persist, value = self.table_model.backing_store[key]
asyncio.ensure_future(self._upload_dataset(key, value))
persist, value, metadata = self.table_model.backing_store[key]
asyncio.ensure_future(self.dataset_ctl.set(key, value, metadata=metadata))
def save_state(self):
return bytes(self.table.header().saveState())

View File

@ -4,111 +4,42 @@ import os
from functools import partial
from collections import OrderedDict
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt6 import QtCore, QtGui, QtWidgets
import h5py
from sipyco import pyon
from artiq import __artiq_dir__ as artiq_dir
from artiq.gui.tools import LayoutWidget, log_level_to_name, get_open_file_name
from artiq.gui.entries import procdesc_to_entry
from artiq.gui.tools import (LayoutWidget, log_level_to_name, get_open_file_name)
from artiq.gui.entries import procdesc_to_entry, EntryTreeWidget
from artiq.master.worker import Worker, log_worker_exception
logger = logging.getLogger(__name__)
class _WheelFilter(QtCore.QObject):
def eventFilter(self, obj, event):
if (event.type() == QtCore.QEvent.Wheel and
event.modifiers() != QtCore.Qt.NoModifier):
event.ignore()
return True
return False
class _ArgumentEditor(QtWidgets.QTreeWidget):
class _ArgumentEditor(EntryTreeWidget):
def __init__(self, dock):
QtWidgets.QTreeWidget.__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()))
self._groups = dict()
self._arg_to_widgets = dict()
EntryTreeWidget.__init__(self)
self._dock = dock
if not self._dock.arguments:
self.addTopLevelItem(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())
self.insertTopLevelItem(0, QtWidgets.QTreeWidgetItem(["No arguments"]))
for name, argument in self._dock.arguments.items():
widgets = dict()
self._arg_to_widgets[name] = widgets
self.set_argument(name, argument)
entry = procdesc_to_entry(argument["desc"])(argument)
widget_item = QtWidgets.QTreeWidgetItem([name])
if argument["tooltip"]:
widget_item.setToolTip(0, argument["tooltip"])
widgets["entry"] = entry
widgets["widget_item"] = widget_item
self.quickStyleClicked.connect(self._dock._run_clicked)
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.setIcon(
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_BrowserReload))
QtWidgets.QStyle.StandardPixmap.SP_BrowserReload))
recompute_arguments.clicked.connect(self._recompute_arguments_clicked)
load = QtWidgets.QPushButton("Set arguments from HDF5")
load.setToolTip("Set arguments from currently selected HDF5 file")
load.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogApplyButton))
QtWidgets.QStyle.StandardPixmap.SP_DialogApplyButton))
load.clicked.connect(self._load_clicked)
buttons = LayoutWidget()
@ -116,21 +47,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
buttons.addWidget(load, 1, 2)
for i, s in enumerate((1, 0, 0, 1)):
buttons.layout.setColumnStretch(i, s)
self.setItemWidget(widget_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
self.setItemWidget(self.bottom_item, 1, buttons)
def _load_clicked(self):
asyncio.ensure_future(self._dock.load_hdf5_task())
@ -138,8 +55,8 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
def _recompute_arguments_clicked(self):
asyncio.ensure_future(self._dock._recompute_arguments())
def _recompute_argument_clicked(self, name):
asyncio.ensure_future(self._recompute_argument(name))
def reset_entry(self, key):
asyncio.ensure_future(self._recompute_argument(key))
async def _recompute_argument(self, name):
try:
@ -154,29 +71,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
state = procdesc_to_entry(procdesc).default_state(procdesc)
argument["desc"] = procdesc
argument["state"] = state
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
self.update_argument(name, argument)
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
@ -191,7 +86,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
self.resize(100*qfm.averageCharWidth(), 30*qfm.lineSpacing())
self.setWindowTitle(expurl)
self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogContentsView))
QtWidgets.QStyle.StandardPixmap.SP_FileDialogContentsView))
self.setAcceptDrops(True)
self.layout = QtWidgets.QGridLayout()
@ -231,22 +126,22 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
run = QtWidgets.QPushButton("Analyze")
run.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton))
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
run.setToolTip("Run analysis stage (Ctrl+Return)")
run.setShortcut("CTRL+RETURN")
run.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
run.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(run, 2, 4)
run.clicked.connect(self._run_clicked)
self._run = run
terminate = QtWidgets.QPushButton("Terminate")
terminate.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogCancelButton))
QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton))
terminate.setToolTip("Terminate analysis (Ctrl+Backspace)")
terminate.setShortcut("CTRL+BACKSPACE")
terminate.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
terminate.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(terminate, 3, 4)
terminate.clicked.connect(self._terminate_clicked)
terminate.setEnabled(False)
@ -285,8 +180,8 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
state = self.argeditor.save_state()
self.argeditor.deleteLater()
self.argeditor = _ArgumentEditor(self)
self.argeditor.restore_state(state)
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
self.argeditor.restore_state(state)
async def load_hdf5_task(self, filename=None):
if filename is None:
@ -378,9 +273,9 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
class LocalDatasetDB:
def __init__(self, datasets_sub):
self.datasets_sub = datasets_sub
datasets_sub.add_setmodel_callback(self.init)
def __init__(self, dataset_sub):
self.dataset_sub = dataset_sub
dataset_sub.add_setmodel_callback(self.init)
def init(self, data):
self._data = data
@ -389,11 +284,11 @@ class LocalDatasetDB:
return self._data.backing_store[key][1]
def update(self, mod):
self.datasets_sub.update(mod)
self.dataset_sub.update(mod)
class ExperimentsArea(QtWidgets.QMdiArea):
def __init__(self, root, datasets_sub):
def __init__(self, root, dataset_sub):
QtWidgets.QMdiArea.__init__(self)
self.pixmap = QtGui.QPixmap(os.path.join(
artiq_dir, "gui", "logo_ver.svg"))
@ -402,11 +297,11 @@ class ExperimentsArea(QtWidgets.QMdiArea):
self.open_experiments = []
self._ddb = LocalDatasetDB(datasets_sub)
self._ddb = LocalDatasetDB(dataset_sub)
self.worker_handlers = {
"get_device_db": lambda: {},
"get_device": lambda k: {"type": "dummy"},
"get_device": lambda key, resolve_alias=False: {"type": "dummy"},
"get_dataset": self._ddb.get,
"update_dataset": self._ddb.update,
}
@ -421,7 +316,7 @@ class ExperimentsArea(QtWidgets.QMdiArea):
asyncio.ensure_future(sub.load_hdf5_task(path))
def mousePressEvent(self, ev):
if ev.button() == QtCore.Qt.LeftButton:
if ev.button() == QtCore.Qt.MouseButton.LeftButton:
self.select_experiment()
def paintEvent(self, event):
@ -474,6 +369,8 @@ class ExperimentsArea(QtWidgets.QMdiArea):
def initialize_submission_arguments(self, arginfo):
arguments = OrderedDict()
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)
arguments[name] = {
"desc": procdesc,
@ -509,12 +406,16 @@ class ExperimentsArea(QtWidgets.QMdiArea):
exc_info=True)
dock = _ExperimentDock(self, expurl, {})
asyncio.ensure_future(dock._recompute_arguments())
dock.setAttribute(QtCore.Qt.WA_DeleteOnClose)
dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
self.addSubWindow(dock)
dock.show()
dock.sigClosed.connect(partial(self.on_dock_closed, dock))
self.open_experiments.append(dock)
return dock
def set_argument_value(self, expurl, name, value):
logger.warning("Unable to set argument '%s', dropping change. "
"'set_argument_value' not supported in browser.", name)
def on_dock_closed(self, dock):
self.open_experiments.remove(dock)

View File

@ -3,7 +3,7 @@ import os
from datetime import datetime
import h5py
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt6 import QtCore, QtWidgets, QtGui
from sipyco import pyon
@ -69,51 +69,52 @@ class ZoomIconView(QtWidgets.QListView):
def __init__(self):
QtWidgets.QListView.__init__(self)
self._char_width = QtGui.QFontMetrics(self.font()).averageCharWidth()
self.setViewMode(self.IconMode)
self.setViewMode(self.ViewMode.IconMode)
w = self._char_width*self.default_size
self.setIconSize(QtCore.QSize(w, int(w*self.aspect)))
self.setFlow(self.LeftToRight)
self.setResizeMode(self.Adjust)
self.setFlow(self.Flow.LeftToRight)
self.setResizeMode(self.ResizeMode.Adjust)
self.setWrapping(True)
def wheelEvent(self, ev):
if ev.modifiers() & QtCore.Qt.ControlModifier:
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
a = self._char_width*self.min_size
b = self._char_width*self.max_size
w = self.iconSize().width()*self.zoom_step**(
ev.angleDelta().y()/120.)
if a <= w <= b:
self.setIconSize(QtCore.QSize(w, w*self.aspect))
self.setIconSize(QtCore.QSize(int(w), int(w*self.aspect)))
else:
QtWidgets.QListView.wheelEvent(self, ev)
class Hdf5FileSystemModel(QtWidgets.QFileSystemModel):
class Hdf5FileSystemModel(QtGui.QFileSystemModel):
def __init__(self):
QtWidgets.QFileSystemModel.__init__(self)
self.setFilter(QtCore.QDir.Drives | QtCore.QDir.NoDotAndDotDot |
QtCore.QDir.AllDirs | QtCore.QDir.Files)
QtGui.QFileSystemModel.__init__(self)
self.setFilter(QtCore.QDir.Filter.Drives | QtCore.QDir.Filter.NoDotAndDotDot |
QtCore.QDir.Filter.AllDirs | QtCore.QDir.Filter.Files)
self.setNameFilterDisables(False)
self.setIconProvider(ThumbnailIconProvider())
def data(self, idx, role):
if role == QtCore.Qt.ToolTipRole:
if role == QtCore.Qt.ItemDataRole.ToolTipRole:
info = self.fileInfo(idx)
h5 = open_h5(info)
if h5 is not None:
try:
expid = pyon.decode(h5["expid"][()])
start_time = datetime.fromtimestamp(h5["start_time"][()])
expid = pyon.decode(h5["expid"][()]) if "expid" in h5 else dict()
start_time = datetime.fromtimestamp(h5["start_time"][()]) if "start_time" in h5 else "<none>"
v = ("artiq_version: {}\nrepo_rev: {}\nfile: {}\n"
"class_name: {}\nrid: {}\nstart_time: {}").format(
h5["artiq_version"][()], expid["repo_rev"],
expid.get("file", "<none>"), expid["class_name"],
h5["rid"][()], start_time)
h5["artiq_version"].asstr()[()] if "artiq_version" in h5 else "<none>",
expid.get("repo_rev", "<none>"),
expid.get("file", "<none>"), expid.get("class_name", "<none>"),
h5["rid"][()] if "rid" in h5 else "<none>", start_time)
return v
except:
logger.warning("unable to read metadata from %s",
info.filePath(), exc_info=True)
return QtWidgets.QFileSystemModel.data(self, idx, role)
return QtGui.QFileSystemModel.data(self, idx, role)
class FilesDock(QtWidgets.QDockWidget):
@ -124,7 +125,7 @@ class FilesDock(QtWidgets.QDockWidget):
def __init__(self, datasets, browse_root=""):
QtWidgets.QDockWidget.__init__(self, "Files")
self.setObjectName("Files")
self.setFeatures(self.DockWidgetMovable | self.DockWidgetFloatable)
self.setFeatures(self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable)
self.splitter = QtWidgets.QSplitter()
self.setWidget(self.splitter)
@ -146,8 +147,8 @@ class FilesDock(QtWidgets.QDockWidget):
self.rt.setRootIndex(rt_model.mapFromSource(
self.model.setRootPath(browse_root)))
self.rt.setHeaderHidden(True)
self.rt.setSelectionBehavior(self.rt.SelectRows)
self.rt.setSelectionMode(self.rt.SingleSelection)
self.rt.setSelectionBehavior(self.rt.SelectionBehavior.SelectRows)
self.rt.setSelectionMode(self.rt.SelectionMode.SingleSelection)
self.rt.selectionModel().currentChanged.connect(
self.tree_current_changed)
self.rt.setRootIsDecorated(False)
@ -174,14 +175,14 @@ class FilesDock(QtWidgets.QDockWidget):
logger.debug("loading datasets from %s", info.filePath())
with f:
try:
expid = pyon.decode(f["expid"][()])
start_time = datetime.fromtimestamp(f["start_time"][()])
expid = pyon.decode(f["expid"][()]) if "expid" in f else dict()
start_time = datetime.fromtimestamp(f["start_time"][()]) if "start_time" in f else "<none>"
v = {
"artiq_version": f["artiq_version"][()],
"repo_rev": expid["repo_rev"],
"artiq_version": f["artiq_version"].asstr()[()] if "artiq_version" in f else "<none>",
"repo_rev": expid.get("repo_rev", "<none>"),
"file": expid.get("file", "<none>"),
"class_name": expid["class_name"],
"rid": f["rid"][()],
"class_name": expid.get("class_name", "<none>"),
"rid": f["rid"][()] if "rid" in f else "<none>",
"start_time": start_time,
}
self.metadata_changed.emit(v)
@ -193,7 +194,9 @@ class FilesDock(QtWidgets.QDockWidget):
if "archive" in f:
def visitor(k, v):
if isinstance(v, h5py.Dataset):
rd[k] = (True, v[()])
# v.attrs is a non-serializable h5py.AttributeManager, need to convert to dict
# See https://docs.h5py.org/en/stable/high/attr.html#h5py.AttributeManager
rd[k] = (True, v[()], dict(v.attrs))
f["archive"].visititems(visitor)
@ -203,7 +206,9 @@ class FilesDock(QtWidgets.QDockWidget):
if k in rd:
logger.warning("dataset '%s' is both in archive "
"and outputs", k)
rd[k] = (True, v[()])
# v.attrs is a non-serializable h5py.AttributeManager, need to convert to dict
# See https://docs.h5py.org/en/stable/high/attr.html#h5py.AttributeManager
rd[k] = (True, v[()], dict(v.attrs))
f["datasets"].visititems(visitor)
@ -247,7 +252,7 @@ class FilesDock(QtWidgets.QDockWidget):
100,
lambda: self.rt.scrollTo(
self.rt.model().mapFromSource(self.model.index(path)),
self.rt.PositionAtCenter)
self.rt.ScrollHint.PositionAtCenter)
)
self.model.directoryLoaded.connect(scroll_when_loaded)
idx = self.rt.model().mapFromSource(idx)

View File

@ -59,7 +59,6 @@ def build_artiq_soc(soc, argdict):
builder.software_packages = []
builder.add_software_package("bootloader", os.path.join(firmware_dir, "bootloader"))
is_kasli_v1 = isinstance(soc.platform, kasli.Platform) and soc.platform.hw_rev in ("v1.0", "v1.1")
if isinstance(soc, AMPSoC):
kernel_cpu_type = "vexriscv" if is_kasli_v1 else "vexriscv-g"
builder.add_software_package("libm", cpu_type=kernel_cpu_type)
builder.add_software_package("libprintf", cpu_type=kernel_cpu_type)
@ -69,9 +68,9 @@ def build_artiq_soc(soc, argdict):
# If the kernel lacks FPU, then the runtime unwinder is already generated
if not is_kasli_v1:
builder.add_software_package("libunwind")
if not soc.config["DRTIO_ROLE"] == "satellite":
builder.add_software_package("runtime", os.path.join(firmware_dir, "runtime"))
else:
# Assume DRTIO satellite.
builder.add_software_package("satman", os.path.join(firmware_dir, "satman"))
try:
builder.build()

View File

@ -21,13 +21,19 @@ class scoped(object):
set of variables resolved as globals
"""
class remote(object):
"""
:ivar remote_fn: (bool) whether function is ran on a remote device,
meaning arguments are received remotely and return is sent remotely
"""
# Typed versions of untyped nodes
class argT(ast.arg, commontyped):
pass
class ClassDefT(ast.ClassDef):
_types = ("constructor_type",)
class FunctionDefT(ast.FunctionDef, scoped):
class FunctionDefT(ast.FunctionDef, scoped, remote):
_types = ("signature_type",)
class QuotedFunctionDefT(FunctionDefT):
"""
@ -58,7 +64,7 @@ class BinOpT(ast.BinOp, commontyped):
pass
class BoolOpT(ast.BoolOp, commontyped):
pass
class CallT(ast.Call, commontyped):
class CallT(ast.Call, commontyped, remote):
"""
:ivar iodelay: (:class:`iodelay.Expr`)
:ivar arg_exprs: (dict of str to :class:`iodelay.Expr`)

View File

@ -38,6 +38,9 @@ class TInt(types.TMono):
def one():
return 1
def TInt8():
return TInt(types.TValue(8))
def TInt32():
return TInt(types.TValue(32))
@ -244,6 +247,18 @@ def fn_at_mu():
def fn_rtio_log():
return types.TBuiltinFunction("rtio_log")
def fn_subkernel_await():
return types.TBuiltinFunction("subkernel_await")
def fn_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
def is_none(typ):
@ -326,7 +341,7 @@ def get_iterable_elt(typ):
# n-dimensional arrays, rather than the n-1 dimensional result of iterating over
# the first axis, which makes the name a bit misleading.
if is_str(typ) or is_bytes(typ) or is_bytearray(typ):
return TInt(types.TValue(8))
return TInt8()
elif types._is_pointer(typ) or is_iterable(typ):
return typ.find()["elt"].find()
else:
@ -342,5 +357,5 @@ def is_allocated(typ):
is_float(typ) or is_range(typ) or
types._is_pointer(typ) or types.is_function(typ) or
types.is_external_function(typ) or types.is_rpc(typ) or
types.is_method(typ) or types.is_tuple(typ) or
types.is_value(typ))
types.is_subkernel(typ) or types.is_method(typ) or
types.is_tuple(typ) or types.is_value(typ))

View File

@ -5,6 +5,7 @@ the references to the host objects and translates the functions
annotated as ``@kernel`` when they are referenced.
"""
import typing
import os, re, linecache, inspect, textwrap, types as pytypes, numpy
from collections import OrderedDict, defaultdict
@ -18,6 +19,13 @@ from . import types, builtins, asttyped, math_fns, prelude
from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer, TypedtreePrinter
from .transforms.asttyped_rewriter import LocalExtractor
try:
# From numpy=1.25.0 dispatching for `__array_function__` is done via
# a C wrapper: https://github.com/numpy/numpy/pull/23020
from numpy.core._multiarray_umath import _ArrayFunctionDispatcher
except ImportError:
_ArrayFunctionDispatcher = None
class SpecializedFunction:
def __init__(self, instance_type, host_function):
@ -39,18 +47,51 @@ class SpecializedFunction:
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:
def __init__(self):
def __init__(self, old_embedding_map=None):
self.object_current_key = 0
self.object_forward_map = {}
self.object_reverse_map = {}
self.module_map = {}
# type_map connects the host Python `type` to the pair of associated
# `(TInstance, TConstructor)`s. The `used_…_names` sets cache the
# respective `.name`s for O(1) collision avoidance.
self.type_map = {}
self.used_instance_type_names = set()
self.used_constructor_type_names = set()
self.function_map = {}
self.str_forward_map = {}
self.str_reverse_map = {}
self.preallocate_runtime_exception_names(["RuntimeError",
# 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
# Keep this list of exceptions in sync with `EXCEPTION_ID_LOOKUP` in `artiq::firmware::ksupport::eh_artiq`
# The exceptions declared here must be defined in `artiq.coredevice.exceptions`
# Verify synchronization by running the test cases in `artiq.test.coredevice.test_exceptions`
self.preallocate_runtime_exception_names([
"RTIOUnderflow",
"RTIOOverflow",
"RTIODestinationUnreachable",
@ -58,8 +99,23 @@ class EmbeddingMap:
"I2CError",
"CacheError",
"SPIError",
"SubkernelError",
"0:AssertionError",
"0:AttributeError",
"0:IndexError",
"0:IOError",
"0:KeyError",
"0:NotImplementedError",
"0:OverflowError",
"0:RuntimeError",
"0:TimeoutError",
"0:TypeError",
"0:ValueError",
"0:ZeroDivisionError",
"0:IndexError"])
"0:LinAlgError",
"UnwrapNoneError",
])
def preallocate_runtime_exception_names(self, names):
for i, name in enumerate(names):
@ -91,16 +147,6 @@ class EmbeddingMap:
# Types
def store_type(self, host_type, instance_type, constructor_type):
self._rename_type(instance_type)
self.type_map[host_type] = (instance_type, constructor_type)
def retrieve_type(self, host_type):
return self.type_map[host_type]
def has_type(self, host_type):
return host_type in self.type_map
def _rename_type(self, new_instance_type):
# Generally, user-defined types that have exact same name (which is to say, classes
# defined inside functions) do not pose a problem to the compiler. The two places which
# cannot handle this are:
@ -109,12 +155,29 @@ class EmbeddingMap:
# Since handling #2 requires renaming on ARTIQ side anyway, it's more straightforward
# to do it once when embedding (since non-embedded code cannot define classes in
# functions). Also, easier to debug.
n = 0
for host_type in self.type_map:
instance_type, constructor_type = self.type_map[host_type]
if instance_type.name == new_instance_type.name:
n += 1
new_instance_type.name = "{}.{}".format(new_instance_type.name, n)
suffix = 0
new_instance_name = instance_type.name
new_constructor_name = constructor_type.name
while True:
if (new_instance_name not in self.used_instance_type_names
and new_constructor_name not in self.used_constructor_type_names):
break
suffix += 1
new_instance_name = f"{instance_type.name}.{suffix}"
new_constructor_name = f"{constructor_type.name}.{suffix}"
self.used_instance_type_names.add(new_instance_name)
instance_type.name = new_instance_name
self.used_constructor_type_names.add(new_constructor_name)
constructor_type.name = new_constructor_name
self.type_map[host_type] = (instance_type, constructor_type)
def retrieve_type(self, host_type):
return self.type_map[host_type]
def has_type(self, host_type):
return host_type in self.type_map
def attribute_count(self):
count = 0
@ -141,6 +204,11 @@ class EmbeddingMap:
return self.object_reverse_map[obj_id]
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_reverse_map[obj_id] = self.object_current_key
return self.object_current_key
@ -153,7 +221,7 @@ class EmbeddingMap:
obj_ref = self.object_forward_map[obj_id]
if isinstance(obj_ref, (pytypes.FunctionType, pytypes.MethodType,
pytypes.BuiltinFunctionType, pytypes.ModuleType,
SpecializedFunction)):
SpecializedFunction, SubkernelMessageType)):
continue
elif isinstance(obj_ref, type):
_, obj_typ = self.type_map[obj_ref]
@ -161,14 +229,55 @@ class EmbeddingMap:
obj_typ, _ = self.type_map[type(obj_ref)]
yield obj_id, obj_ref, obj_typ
def subkernels(self):
subkernels = {}
for k, v in self.object_forward_map.items():
if hasattr(v, "artiq_embedded"):
if v.artiq_embedded.destination is not None:
subkernels[k] = v
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):
return any(filter(lambda x: inspect.isfunction(x) or inspect.ismethod(x),
self.object_forward_map.values()))
return any(filter(
lambda x: (inspect.isfunction(x) or inspect.ismethod(x)) and \
(not hasattr(x, "artiq_embedded") or x.artiq_embedded.destination is None),
self.object_forward_map.values()
))
class ASTSynthesizer:
def __init__(self, embedding_map, value_map, quote_function=None, expanded_from=None):
self.source = ""
self.source_last_new_line = 0
self.source_buffer = source.Buffer(self.source, "<synthesized>")
self.embedding_map = embedding_map
self.value_map = value_map
@ -187,6 +296,14 @@ class ASTSynthesizer:
return source.Range(self.source_buffer, range_from, range_to,
expanded_from=self.expanded_from)
def _add_iterable(self, fragment):
# Since DILocation points on the beginning of the piece of source
# we don't care if the fragment's end will overflow LLVM's limit.
if len(self.source) - self.source_last_new_line >= 2**16:
fragment = "\\\n" + fragment
self.source_last_new_line = len(self.source) + 2
return self._add(fragment)
def fast_quote_list(self, value):
elts = [None] * len(value)
is_T = False
@ -245,7 +362,7 @@ class ASTSynthesizer:
for index, elt in enumerate(value):
elts[index] = self.quote(elt)
if index < len(value) - 1:
self._add(", ")
self._add_iterable(", ")
return elts
def quote(self, value):
@ -296,28 +413,28 @@ class ASTSynthesizer:
loc=self._add(repr(value)))
elif isinstance(value, str):
return asttyped.StrT(s=value, ctx=None, type=builtins.TStr(),
loc=self._add(repr(value)))
loc=self._add_iterable(repr(value)))
elif isinstance(value, bytes):
return asttyped.StrT(s=value, ctx=None, type=builtins.TBytes(),
loc=self._add(repr(value)))
loc=self._add_iterable(repr(value)))
elif isinstance(value, bytearray):
quote_loc = self._add('`')
repr_loc = self._add(repr(value))
unquote_loc = self._add('`')
quote_loc = self._add_iterable('`')
repr_loc = self._add_iterable(repr(value))
unquote_loc = self._add_iterable('`')
loc = quote_loc.join(unquote_loc)
return asttyped.QuoteT(value=value, type=builtins.TByteArray(), loc=loc)
elif isinstance(value, list):
begin_loc = self._add("[")
begin_loc = self._add_iterable("[")
elts = self.fast_quote_list(value)
end_loc = self._add("]")
end_loc = self._add_iterable("]")
return asttyped.ListT(elts=elts, ctx=None, type=builtins.TList(),
begin_loc=begin_loc, end_loc=end_loc,
loc=begin_loc.join(end_loc))
elif isinstance(value, tuple):
begin_loc = self._add("(")
begin_loc = self._add_iterable("(")
elts = self.fast_quote_list(value)
end_loc = self._add(")")
end_loc = self._add_iterable(")")
return asttyped.TupleT(elts=elts, ctx=None,
type=types.TTuple([e.type for e in elts]),
begin_loc=begin_loc, end_loc=end_loc,
@ -327,7 +444,9 @@ class ASTSynthesizer:
elif inspect.isfunction(value) or inspect.ismethod(value) or \
isinstance(value, pytypes.BuiltinFunctionType) or \
isinstance(value, SpecializedFunction) or \
isinstance(value, numpy.ufunc):
isinstance(value, numpy.ufunc) or \
(isinstance(value, _ArrayFunctionDispatcher) if
_ArrayFunctionDispatcher is not None else False):
if inspect.ismethod(value):
quoted_self = self.quote(value.__self__)
function_type = self.quote_function(value.__func__, self.expanded_from)
@ -436,7 +555,7 @@ class ASTSynthesizer:
return asttyped.QuoteT(value=value, type=instance_type,
loc=loc)
def call(self, callee, args, kwargs, callback=None):
def call(self, callee, args, kwargs, callback=None, remote_fn=False):
"""
Construct an AST fragment calling a function specified by
an AST node `function_node`, with given arguments.
@ -480,7 +599,7 @@ class ASTSynthesizer:
starargs=None, kwargs=None,
type=types.TVar(), iodelay=None, arg_exprs={},
begin_loc=begin_loc, end_loc=end_loc, star_loc=None, dstar_loc=None,
loc=callee_node.loc.join(end_loc))
loc=callee_node.loc.join(end_loc), remote_fn=remote_fn)
if callback is not None:
node = asttyped.CallT(
@ -515,7 +634,7 @@ class StitchingASTTypedRewriter(ASTTypedRewriter):
arg=node.arg, annotation=None,
arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc)
def visit_quoted_function(self, node, function):
def visit_quoted_function(self, node, function, remote_fn):
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
@ -532,11 +651,11 @@ class StitchingASTTypedRewriter(ASTTypedRewriter):
node = asttyped.QuotedFunctionDefT(
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
signature_type=types.TVar(), return_type=types.TVar(),
name=node.name, args=node.args, returns=node.returns,
name=node.name, args=node.args, returns=None,
body=node.body, decorator_list=node.decorator_list,
keyword_loc=node.keyword_loc, name_loc=node.name_loc,
arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs,
loc=node.loc)
loc=node.loc, remote_fn=remote_fn)
try:
self.env_stack.append(node.typing_env)
@ -644,9 +763,9 @@ class StitchingInferencer(Inferencer):
if elt.__class__ == float:
state |= IS_FLOAT
elif elt.__class__ == int:
if -2**31 < elt < 2**31-1:
if -2**31 <= elt <= 2**31-1:
state |= IS_INT32
elif -2**63 < elt < 2**63-1:
elif -2**63 <= elt <= 2**63-1:
state |= IS_INT64
else:
state = -1
@ -744,7 +863,7 @@ class TypedtreeHasher(algorithm.Visitor):
return hash(tuple(freeze(getattr(node, field_name)) for field_name in fields))
class Stitcher:
def __init__(self, core, dmgr, engine=None, print_as_rpc=True):
def __init__(self, core, dmgr, engine=None, print_as_rpc=True, destination=0, subkernel_arg_types=[], old_embedding_map=None):
self.core = core
self.dmgr = dmgr
if engine is None:
@ -766,15 +885,23 @@ class Stitcher:
self.functions = {}
self.embedding_map = EmbeddingMap()
self.embedding_map = EmbeddingMap(old_embedding_map)
self.value_map = defaultdict(lambda: [])
self.definitely_changed = False
self.destination = destination
self.first_call = True
# for non-annotated subkernels:
# main kernel inferencer output with types of arguments
self.subkernel_arg_types = subkernel_arg_types
def stitch_call(self, function, args, kwargs, callback=None):
# We synthesize source code for the initial call so that
# diagnostics would have something meaningful to display to the user.
synthesizer = self._synthesizer(self._function_loc(function.artiq_embedded.function))
call_node = synthesizer.call(function, args, kwargs, callback)
# first call of a subkernel will get its arguments from remote (DRTIO)
remote_fn = self.destination != 0
call_node = synthesizer.call(function, args, kwargs, callback, remote_fn=remote_fn)
synthesizer.finalize()
self.typedtree.append(call_node)
@ -886,6 +1013,10 @@ class Stitcher:
return [diagnostic.Diagnostic("note",
"in kernel function here", {},
call_loc)]
elif fn_kind == 'subkernel':
return [diagnostic.Diagnostic("note",
"in subkernel call here", {},
call_loc)]
else:
assert False
else:
@ -905,7 +1036,7 @@ class Stitcher:
self._function_loc(function),
notes=self._call_site_note(loc, fn_kind))
self.engine.process(diag)
elif fn_kind == 'rpc' and param.default is not inspect.Parameter.empty:
elif fn_kind == 'rpc' or fn_kind == 'subkernel' and param.default is not inspect.Parameter.empty:
notes = []
notes.append(diagnostic.Diagnostic("note",
"expanded from here while trying to infer a type for an"
@ -924,11 +1055,18 @@ class Stitcher:
Inferencer(engine=self.engine).visit(ast)
IntMonomorphizer(engine=self.engine).visit(ast)
return ast.type
else:
elif fn_kind == 'kernel' and self.first_call and self.destination != 0:
# subkernels do not have access to the main kernel code to infer
# arg types - so these are cached and passed onto subkernel
# compilation, to avoid having to annotate them fully
for name, typ in self.subkernel_arg_types:
if param.name == name:
return typ
# Let the rest of the program decide.
return types.TVar()
def _quote_embedded_function(self, function, flags):
def _quote_embedded_function(self, function, flags, remote_fn=False):
# we are now parsing new functions... definitely changed the type
self.definitely_changed = True
@ -1027,7 +1165,7 @@ class Stitcher:
engine=self.engine, prelude=self.prelude,
globals=self.globals, host_environment=host_environment,
quote=self._quote)
function_node = asttyped_rewriter.visit_quoted_function(function_node, embedded_function)
function_node = asttyped_rewriter.visit_quoted_function(function_node, embedded_function, remote_fn)
function_node.flags = flags
# Add it into our typedtree so that it gets inferenced and codegen'd.
@ -1039,9 +1177,6 @@ class Stitcher:
return function_node
def _extract_annot(self, function, annot, kind, call_loc, fn_kind):
if annot is None:
annot = builtins.TNone()
if isinstance(function, SpecializedFunction):
host_function = function.host_function
else:
@ -1055,9 +1190,20 @@ class Stitcher:
if isinstance(embedded_function, str):
embedded_function = host_function
return self._to_artiq_type(
annot,
function=function,
kind=kind,
eval_in_scope=lambda x: eval(x, embedded_function.__globals__),
call_loc=call_loc,
fn_kind=fn_kind)
def _to_artiq_type(
self, annot, *, function, kind: str, eval_in_scope, call_loc: str, fn_kind: str
) -> types.Type:
if isinstance(annot, str):
try:
annot = eval(annot, embedded_function.__globals__)
annot = eval_in_scope(annot)
except Exception:
diag = diagnostic.Diagnostic(
"error",
@ -1067,23 +1213,72 @@ class Stitcher:
notes=self._call_site_note(call_loc, fn_kind))
self.engine.process(diag)
if not isinstance(annot, types.Type):
if isinstance(annot, types.Type):
return annot
# Convert built-in Python types to ARTIQ ones.
if annot is None:
return builtins.TNone()
elif annot is numpy.int64:
return builtins.TInt64()
elif annot is numpy.int32:
return builtins.TInt32()
elif annot is float:
return builtins.TFloat()
elif annot is bool:
return builtins.TBool()
elif annot is str:
return builtins.TStr()
elif annot is bytes:
return builtins.TBytes()
elif annot is bytearray:
return builtins.TByteArray()
# Convert generic Python types to ARTIQ ones.
generic_ty = typing.get_origin(annot)
if generic_ty is not None:
type_args = typing.get_args(annot)
artiq_args = [
self._to_artiq_type(
x,
function=function,
kind=kind,
eval_in_scope=eval_in_scope,
call_loc=call_loc,
fn_kind=fn_kind)
for x in type_args
]
if generic_ty is list and len(artiq_args) == 1:
return builtins.TList(artiq_args[0])
elif generic_ty is tuple:
return types.TTuple(artiq_args)
# Otherwise report an unknown type and just use a fresh tyvar.
if annot is int:
message = (
"type annotation for {kind}, 'int' cannot be used as an ARTIQ type. "
"Use numpy's int32 or int64 instead."
)
ty = builtins.TInt()
else:
message = "type annotation for {kind}, '{annot}', is not an ARTIQ type"
ty = types.TVar()
diag = diagnostic.Diagnostic("error",
"type annotation for {kind}, '{annot}', is not an ARTIQ type",
message,
{"kind": kind, "annot": repr(annot)},
self._function_loc(function),
notes=self._call_site_note(call_loc, fn_kind))
self.engine.process(diag)
return types.TVar()
else:
return annot
return ty
def _quote_syscall(self, function, loc):
signature = inspect.signature(function)
arg_types = OrderedDict()
optarg_types = OrderedDict()
for param in signature.parameters.values():
if param.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD:
diag = diagnostic.Diagnostic("error",
@ -1121,6 +1316,40 @@ class Stitcher:
self.functions[function] = function_type
return function_type
def _quote_subkernel(self, function, loc):
if isinstance(function, SpecializedFunction):
host_function = function.host_function
else:
host_function = function
ret_type = builtins.TNone()
signature = inspect.signature(host_function)
if signature.return_annotation is not inspect.Signature.empty:
ret_type = self._extract_annot(host_function, signature.return_annotation,
"return type", loc, fn_kind='subkernel')
arg_types = OrderedDict()
optarg_types = OrderedDict()
for param in signature.parameters.values():
if param.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD:
diag = diagnostic.Diagnostic("error",
"subkernels must only use positional arguments; '{argument}' isn't",
{"argument": param.name},
self._function_loc(function),
notes=self._call_site_note(loc, fn_kind='subkernel'))
self.engine.process(diag)
arg_type = self._type_of_param(function, loc, param, fn_kind='subkernel')
if param.default is inspect.Parameter.empty:
arg_types[param.name] = arg_type
else:
optarg_types[param.name] = arg_type
function_type = types.TSubkernel(arg_types, optarg_types, ret_type,
sid=self.embedding_map.store_object(host_function),
destination=host_function.artiq_embedded.destination)
self.functions[function] = function_type
return function_type
def _quote_rpc(self, function, loc):
if isinstance(function, SpecializedFunction):
host_function = function.host_function
@ -1180,8 +1409,18 @@ class Stitcher:
(host_function.artiq_embedded.core_name is None and
host_function.artiq_embedded.portable is False and
host_function.artiq_embedded.syscall is None and
host_function.artiq_embedded.destination is None and
host_function.artiq_embedded.forbidden is False):
self._quote_rpc(function, loc)
elif host_function.artiq_embedded.destination is not None and \
host_function.artiq_embedded.destination != self.destination:
# treat subkernels as kernels if running on the same device
if not 0 < host_function.artiq_embedded.destination <= 255:
diag = diagnostic.Diagnostic("error",
"subkernel destination must be between 1 and 255 (inclusive)", {},
self._function_loc(host_function))
self.engine.process(diag)
self._quote_subkernel(function, loc)
elif host_function.artiq_embedded.function is not None:
if host_function.__name__ == "<lambda>":
note = diagnostic.Diagnostic("note",
@ -1205,8 +1444,13 @@ class Stitcher:
notes=[note])
self.engine.process(diag)
destination = host_function.artiq_embedded.destination
# remote_fn only for first call in subkernels
remote_fn = destination is not None and self.first_call
self._quote_embedded_function(function,
flags=host_function.artiq_embedded.flags)
flags=host_function.artiq_embedded.flags,
remote_fn=remote_fn)
self.first_call = False
elif host_function.artiq_embedded.syscall is not None:
# Insert a storage-less global whose type instructs the compiler
# to perform a system call instead of a regular call.

View File

@ -706,6 +706,81 @@ class SetLocal(Instruction):
def value(self):
return self.operands[1]
class GetArgFromRemote(Instruction):
"""
An instruction that receives function arguments from remote
(ie. subkernel in DRTIO context)
:ivar arg_name: (string) argument name
:ivar arg_type: argument type
"""
"""
:param arg_name: (string) argument name
:param arg_type: argument type
"""
def __init__(self, arg_name, arg_type, name=""):
assert isinstance(arg_name, str)
super().__init__([], arg_type, name)
self.arg_name = arg_name
self.arg_type = arg_type
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.arg_name = self.arg_name
self_copy.arg_type = self.arg_type
return self_copy
def opcode(self):
return "getargfromremote({})".format(repr(self.arg_name))
class GetOptArgFromRemote(GetArgFromRemote):
"""
An instruction that may or may not retrieve an optional function argument
from remote, depending on number of values received by firmware.
:ivar rcv_count: number of received values,
determined by firmware
:ivar index: (integer) index of the current argument,
in reference to remote arguments
"""
"""
:param rcv_count: number of received valuese
:param index: (integer) index of the current argument,
in reference to remote arguments
"""
def __init__(self, arg_name, arg_type, rcv_count, index, name=""):
super().__init__(arg_name, arg_type, name)
self.rcv_count = rcv_count
self.index = index
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.rcv_count = self.rcv_count
self_copy.index = self.index
return self_copy
def opcode(self):
return "getoptargfromremote({})".format(repr(self.arg_name))
class SubkernelAwaitArgs(Instruction):
"""
A builtin instruction that takes min and max received messages as operands,
and a list of received types.
:ivar arg_types: (list of types) types of passed arguments (including optional)
"""
"""
:param arg_types: (list of types) types of passed arguments (including optional)
"""
def __init__(self, operands, arg_types, name=None):
assert isinstance(arg_types, list)
self.arg_types = arg_types
super().__init__(operands, builtins.TNone(), name)
class GetAttr(Instruction):
"""
An intruction that loads an attribute from an object,
@ -728,7 +803,7 @@ class GetAttr(Instruction):
typ = obj.type.attributes[attr]
else:
typ = obj.type.constructor.attributes[attr]
if types.is_function(typ) or types.is_rpc(typ):
if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(typ):
typ = types.TMethod(obj.type, typ)
super().__init__([obj], typ, name)
self.attr = attr
@ -972,6 +1047,42 @@ class Builtin(Instruction):
def opcode(self):
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):
"""
A closure creation operation.
@ -1190,14 +1301,18 @@ class IndirectBranch(Terminator):
class Return(Terminator):
"""
A return instruction.
:param remote_return: (bool)
marks a return in subkernel context,
where the return value is sent back through DRTIO
"""
"""
:param value: (:class:`Value`) return value
"""
def __init__(self, value, name=""):
def __init__(self, value, remote_return=False, name=""):
assert isinstance(value, Value)
super().__init__([value], builtins.TNone(), name)
self.remote_return = remote_return
def opcode(self):
return "return"

View File

@ -33,9 +33,19 @@ SECTIONS
KEEP(*(.eh_frame_hdr))
} : text : eh_frame
.got :
{
*(.got)
} : text
.got.plt :
{
*(.got.plt)
} : text
.data :
{
*(.data)
*(.data .data.*)
} : data
.dynamic :
@ -51,6 +61,10 @@ SECTIONS
_end = .;
}
/* Kernel stack grows downward from end of memory, so put guard page after
* all the program contents. Note: This requires all loaded sections (at
* least those accessed) to be explicitly listed in the above!
*/
. = ALIGN(0x1000);
_sstack_guard = .;
}

View File

@ -61,22 +61,6 @@ unary_fp_runtime_calls = [
("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 = [
("erf", "erf"),
("erfc", "erfc"),

View File

@ -84,6 +84,8 @@ class Module:
constant_hoister.process(self.artiq_ir)
if remarks:
invariant_detection.process(self.artiq_ir)
# for subkernels: main kernel inferencer output, to be passed to further compilations
self.subkernel_arg_types = inferencer.subkernel_arg_types
def build_llvm_ir(self, target):
"""Compile the module to LLVM IR for the specified target."""

View File

@ -37,6 +37,7 @@ def globals():
# ARTIQ decorators
"kernel": builtins.fn_kernel(),
"subkernel": builtins.fn_kernel(),
"portable": builtins.fn_kernel(),
"rpc": builtins.fn_kernel(),
@ -54,4 +55,10 @@ def globals():
# ARTIQ utility functions
"rtio_log": builtins.fn_rtio_log(),
"core_log": builtins.fn_print(),
# ARTIQ subkernel utility functions
"subkernel_await": builtins.fn_subkernel_await(),
"subkernel_preload": builtins.fn_subkernel_preload(),
"subkernel_send": builtins.fn_subkernel_send(),
"subkernel_recv": builtins.fn_subkernel_recv(),
}

View File

@ -91,11 +91,12 @@ class Target:
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_addr2line = "llvm-addr2line"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"
def __init__(self):
def __init__(self, subkernel_id=None):
self.llcontext = ll.Context()
self.subkernel_id = subkernel_id
def target_machine(self):
lltarget = llvm.Target.from_triple(self.triple)
@ -148,7 +149,8 @@ class Target:
ir.BasicBlock._dump_loc = False
type_printer = types.TypePrinter()
_dump(os.getenv("ARTIQ_DUMP_IR"), "ARTIQ IR", ".txt",
suffix = "_subkernel_{}".format(self.subkernel_id) if self.subkernel_id is not None else ""
_dump(os.getenv("ARTIQ_DUMP_IR"), "ARTIQ IR", suffix + ".txt",
lambda: "\n".join(fn.as_entity(type_printer) for fn in module.artiq_ir))
llmod = module.build_llvm_ir(self)
@ -160,12 +162,12 @@ class Target:
_dump("", "LLVM IR (broken)", ".ll", lambda: str(llmod))
raise
_dump(os.getenv("ARTIQ_DUMP_UNOPT_LLVM"), "LLVM IR (generated)", "_unopt.ll",
_dump(os.getenv("ARTIQ_DUMP_UNOPT_LLVM"), "LLVM IR (generated)", suffix + "_unopt.ll",
lambda: str(llparsedmod))
self.optimize(llparsedmod)
_dump(os.getenv("ARTIQ_DUMP_LLVM"), "LLVM IR (optimized)", ".ll",
_dump(os.getenv("ARTIQ_DUMP_LLVM"), "LLVM IR (optimized)", suffix + ".ll",
lambda: str(llparsedmod))
return llparsedmod
@ -218,8 +220,8 @@ class Target:
# the backtrace entry should point at.
last_inlined = None
offset_addresses = [hex(addr - 1) for addr in addresses]
with RunTool([self.tool_addr2line, "--addresses", "--functions", "--inlines",
"--demangle", "--exe={library}"] + offset_addresses,
with RunTool([self.tool_symbolizer, "--addresses", "--functions", "--inlines",
"--demangle", "--output-style=GNU", "--exe={library}"] + offset_addresses,
library=library) \
as results:
lines = iter(results["__stdout__"].read().rstrip().split("\n"))
@ -256,6 +258,8 @@ class Target:
return backtrace
def demangle(self, names):
if not any(names):
return names
with RunTool([self.tool_cxxfilt] + names) as results:
return results["__stdout__"].read().rstrip().split("\n")
@ -263,43 +267,43 @@ class NativeTarget(Target):
def __init__(self):
super().__init__()
self.triple = llvm.get_default_triple()
host_data_layout = str(llvm.targets.Target.from_default_triple().create_target_machine().target_data)
self.data_layout = str(llvm.targets.Target.from_default_triple().create_target_machine().target_data)
class RV32IMATarget(Target):
triple = "riscv32-unknown-linux"
data_layout = "e-m:e-p:32:32-i64:64-n32-S128"
features = ["m", "a"]
additional_linker_options = []
additional_linker_options = ["-m", "elf32lriscv"]
print_function = "core_log"
now_pinning = True
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_addr2line = "llvm-addr2line"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"
class RV32GTarget(Target):
triple = "riscv32-unknown-linux"
data_layout = "e-m:e-p:32:32-i64:64-n32-S128"
features = ["m", "a", "f", "d"]
additional_linker_options = []
additional_linker_options = ["-m", "elf32lriscv"]
print_function = "core_log"
now_pinning = True
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_addr2line = "llvm-addr2line"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"
class CortexA9Target(Target):
triple = "armv7-unknown-linux-gnueabihf"
data_layout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
features = ["dsp", "fp16", "neon", "vfp3"]
additional_linker_options = ["--target2=rel"]
additional_linker_options = ["-m", "armelf_linux_eabi", "--target2=rel"]
print_function = "core_log"
now_pinning = False
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_addr2line = "llvm-addr2line"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"

View File

@ -30,8 +30,9 @@ def main():
device_db_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py")
device_mgr = DeviceManager(DeviceDB(device_db_path))
dataset_db_path = os.path.join(os.path.dirname(sys.argv[1]), "dataset_db.pyon")
dataset_mgr = DatasetManager(DatasetDB(dataset_db_path))
dataset_db_path = os.path.join(os.path.dirname(sys.argv[1]), "dataset_db.mdb")
dataset_db = DatasetDB(dataset_db_path)
dataset_mgr = DatasetManager()
argument_mgr = ProcessArgumentManager({})
@ -68,5 +69,7 @@ def main():
benchmark(lambda: target.strip(elf_shlib),
"Stripping debug information")
dataset_db.close_db()
if __name__ == "__main__":
main()

View File

@ -108,6 +108,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.current_args = None
self.current_assign = None
self.current_exception = None
self.current_remote_fn = False
self.break_target = None
self.continue_target = None
self.return_target = None
@ -211,7 +212,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
old_priv_env, self.current_private_env = self.current_private_env, priv_env
self.generic_visit(node)
self.terminate(ir.Return(ir.Constant(None, builtins.TNone())))
self.terminate(ir.Return(ir.Constant(None, builtins.TNone()),
remote_return=self.current_remote_fn))
return self.functions
finally:
@ -294,6 +296,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
old_block, self.current_block = self.current_block, entry
old_globals, self.current_globals = self.current_globals, node.globals_in_scope
old_remote_fn = self.current_remote_fn
self.current_remote_fn = getattr(node, "remote_fn", False)
env_without_globals = \
{var: node.typing_env[var]
@ -326,7 +330,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.terminate(ir.Return(result))
elif builtins.is_none(typ.ret):
if not self.current_block.is_terminated():
self.current_block.append(ir.Return(ir.Constant(None, builtins.TNone())))
self.current_block.append(ir.Return(ir.Constant(None, builtins.TNone()),
remote_return=self.current_remote_fn))
else:
if not self.current_block.is_terminated():
if len(self.current_block.predecessors()) != 0:
@ -345,6 +350,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.current_block = old_block
self.current_globals = old_globals
self.current_env = old_env
self.current_remote_fn = old_remote_fn
if not is_lambda:
self.current_private_env = old_priv_env
@ -367,7 +373,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
return_value = self.visit(node.value)
if self.return_target is None:
self.append(ir.Return(return_value))
self.append(ir.Return(return_value,
remote_return=self.current_remote_fn))
else:
self.append(ir.SetLocal(self.current_private_env, "$return", return_value))
self.append(ir.Branch(self.return_target))
@ -1198,7 +1205,27 @@ class ARTIQIRGenerator(algorithm.Visitor):
finally:
self.current_assign = old_assign
if isinstance(node.slice, ast.Index):
if types.is_tuple(node.value.type):
assert isinstance(node.slice, ast.Index), \
"Internal compiler error: tuple index should be an Index"
assert isinstance(node.slice.value, ast.Num), \
"Internal compiler error: tuple index should be a constant"
if self.current_assign is not None:
diag = diagnostic.Diagnostic("error",
"cannot assign to a tuple element",
{}, node.loc)
self.engine.process(diag)
index = node.slice.value.n
indexed = self.append(
ir.GetAttr(value, index, name="{}.e{}".format(value.name, index)),
loc=node.loc
)
return indexed
elif isinstance(node.slice, ast.Index):
try:
old_assign, self.current_assign = self.current_assign, None
index = self.visit(node.slice.value)
@ -2504,6 +2531,89 @@ class ARTIQIRGenerator(algorithm.Visitor):
or types.is_builtin(typ, "at_mu"):
return self.append(ir.Builtin(typ.name,
[self.visit(arg) for arg in node.args], node.type))
elif types.is_builtin(typ, "subkernel_await"):
if len(node.args) == 2 and len(node.keywords) == 0:
fn = node.args[0].type
timeout = self.visit(node.args[1])
elif len(node.args) == 1 and len(node.keywords) == 0:
fn = node.args[0].type
timeout = ir.Constant(-1, builtins.TInt64())
else:
assert False
if types.is_method(fn):
fn = types.get_method_function(fn)
sid = ir.Constant(fn.sid, builtins.TInt32())
if not builtins.is_none(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:
ret = ir.Constant(None, 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
elif types.is_builtin(typ, "subkernel_preload"):
if len(node.args) == 1 and len(node.keywords) == 0:
fn = node.args[0].type
else:
assert False
if types.is_method(fn):
fn = types.get_method_function(fn)
sid = ir.Constant(fn.sid, builtins.TInt32())
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):
return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args])
elif types.is_constructor(typ):
@ -2515,8 +2625,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
node.loc)
self.engine.process(diag)
def _user_call(self, callee, positional, keywords, arg_exprs={}):
if types.is_function(callee.type) or types.is_rpc(callee.type):
def _user_call(self, callee, positional, keywords, arg_exprs={}, remote_fn=False):
if types.is_function(callee.type) or types.is_rpc(callee.type) or types.is_subkernel(callee.type):
func = callee
self_arg = None
fn_typ = callee.type
@ -2531,16 +2641,51 @@ class ARTIQIRGenerator(algorithm.Visitor):
else:
assert False
if types.is_rpc(fn_typ):
if self_arg is None:
if types.is_rpc(fn_typ) or types.is_subkernel(fn_typ):
if self_arg is None or types.is_subkernel(fn_typ):
# self is not passed to subkernels by remote
args = positional
else:
elif self_arg is not None:
args = [self_arg] + positional
for keyword in keywords:
arg = keywords[keyword]
args.append(self.append(ir.Alloc([ir.Constant(keyword, builtins.TStr()), arg],
ir.TKeyword(arg.type))))
elif remote_fn:
assert self_arg is None
assert len(fn_typ.args) >= len(positional)
assert len(keywords) == 0 # no keyword support
args = [None] * fn_typ.arity()
index = 0
# fill in first available args
for arg in positional:
args[index] = arg
index += 1
# remaining args are received through DRTIO
if index < len(args):
# min/max args received remotely (minus already filled)
offset = index
min_args = ir.Constant(len(fn_typ.args)-offset, builtins.TInt8())
max_args = ir.Constant(fn_typ.arity()-offset, builtins.TInt8())
arg_types = list(fn_typ.args.items())[offset:]
arg_type_list = [a[1] for a in arg_types] + [a[1] for a in fn_typ.optargs.items()]
rcvd_count = self.append(ir.SubkernelAwaitArgs([min_args, max_args], arg_type_list))
# obligatory arguments
for arg_name, arg_type in arg_types:
args[index] = self.append(ir.GetArgFromRemote(arg_name, arg_type,
name="ARG.{}".format(arg_name)))
index += 1
# optional arguments
for optarg_name, optarg_type in fn_typ.optargs.items():
idx = ir.Constant(index-offset, builtins.TInt8())
args[index] = \
self.append(ir.GetOptArgFromRemote(optarg_name, optarg_type, rcvd_count, idx))
index += 1
else:
args = [None] * (len(fn_typ.args) + len(fn_typ.optargs))
@ -2626,7 +2771,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
else:
assert False, "Broadcasting for {} arguments not implemented".format(len)
else:
insn = self._user_call(callee, args, keywords, node.arg_exprs)
remote_fn = getattr(node, "remote_fn", False)
insn = self._user_call(callee, args, keywords, node.arg_exprs, remote_fn)
if isinstance(node.func, asttyped.AttributeT):
attr_node = node.func
self.method_map[(attr_node.value.type.find(),
@ -2819,7 +2965,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
format_string += ")"
elif builtins.is_exception(value.type):
# 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__"))
param1 = self.append(ir.GetAttr(value, "#__param0__"))
param2 = self.append(ir.GetAttr(value, "#__param1__"))

View File

@ -238,7 +238,7 @@ class ASTTypedRewriter(algorithm.Transformer):
body=node.body, decorator_list=node.decorator_list,
keyword_loc=node.keyword_loc, name_loc=node.name_loc,
arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs,
loc=node.loc)
loc=node.loc, remote_fn=False)
try:
self.env_stack.append(node.typing_env)
@ -440,7 +440,8 @@ class ASTTypedRewriter(algorithm.Transformer):
def visit_Call(self, node):
node = self.generic_visit(node)
node = asttyped.CallT(type=types.TVar(), iodelay=None, arg_exprs={},
func=node.func, args=node.args, keywords=node.keywords,
remote_fn=False, func=node.func,
args=node.args, keywords=node.keywords,
starargs=node.starargs, kwargs=node.kwargs,
star_loc=node.star_loc, dstar_loc=node.dstar_loc,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)

View File

@ -46,6 +46,7 @@ class Inferencer(algorithm.Visitor):
self.function = None # currently visited function, for Return inference
self.in_loop = False
self.has_return = False
self.subkernel_arg_types = dict()
def _unify(self, typea, typeb, loca, locb, makenotes=None, when=""):
try:
@ -178,7 +179,7 @@ class Inferencer(algorithm.Visitor):
# Convert to a method.
attr_type = types.TMethod(object_type, attr_type)
self._unify_method_self(attr_type, attr_name, attr_loc, loc, value_node.loc)
elif types.is_rpc(attr_type):
elif types.is_rpc(attr_type) or types.is_subkernel(attr_type):
# Convert to a method. We don't have to bother typechecking
# the self argument, since for RPCs anything goes.
attr_type = types.TMethod(object_type, attr_type)
@ -259,7 +260,31 @@ class Inferencer(algorithm.Visitor):
def visit_SubscriptT(self, node):
self.generic_visit(node)
if isinstance(node.slice, ast.Index):
if types.is_tuple(node.value.type):
if (not isinstance(node.slice, ast.Index) or
not isinstance(node.slice.value, ast.Num)):
diag = diagnostic.Diagnostic(
"error", "tuples can only be indexed by a constant", {},
node.slice.loc, []
)
self.engine.process(diag)
return
tuple_type = node.value.type.find()
index = node.slice.value.n
if index < 0 or index >= len(tuple_type.elts):
diag = diagnostic.Diagnostic(
"error",
"index {index} is out of range for tuple of size {size}",
{"index": index, "size": len(tuple_type.elts)},
node.slice.loc, []
)
self.engine.process(diag)
return
self._unify(node.type, tuple_type.elts[index], node.loc, node.value.loc)
elif isinstance(node.slice, ast.Index):
if types.is_tuple(node.slice.value.type):
if types.is_var(node.value.type):
return
@ -1269,6 +1294,106 @@ class Inferencer(algorithm.Visitor):
# Ignored.
self._unify(node.type, builtins.TNone(),
node.loc, None)
elif types.is_builtin(typ, "subkernel_await"):
valid_forms = lambda: [
valid_form("subkernel_await(f: subkernel) -> f return type"),
valid_form("subkernel_await(f: subkernel, timeout: numpy.int64) -> f return type")
]
if 1 <= len(node.args) <= 2:
arg0 = node.args[0].type
if types.is_var(arg0):
pass # undetermined yet
else:
if types.is_method(arg0):
fn = types.get_method_function(arg0)
elif types.is_function(arg0) or types.is_subkernel(arg0):
fn = arg0
else:
diagnose(valid_forms())
self._unify(node.type, fn.ret,
node.loc, None)
if len(node.args) == 2:
arg1 = node.args[1]
if types.is_var(arg1.type):
pass
elif builtins.is_int(arg1.type):
# promote to TInt64
self._unify(arg1.type, builtins.TInt64(),
arg1.loc, None)
else:
diagnose(valid_forms())
else:
diagnose(valid_forms())
elif types.is_builtin(typ, "subkernel_preload"):
valid_forms = lambda: [
valid_form("subkernel_preload(f: subkernel) -> None")
]
if len(node.args) == 1:
arg0 = node.args[0].type
if types.is_var(arg0):
pass # undetermined yet
else:
if types.is_method(arg0):
fn = types.get_method_function(arg0)
elif types.is_function(arg0) or types.is_subkernel(arg0):
fn = arg0
else:
diagnose(valid_forms())
self._unify(node.type, fn.ret,
node.loc, None)
else:
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:
assert False
@ -1307,6 +1432,7 @@ class Inferencer(algorithm.Visitor):
typ_args = typ.args
typ_optargs = typ.optargs
typ_ret = typ.ret
typ_func = typ
else:
typ_self = types.get_method_self(typ)
typ_func = types.get_method_function(typ)
@ -1364,12 +1490,23 @@ class Inferencer(algorithm.Visitor):
other_node=node.args[0])
self._unify(node.type, ret, node.loc, None)
return
if types.is_subkernel(typ_func) and typ_func.sid not in self.subkernel_arg_types:
self.subkernel_arg_types[typ_func.sid] = []
for actualarg, (formalname, formaltyp) in \
zip(node.args, list(typ_args.items()) + list(typ_optargs.items())):
self._unify(actualarg.type, formaltyp,
actualarg.loc, None)
passed_args[formalname] = actualarg.loc
if types.is_subkernel(typ_func):
if types.is_instance(actualarg.type):
# objects cannot be passed to subkernels, as rpc code doesn't support them
diag = diagnostic.Diagnostic("error",
"argument '{name}' of type: {typ} is not supported in subkernels",
{"name": formalname, "typ": actualarg.type},
actualarg.loc, [])
self.engine.process(diag)
self.subkernel_arg_types[typ_func.sid].append((formalname, formaltyp))
for keyword in node.keywords:
if keyword.arg in passed_args:
@ -1400,7 +1537,7 @@ class Inferencer(algorithm.Visitor):
passed_args[keyword.arg] = keyword.arg_loc
for formalname in typ_args:
if formalname not in passed_args:
if formalname not in passed_args and not node.remote_fn:
note = diagnostic.Diagnostic("note",
"the called function is of type {type}",
{"type": types.TypePrinter().name(node.func.type)},

View File

@ -14,9 +14,9 @@ class IntMonomorphizer(algorithm.Visitor):
def visit_NumT(self, node):
if builtins.is_int(node.type):
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
elif -2**63 < node.n < 2**63-1:
elif -2**63 <= node.n <= 2**63-1:
width = 64
else:
diag = diagnostic.Diagnostic("error",

View File

@ -280,7 +280,7 @@ class IODelayEstimator(algorithm.Visitor):
context="as an argument for delay_mu()")
call_delay = value
elif not types.is_builtin(typ):
if types.is_function(typ) or types.is_rpc(typ):
if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(typ):
offset = 0
elif types.is_method(typ):
offset = 1
@ -288,7 +288,7 @@ class IODelayEstimator(algorithm.Visitor):
else:
assert False
if types.is_rpc(typ):
if types.is_rpc(typ) or types.is_subkernel(typ):
call_delay = iodelay.Const(0)
else:
delay = typ.find().delay.find()
@ -311,6 +311,7 @@ class IODelayEstimator(algorithm.Visitor):
args[arg_name] = arg_node
free_vars = delay.duration.free_vars()
try:
node.arg_exprs = {
arg: self.evaluate(args[arg], abort=abort,
context="in the expression for argument '{}' "
@ -318,6 +319,12 @@ class IODelayEstimator(algorithm.Visitor):
for arg in free_vars
}
call_delay = delay.duration.fold(node.arg_exprs)
except KeyError as e:
if getattr(node, "remote_fn", False):
note = diagnostic.Diagnostic("note",
"function called here", {},
node.loc)
self.abort("due to arguments passed remotely", node.loc, note)
else:
assert False
else:

View File

@ -177,6 +177,15 @@ class LLVMIRGenerator:
self.empty_metadata = self.llmodule.add_metadata([])
self.quote_fail_msg = None
# Maximum alignment required according to the target platform ABI. As this is
# not directly exposed by LLVM, just take the maximum across all the "big"
# elementary types we use. (Vector types, should we ever support them, are
# likely contenders for even larger alignment requirements.)
self.max_target_alignment = max(map(
lambda t: self.abi_layout_info.get_size_align(t)[1],
[lli64, lldouble, llptr]
))
def add_pred(self, pred, block):
if block not in self.llpred_map:
self.llpred_map[block] = set()
@ -206,7 +215,7 @@ class LLVMIRGenerator:
typ = typ.find()
if types.is_tuple(typ):
return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.elts])
elif types.is_rpc(typ) or types.is_external_function(typ):
elif types.is_rpc(typ) or types.is_external_function(typ) or types.is_subkernel(typ):
if for_return:
return llvoid
else:
@ -335,8 +344,8 @@ class LLVMIRGenerator:
else:
value = const.value
llptr = self.llstr_of_str(const.value, linkage="private", unnamed_addr=True)
lllen = ll.Constant(lli32, len(const.value))
llptr = self.llstr_of_str(value, linkage="private", unnamed_addr=True)
lllen = ll.Constant(lli32, len(value))
return ll.Constant(llty, (llptr, lllen))
else:
assert False
@ -389,6 +398,15 @@ class LLVMIRGenerator:
elif name == "rpc_recv":
llty = ll.FunctionType(lli32, [llptr])
elif name == "subkernel_send_message":
llty = ll.FunctionType(llvoid, [lli32, lli1, lli8, lli8, llsliceptr, llptrptr])
elif name == "subkernel_load_run":
llty = ll.FunctionType(llvoid, [lli32, lli8, lli1])
elif name == "subkernel_await_finish":
llty = ll.FunctionType(llvoid, [lli32, lli64])
elif name == "subkernel_await_message":
llty = ll.FunctionType(lli8, [lli32, lli64, llsliceptr, lli8, lli8])
# with now-pinning
elif name == "now":
llty = lli64
@ -865,6 +883,53 @@ class LLVMIRGenerator:
llvalue = self.llbuilder.bitcast(llvalue, llptr.type.pointee)
return self.llbuilder.store(llvalue, llptr)
def process_GetArgFromRemote(self, insn):
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
name="subkernel.arg.stack")
llval = self._build_rpc_recv(insn.arg_type, llstackptr)
return llval
def process_GetOptArgFromRemote(self, insn):
# optarg = index < rcv_count ? Some(rcv_recv()) : None
llhead = self.llbuilder.basic_block
llrcv = self.llbuilder.append_basic_block(name="optarg.get.{}".format(insn.arg_name))
# argument received
self.llbuilder.position_at_end(llrcv)
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
name="subkernel.arg.stack")
llval = self._build_rpc_recv(insn.arg_type, llstackptr)
llrpcretblock = self.llbuilder.basic_block # 'return' from rpc_recv, will be needed later
# create the tail block, needs to be after the rpc recv tail block
lltail = self.llbuilder.append_basic_block(name="optarg.tail.{}".format(insn.arg_name))
self.llbuilder.branch(lltail)
# go back to head to add a branch to the tail
self.llbuilder.position_at_end(llhead)
llargrcvd = self.llbuilder.icmp_unsigned("<", self.map(insn.index), self.map(insn.rcv_count))
self.llbuilder.cbranch(llargrcvd, llrcv, lltail)
# argument not received/after arg recvd
self.llbuilder.position_at_end(lltail)
llargtype = self.llty_of_type(insn.arg_type)
llphi_arg_present = self.llbuilder.phi(lli1, name="optarg.phi.present.{}".format(insn.arg_name))
llphi_arg = self.llbuilder.phi(llargtype, name="optarg.phi.{}".format(insn.arg_name))
llphi_arg_present.add_incoming(ll.Constant(lli1, 0), llhead)
llphi_arg.add_incoming(ll.Constant(llargtype, ll.Undefined), llhead)
llphi_arg_present.add_incoming(ll.Constant(lli1, 1), llrpcretblock)
llphi_arg.add_incoming(llval, llrpcretblock)
lloptarg = ll.Constant(ll.LiteralStructType([lli1, llargtype]), ll.Undefined)
lloptarg = self.llbuilder.insert_value(lloptarg, llphi_arg_present, 0)
lloptarg = self.llbuilder.insert_value(lloptarg, llphi_arg, 1)
return lloptarg
def attr_index(self, typ, attr):
return list(typ.attributes.keys()).index(attr)
@ -889,8 +954,8 @@ class LLVMIRGenerator:
def get_global_closure_ptr(self, typ, attr):
closure_type = typ.attributes[attr]
assert types.is_constructor(typ)
assert types.is_function(closure_type) or types.is_rpc(closure_type)
if types.is_external_function(closure_type) or types.is_rpc(closure_type):
assert types.is_function(closure_type) or types.is_rpc(closure_type) or types.is_subkernel(closure_type)
if types.is_external_function(closure_type) or types.is_rpc(closure_type) or types.is_subkernel(closure_type):
return None
llty = self.llty_of_type(typ.attributes[attr])
@ -1335,9 +1400,89 @@ class LLVMIRGenerator:
return self.llbuilder.call(self.llbuiltin("delay_mu"), [llinterval])
elif insn.op == "end_catch":
return self.llbuilder.call(self.llbuiltin("__artiq_end_catch"), [])
elif insn.op == "subkernel_await_finish":
llsid = self.map(insn.operands[0])
lltimeout = self.map(insn.operands[1])
return self.llbuilder.call(self.llbuiltin("subkernel_await_finish"), [llsid, lltimeout],
name="subkernel.await.finish")
elif insn.op == "subkernel_retrieve_return":
llsid = 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"),
[llsid, 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)
elif insn.op == "subkernel_preload":
llsid = self.map(insn.operands[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")
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:
assert False
def process_SubkernelAwaitArgs(self, insn):
llmin = self.map(insn.operands[0])
llmax = self.map(insn.operands[1])
lltagptr = self._build_subkernel_tags(insn.arg_types)
return self.llbuilder.call(self.llbuiltin("subkernel_await_message"),
[ll.Constant(lli32, -1), ll.Constant(lli64, 10_000), lltagptr, llmin, llmax],
name="subkernel.await.args")
def process_Closure(self, insn):
llenv = self.map(insn.environment())
llenv = self.llbuilder.bitcast(llenv, llptr)
@ -1417,11 +1562,78 @@ class LLVMIRGenerator:
return llfun, list(llargs), llarg_attrs, llcallstackptr
def _build_rpc(self, fun_loc, fun_type, args, llnormalblock, llunwindblock):
llservice = ll.Constant(lli32, fun_type.service)
def _build_subkernel_tags(self, tag_list):
def ret_error_handler(typ):
printer = types.TypePrinter()
note = diagnostic.Diagnostic("note",
"value of type {type}",
{"type": printer.name(typ)},
fun_loc)
diag = diagnostic.Diagnostic("error",
"type {type} is not supported in subkernels",
{"type": printer.name(fun_type.ret)},
fun_loc, notes=[note])
self.engine.process(diag)
tag = b"".join([ir.rpc_tag(arg_type, ret_error_handler) for arg_type in tag_list])
lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr()))
lltagptr = self.llbuilder.alloca(lltag.type)
self.llbuilder.store(lltag, lltagptr)
return lltagptr
def _build_rpc_recv(self, ret, llstackptr, llnormalblock=None, llunwindblock=None):
# T result = {
# void *ret_ptr = alloca(sizeof(T));
# void *ptr = ret_ptr;
# loop: int size = rpc_recv(ptr);
# // Non-zero: Provide `size` bytes of extra storage for variable-length data.
# if(size) { ptr = alloca(size); goto loop; }
# else *(T*)ret_ptr
# }
llprehead = self.llbuilder.basic_block
llhead = self.llbuilder.append_basic_block(name="rpc.head")
if llunwindblock:
llheadu = self.llbuilder.append_basic_block(name="rpc.head.unwind")
llalloc = self.llbuilder.append_basic_block(name="rpc.continue")
lltail = self.llbuilder.append_basic_block(name="rpc.tail")
llretty = self.llty_of_type(ret)
llslot = self.llbuilder.alloca(llretty, name="rpc.ret.alloc")
llslotgen = self.llbuilder.bitcast(llslot, llptr, name="rpc.ret.ptr")
self.llbuilder.branch(llhead)
self.llbuilder.position_at_end(llhead)
llphi = self.llbuilder.phi(llslotgen.type, name="rpc.ptr")
llphi.add_incoming(llslotgen, llprehead)
if llunwindblock:
llsize = self.llbuilder.invoke(self.llbuiltin("rpc_recv"), [llphi],
llheadu, llunwindblock,
name="rpc.size.next")
self.llbuilder.position_at_end(llheadu)
else:
llsize = self.llbuilder.call(self.llbuiltin("rpc_recv"), [llphi],
name="rpc.size.next")
lldone = self.llbuilder.icmp_unsigned('==', llsize, ll.Constant(llsize.type, 0),
name="rpc.done")
self.llbuilder.cbranch(lldone, lltail, llalloc)
self.llbuilder.position_at_end(llalloc)
llalloca = self.llbuilder.alloca(lli8, llsize, name="rpc.alloc")
llalloca.align = self.max_target_alignment
llphi.add_incoming(llalloca, llalloc)
self.llbuilder.branch(llhead)
self.llbuilder.position_at_end(lltail)
llret = self.llbuilder.load(llslot, name="rpc.ret")
if not ret.fold(False, lambda r, t: r or builtins.is_allocated(t)):
# We didn't allocate anything except the slot for the value itself.
# Don't waste stack space.
self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr])
if llnormalblock:
self.llbuilder.branch(llnormalblock)
return llret
def _build_arg_tag(self, args, call_type):
tag = b""
for arg in args:
def arg_error_handler(typ):
printer = types.TypePrinter()
@ -1430,12 +1642,18 @@ class LLVMIRGenerator:
{"type": printer.name(typ)},
arg.loc)
diag = diagnostic.Diagnostic("error",
"type {type} is not supported in remote procedure calls",
{"type": printer.name(arg.type)},
"type {type} is not supported in {call_type} calls",
{"type": printer.name(arg.type), "call_type": call_type},
arg.loc, notes=[note])
self.engine.process(diag)
tag += ir.rpc_tag(arg.type, arg_error_handler)
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):
printer = types.TypePrinter()
@ -1492,57 +1710,94 @@ class LLVMIRGenerator:
return ll.Undefined
# T result = {
# void *ret_ptr = alloca(sizeof(T));
# void *ptr = ret_ptr;
# loop: int size = rpc_recv(ptr);
# // Non-zero: Provide `size` bytes of extra storage for variable-length data.
# if(size) { ptr = alloca(size); goto loop; }
# else *(T*)ret_ptr
# }
llprehead = self.llbuilder.basic_block
llhead = self.llbuilder.append_basic_block(name="rpc.head")
if llunwindblock:
llheadu = self.llbuilder.append_basic_block(name="rpc.head.unwind")
llalloc = self.llbuilder.append_basic_block(name="rpc.continue")
lltail = self.llbuilder.append_basic_block(name="rpc.tail")
llret = self._build_rpc_recv(fun_type.ret, llstackptr, llnormalblock, llunwindblock)
llretty = self.llty_of_type(fun_type.ret)
llslot = self.llbuilder.alloca(llretty, name="rpc.ret.alloc")
llslotgen = self.llbuilder.bitcast(llslot, llptr, name="rpc.ret.ptr")
self.llbuilder.branch(llhead)
self.llbuilder.position_at_end(llhead)
llphi = self.llbuilder.phi(llslotgen.type, name="rpc.ptr")
llphi.add_incoming(llslotgen, llprehead)
if llunwindblock:
llsize = self.llbuilder.invoke(self.llbuiltin("rpc_recv"), [llphi],
llheadu, llunwindblock,
name="rpc.size.next")
self.llbuilder.position_at_end(llheadu)
else:
llsize = self.llbuilder.call(self.llbuiltin("rpc_recv"), [llphi],
name="rpc.size.next")
lldone = self.llbuilder.icmp_unsigned('==', llsize, ll.Constant(llsize.type, 0),
name="rpc.done")
self.llbuilder.cbranch(lldone, lltail, llalloc)
self.llbuilder.position_at_end(llalloc)
llalloca = self.llbuilder.alloca(lli8, llsize, name="rpc.alloc")
llalloca.align = 4 # maximum alignment required by OR1K ABI
llphi.add_incoming(llalloca, llalloc)
self.llbuilder.branch(llhead)
self.llbuilder.position_at_end(lltail)
llret = self.llbuilder.load(llslot, name="rpc.ret")
if not fun_type.ret.fold(False, lambda r, t: r or builtins.is_allocated(t)):
# We didn't allocate anything except the slot for the value itself.
# Don't waste stack space.
self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr])
if llnormalblock:
self.llbuilder.branch(llnormalblock)
return llret
def _build_subkernel_call(self, fun_loc, fun_type, args):
llsid = ll.Constant(lli32, fun_type.sid)
lldest = ll.Constant(lli8, fun_type.destination)
# run the kernel first
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")
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
name="subkernel.stack")
lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr()))
lltagptr = self.llbuilder.alloca(lltag.type)
self.llbuilder.store(lltag, lltagptr)
llargs = self.llbuilder.alloca(llptr, ll.Constant(lli32, len(args)),
name="subkernel.args")
for index, arg in enumerate(args):
if builtins.is_none(arg.type):
llargslot = self.llbuilder.alloca(llunit,
name="subkernel.arg{}".format(index))
else:
llarg = self.map(arg)
llargslot = self.llbuilder.alloca(llarg.type,
name="subkernel.arg{}".format(index))
self.llbuilder.store(llarg, llargslot)
llargslot = self.llbuilder.bitcast(llargslot, llptr)
llargptr = self.llbuilder.gep(llargs, [ll.Constant(lli32, index)])
self.llbuilder.store(llargslot, llargptr)
llargcount = ll.Constant(lli8, len(args))
llisreturn = ll.Constant(lli1, False)
self.llbuilder.call(self.llbuiltin("subkernel_send_message"),
[llid, llisreturn, lldest, llargcount, lltagptr, llargs])
return self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr])
def _build_subkernel_return(self, insn):
# builds a remote return.
# unlike args, return only sends one thing.
if builtins.is_none(insn.value().type):
# do not waste time and bandwidth on Nones
return
def ret_error_handler(typ):
printer = types.TypePrinter()
note = diagnostic.Diagnostic("note",
"value of type {type}",
{"type": printer.name(typ)},
fun_loc)
diag = diagnostic.Diagnostic("error",
"return type {type} is not supported in subkernel returns",
{"type": printer.name(fun_type.ret)},
fun_loc, notes=[note])
self.engine.process(diag)
tag = ir.rpc_tag(insn.value().type, ret_error_handler)
tag += b":"
lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr()))
lltagptr = self.llbuilder.alloca(lltag.type)
self.llbuilder.store(lltag, lltagptr)
llrets = self.llbuilder.alloca(llptr, ll.Constant(lli32, 1),
name="subkernel.return")
llret = self.map(insn.value())
llretslot = self.llbuilder.alloca(llret.type, name="subkernel.retval")
self.llbuilder.store(llret, llretslot)
llretslot = self.llbuilder.bitcast(llretslot, llptr)
self.llbuilder.store(llretslot, llrets)
llsid = ll.Constant(lli32, 0) # return goes back to the caller, sid is ignored
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"),
[llsid, llisreturn, lldest, lltagcount, lltagptr, llrets])
def process_Call(self, insn):
functiontyp = insn.target_function().type
if types.is_rpc(functiontyp):
@ -1550,6 +1805,10 @@ class LLVMIRGenerator:
functiontyp,
insn.arguments(),
llnormalblock=None, llunwindblock=None)
elif types.is_subkernel(functiontyp):
return self._build_subkernel_call(insn.target_function().loc,
functiontyp,
insn.arguments())
elif types.is_external_function(functiontyp):
llfun, llargs, llarg_attrs, llcallstackptr = self._prepare_ffi_call(insn)
else:
@ -1586,6 +1845,11 @@ class LLVMIRGenerator:
functiontyp,
insn.arguments(),
llnormalblock, llunwindblock)
elif types.is_subkernel(functiontyp):
return self._build_subkernel_call(insn.target_function().loc,
functiontyp,
insn.arguments(),
llnormalblock, llunwindblock)
elif types.is_external_function(functiontyp):
llfun, llargs, llarg_attrs, llcallstackptr = self._prepare_ffi_call(insn)
else:
@ -1664,7 +1928,8 @@ class LLVMIRGenerator:
attrvalue = getattr(value, attr)
is_class_function = (types.is_constructor(typ) and
types.is_function(typ.attributes[attr]) and
not types.is_external_function(typ.attributes[attr]))
not types.is_external_function(typ.attributes[attr]) and
not types.is_subkernel(typ.attributes[attr]))
if is_class_function:
attrvalue = self.embedding_map.specialize_function(typ.instance, attrvalue)
if not (types.is_instance(typ) and attr in typ.constant_attributes):
@ -1749,7 +2014,8 @@ class LLVMIRGenerator:
llelts = [self._quote(v, t, lambda: path() + [str(i)])
for i, (v, t) in enumerate(zip(value, typ.elts))]
return ll.Constant(llty, llelts)
elif types.is_rpc(typ) or types.is_external_function(typ) or types.is_builtin_function(typ):
elif types.is_rpc(typ) or types.is_external_function(typ) or \
types.is_builtin_function(typ) or types.is_subkernel(typ):
# RPC, C and builtin functions have no runtime representation.
return ll.Constant(llty, ll.Undefined)
elif types.is_function(typ):
@ -1804,6 +2070,8 @@ class LLVMIRGenerator:
return llinsn
def process_Return(self, insn):
if insn.remote_return:
self._build_subkernel_return(insn)
if builtins.is_none(insn.value().type):
return self.llbuilder.ret_void()
else:

View File

@ -385,6 +385,50 @@ class TRPC(Type):
def __hash__(self):
return hash(self.service)
class TSubkernel(TFunction):
"""
A kernel to be run on a satellite.
:ivar args: (:class:`collections.OrderedDict` of string to :class:`Type`)
function arguments
:ivar ret: (:class:`Type`)
return type
:ivar sid: (int) subkernel ID number
:ivar destination: (int) satellite destination number
"""
attributes = OrderedDict()
def __init__(self, args, optargs, ret, sid, destination):
assert isinstance(ret, Type)
super().__init__(args, optargs, ret)
self.sid, self.destination = sid, destination
self.delay = TFixedDelay(iodelay.Const(0))
def unify(self, other):
if other is self:
return
if isinstance(other, TSubkernel) and \
self.sid == other.sid and \
self.destination == other.destination:
self.ret.unify(other.ret)
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TSubkernel({})".format(repr(self.ret))
def __eq__(self, other):
return isinstance(other, TSubkernel) and \
self.sid == other.sid
def __hash__(self):
return hash(self.sid)
class TBuiltin(Type):
"""
An instance of builtin type. Every instance of a builtin
@ -644,6 +688,9 @@ def is_function(typ):
def is_rpc(typ):
return isinstance(typ.find(), TRPC)
def is_subkernel(typ):
return isinstance(typ.find(), TSubkernel)
def is_external_function(typ, name=None):
typ = typ.find()
if name is None:
@ -810,6 +857,10 @@ class TypePrinter(object):
return "[rpc{} #{}](...)->{}".format(typ.service,
" async" if typ.is_async else "",
self.name(typ.ret, depth + 1))
elif isinstance(typ, TSubkernel):
return "<subkernel{} dest#{}>->{}".format(typ.sid,
typ.destination,
self.name(typ.ret, depth + 1))
elif isinstance(typ, TBuiltinFunction):
return "<function {}>".format(typ.name)
elif isinstance(typ, (TConstructor, TExceptionConstructor)):

View File

@ -102,7 +102,19 @@ class RegionOf(algorithm.Visitor):
if types.is_external_function(node.func.type, "cache_get"):
# The cache is borrow checked dynamically
return Global()
else:
if (types.is_builtin_function(node.func.type, "array")
or types.is_builtin_function(node.func.type, "make_array")
or types.is_builtin_function(node.func.type, "numpy.transpose")):
# While lifetime tracking across function calls in general is currently
# broken (see below), these special builtins that allocate an array on
# the stack of the caller _always_ allocate regardless of the parameters,
# and we can thus handle them without running into the precision issue
# mentioned in commit ae999db.
return self.visit_allocating(node)
# FIXME: Return statement missing here, but see m-labs/artiq#1497 and
# commit ae999db.
self.visit_sometimes_allocating(node)
# Value lives as long as the object/container, if it's mutable,

View File

@ -1,8 +1,8 @@
""""RTIO driver for the Analog Devices AD53[67][0123] family of multi-channel
"""RTIO driver for the Analog Devices AD53[67][0123] family of multi-channel
Digital to Analog Converters.
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
@ -131,10 +131,10 @@ class AD53xx:
optimized for speed; datasheet says t22: 25ns min SCLK edge to SDO
valid, and suggests the SPI speed for reads should be <=20 MHz)
:param vref: DAC reference voltage (default: 5.)
:param offset_dacs: Initial register value for the two offset DACs, device
dependent and must be set correctly for correct voltage to mu
conversions. Knowledge of his state is not transferred between
experiments. (default: 8192)
:param offset_dacs: Initial register value for the two offset DACs
(default: 8192). Device dependent and must be set correctly for
correct voltage-to-mu conversions. Knowledge of this state is
not transferred between experiments.
:param core_device: Core device name (default: "core")
"""
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`,
:const:`AD53XX_READ_X1B`, :const:`AD53XX_READ_OFFSET`,
: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.set_config_mu(SPI_AD53XX_CONFIG | spi.SPI_INPUT, 24,
@ -233,7 +233,7 @@ class AD53xx:
def write_gain_mu(self, channel, gain=0xffff):
"""Program the gain register for a DAC channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:).
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
This method advances the timeline by the duration of one SPI transfer.
:param gain: 16-bit gain register value (default: 0xffff)
@ -245,7 +245,7 @@ class AD53xx:
def write_offset_mu(self, channel, offset=0x8000):
"""Program the offset register for a DAC channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:).
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
This method advances the timeline by the duration of one SPI transfer.
:param offset: 16-bit offset register value (default: 0x8000)
@ -258,7 +258,7 @@ class AD53xx:
"""Program the DAC offset voltage for a channel.
An offset of +V can be used to trim out a DAC offset error of -V.
The DAC output is not updated until LDAC is pulsed (see :meth load:).
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
This method advances the timeline by the duration of one SPI transfer.
:param voltage: the offset voltage
@ -270,7 +270,7 @@ class AD53xx:
def write_dac_mu(self, channel, value):
"""Program the DAC input register for a channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:).
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
This method advances the timeline by the duration of one SPI transfer.
"""
self.bus.write(
@ -280,7 +280,7 @@ class AD53xx:
def write_dac(self, channel, voltage):
"""Program the DAC output voltage for a channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:).
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
This method advances the timeline by the duration of one SPI transfer.
"""
self.write_dac_mu(channel, voltage_to_mu(voltage, self.offset_dacs,
@ -309,11 +309,11 @@ class AD53xx:
This method does not advance the timeline; write events are scheduled
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.
See :meth load:.
See :meth:`load`.
:param values: list of DAC values to program
:param channels: list of DAC channels to program. If not specified,
@ -355,7 +355,7 @@ class AD53xx:
""" Two-point calibration of a DAC channel.
Programs the offset and gain register to trim out DAC errors. Does not
take effect until LDAC is pulsed (see :meth load:).
take effect until LDAC is pulsed (see :meth:`load`).
Calibration consists of measuring the DAC output voltage for a channel
with the DAC set to zero-scale (0x0000) and full-scale (0xffff).
@ -364,8 +364,8 @@ class AD53xx:
high) can be calibrated in this fashion.
:param channel: The number of the calibrated channel
:params vzs: Measured voltage with the DAC set to zero-scale (0x0000)
:params vfs: Measured voltage with the DAC set to full-scale (0xffff)
:param vzs: Measured voltage with the DAC set to zero-scale (0x0000)
:param vfs: Measured voltage with the DAC set to full-scale (0xffff)
"""
offset_err = voltage_to_mu(vzs, self.offset_dacs, self.vref)
gain_err = voltage_to_mu(vfs, self.offset_dacs, self.vref) - (

File diff suppressed because it is too large Load Diff

View File

@ -1,23 +0,0 @@
from artiq.language.core import kernel
class AD9154:
"""Kernel interface to AD9154 registers, using non-realtime SPI."""
def __init__(self, dmgr, spi_device, chip_select):
self.core = dmgr.get("core")
self.bus = dmgr.get(spi_device)
self.chip_select = chip_select
@kernel
def setup_bus(self, div=16):
self.bus.set_config_mu(0, 24, div, self.chip_select)
@kernel
def write(self, addr, data):
self.bus.write((addr << 16) | (data<< 8))
@kernel
def read(self, addr):
self.write((1 << 15) | addr, 0)
return self.bus.read()

View File

@ -114,27 +114,27 @@ class AD9910:
(as configured through CFG_MASK_NU), 4-7 for individual channels.
: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
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
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
``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
Urukul CPLD instance).
: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.
:param pll_cp: DDS PLL charge pump setting.
:param pll_vco: DDS PLL VCO range selection.
:param sync_delay_seed: SYNC_IN delay tuning starting value.
To stabilize the SYNC_IN delay tuning, run :meth:`tune_sync_delay` once
:param sync_delay_seed: ``SYNC_IN`` delay tuning starting value.
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
synchronization and no tuning during :meth:`init`).
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
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
to the same string value.
:param io_update_delay: IO_UPDATE pulse alignment delay.
To align IO_UPDATE to SYNC_CLK, run :meth:`tune_io_update_delay` and
:param io_update_delay: ``IO_UPDATE`` pulse alignment delay.
To align ``IO_UPDATE`` to ``SYNC_CLK``, run :meth:`tune_io_update_delay` and
set this to the delay tap number returned.
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
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
to the same string value.
"""
@ -188,9 +188,7 @@ class AD9910:
@kernel
def set_phase_mode(self, phase_mode: TInt32):
r"""Set the default phase mode.
for future calls to :meth:`set` and
r"""Set the default phase mode for future calls to :meth:`set` and
:meth:`set_mu`. Supported phase modes are:
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged
@ -233,7 +231,7 @@ class AD9910:
@kernel
def write16(self, addr: TInt32, data: TInt32):
"""Write to 16 bit register.
"""Write to 16-bit register.
:param addr: Register address
:param data: Data to be written
@ -244,7 +242,7 @@ class AD9910:
@kernel
def write32(self, addr: TInt32, data: TInt32):
"""Write to 32 bit register.
"""Write to 32-bit register.
:param addr: Register address
:param data: Data to be written
@ -258,7 +256,7 @@ class AD9910:
@kernel
def read16(self, addr: TInt32) -> TInt32:
"""Read from 16 bit register.
"""Read from 16-bit register.
:param addr: Register address
"""
@ -273,7 +271,7 @@ class AD9910:
@kernel
def read32(self, addr: TInt32) -> TInt32:
"""Read from 32 bit register.
"""Read from 32-bit register.
:param addr: Register address
"""
@ -288,10 +286,10 @@ class AD9910:
@kernel
def read64(self, addr: TInt32) -> TInt64:
"""Read from 64 bit register.
"""Read from 64-bit register.
:param addr: Register address
:return: 64 bit integer register value
:return: 64-bit integer register value
"""
self.bus.set_config_mu(
urukul.SPI_CONFIG, 8,
@ -311,10 +309,10 @@ class AD9910:
@kernel
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 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
"""
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
@ -332,8 +330,9 @@ class AD9910:
"""Write data to RAM.
The profile to write to and the step, start, and end address
need to be configured before and separately using
:meth:`set_profile_ram` and the parent CPLD `set_profile`.
need to be configured in advance and separately using
:meth:`set_profile_ram` and the parent CPLD
:meth:`~artiq.coredevice.urukul.CPLD.set_profile`.
: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
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.
"""
@ -390,9 +390,9 @@ class AD9910:
manual_osk_external: TInt32 = 0,
osk_enable: 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 phase_autoclear: Autoclear phase accumulator.
@ -429,9 +429,9 @@ class AD9910:
effective_ftw: TInt32 = 1,
sync_validation_disable: 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 drg_enable: Digital ramp enable.
@ -456,14 +456,14 @@ class AD9910:
"""Initialize and configure the DDS.
Sets up SPI mode, confirms chip presence, powers down unused blocks,
configures the PLL, waits for PLL lock. Uses the
IO_UPDATE signal multiple times.
configures the PLL, waits for PLL lock. Uses the ``IO_UPDATE``
signal multiple times.
:param blind: Do not read back DDS identity and do not wait for lock.
"""
self.sync_data.init()
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.sysclk_per_mu != self.sysclk * self.core.ref_period:
raise ValueError("incorrect clock ratio for synchronization")
@ -514,7 +514,7 @@ class AD9910:
def power_down(self, bits: TInt32 = 0b1111):
"""Power down DDS.
:param bits: Power down bits, see datasheet
:param bits: Power-down bits, see datasheet
"""
self.set_cfr1(power_down=bits)
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
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.
:param ftw: Frequency tuning word: 32 bit.
:param pow_: Phase tuning word: 16 bit unsigned.
:param asf: Amplitude scale factor: 14 bit unsigned.
:param ftw: Frequency tuning word: 32-bit.
:param pow_: Phase tuning word: 16-bit unsigned.
:param asf: Amplitude scale factor: 14-bit unsigned.
:param phase_mode: If specified, overrides the default phase mode set
by :meth:`set_phase_mode` for this call.
: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).
Ineffective if `ram_destination` is specified.
Ineffective if ``ram_destination`` is specified.
:param ram_destination: RAM destination (:const:`RAM_DEST_FTW`,
:const:`RAM_DEST_POW`, :const:`RAM_DEST_ASF`,
:const:`RAM_DEST_POWASF`). If specified, write free DDS parameters
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
tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in
subsequent calls, use this value as the "current" phase.
@ -598,10 +598,10 @@ class AD9910:
"""Get the frequency tuning word, phase offset word,
and amplitude scale factor.
.. seealso:: :meth:`get`
See also :meth:`AD9910.get`.
:param profile: Profile number to get (0-7, default: 7)
:return: A tuple ``(ftw, pow, asf)``
:return: A tuple (FTW, POW, ASF)
"""
# Read data
@ -617,12 +617,12 @@ class AD9910:
profile: TInt32 = _DEFAULT_PROFILE_RAM,
nodwell_high: TInt32 = 0, zero_crossing: TInt32 = 0,
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 end: Profile end address in RAM (last address).
:param step: Profile time step in units of t_DDS, typically 4 ns
(default: 1).
:param start: Profile start address in RAM (10-bit).
:param end: Profile end address in RAM, inclusive (10-bit).
:param step: Profile time step, counted in DDS sample clock
cycles, typically 4 ns (16-bit, default: 1)
:param profile: Profile index (0 to 7) (default: 0).
:param nodwell_high: No-dwell high bit (default: 0,
see AD9910 documentation).
@ -850,7 +850,7 @@ class AD9910:
ram_destination: TInt32 = -1) -> TFloat:
"""Set DDS data in SI units.
.. seealso:: :meth:`set_mu`
See also :meth:`AD9910.set_mu`.
:param frequency: Frequency in Hz
:param phase: Phase tuning word in turns
@ -871,10 +871,10 @@ class AD9910:
) -> TTuple([TFloat, TFloat, TFloat]):
"""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)
:return: A tuple ``(frequency, phase, amplitude)``
:return: A tuple (frequency, phase, amplitude)
"""
# Get values
@ -887,11 +887,10 @@ class AD9910:
def set_att_mu(self, att: TInt32):
"""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)
@ -899,9 +898,8 @@ class AD9910:
def set_att(self, att: TFloat):
"""Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.set_att`
This method will write the attenuator settings of all four channels. See also
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.set_att>`.
:param att: Attenuation in dB.
"""
@ -909,19 +907,17 @@ class AD9910:
@kernel
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)
@kernel
def get_att(self) -> TFloat:
"""Get digital step attenuator value in SI units.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att`
"""Get digital step attenuator value in SI units. See also
:meth:`CPLD.get_channel_att <artiq.coredevice.urukul.CPLD.get_channel_att>`.
:return: Attenuation in dB.
"""
@ -943,16 +939,16 @@ class AD9910:
window: TInt32,
en_sync_gen: TInt32 = 0):
"""Set the relevant parameters in the multi device synchronization
register. See the AD9910 datasheet for details. The SYNC clock
generator preset value is set to zero, and the SYNC_OUT generator is
register. See the AD9910 datasheet for details. The ``SYNC`` clock
generator preset value is set to zero, and the ``SYNC_OUT`` generator is
disabled by default.
:param in_delay: SYNC_IN delay tap (0-31) in steps of ~75ps
:param window: Symmetric SYNC_IN validation window (0-15) in
:param in_delay: ``SYNC_IN`` delay tap (0-31) in steps of ~75ps
:param window: Symmetric ``SYNC_IN`` validation window (0-15) in
steps of ~75ps for both hold and setup margin.
: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
use case, where the SYNC clock is supplied by the core device.
(``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.
"""
self.write32(_AD9910_REG_SYNC,
(window << 28) | # SYNC S/H validation delay
@ -965,9 +961,9 @@ class AD9910:
@kernel
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
the respective Urukul channel.
@ -982,9 +978,9 @@ class AD9910:
@kernel
def tune_sync_delay(self,
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
then looks for similar valid delays at successively larger 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.
: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).
:return: Tuple of optimal delay and window size.
"""
if not self.cpld.sync_div:
raise ValueError("parent cpld does not drive SYNC")
search_span = 31
search_span = 13
# FIXME https://github.com/sinara-hw/Urukul/issues/16
# should both be 2-4 once kasli sync_in jitter is identified
min_window = 0
@ -1040,16 +1036,16 @@ class AD9910:
def measure_io_update_alignment(self, delay_start: TInt64,
delay_stop: TInt64) -> TInt32:
"""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
(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_stop`.
``(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_stop``.
:param delay_start: Start IO_UPDATE delay in machine units.
:param delay_stop: Stop IO_UPDATE delay in machine units.
:return: Odd/even SYNC_CLK cycle indicator.
:param delay_start: Start ``IO_UPDATE`` delay in machine units.
:param delay_stop: Stop ``IO_UPDATE`` delay in machine units.
:return: Odd/even ``SYNC_CLK`` cycle indicator.
"""
# set up DRG
self.set_cfr1(drg_load_lrr=1, drg_autoclear=1)
@ -1081,19 +1077,19 @@ class AD9910:
@kernel
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
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
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
``IO_UPDATE`` delay that is as far away from that ``SYNC_CLK`` edge
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).
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.
"""
period = self.sysclk_per_mu * 4 # SYNC_CLK period

View File

@ -11,7 +11,7 @@ from artiq.coredevice import urukul
class AD9912:
"""
AD9912 DDS channel on Urukul
AD9912 DDS channel on Urukul.
This class supports a single DDS channel and exposes the DDS,
the digital step attenuator, and the RF switch.
@ -22,15 +22,17 @@ class AD9912:
: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.
: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
is the reference clock divider (both set in the parent Urukul CPLD
instance).
``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
Urukul CPLD instance).
: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.
"""
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
pll_n=10):
pll_n=10, pll_en=1):
self.kernel_invariants = {"cpld", "core", "bus", "chip_select",
"pll_n", "ftw_per_hz"}
"pll_n", "pll_en", "ftw_per_hz"}
self.cpld = dmgr.get(cpld_device)
self.core = self.cpld.core
self.bus = self.cpld.bus
@ -39,8 +41,16 @@ class AD9912:
if sw_device:
self.sw = dmgr.get(sw_device)
self.kernel_invariants.add("sw")
self.pll_en = pll_en
self.pll_n = pll_n
sysclk = self.cpld.refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n
if pll_en:
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:
sysclk = self.cpld.refclk
assert sysclk <= 1e9
self.ftw_per_hz = 1 / sysclk * (int64(1) << 48)
@ -91,7 +101,7 @@ class AD9912:
Sets up SPI mode, confirms chip presence, powers down unused blocks,
and configures the PLL. Does not wait for PLL lock. Uses the
IO_UPDATE signal multiple times.
``IO_UPDATE`` signal multiple times.
"""
# SPI mode
self.write(AD9912_SER_CONF, 0x99, length=1)
@ -102,11 +112,17 @@ class AD9912:
raise ValueError("Urukul AD9912 product id mismatch")
delay(50 * us)
# HSTL power down, CMOS power down
self.write(AD9912_PWRCNTRL1, 0x80, length=1)
pwrcntrl1 = 0x80 | ((~self.pll_en & 1) << 4)
self.write(AD9912_PWRCNTRL1, pwrcntrl1, length=1)
self.cpld.io_update.pulse(2 * us)
if self.pll_en:
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1)
self.cpld.io_update.pulse(2 * us)
# I_cp = 375 µA, VCO high range
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)
delay(1 * ms)
@ -117,9 +133,9 @@ class AD9912:
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)
@ -129,7 +145,7 @@ class AD9912:
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.
"""
@ -139,9 +155,9 @@ class AD9912:
def get_att_mu(self) -> TInt32:
"""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)
@ -149,7 +165,7 @@ class AD9912:
def get_att(self) -> TFloat:
"""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.
"""
@ -162,8 +178,8 @@ class AD9912:
After the SPI transfer, the shared IO update pin is pulsed to
activate the data.
:param ftw: Frequency tuning word: 48 bit unsigned.
:param pow_: Phase tuning word: 16 bit unsigned.
:param ftw: Frequency tuning word: 48-bit unsigned.
:param pow_: Phase tuning word: 16-bit unsigned.
"""
# streaming transfer of FTW and POW
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
@ -181,9 +197,9 @@ class AD9912:
def get_mu(self) -> TTuple([TInt64, TInt32]):
"""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
@ -231,7 +247,7 @@ class AD9912:
def set(self, frequency: TFloat, phase: TFloat = 0.0):
"""Set profile 0 data in SI units.
.. seealso:: :meth:`set_mu`
See also :meth:`AD9912.set_mu`.
:param frequency: Frequency in Hz
:param phase: Phase tuning word in turns
@ -243,9 +259,9 @@ class AD9912:
def get(self) -> TTuple([TFloat, TFloat]):
"""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

View File

@ -49,7 +49,7 @@ class AD9914:
The time cursor is not modified by any function in this class.
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
phase-locked multiple of the RTIO clock.
@ -80,6 +80,13 @@ class AD9914:
self.set_x_duration_mu = 7 * self.write_duration_mu
self.exit_x_duration_mu = 3 * self.write_duration_mu
@staticmethod
def get_rtio_channels(bus_channel, channel, **kwargs):
# return only first entry, as there are several devices with the same RTIO channel
if channel == 0:
return [(bus_channel, None)]
return []
@kernel
def write(self, addr, data):
rtio_output((self.bus_channel << 8) | addr, data)
@ -127,7 +134,7 @@ class AD9914:
timing margin.
: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)
self.write(AD9914_GPIO, (1 << self.channel) << 1)

View File

@ -73,6 +73,10 @@ class ADF5356:
self._init_registers()
@staticmethod
def get_rtio_channels(**kwargs):
return []
@kernel
def init(self, blind=False):
"""
@ -80,10 +84,11 @@ class ADF5356:
:param blind: Do not attempt to verify presence.
"""
self.sync()
if not blind:
# MUXOUT = VDD
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 1)
self.sync()
self.write(self.regs[4])
delay(1000 * us)
if not self.read_muxout():
raise ValueError("MUXOUT not high")
@ -91,7 +96,7 @@ class ADF5356:
# MUXOUT = DGND
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 2)
self.sync()
self.write(self.regs[4])
delay(1000 * us)
if self.read_muxout():
raise ValueError("MUXOUT not low")
@ -99,8 +104,7 @@ class ADF5356:
# MUXOUT = digital lock-detect
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 6)
else:
self.sync()
self.write(self.regs[4])
@kernel
def set_att(self, att):
@ -108,7 +112,7 @@ class ADF5356:
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.
"""
@ -118,7 +122,7 @@ class ADF5356:
def set_att_mu(self, att):
"""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)
@ -527,14 +531,14 @@ class ADF5356:
@portable
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))))
@portable
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])
t = ADF5356_REG4_R_DIVIDER_GET(self.regs[4])
@ -561,14 +565,15 @@ def calculate_pll(f_vco: TInt64, f_pfd: TInt64):
"""
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
``mod1 = 2**24`` and ``mod2 <= 2**28``
:param f_vco: target VCO 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_vco = int64(f_vco)

182
artiq/coredevice/almazny.py Normal file
View File

@ -0,0 +1,182 @@
from artiq.language.core import kernel, portable
from artiq.language.units import us
from numpy import int32
# almazny-specific data
ALMAZNY_LEGACY_REG_BASE = 0x0C
ALMAZNY_LEGACY_OE_SHIFT = 12
# higher SPI write divider to match almazny shift register timing
# min SER time before SRCLK rise = 125ns
# -> div=32 gives 125ns for data before clock rise
# works at faster dividers too but could be less reliable
ALMAZNY_LEGACY_SPIT_WR = 32
class AlmaznyLegacy:
"""
Almazny (High-frequency mezzanine board for Mirny)
This applies to Almazny hardware v1.1 and earlier.
Use :class:`~artiq.coredevice.almazny.AlmaznyChannel` for Almazny v1.2 and later.
:param host_mirny: :class:`~artiq.coredevice.mirny.Mirny` device Almazny is connected to
"""
def __init__(self, dmgr, host_mirny):
self.mirny_cpld = dmgr.get(host_mirny)
self.att_mu = [0x3f] * 4
self.channel_sw = [0] * 4
self.output_enable = False
@kernel
def init(self):
self.output_toggle(self.output_enable)
@kernel
def att_to_mu(self, att):
"""
Convert an attenuator setting in dB to machine units.
:param att: attenuator setting in dB [0-31.5]
:return: attenuator setting in machine units
"""
mu = round(att * 2.0)
if mu > 63 or mu < 0:
raise ValueError("Invalid Almazny attenuator settings!")
return mu
@kernel
def mu_to_att(self, att_mu):
"""
Convert a digital attenuator setting to dB.
:param att_mu: attenuator setting in machine units
:return: attenuator setting in dB
"""
return att_mu / 2
@kernel
def set_att(self, channel, att, rf_switch=True):
"""
Sets attenuators on chosen shift register (channel).
:param channel: index of the register [0-3]
:param att: attenuation setting in dBm [0-31.5]
:param rf_switch: rf switch (bool)
"""
self.set_att_mu(channel, self.att_to_mu(att), rf_switch)
@kernel
def set_att_mu(self, channel, att_mu, rf_switch=True):
"""
Sets attenuators on chosen shift register (channel).
:param channel: index of the register [0-3]
:param att_mu: attenuation setting in machine units [0-63]
:param rf_switch: rf switch (bool)
"""
self.channel_sw[channel] = 1 if rf_switch else 0
self.att_mu[channel] = att_mu
self._update_register(channel)
@kernel
def output_toggle(self, oe):
"""
Toggles output on all shift registers on or off.
:param oe: toggle output enable (bool)
"""
self.output_enable = oe
cfg_reg = self.mirny_cpld.read_reg(1)
en = 1 if self.output_enable else 0
delay(100 * us)
new_reg = (en << ALMAZNY_LEGACY_OE_SHIFT) | (cfg_reg & 0x3FF)
self.mirny_cpld.write_reg(1, new_reg)
delay(100 * us)
@kernel
def _flip_mu_bits(self, mu):
# in this form MSB is actually 0.5dB attenuator
# unnatural for users, so we flip the six bits
return (((mu & 0x01) << 5)
| ((mu & 0x02) << 3)
| ((mu & 0x04) << 1)
| ((mu & 0x08) >> 1)
| ((mu & 0x10) >> 3)
| ((mu & 0x20) >> 5))
@kernel
def _update_register(self, ch):
self.mirny_cpld.write_ext(
ALMAZNY_LEGACY_REG_BASE + ch,
8,
self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6),
ALMAZNY_LEGACY_SPIT_WR
)
delay(100 * us)
class AlmaznyChannel:
"""
Driver for one Almazny channel.
Almazny is a mezzanine for the Quad PLL RF source Mirny that exposes and
controls the frequency-doubled outputs.
This driver requires Almazny hardware revision v1.2 or later
and Mirny CPLD gateware v0.3 or later.
Use :class:`~artiq.coredevice.almazny.AlmaznyLegacy` for Almazny hardware v1.1 and earlier.
:param host_mirny: Mirny CPLD device name
:param channel: channel index (0-3)
"""
def __init__(self, dmgr, host_mirny, channel):
self.channel = channel
self.mirny_cpld = dmgr.get(host_mirny)
@portable
def to_mu(self, att, enable, led):
"""
Convert an attenuation in dB, RF switch state and LED state to machine
units.
:param att: attenuator setting in dB (0-31.5)
:param enable: RF switch state (bool)
:param led: LED state (bool)
:return: channel setting in machine units
"""
mu = int32(round(att * 2.))
if mu >= 64 or mu < 0:
raise ValueError("Attenuation out of range")
# unfortunate hardware design: bit reverse
mu = ((mu & 0x15) << 1) | ((mu >> 1) & 0x15)
mu = ((mu & 0x03) << 4) | (mu & 0x0c) | ((mu >> 4) & 0x03)
if enable:
mu |= 1 << 6
if led:
mu |= 1 << 7
return mu
@kernel
def set_mu(self, mu):
"""
Set channel state (machine units).
:param mu: channel state in machine units.
"""
self.mirny_cpld.write_ext(
addr=0xc + self.channel, length=8, data=mu, ext_div=32)
@kernel
def set(self, att, enable, led=False):
"""
Set attenuation, RF switch, and LED state (SI units).
:param att: attenuator setting in dB (0-31.5)
:param enable: RF switch state (bool)
:param led: LED state (bool)
"""
self.set_mu(self.to_mu(att, enable, led))

View File

@ -1,79 +0,0 @@
from artiq.language.core import kernel, portable, delay
from artiq.language.units import us, ms
from artiq.coredevice.shiftreg import ShiftReg
@portable
def to_mu(att):
return round(att*2.0) ^ 0x3f
@portable
def from_mu(att_mu):
return 0.5*(att_mu ^ 0x3f)
class BaseModAtt:
def __init__(self, dmgr, rst_n, clk, le, mosi, miso):
self.rst_n = dmgr.get(rst_n)
self.shift_reg = ShiftReg(dmgr,
clk=clk, ser=mosi, latch=le, ser_in=miso, n=8*4)
@kernel
def reset(self):
# HMC's incompetence in digital design and interfaces means that
# the HMC542 needs a level low on RST_N and then a rising edge
# on Latch Enable. Their "latch" isn't a latch but a DFF.
# Of course, it also powers up with a random attenuation, and
# that cannot be fixed with simple pull-ups/pull-downs.
self.rst_n.off()
self.shift_reg.latch.off()
delay(1*us)
self.shift_reg.latch.on()
delay(1*us)
self.shift_reg.latch.off()
self.rst_n.on()
delay(1*us)
@kernel
def set_mu(self, att0, att1, att2, att3):
"""
Sets the four attenuators on BaseMod.
The values are in half decibels, between 0 (no attenuation)
and 63 (31.5dB attenuation).
"""
word = (
(att0 << 2) |
(att1 << 10) |
(att2 << 18) |
(att3 << 26)
)
self.shift_reg.set(word)
@kernel
def get_mu(self):
"""
Retrieves the current settings of the four attenuators on BaseMod.
"""
word = self.shift_reg.get()
att0 = (word >> 2) & 0x3f
att1 = (word >> 10) & 0x3f
att2 = (word >> 18) & 0x3f
att3 = (word >> 26) & 0x3f
return att0, att1, att2, att3
@kernel
def set(self, att0, att1, att2, att3):
"""
Sets the four attenuators on BaseMod.
The values are in decibels.
"""
self.set_mu(to_mu(att0), to_mu(att1), to_mu(att2), to_mu(att3))
@kernel
def get(self):
"""
Retrieves the current settings of the four attenuators on BaseMod.
The values are in decibels.
"""
att0, att1, att2, att3 = self.get_mu()
return from_mu(att0), from_mu(att1), from_mu(att2), from_mu(att3)

View File

@ -21,9 +21,9 @@ class CoreCache:
"""Extract a value from the core device cache.
After a value is extracted, it cannot be replaced with another value using
: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.
The value is not copied, so mutating it will change what's stored in the cache.

View File

@ -2,15 +2,22 @@ from operator import itemgetter
from collections import namedtuple
from itertools import count
from contextlib import contextmanager
from sipyco import keepalive
import asyncio
from enum import Enum
import struct
import logging
import socket
import math
logger = logging.getLogger(__name__)
DEFAULT_REF_PERIOD = 1e-9
ANALYZER_MAGIC = b"ARTIQ Analyzer Proxy\n"
class MessageType(Enum):
output = 0b00
input = 0b01
@ -34,6 +41,13 @@ class ExceptionType(Enum):
i_overflow = 0b100001
class WaveformType(Enum):
ANALOG = 0
BIT = 1
VECTOR = 2
LOG = 3
def get_analyzer_dump(host, port=1382):
sock = socket.create_connection((host, port))
try:
@ -102,28 +116,98 @@ def decode_dump(data):
# messages are big endian
parts = struct.unpack(endian + "IQbbb", data[:15])
(sent_bytes, total_byte_count,
error_occured, 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
if expected_len != len(data):
raise ValueError("analyzer dump has incorrect length "
"(got {}, expected {})".format(
len(data), expected_len))
if error_occured:
logger.warning("error occured within the analyzer, "
if error_occurred:
logger.warning("error occurred within the analyzer, "
"data may be corrupted")
if total_byte_count > sent_bytes:
logger.info("analyzer ring buffer has wrapped %d times",
total_byte_count//sent_bytes)
if sent_bytes == 0:
logger.warning("analyzer dump is empty")
position = 15
messages = []
for _ in range(sent_bytes//32):
messages.append(decode_message(data[position: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)
# 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():
codechars = [chr(i) for i in range(33, 127)]
for n in count():
@ -150,38 +234,129 @@ class VCDChannel:
integer_cast = struct.unpack(">Q", struct.pack(">d", x))[0]
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:
def __init__(self, fileobj):
self.out = fileobj
self.codes = vcd_codes()
self.current_time = None
self.start_time = 0
def set_timescale_ps(self, 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)
self.out.write("$var wire {width} {code} {name} $end\n"
.format(name=name, code=code, width=width))
return VCDChannel(self.out, code)
@contextmanager
def scope(self, name):
self.out.write("$scope module {} $end\n".format(name))
def scope(self, scope, name):
self.out.write("$scope module {}/{} $end\n".format(scope, name))
yield
self.out.write("$upscope $end\n")
def set_time(self, time):
time -= self.start_time
if time != self.current_time:
self.out.write("#{}\n".format(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:
def __init__(self, vcd_manager, name):
def __init__(self, manager, 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.oe = True
@ -206,11 +381,12 @@ class TTLHandler:
class TTLClockGenHandler:
def __init__(self, vcd_manager, name, ref_period):
def __init__(self, manager, name, ref_period):
self.name = name
self.ref_period = ref_period
self.channel_frequency = vcd_manager.get_channel(
"ttl_clkgen/" + name, 64)
precision = max(0, math.ceil(math.log10(2**24 * ref_period) + 6))
self.channel_frequency = manager.get_channel(
"ttl_clkgen/" + name, 64, ty=WaveformType.ANALOG, precision=precision, unit="MHz")
def process_message(self, message):
if isinstance(message, OutputMessage):
@ -221,8 +397,8 @@ class TTLClockGenHandler:
class DDSHandler:
def __init__(self, vcd_manager, onehot_sel, sysclk):
self.vcd_manager = vcd_manager
def __init__(self, manager, onehot_sel, sysclk):
self.manager = manager
self.onehot_sel = onehot_sel
self.sysclk = sysclk
@ -231,11 +407,18 @@ class DDSHandler:
def add_dds_channel(self, name, dds_channel_nr):
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"] = \
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"] = \
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["pow"] = None
self.dds_channels[dds_channel_nr] = dds_channel
@ -285,10 +468,10 @@ class DDSHandler:
class WishboneHandler:
def __init__(self, vcd_manager, name, read_bit):
def __init__(self, manager, name, read_bit):
self._reads = []
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):
self.stb.set_value("1")
@ -318,16 +501,17 @@ class WishboneHandler:
class SPIMasterHandler(WishboneHandler):
def __init__(self, vcd_manager, name):
def __init__(self, manager, name):
self.channels = {}
with vcd_manager.scope("spi/{}".format(name)):
super().__init__(vcd_manager, name, read_bit=0b100)
self.scope = "spi"
with manager.scope("spi", name):
super().__init__(manager, name, read_bit=0b100)
for reg_name, reg_width in [
("config", 32), ("chip_select", 16),
("write_length", 8), ("read_length", 8),
("write", 32), ("read", 32)]:
self.channels[reg_name] = vcd_manager.get_channel(
"{}/{}".format(name, reg_name), reg_width)
self.channels[reg_name] = manager.get_channel(
"{}/{}".format(name, reg_name), reg_width, ty=WaveformType.VECTOR)
def process_write(self, address, data):
if address == 0:
@ -352,11 +536,12 @@ class SPIMasterHandler(WishboneHandler):
class SPIMaster2Handler(WishboneHandler):
def __init__(self, vcd_manager, name):
def __init__(self, manager, name):
self._reads = []
self.channels = {}
with vcd_manager.scope("spi2/{}".format(name)):
self.stb = vcd_manager.get_channel("{}/{}".format(name, "stb"), 1)
self.scope = "spi2"
with manager.scope("spi2", name):
self.stb = manager.get_channel(name + "/stb", 1, ty=WaveformType.BIT)
for reg_name, reg_width in [
("flags", 8),
("length", 5),
@ -364,8 +549,8 @@ class SPIMaster2Handler(WishboneHandler):
("chip_select", 8),
("write", 32),
("read", 32)]:
self.channels[reg_name] = vcd_manager.get_channel(
"{}/{}".format(name, reg_name), reg_width)
self.channels[reg_name] = manager.get_channel(
"{}/{}".format(name, reg_name), reg_width, ty=WaveformType.VECTOR)
def process_message(self, message):
self.stb.set_value("1")
@ -413,11 +598,12 @@ def _extract_log_chars(data):
class LogHandler:
def __init__(self, vcd_manager, vcd_log_channels):
self.vcd_channels = dict()
for name, maxlength in vcd_log_channels.items():
self.vcd_channels[name] = vcd_manager.get_channel("log/" + name,
maxlength*8)
def __init__(self, manager, log_channels):
self.channels = dict()
for name, maxlength in log_channels.items():
self.channels[name] = manager.get_channel("logs/" + name,
maxlength * 8,
ty=WaveformType.LOG)
self.current_entry = ""
def process_message(self, message):
@ -425,15 +611,12 @@ class LogHandler:
self.current_entry += _extract_log_chars(message.data)
if len(self.current_entry) > 1 and self.current_entry[-1] == "\x1D":
channel_name, log_message = self.current_entry[:-1].split("\x1E", maxsplit=1)
vcd_value = ""
for c in log_message:
vcd_value += "{:08b}".format(ord(c))
self.vcd_channels[channel_name].set_value(vcd_value)
self.channels[channel_name].set_log(log_message)
self.current_entry = ""
def get_vcd_log_channels(log_channel, messages):
vcd_log_channels = dict()
def get_log_channels(log_channel, messages):
log_channels = dict()
log_entry = ""
for message in messages:
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":
channel_name, log_message = log_entry[:-1].split("\x1E", maxsplit=1)
l = len(log_message)
if channel_name in vcd_log_channels:
if vcd_log_channels[channel_name] < l:
vcd_log_channels[channel_name] = l
if channel_name in log_channels:
if log_channels[channel_name] < l:
log_channels[channel_name] = l
else:
vcd_log_channels[channel_name] = l
log_channels[channel_name] = l
log_entry = ""
return vcd_log_channels
return log_channels
def get_single_device_argument(devices, module, cls, argument):
@ -475,7 +658,7 @@ def get_dds_sysclk(devices):
("AD9914",), "sysclk")
def create_channel_handlers(vcd_manager, devices, ref_period,
def create_channel_handlers(manager, devices, ref_period,
dds_sysclk, dds_onehot_sel):
channel_handlers = dict()
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"
and desc["class"] in {"TTLOut", "TTLInOut"}):
channel = desc["arguments"]["channel"]
channel_handlers[channel] = TTLHandler(vcd_manager, name)
channel_handlers[channel] = TTLHandler(manager, name)
if (desc["module"] == "artiq.coredevice.ttl"
and desc["class"] == "TTLClockGen"):
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"
and desc["class"] == "AD9914"):
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:
dds_handler = channel_handlers[dds_bus_channel]
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
dds_handler.add_dds_channel(name, dds_channel)
if (desc["module"] == "artiq.coredevice.spi2" and
desc["class"] == "SPIMaster"):
channel = desc["arguments"]["channel"]
channel_handlers[channel] = SPIMaster2Handler(
vcd_manager, name)
manager, name)
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):
return getattr(message, "timestamp", message.rtio_counter)
def decoded_dump_to_vcd(fileobj, devices, dump, uniform_interval=False):
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)
if ref_period is not None:
if not uniform_interval:
vcd_manager.set_timescale_ps(ref_period*1e12)
else:
if ref_period is None:
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)
if dds_sysclk is None:
logger.warning("unable to determine DDS sysclk")
dds_sysclk = 3e9 # guess
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]
else:
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)
channel_handlers = create_channel_handlers(
vcd_manager, devices, ref_period,
manager, devices, ref_period,
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(
vcd_manager, vcd_log_channels)
manager, log_channels)
if uniform_interval:
# 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
# in SI seconds
interval = vcd_manager.get_channel("interval", 64)
slack = vcd_manager.get_channel("rtio_slack", 64)
interval = manager.get_channel("interval", 64, ty=WaveformType.ANALOG)
slack = manager.get_channel("rtio_slack", 64, ty=WaveformType.ANALOG)
vcd_manager.set_time(0)
manager.set_time(0)
start_time = 0
for m in messages:
start_time = get_message_time(m)
if start_time:
break
t0 = 0
if not uniform_interval:
manager.set_start_time(start_time)
t0 = start_time
for i, message in enumerate(messages):
if message.channel in channel_handlers:
t = get_message_time(message) - start_time
t = get_message_time(message)
if t >= 0:
if uniform_interval:
interval.set_value_double((t - t0)*ref_period)
vcd_manager.set_time(i)
manager.set_time(i)
timestamp.set_value("{:064b}".format(t))
t0 = t
else:
vcd_manager.set_time(t)
manager.set_time(t)
channel_handlers[message.channel].process_message(message)
if isinstance(message, OutputMessage):
slack.set_value_double(

View File

@ -3,6 +3,7 @@ import logging
import traceback
import numpy
import socket
import builtins
from enum import Enum
from fractions import Fraction
from collections import namedtuple
@ -23,6 +24,8 @@ class Request(Enum):
RPCReply = 7
RPCException = 8
SubkernelUpload = 9
class Reply(Enum):
SystemInfo = 2
@ -208,6 +211,7 @@ class CommKernel:
self.unpack_float64 = struct.Struct(self.endian + "d").unpack
self.pack_header = struct.Struct(self.endian + "lB").pack
self.pack_int8 = struct.Struct(self.endian + "B").pack
self.pack_int32 = struct.Struct(self.endian + "l").pack
self.pack_int64 = struct.Struct(self.endian + "q").pack
self.pack_float64 = struct.Struct(self.endian + "d").pack
@ -322,7 +326,7 @@ class CommKernel:
self._write(chunk)
def _write_int8(self, value):
self._write(value)
self._write(self.pack_int8(value))
def _write_int32(self, value):
self._write(self.pack_int32(value))
@ -382,6 +386,19 @@ class CommKernel:
else:
self._read_expect(Reply.LoadCompleted)
def upload_subkernel(self, kernel_library, id, destination):
self._write_header(Request.SubkernelUpload)
self._write_int32(id)
self._write_int8(destination)
self._write_bytes(kernel_library)
self._flush()
self._read_header()
if self._read_type == Reply.LoadFailed:
raise LoadError(self._read_string())
else:
self._read_expect(Reply.LoadCompleted)
def run(self):
self._write_empty(Request.RunKernel)
self._flush()
@ -449,12 +466,12 @@ class CommKernel:
self._write_bool(value)
elif tag == "i":
check(isinstance(value, (int, numpy.int32)) and
(-2**31 <= value < 2**31),
(-2**31 <= value <= 2**31-1),
lambda: "32-bit int")
self._write_int32(value)
elif tag == "I":
check(isinstance(value, (int, numpy.int32, numpy.int64)) and
(-2**63 <= value < 2**63),
(-2**63 <= value <= 2**63-1),
lambda: "64-bit int")
self._write_int64(value)
elif tag == "f":
@ -463,8 +480,8 @@ class CommKernel:
self._write_float64(value)
elif tag == "F":
check(isinstance(value, Fraction) and
(-2**63 <= value.numerator < 2**63) and
(-2**63 <= value.denominator < 2**63),
(-2**63 <= value.numerator <= 2**63-1) and
(-2**63 <= value.denominator <= 2**63-1),
lambda: "64-bit Fraction")
self._write_int64(value.numerator)
self._write_int64(value.denominator)
@ -600,9 +617,10 @@ class CommKernel:
self._write_int32(embedding_map.store_str(function))
else:
exn_type = type(exn)
if exn_type in (ZeroDivisionError, ValueError, IndexError, RuntimeError) or \
hasattr(exn, "artiq_builtin"):
name = "0:{}".format(exn_type.__name__)
if exn_type in builtins.__dict__.values():
name = "0:{}".format(exn_type.__qualname__)
elif hasattr(exn, "artiq_builtin"):
name = "0:{}.{}".format(exn_type.__module__, exn_type.__qualname__)
else:
exn_id = embedding_map.store_object(exn_type)
name = "{}:{}.{}".format(exn_id,
@ -686,8 +704,14 @@ class CommKernel:
else:
python_exn_type = embedding_map.retrieve_object(core_exn.id)
try:
python_exn = python_exn_type(
nested_exceptions[-1][1].format(*nested_exceptions[0][2]))
except Exception as ex:
python_exn = RuntimeError(
f"Exception type={python_exn_type}, which couldn't be "
f"reconstructed ({ex})"
)
python_exn.artiq_core_exception = core_exn
raise python_exn

View File

@ -94,9 +94,7 @@ class CommMonInj:
self.injection_status_cb(channel, override, value)
else:
raise ValueError("Unknown packet type", ty)
except asyncio.CancelledError:
raise
except:
except Exception:
logger.error("Moninj connection terminating with exception", exc_info=True)
finally:
if self.disconnect_cb is not None:

View File

@ -1,5 +1,6 @@
import os, sys
import numpy
from inspect import getfullargspec
from functools import wraps
from pythonparser import diagnostic
@ -52,6 +53,20 @@ def rtio_get_destination_status(linkno: TInt32) -> TBool:
def rtio_get_counter() -> TInt64:
raise NotImplementedError("syscall not simulated")
@syscall
def test_exception_id_sync(id: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
def get_target_cls(target):
if target == "rv32g":
return RV32GTarget
elif target == "rv32ima":
return RV32IMATarget
elif target == "cortexa9":
return CortexA9Target
else:
raise ValueError("Unsupported target")
class Core:
"""Core device driver.
@ -61,63 +76,79 @@ class Core:
On platforms that use clock multiplication and SERDES-based PHYs,
this is the period after multiplication. For example, with a RTIO core
clocked at 125MHz and a SERDES multiplication factor of 8, the
reference period is 1ns.
The time machine unit is equal to this period.
reference period is ``1 ns``.
The machine time unit (``mu``) is equal to this period.
:param ref_multiplier: ratio between the RTIO fine timestamp frequency
and the RTIO coarse timestamp frequency (e.g. SERDES multiplication
factor).
:param analyzer_proxy: name of the core device analyzer proxy to trigger
(optional).
:param analyze_at_run_end: automatically trigger the core device analyzer
proxy after the Experiment's run stage finishes.
"""
kernel_invariants = {
"core", "ref_period", "coarse_ref_period", "ref_multiplier",
}
def __init__(self, dmgr, host, ref_period, ref_multiplier=8, target="rv32g"):
def __init__(self, dmgr,
host, ref_period,
analyzer_proxy=None, analyze_at_run_end=False,
ref_multiplier=8,
target="rv32g", satellite_cpu_targets={}):
self.ref_period = ref_period
self.ref_multiplier = ref_multiplier
if target == "rv32g":
self.target_cls = RV32GTarget
elif target == "rv32ima":
self.target_cls = RV32IMATarget
elif target == "cortexa9":
self.target_cls = CortexA9Target
else:
raise ValueError("Unsupported target")
self.satellite_cpu_targets = satellite_cpu_targets
self.target_cls = get_target_cls(target)
self.coarse_ref_period = ref_period*ref_multiplier
if host is None:
self.comm = CommKernelDummy()
else:
self.comm = CommKernel(host)
self.analyzer_proxy_name = analyzer_proxy
self.analyze_at_run_end = analyze_at_run_end
self.first_run = True
self.dmgr = dmgr
self.core = self
self.comm.core = self
self.analyzer_proxy = None
def notify_run_end(self):
if self.analyze_at_run_end:
self.trigger_analyzer_proxy()
def close(self):
"""Disconnect core device and close sockets.
"""
self.comm.close()
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=[],
old_embedding_map=None):
try:
engine = _DiagnosticEngine(all_errors_are_fatal=True)
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,
old_embedding_map=old_embedding_map)
stitcher.stitch_call(function, args, kwargs, set_result)
stitcher.finalize()
module = Module(stitcher,
ref_period=self.ref_period,
attribute_writeback=attribute_writeback)
target = self.target_cls()
target = target if target is not None else self.target_cls()
library = target.compile_and_link([module])
stripped_library = target.strip(library)
return stitcher.embedding_map, stripped_library, \
lambda addresses: target.symbolize(library, addresses), \
lambda symbols: target.demangle(symbols)
lambda symbols: target.demangle(symbols), \
module.subkernel_arg_types
except diagnostic.Error as error:
raise CompileError(error.diagnostic) from error
@ -135,11 +166,70 @@ class Core:
def set_result(new_result):
nonlocal result
result = new_result
embedding_map, kernel_library, symbolizer, demangler = \
embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
self.compile(function, args, kwargs, set_result)
self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types)
self._run_compiled(kernel_library, embedding_map, symbolizer, demangler)
return result
def compile_subkernel(self, sid, subkernel_fn, embedding_map, args, subkernel_arg_types, subkernels):
# pass self to subkernels (if applicable)
# assuming the first argument is self
subkernel_args = getfullargspec(subkernel_fn.artiq_embedded.function)
self_arg = []
if len(subkernel_args[0]) > 0:
if subkernel_args[0][0] == 'self':
self_arg = args[:1]
destination = subkernel_fn.artiq_embedded.destination
destination_tgt = self.satellite_cpu_targets[destination]
target = get_target_cls(destination_tgt)(subkernel_id=sid)
object_map, kernel_library, _, _, _ = \
self.compile(subkernel_fn, self_arg, {}, attribute_writeback=False,
print_as_rpc=False, target=target, destination=destination,
subkernel_arg_types=subkernel_arg_types.get(sid, []),
old_embedding_map=embedding_map)
if object_map.has_rpc():
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):
subkernels = embedding_map.subkernels()
subkernels_compiled = []
while True:
new_subkernels = {}
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):
"""Precompile a kernel and return a callable that executes it on the core device
at a later time.
@ -148,7 +238,7 @@ class Core:
as additional positional and keyword arguments.
The returned callable accepts no arguments.
Precompiled kernels may use RPCs.
Precompiled kernels may use RPCs and subkernels.
Object attributes at the beginning of a precompiled kernel execution have the
values they had at precompilation time. If up-to-date values are required,
@ -156,8 +246,8 @@ class Core:
Similarly, modified values are not written back, and explicit RPC should be used
to modify host objects.
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.
Examples include code used to control DDS phase, and Urukul RF switch control
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
via the CPLD register.
The return value of the callable is the return value of the kernel, if any.
@ -173,8 +263,9 @@ class Core:
nonlocal result
result = new_result
embedding_map, kernel_library, symbolizer, demangler = \
embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
self.compile(function, args, kwargs, set_result, attribute_writeback=False)
self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types)
@wraps(function)
def run_precompiled():
@ -187,7 +278,7 @@ class Core:
@portable
def seconds_to_mu(self, seconds):
"""Convert seconds to the corresponding number of machine units
(RTIO cycles).
(fine RTIO cycles).
:param seconds: time (in seconds) to convert.
"""
@ -195,7 +286,7 @@ class Core:
@portable
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.
"""
@ -210,7 +301,7 @@ class Core:
for the actual value of the hardware register at the instant when
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()
@ -229,7 +320,7 @@ class Core:
def get_rtio_destination_status(self, destination):
"""Returns whether the specified RTIO destination is up.
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)
@kernel
@ -250,3 +341,21 @@ class Core:
min_now = rtio_get_counter() + 125000
if now_mu() < min_now:
at_mu(min_now)
def trigger_analyzer_proxy(self):
"""Causes the core analyzer proxy to retrieve a dump from the device,
and distribute it to all connected clients (typically dashboards).
Returns only after the dump has been retrieved from the device.
Raises :exc:`IOError` if no analyzer proxy has been configured, or if the
analyzer proxy fails. In the latter case, more details would be
available in the proxy log.
"""
if self.analyzer_proxy is None:
if self.analyzer_proxy_name is not None:
self.analyzer_proxy = self.dmgr.get(self.analyzer_proxy_name)
if self.analyzer_proxy is None:
raise IOError("No analyzer proxy configured")
else:
self.analyzer_proxy.trigger()

View File

@ -19,16 +19,24 @@
},
"min_artiq_version": {
"type": "string",
"description": "Minimum required ARTIQ version"
"description": "Minimum required ARTIQ version",
"default": "0"
},
"hw_rev": {
"type": "string",
"description": "Hardware revision"
},
"base": {
"type": "string",
"enum": ["use_drtio_role", "standalone", "master", "satellite"],
"description": "Deprecated, use drtio_role instead",
"default": "use_drtio_role"
},
"drtio_role": {
"type": "string",
"enum": ["standalone", "master", "satellite"],
"description": "SoC base; value depends on intended system topology"
"description": "Role that this device takes in a DRTIO network; 'standalone' means no DRTIO",
"default": "standalone"
},
"ext_ref_frequency": {
"type": "number",
@ -41,6 +49,10 @@
"default": 125e6,
"description": "RTIO frequency"
},
"enable_wrpll": {
"type": "boolean",
"default": false
},
"core_addr": {
"type": "string",
"format": "ipv4",
@ -122,7 +134,7 @@
},
"hw_rev": {
"type": "string",
"enum": ["v1.0"]
"enum": ["v1.0", "v1.1"]
}
}
}
@ -134,7 +146,7 @@
"properties": {
"type": {
"type": "string",
"enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp"]
"enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"]
},
"board": {
"type": "string"
@ -204,7 +216,8 @@
"type": "object",
"properties": {
"name": {
"type": "string"
"type": "string",
"default": "dio_spi"
},
"clk": {
"type": "integer",
@ -240,7 +253,8 @@
"type": "object",
"properties": {
"name": {
"type": "string"
"type": "string",
"default": "ttl"
},
"pin": {
"type": "integer",
@ -257,7 +271,8 @@
}
},
"required": ["pin", "direction"]
}
},
"default": []
}
},
"required": ["ports", "spi"]
@ -302,6 +317,12 @@
"pll_n": {
"type": "integer"
},
"pll_en": {
"type": "integer",
"minimum": 0,
"maximum": 1,
"default": 1
},
"pll_vco": {
"type": "integer"
},
@ -376,6 +397,11 @@
"minItems": 2,
"maxItems": 2
},
"sampler_hw_rev": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+",
"default": "v2.2"
},
"urukul0_ports": {
"type": "array",
"items": {
@ -405,6 +431,12 @@
"type": "integer",
"default": 32
},
"pll_en": {
"type": "integer",
"minimum": 0,
"maximum": 1,
"default": 1
},
"pll_vco": {
"type": "integer"
}
@ -496,6 +528,11 @@
"almazny": {
"type": "boolean",
"default": false
},
"almazny_hw_rev": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+",
"default": "v1.2"
}
},
"required": ["ports"]
@ -545,6 +582,11 @@
},
"minItems": 1,
"maxItems": 1
},
"mode": {
"type": "string",
"enum": ["base", "miqro"],
"default": "base"
}
},
"required": ["ports"]
@ -571,6 +613,35 @@
},
"required": ["ports"]
}
},{
"title": "Shuttler",
"if": {
"properties": {
"type": {
"const": "shuttler"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 2
},
"drtio_destination": {
"type": "integer"
},
"hw_rev": {
"type": "string",
"enum": ["v1.0", "v1.1"]
}
},
"required": ["ports"]
}
}]
}
}

View File

@ -6,7 +6,7 @@ alone could achieve.
"""
from artiq.language.core import syscall, kernel
from artiq.language.types import TInt32, TInt64, TStr, TNone, TTuple
from artiq.language.types import TInt32, TInt64, TStr, TNone, TTuple, TBool
from artiq.coredevice.exceptions import DMAError
from numpy import int64
@ -17,7 +17,7 @@ def dma_record_start(name: TStr) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def dma_record_stop(duration: TInt64) -> TNone:
def dma_record_stop(duration: TInt64, enable_ddma: TBool) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
@ -25,11 +25,11 @@ def dma_erase(name: TStr) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def dma_retrieve(name: TStr) -> TTuple([TInt64, TInt32]):
def dma_retrieve(name: TStr) -> TTuple([TInt64, TInt32, TBool]):
raise NotImplementedError("syscall not simulated")
@syscall
def dma_playback(timestamp: TInt64, ptr: TInt32) -> TNone:
def dma_playback(timestamp: TInt64, ptr: TInt32, enable_ddma: TBool) -> TNone:
raise NotImplementedError("syscall not simulated")
@ -47,6 +47,7 @@ class DMARecordContextManager:
def __init__(self):
self.name = ""
self.saved_now_mu = int64(0)
self.enable_ddma = False
@kernel
def __enter__(self):
@ -56,7 +57,7 @@ class DMARecordContextManager:
@kernel
def __exit__(self, type, value, traceback):
dma_record_stop(now_mu()) # see above
dma_record_stop(now_mu(), self.enable_ddma) # see above
at_mu(self.saved_now_mu)
@ -74,12 +75,20 @@ class CoreDMA:
self.epoch = 0
@kernel
def record(self, name):
"""Returns a context manager that will record a DMA trace called ``name``.
def record(self, name, enable_ddma=False):
"""Returns a context manager that will record a DMA trace called `name`.
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`.
Enabling it allows running DMA on satellites, rather than sending all
events from the master.
Keeping it disabled it may improve performance in some scenarios,
e.g. when there are many small satellite buffers."""
self.epoch += 1
self.recorder.name = name
self.recorder.enable_ddma = enable_ddma
return self.recorder
@kernel
@ -92,24 +101,24 @@ class CoreDMA:
def playback(self, name):
"""Replays a previously recorded DMA trace. This function blocks until
the entire trace is submitted to the RTIO FIFOs."""
(advance_mu, ptr) = dma_retrieve(name)
dma_playback(now_mu(), ptr)
(advance_mu, ptr, uses_ddma) = dma_retrieve(name)
dma_playback(now_mu(), ptr, uses_ddma)
delay_mu(advance_mu)
@kernel
def get_handle(self, name):
"""Returns a handle to a previously recorded DMA trace. The returned handle
is only valid until the next call to :meth:`record` or :meth:`erase`."""
(advance_mu, ptr) = dma_retrieve(name)
return (self.epoch, advance_mu, ptr)
(advance_mu, ptr, uses_ddma) = dma_retrieve(name)
return (self.epoch, advance_mu, ptr, uses_ddma)
@kernel
def playback_handle(self, handle):
"""Replays a handle obtained with :meth:`get_handle`. Using this function
is much faster than :meth:`playback` for replaying a set of traces repeatedly,
but incurs the overhead of managing the handles onto the programmer."""
(epoch, advance_mu, ptr) = handle
but offloads the overhead of managing the handles onto the programmer."""
(epoch, advance_mu, ptr, uses_ddma) = handle
if self.epoch != epoch:
raise DMAError("Invalid handle")
dma_playback(now_mu(), ptr)
dma_playback(now_mu(), ptr, uses_ddma)
delay_mu(advance_mu)

View File

@ -1,9 +1,9 @@
"""Driver for RTIO-enabled TTL edge counter.
Like for the TTL input PHYs, sensitivity can be configured over RTIO
(``gate_rising()``, etc.). In contrast to the former, however, the count is
As for the TTL input PHYs, sensitivity can be configured over RTIO
(: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
of each gate period::
of each gate period: ::
with parallel:
doppler_cool()
@ -17,12 +17,12 @@ of each gate period::
print("Readout counts:", self.pmt_counter.fetch_count())
For applications where the timestamps of the individual input events are not
required, this has two advantages over ``TTLInOut.count()`` beyond raw
throughput. First, it is easy to count events during multiple separate periods
without blocking to read back counts in between, as illustrated in the above
example. Secondly, as each count total only takes up a single input event, it
is much easier to acquire counts on several channels in parallel without
risking input FIFO overflows::
required, this has two advantages over :meth:`TTLInOut.count<artiq.coredevice.ttl.TTLInOut.count>`
beyond raw throughput. First, it is easy to count events during multiple separate
periods without blocking to read back counts in between, as illustrated in the
above example. Secondly, as each count total only takes up a single input event,
it is much easier to acquire counts on several channels in parallel without
risking input RTIO overflows: ::
# Using the TTLInOut driver, pmt_1 input events are only processed
# 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_1 = self.pmt_1.count(now_mu())
#
# Using gateware counters, only a single input event each is
# 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_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.
"""
@ -91,6 +89,10 @@ class EdgeCounter:
self.channel = channel
self.counter_max = (1 << (gateware_width - 1)) - 1
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel
def gate_rising(self, duration):
"""Count rising edges for the given duration and request the total at
@ -172,13 +174,13 @@ class EdgeCounter:
"""Emit an RTIO event at the current timeline position to set the
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_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).
: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).
"""
config = int32(0)

View File

@ -2,16 +2,31 @@ import builtins
import linecache
import re
import os
from numpy.linalg import LinAlgError
from artiq import __artiq_dir__ as artiq_dir
from artiq.coredevice.runtime import source_loader
"""
This file provides class definition for all the exceptions declared in `EmbeddingMap` in `artiq.compiler.embedding`
For Python builtin exceptions, use the `builtins` module
For ARTIQ specific exceptions, inherit from `Exception` class
"""
ZeroDivisionError = builtins.ZeroDivisionError
ValueError = builtins.ValueError
IndexError = builtins.IndexError
RuntimeError = builtins.RuntimeError
AssertionError = builtins.AssertionError
AttributeError = builtins.AttributeError
IndexError = builtins.IndexError
IOError = builtins.IOError
KeyError = builtins.KeyError
NotImplementedError = builtins.NotImplementedError
OverflowError = builtins.OverflowError
RuntimeError = builtins.RuntimeError
TimeoutError = builtins.TimeoutError
TypeError = builtins.TypeError
ValueError = builtins.ValueError
ZeroDivisionError = builtins.ZeroDivisionError
OSError = builtins.OSError
class CoreException:
@ -137,7 +152,7 @@ class RTIOOverflow(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.
"""
artiq_builtin = True
@ -148,15 +163,27 @@ class DMAError(Exception):
artiq_builtin = True
class SubkernelError(Exception):
"""Raised when an operation regarding a subkernel is invalid
or cannot be completed.
"""
artiq_builtin = True
class ClockFailure(Exception):
"""Raised when RTIO PLL has lost lock."""
artiq_builtin = True
class I2CError(Exception):
"""Raised when a I2C transaction fails."""
pass
artiq_builtin = True
class SPIError(Exception):
"""Raised when a SPI transaction fails."""
pass
artiq_builtin = True
class UnwrapNoneError(Exception):
"""Raised when unwrapping a none Option."""
artiq_builtin = True

View File

@ -1,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.
"""
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
using :meth:`set_hold`, the next frame will contain the update. For the
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.
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
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
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.
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.
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
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`
interface must be used.
@ -52,6 +52,10 @@ class Fastino:
assert self.core.ref_period == 1*ns
self.t_frame = int64(14*7*4)
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel
def init(self):
"""Initialize the device.
@ -59,14 +63,15 @@ class Fastino:
* disables RESET, DAC_CLR, enables AFE_PWR
* clears error counters, enables error counting
* 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
channels
It does not change set channel voltages and does not reset the PLLs or clock
domains.
Note: On Fastino gateware before v0.2 this may lead to 0 voltage being emitted
.. warning::
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)
@ -111,7 +116,7 @@ class Fastino:
"""Write DAC data in machine units.
: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.
"""
self.write(dac, data)
@ -120,9 +125,9 @@ class Fastino:
def set_group_mu(self, dac: TInt32, data: TList(TInt32)):
"""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.
: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.
If the list length is less than group size, the remaining
DAC channels within the group are cleared to 0 (machine units).
@ -133,10 +138,10 @@ class Fastino:
@portable
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.
:return: DAC data word in machine units, 16 bit integer.
:param voltage: Voltage in SI volts.
:return: DAC data word in machine units, 16-bit integer.
"""
data = int32(round((0x8000/10.)*voltage)) + int32(0x8000)
if data < 0 or data > 0xffff:
@ -145,9 +150,9 @@ class Fastino:
@portable
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.
Half the length of `voltage`.
"""
@ -181,7 +186,7 @@ class Fastino:
def update(self, 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)
@ -189,7 +194,7 @@ class Fastino:
def set_hold(self, hold):
"""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)
@ -210,9 +215,9 @@ class Fastino:
@kernel
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.
"""
self.write(0x23, leds)
@ -241,16 +246,16 @@ class Fastino:
def stage_cic(self, rate) -> TInt32:
"""Compute and stage interpolator configuration.
This method approximates the desired interpolation rate using a 10 bit
floating point representation (6 bit mantissa, 4 bit exponent) and
This method approximates the desired interpolation rate using a 10-bit
floating point representation (6-bit mantissa, 4-bit exponent) and
then determines an optimal interpolation gain compensation exponent
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
than 0.5 gain).
The overall gain including gain compensation is
`actual_rate**order/2**ceil(log2(actual_rate**order))`
where `order = 3`.
The overall gain including gain compensation is ``actual_rate ** order /
2 ** ceil(log2(actual_rate ** order))``
where ``order = 3``.
Returns the actual interpolation rate.
"""
@ -289,7 +294,7 @@ class Fastino:
their output is supposed to be constant.
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
period. This method synchronizes the input sample period to the current
frame on the affected channels.

View File

@ -2,7 +2,7 @@ from numpy import int32, int64
from artiq.language.core 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):
@ -11,6 +11,11 @@ class OutOfSyncException(Exception):
pass
class GrabberTimeoutException(Exception):
"""Raised when a timeout occurs while attempting to read Grabber RTIO input events."""
pass
class Grabber:
"""Driver for the Grabber camera interface."""
kernel_invariants = {"core", "channel_base", "sentinel"}
@ -25,6 +30,10 @@ class Grabber:
# ROI engine outputs for one video frame.
self.sentinel = int32(int64(2**count_width))
@staticmethod
def get_rtio_channels(channel_base, **kwargs):
return [(channel_base, "ROI coordinates"), (channel_base + 1, "ROI mask")]
@kernel
def setup_roi(self, n, x0, y0, x1, y1):
"""
@ -78,10 +87,10 @@ class Grabber:
self.gate_roi(0)
@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.
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
are enabled ROI engines. This method replaces the elements of the
@ -91,15 +100,26 @@ class Grabber:
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
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
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:
raise OutOfSyncException
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:
raise OutOfSyncException
if timestamp == -1:
raise GrabberTimeoutException(
"Timeout retrieving ROIs (attempting to read more ROIs than enabled?)")
data[i] = roi_output

View File

@ -43,7 +43,7 @@ def i2c_poll(busno, busaddr):
"""Poll I2C device at address.
: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
"""
i2c_start(busno)
@ -57,7 +57,7 @@ def i2c_write_byte(busno, busaddr, data, ack=True):
"""Write one byte to a device.
: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 nack: Allow NACK
"""
@ -76,7 +76,7 @@ def i2c_read_byte(busno, busaddr):
"""Read one byte from a device.
: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
"""
i2c_start(busno)
@ -95,10 +95,10 @@ def i2c_write_many(busno, busaddr, addr, data, ack_last=True):
"""Transfer multiple bytes to a device.
:param busno: I2c bus number
:param busaddr: 8 bit I2C device address (LSB=0)
:param addr: 8 bit data address
:param busaddr: 8-bit I2C device address (LSB=0)
:param addr: 8-bit data address
: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).
"""
n = len(data)
@ -121,8 +121,8 @@ def i2c_read_many(busno, busaddr, addr, data):
"""Transfer multiple bytes from a device.
:param busno: I2c bus number
:param busaddr: 8 bit I2C device address (LSB=0)
:param addr: 8 bit data address
:param busaddr: 8-bit I2C device address (LSB=0)
:param addr: 8-bit data address
:param data: List of integers to be filled with the data read.
One entry ber byte.
"""
@ -147,7 +147,7 @@ class I2CSwitch:
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.
On the KC705, this chip is used for selecting the I2C buses on the two FMC
@ -161,6 +161,7 @@ class I2CSwitch:
@kernel
def set(self, channel):
"""Enable one channel.
:param channel: channel number (0-7)
"""
i2c_switch_select(self.busno, self.address >> 1, 1 << channel)
@ -175,7 +176,7 @@ class I2CSwitch:
class TCA6424A:
"""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.
On the NIST QC2 hardware, this chip is used for switching the directions
@ -211,7 +212,7 @@ class TCA6424A:
class PCF8574A:
"""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.
"""
def __init__(self, dmgr, busno=0, address=0x7c, core_device="core"):

View File

@ -32,4 +32,7 @@ def load(description_path):
global validator
validator.validate(result)
if result["base"] != "use_drtio_role":
result["drtio_role"] = result["base"]
return result

View File

@ -25,14 +25,14 @@ port_mapping = {
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"):
self.core = dmgr.get(core_device)
self.sw0 = dmgr.get(sw0_device)
self.sw1 = dmgr.get(sw1_device)
self.busno = busno
self.port = port_mapping[port]
self.address = 0xa0 # i2c 8 bit
self.address = address # i2c 8 bit
@kernel
def select(self):

View File

@ -1,4 +1,4 @@
"""RTIO driver for Mirny (4 channel GHz PLLs)
"""RTIO driver for Mirny (4-channel GHz PLLs)
"""
from artiq.language.core import kernel, delay, portable
@ -31,16 +31,6 @@ WE = 1 << 24
# supported CPLD code version
PROTO_REV_MATCH = 0x0
# almazny-specific data
ALMAZNY_REG_BASE = 0x0C
ALMAZNY_OE_SHIFT = 12
# higher SPI write divider to match almazny shift register timing
# min SER time before SRCLK rise = 125ns
# -> div=32 gives 125ns for data before clock rise
# works at faster dividers too but could be less reliable
ALMAZNY_SPIT_WR = 32
class Mirny:
"""
@ -92,7 +82,7 @@ class Mirny:
@kernel
def read_reg(self, addr):
"""Read a register"""
"""Read a register."""
self.bus.set_config_mu(
SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 24, SPIT_RD, SPI_CS
)
@ -101,7 +91,7 @@ class Mirny:
@kernel
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.write((addr << 25) | WE | ((data & 0xFFFF) << 8))
@ -111,9 +101,9 @@ class Mirny:
Initialize and detect Mirny.
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)
self.hw_rev = reg0 & 0x3
@ -148,7 +138,7 @@ class Mirny:
def set_att_mu(self, channel, att):
"""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.write(((channel | 8) << 25) | (att << 16))
@ -159,7 +149,7 @@ class Mirny:
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 att: Attenuation setting in dB. Higher value is more
@ -170,113 +160,10 @@ class Mirny:
@kernel
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.write(addr << 25)
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, length, ext_div, SPI_CS)
if length < 32:
data <<= 32 - length
self.bus.write(data)
class Almazny:
"""
Almazny (High frequency mezzanine board for Mirny)
:param host_mirny - Mirny device Almazny is connected to
"""
def __init__(self, dmgr, host_mirny):
self.mirny_cpld = dmgr.get(host_mirny)
self.att_mu = [0x3f] * 4
self.channel_sw = [0] * 4
self.output_enable = False
@kernel
def init(self):
self.output_toggle(self.output_enable)
@kernel
def att_to_mu(self, att):
"""
Convert an attenuator setting in dB to machine units.
:param att: attenuator setting in dB [0-31.5]
:return: attenuator setting in machine units
"""
mu = round(att * 2.0)
if mu > 63 or mu < 0:
raise ValueError("Invalid Almazny attenuator settings!")
return mu
@kernel
def mu_to_att(self, att_mu):
"""
Convert a digital attenuator setting to dB.
:param att_mu: attenuator setting in machine units
:return: attenuator setting in dB
"""
return att_mu / 2
@kernel
def set_att(self, channel, att, rf_switch=True):
"""
Sets attenuators on chosen shift register (channel).
:param channel - index of the register [0-3]
:param att_mu - attenuation setting in dBm [0-31.5]
:param rf_switch - rf switch (bool)
"""
self.set_att_mu(channel, self.att_to_mu(att), rf_switch)
@kernel
def set_att_mu(self, channel, att_mu, rf_switch=True):
"""
Sets attenuators on chosen shift register (channel).
:param channel - index of the register [0-3]
:param att_mu - attenuation setting in machine units [0-63]
:param rf_switch - rf switch (bool)
"""
self.channel_sw[channel] = 1 if rf_switch else 0
self.att_mu[channel] = att_mu
self._update_register(channel)
@kernel
def output_toggle(self, oe):
"""
Toggles output on all shift registers on or off.
:param oe - toggle output enable (bool)
"""
self.output_enable = oe
cfg_reg = self.mirny_cpld.read_reg(1)
en = 1 if self.output_enable else 0
delay(100 * us)
new_reg = (en << ALMAZNY_OE_SHIFT) | (cfg_reg & 0x3FF)
self.mirny_cpld.write_reg(1, new_reg)
delay(100 * us)
@kernel
def _flip_mu_bits(self, mu):
# in this form MSB is actually 0.5dB attenuator
# unnatural for users, so we flip the six bits
return (((mu & 0x01) << 5)
| ((mu & 0x02) << 3)
| ((mu & 0x04) << 1)
| ((mu & 0x08) >> 1)
| ((mu & 0x10) >> 3)
| ((mu & 0x20) >> 5))
@kernel
def _update_register(self, ch):
self.mirny_cpld.write_ext(
ALMAZNY_REG_BASE + ch,
8,
self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6),
ALMAZNY_SPIT_WR
)
delay(100 * us)
@kernel
def _update_all_registers(self):
for i in range(4):
self._update_register(i)

View File

@ -16,31 +16,31 @@ SPI_CS_SR = 2
@portable
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
@portable
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
@portable
def adc_channel(data):
"""Return the channel index from a result packet"""
"""Return the channel index from a result packet."""
return (data >> 3) & 0x7
@portable
def adc_data(data):
"""Return the ADC value from a result packet"""
"""Return the ADC value from a result packet."""
return (data >> 8) & 0xffff
@portable
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)
data = adc_data(data)
g = 625
@ -107,7 +107,7 @@ class Novogorny:
def configure(self, data):
"""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.
"""
if len(data) > 1:
@ -137,12 +137,10 @@ class Novogorny:
@kernel
def sample(self, next_ctrl=0):
"""Acquire a sample
.. seealso:: :meth:`sample_mu`
"""Acquire a sample. See also :meth:`Novogorny.sample_mu`.
: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)
@ -151,7 +149,7 @@ class Novogorny:
"""Acquire a burst of samples.
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
instrumentation amplifier and the ADC is high.

File diff suppressed because it is too large Load Diff

View File

@ -25,7 +25,7 @@ def rtio_input_data(channel: TInt32) -> TInt32:
@syscall(flags={"nowrite"})
def rtio_input_timestamped_data(timeout_mu: TInt64,
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
reached."""
raise NotImplementedError("syscall not simulated")

View File

@ -15,30 +15,32 @@ SPI_CS_PGIA = 1 # separate SPI bus, CS used as RCLK
@portable
def adc_mu_to_volt(data, gain=0):
"""Convert ADC data in machine units to Volts.
def adc_mu_to_volt(data, gain=0, corrected_fs=True):
"""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)
:return: Voltage in Volts
:param corrected_fs: use corrected ADC FS reference.
Should be ``True`` for Sampler revisions after v2.1. ``False`` for v2.1 and earlier.
:return: Voltage in volts
"""
if gain == 0:
volt_per_lsb = 20./(1 << 16)
volt_per_lsb = 20.48 / (1 << 16) if corrected_fs else 20. / (1 << 16)
elif gain == 1:
volt_per_lsb = 2./(1 << 16)
volt_per_lsb = 2.048 / (1 << 16) if corrected_fs else 2. / (1 << 16)
elif gain == 2:
volt_per_lsb = .2/(1 << 16)
volt_per_lsb = .2048 / (1 << 16) if corrected_fs else .2 / (1 << 16)
elif gain == 3:
volt_per_lsb = .02/(1 << 16)
volt_per_lsb = 0.02048 / (1 << 16) if corrected_fs else .02 / (1 << 16)
else:
raise ValueError("invalid gain")
return data*volt_per_lsb
return data * volt_per_lsb
class Sampler:
"""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.
:param spi_adc_device: ADC SPI bus device name
@ -48,12 +50,13 @@ class Sampler:
:param gains: Initial value for PGIA gains shift register
(default: 0x0000). Knowledge of this state is not transferred
between experiments.
:param hw_rev: Sampler's hardware revision string (default 'v2.2')
:param core_device: Core device name
"""
kernel_invariants = {"bus_adc", "bus_pgia", "core", "cnv", "div"}
kernel_invariants = {"bus_adc", "bus_pgia", "core", "cnv", "div", "corrected_fs"}
def __init__(self, dmgr, spi_adc_device, spi_pgia_device, cnv_device,
div=8, gains=0x0000, core_device="core"):
div=8, gains=0x0000, hw_rev="v2.2", core_device="core"):
self.bus_adc = dmgr.get(spi_adc_device)
self.bus_adc.update_xfer_duration_mu(div, 32)
self.bus_pgia = dmgr.get(spi_pgia_device)
@ -62,6 +65,11 @@ class Sampler:
self.cnv = dmgr.get(cnv_device)
self.div = div
self.gains = gains
self.corrected_fs = self.use_corrected_fs(hw_rev)
@staticmethod
def use_corrected_fs(hw_rev):
return hw_rev != "v2.1"
@kernel
def init(self):
@ -111,12 +119,12 @@ class Sampler:
Perform a conversion and transfer the samples.
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.
:param data: List of data samples to fill. Must have even length.
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.
"""
self.cnv.pulse(30*ns) # t_CNVH
@ -134,7 +142,7 @@ class Sampler:
def sample(self, data):
"""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.
"""
@ -144,4 +152,4 @@ class Sampler:
for i in range(n):
channel = i + 8 - len(data)
gain = (self.gains >> (channel*2)) & 0b11
data[i] = adc_mu_to_volt(adc_data[i], gain)
data[i] = adc_mu_to_volt(adc_data[i], gain, self.corrected_fs)

View File

@ -1,372 +0,0 @@
"""
Driver for the Smart Arbitrary Waveform Generator (SAWG) on RTIO.
The SAWG is an "improved DDS" built in gateware and interfacing to
high-speed DACs.
Output event replacement is supported except on the configuration channel.
"""
from artiq.language.types import TInt32, TFloat
from numpy import int32, int64
from artiq.language.core import kernel
from artiq.coredevice.spline import Spline
from artiq.coredevice.rtio import rtio_output
# sawg.Config addresses
_SAWG_DIV = 0
_SAWG_CLR = 1
_SAWG_IQ_EN = 2
# _SAWF_PAD = 3 # reserved
_SAWG_OUT_MIN = 4
_SAWG_OUT_MAX = 5
_SAWG_DUC_MIN = 6
_SAWG_DUC_MAX = 7
class Config:
"""SAWG configuration.
Exposes the configurable quantities of a single SAWG channel.
Access to the configuration registers for a SAWG channel can not
be concurrent. There must be at least :attr:`_rtio_interval` machine
units of delay between accesses. Replacement is not supported and will be
lead to an ``RTIOCollision`` as this is likely a programming error.
All methods therefore advance the timeline by the duration of one
configuration register transfer.
:param channel: RTIO channel number of the channel.
:param core: Core device.
"""
kernel_invariants = {"channel", "core", "_out_scale", "_duc_scale",
"_rtio_interval"}
def __init__(self, channel, core, cordic_gain=1.):
self.channel = channel
self.core = core
# normalized DAC output
self._out_scale = (1 << 15) - 1.
# normalized DAC output including DUC cordic gain
self._duc_scale = self._out_scale/cordic_gain
# configuration channel access interval
self._rtio_interval = int64(3*self.core.ref_multiplier)
@kernel
def set_div(self, div: TInt32, n: TInt32=0):
"""Set the spline evolution divider and current counter value.
The divider and the spline evolution are synchronized across all
spline channels within a SAWG channel. The DDS/DUC phase accumulators
always evolves at full speed.
.. note:: The spline evolution divider has not been tested extensively
and is currently considered a technological preview only.
:param div: Spline evolution divider, such that
``t_sawg_spline/t_rtio_coarse = div + 1``. Default: ``0``.
:param n: Current value of the counter. Default: ``0``.
"""
rtio_output((self.channel << 8) | _SAWG_DIV, div | (n << 16))
delay_mu(self._rtio_interval)
@kernel
def set_clr(self, clr0: TInt32, clr1: TInt32, clr2: TInt32):
"""Set the accumulator clear mode for the three phase accumulators.
When the ``clr`` bit for a given DDS/DUC phase accumulator is
set, that phase accumulator will be cleared with every phase offset
RTIO command and the output phase of the DDS/DUC will be
exactly the phase RTIO value ("absolute phase update mode").
.. math::
q^\prime(t) = p^\prime + (t - t^\prime) f^\prime
In turn, when the bit is cleared, the phase RTIO channels
determine a phase offset to the current (carrier-) value of the
DDS/DUC phase accumulator. This "relative phase update mode" is
sometimes also called continuous phase mode.
.. math::
q^\prime(t) = q(t^\prime) + (p^\prime - p) +
(t - t^\prime) f^\prime
Where:
* :math:`q`, :math:`q^\prime`: old/new phase accumulator
* :math:`p`, :math:`p^\prime`: old/new phase offset
* :math:`f^\prime`: new frequency
* :math:`t^\prime`: timestamp of setting new :math:`p`, :math:`f`
* :math:`t`: running time
:param clr0: Auto-clear phase accumulator of the ``phase0``/
``frequency0`` DUC. Default: ``True``
:param clr1: Auto-clear phase accumulator of the ``phase1``/
``frequency1`` DDS. Default: ``True``
:param clr2: Auto-clear phase accumulator of the ``phase2``/
``frequency2`` DDS. Default: ``True``
"""
rtio_output((self.channel << 8) | _SAWG_CLR, clr0 |
(clr1 << 1) | (clr2 << 2))
delay_mu(self._rtio_interval)
@kernel
def set_iq_en(self, i_enable: TInt32, q_enable: TInt32):
"""Enable I/Q data on this DAC channel.
Every pair of SAWG channels forms a buddy pair.
The ``iq_en`` configuration controls which DDS data is emitted to the
DACs.
Refer to the documentation of :class:`SAWG` for a mathematical
description of ``i_enable`` and ``q_enable``.
.. note:: Quadrature data from the buddy channel is currently
a technological preview only. The data is ignored in the SAWG
gateware and not added to the DAC output.
This is equivalent to the ``q_enable`` switch always being ``0``.
:param i_enable: Controls adding the in-phase
DUC-DDS data of *this* SAWG channel to *this* DAC channel.
Default: ``1``.
:param q_enable: controls adding the quadrature
DUC-DDS data of this SAWG's *buddy* channel to *this* DAC
channel. Default: ``0``.
"""
rtio_output((self.channel << 8) | _SAWG_IQ_EN, i_enable |
(q_enable << 1))
delay_mu(self._rtio_interval)
@kernel
def set_duc_max_mu(self, limit: TInt32):
"""Set the digital up-converter (DUC) I and Q data summing junctions
upper limit. In machine units.
The default limits are chosen to reach maximum and minimum DAC output
amplitude.
For a description of the limiter functions in normalized units see:
.. seealso:: :meth:`set_duc_max`
"""
rtio_output((self.channel << 8) | _SAWG_DUC_MAX, limit)
delay_mu(self._rtio_interval)
@kernel
def set_duc_min_mu(self, limit: TInt32):
""".. seealso:: :meth:`set_duc_max_mu`"""
rtio_output((self.channel << 8) | _SAWG_DUC_MIN, limit)
delay_mu(self._rtio_interval)
@kernel
def set_out_max_mu(self, limit: TInt32):
""".. seealso:: :meth:`set_duc_max_mu`"""
rtio_output((self.channel << 8) | _SAWG_OUT_MAX, limit)
delay_mu(self._rtio_interval)
@kernel
def set_out_min_mu(self, limit: TInt32):
""".. seealso:: :meth:`set_duc_max_mu`"""
rtio_output((self.channel << 8) | _SAWG_OUT_MIN, limit)
delay_mu(self._rtio_interval)
@kernel
def set_duc_max(self, limit: TFloat):
"""Set the digital up-converter (DUC) I and Q data summing junctions
upper limit.
Each of the three summing junctions has a saturating adder with
configurable upper and lower limits. The three summing junctions are:
* At the in-phase input to the ``phase0``/``frequency0`` fast DUC,
after the anti-aliasing FIR filter.
* At the quadrature input to the ``phase0``/``frequency0``
fast DUC, after the anti-aliasing FIR filter. The in-phase and
quadrature data paths both use the same limits.
* Before the DAC, where the following three data streams
are added together:
* the output of the ``offset`` spline,
* (optionally, depending on ``i_enable``) the in-phase output
of the ``phase0``/``frequency0`` fast DUC, and
* (optionally, depending on ``q_enable``) the quadrature
output of the ``phase0``/``frequency0`` fast DUC of the
buddy channel.
Refer to the documentation of :class:`SAWG` for a mathematical
description of the summing junctions.
:param limit: Limit value ``[-1, 1]``. The output of the limiter will
never exceed this limit. The default limits are the full range
``[-1, 1]``.
.. seealso::
* :meth:`set_duc_max`: Upper limit of the in-phase and quadrature
inputs to the DUC.
* :meth:`set_duc_min`: Lower limit of the in-phase and quadrature
inputs to the DUC.
* :meth:`set_out_max`: Upper limit of the DAC output.
* :meth:`set_out_min`: Lower limit of the DAC output.
"""
self.set_duc_max_mu(int32(round(limit*self._duc_scale)))
@kernel
def set_duc_min(self, limit: TFloat):
""".. seealso:: :meth:`set_duc_max`"""
self.set_duc_min_mu(int32(round(limit*self._duc_scale)))
@kernel
def set_out_max(self, limit: TFloat):
""".. seealso:: :meth:`set_duc_max`"""
self.set_out_max_mu(int32(round(limit*self._out_scale)))
@kernel
def set_out_min(self, limit: TFloat):
""".. seealso:: :meth:`set_duc_max`"""
self.set_out_min_mu(int32(round(limit*self._out_scale)))
class SAWG:
"""Smart arbitrary waveform generator channel.
The channel is parametrized as: ::
oscillators = exp(2j*pi*(frequency0*t + phase0))*(
amplitude1*exp(2j*pi*(frequency1*t + phase1)) +
amplitude2*exp(2j*pi*(frequency2*t + phase2)))
output = (offset +
i_enable*Re(oscillators) +
q_enable*Im(buddy_oscillators))
This parametrization can be viewed as two complex (quadrature) oscillators
(``frequency1``/``phase1`` and ``frequency2``/``phase2``) that are
executing and sampling at the coarse RTIO frequency. They can represent
frequencies within the first Nyquist zone from ``-f_rtio_coarse/2`` to
``f_rtio_coarse/2``.
.. note:: The coarse RTIO frequency ``f_rtio_coarse`` is the inverse of
``ref_period*multiplier``. Both are arguments of the ``Core`` device,
specified in the device database ``device_db.py``.
The sum of their outputs is then interpolated by a factor of
:attr:`parallelism` (2, 4, 8 depending on the bitstream) using a
finite-impulse-response (FIR) anti-aliasing filter (more accurately
a half-band filter).
The filter is followed by a configurable saturating limiter.
After the limiter, the data is shifted in frequency using a complex
digital up-converter (DUC, ``frequency0``/``phase0``) running at
:attr:`parallelism` times the coarse RTIO frequency. The first Nyquist
zone of the DUC extends from ``-f_rtio_coarse*parallelism/2`` to
``f_rtio_coarse*parallelism/2``. Other Nyquist zones are usable depending
on the interpolation/modulation options configured in the DAC.
The real/in-phase data after digital up-conversion can be offset using
another spline interpolator ``offset``.
The ``i_enable``/``q_enable`` switches enable emission of quadrature
signals for later analog quadrature mixing distinguishing upper and lower
sidebands and thus doubling the bandwidth. They can also be used to emit
four-tone signals.
.. note:: Quadrature data from the buddy channel is currently
ignored in the SAWG gateware and not added to the DAC output.
This is equivalent to the ``q_enable`` switch always being ``0``.
The configuration channel and the nine
:class:`artiq.coredevice.spline.Spline` interpolators are accessible as
attributes:
* :attr:`config`: :class:`Config`
* :attr:`offset`, :attr:`amplitude1`, :attr:`amplitude2`: in units
of full scale
* :attr:`phase0`, :attr:`phase1`, :attr:`phase2`: in units of turns
* :attr:`frequency0`, :attr:`frequency1`, :attr:`frequency2`: in units
of Hz
.. note:: The latencies (pipeline depths) of the nine data channels (i.e.
all except :attr:`config`) are matched. Equivalent channels (e.g.
:attr:`phase1` and :attr:`phase2`) are exactly matched. Channels of
different type or functionality (e.g. :attr:`offset` vs
:attr:`amplitude1`, DDS vs DUC, :attr:`phase0` vs :attr:`phase1`) are
only matched to within one coarse RTIO cycle.
:param channel_base: RTIO channel number of the first channel (amplitude).
The configuration channel and frequency/phase/amplitude channels are
then assumed to be successive channels.
:param parallelism: Number of output samples per coarse RTIO clock cycle.
:param core_device: Name of the core device that this SAWG is on.
"""
kernel_invariants = {"channel_base", "core", "parallelism",
"amplitude1", "frequency1", "phase1",
"amplitude2", "frequency2", "phase2",
"frequency0", "phase0", "offset"}
def __init__(self, dmgr, channel_base, parallelism, core_device="core"):
self.core = dmgr.get(core_device)
self.channel_base = channel_base
self.parallelism = parallelism
width = 16
time_width = 16
cordic_gain = 1.646760258057163 # Cordic(width=16, guard=None).gain
head_room = 1.001
self.config = Config(channel_base, self.core, cordic_gain)
self.offset = Spline(width, time_width, channel_base + 1,
self.core, 2.*head_room)
self.amplitude1 = Spline(width, time_width, channel_base + 2,
self.core, 2*head_room*cordic_gain**2)
self.frequency1 = Spline(3*width, time_width, channel_base + 3,
self.core, 1/self.core.coarse_ref_period)
self.phase1 = Spline(width, time_width, channel_base + 4,
self.core, 1.)
self.amplitude2 = Spline(width, time_width, channel_base + 5,
self.core, 2*head_room*cordic_gain**2)
self.frequency2 = Spline(3*width, time_width, channel_base + 6,
self.core, 1/self.core.coarse_ref_period)
self.phase2 = Spline(width, time_width, channel_base + 7,
self.core, 1.)
self.frequency0 = Spline(2*width, time_width, channel_base + 8,
self.core,
parallelism/self.core.coarse_ref_period)
self.phase0 = Spline(width, time_width, channel_base + 9,
self.core, 1.)
@kernel
def reset(self):
"""Re-establish initial conditions.
This clears all spline interpolators, accumulators and configuration
settings.
This method advances the timeline by the time required to perform all
7 writes to the configuration channel, plus 9 coarse RTIO cycles.
"""
self.config.set_div(0, 0)
self.config.set_clr(1, 1, 1)
self.config.set_iq_en(1, 0)
self.config.set_duc_min(-1.)
self.config.set_duc_max(1.)
self.config.set_out_min(-1.)
self.config.set_out_max(1.)
self.frequency0.set_mu(0)
coarse_cycle = int64(self.core.ref_multiplier)
delay_mu(coarse_cycle)
self.frequency1.set_mu(0)
delay_mu(coarse_cycle)
self.frequency2.set_mu(0)
delay_mu(coarse_cycle)
self.phase0.set_mu(0)
delay_mu(coarse_cycle)
self.phase1.set_mu(0)
delay_mu(coarse_cycle)
self.phase2.set_mu(0)
delay_mu(coarse_cycle)
self.amplitude1.set_mu(0)
delay_mu(coarse_cycle)
self.amplitude2.set_mu(0)
delay_mu(coarse_cycle)
self.offset.set_mu(0)
delay_mu(coarse_cycle)

View File

@ -1,54 +0,0 @@
from artiq.language.core import kernel, delay
from artiq.language.units import us
class ShiftReg:
"""Driver for shift registers/latch combos connected to TTLs"""
kernel_invariants = {"dt", "n"}
def __init__(self, dmgr, clk, ser, latch, n=32, dt=10*us, ser_in=None):
self.core = dmgr.get("core")
self.clk = dmgr.get(clk)
self.ser = dmgr.get(ser)
self.latch = dmgr.get(latch)
self.n = n
self.dt = dt
if ser_in is not None:
self.ser_in = dmgr.get(ser_in)
@kernel
def set(self, data):
"""Sets the values of the latch outputs. This does not
advance the timeline and the waveform is generated before
`now`."""
delay(-2*(self.n + 1)*self.dt)
for i in range(self.n):
if (data >> (self.n-i-1)) & 1 == 0:
self.ser.off()
else:
self.ser.on()
self.clk.off()
delay(self.dt)
self.clk.on()
delay(self.dt)
self.clk.off()
self.latch.on()
delay(self.dt)
self.latch.off()
delay(self.dt)
@kernel
def get(self):
delay(-2*(self.n + 1)*self.dt)
data = 0
for i in range(self.n):
data <<= 1
self.ser_in.sample_input()
if self.ser_in.sample_get():
data |= 1
delay(self.dt)
self.clk.on()
delay(self.dt)
self.clk.off()
delay(self.dt)
return data

View File

@ -0,0 +1,613 @@
from artiq.language.core import *
from artiq.language.types import *
from artiq.coredevice.rtio import rtio_output, rtio_input_data
from artiq.coredevice import spi2 as spi
from artiq.language.units import us
@portable
def shuttler_volt_to_mu(volt):
"""Return the equivalent DAC code. Valid input range is from -10 to
10 - LSB.
"""
return round((1 << 16) * (volt / 20.0)) & 0xffff
class Config:
"""Shuttler configuration registers interface.
The configuration registers control waveform phase auto-clear, pre-DAC
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
output data with pre-DAC gain, then adds the offset.
.. note::
The DAC code is capped at 0x7fff and 0x8000.
:param channel: RTIO channel number of this interface.
:param core_device: Core device name.
"""
kernel_invariants = {
"core", "channel", "target_base", "target_read",
"target_gain", "target_offset", "target_clr"
}
def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device)
self.channel = channel
self.target_base = channel << 8
self.target_read = 1 << 6
self.target_gain = 0 * (1 << 4)
self.target_offset = 1 * (1 << 4)
self.target_clr = 1 * (1 << 5)
@kernel
def set_clr(self, clr):
"""Set/Unset waveform phase clear bits.
Each bit corresponds to a Shuttler waveform generator core. Setting a
clear bit forces the Shuttler Core to clear the phase accumulator on
waveform trigger (See :class:`Trigger` for the trigger method).
Otherwise, the phase accumulator increments from its original value.
:param clr: Waveform phase clear bits. The MSB corresponds to Channel
15, LSB corresponds to Channel 0.
"""
rtio_output(self.target_base | self.target_clr, clr)
@kernel
def set_gain(self, channel, gain):
"""Set the 16-bits pre-DAC gain register of a Shuttler Core channel.
The `gain` parameter represents the decimal portion of the gain
factor. The MSB represents 0.5 and the sign bit. Hence, the valid
total gain value (1 +/- 0.gain) ranges from 0.5 to 1.5 - LSB.
:param channel: Shuttler Core channel to be configured.
:param gain: Shuttler Core channel gain.
"""
rtio_output(self.target_base | self.target_gain | channel, gain)
@kernel
def get_gain(self, channel):
"""Return the pre-DAC gain value of a Shuttler Core channel.
:param channel: The Shuttler Core channel.
:return: Pre-DAC gain value. See :meth:`set_gain`.
"""
rtio_output(self.target_base | self.target_gain |
self.target_read | channel, 0)
return rtio_input_data(self.channel)
@kernel
def set_offset(self, channel, offset):
"""Set the 16-bits pre-DAC offset register of a Shuttler Core channel.
See also :meth:`shuttler_volt_to_mu`.
:param channel: Shuttler Core channel to be configured.
:param offset: Shuttler Core channel offset.
"""
rtio_output(self.target_base | self.target_offset | channel, offset)
@kernel
def get_offset(self, channel):
"""Return the pre-DAC offset value of a Shuttler Core channel.
:param channel: The Shuttler Core channel.
:return: Pre-DAC offset value. See :meth:`set_offset`.
"""
rtio_output(self.target_base | self.target_offset |
self.target_read | channel, 0)
return rtio_input_data(self.channel)
class DCBias:
"""Shuttler Core cubic DC-bias spline.
A Shuttler channel can generate a waveform `w(t)` that is the sum of a
cubic spline `a(t)` and a sinusoid modulated in amplitude by a cubic
spline `b(t)` and in phase/frequency by a quadratic spline `c(t)`, where
.. math::
w(t) = a(t) + b(t) * cos(c(t))
and `t` corresponds to time in seconds.
This class controls the cubic spline `a(t)`, in which
.. math::
a(t) = p_0 + p_1t + \\frac{p_2t^2}{2} + \\frac{p_3t^3}{6}
and `a(t)` is measured in volts.
:param channel: RTIO channel number of this DC-bias spline interface.
:param core_device: Core device name.
"""
kernel_invariants = {"core", "channel", "target_o"}
def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device)
self.channel = channel
self.target_o = channel << 8
@kernel
def set_waveform(self, a0: TInt32, a1: TInt32, a2: TInt64, a3: TInt64):
"""Set the DC-bias spline waveform.
Given `a(t)` as defined in :class:`DCBias`, the coefficients should be
configured by the following formulae:
.. math::
T &= 8*10^{-9}
a_0 &= p_0
a_1 &= p_1T + \\frac{p_2T^2}{2} + \\frac{p_3T^3}{6}
a_2 &= p_2T^2 + p_3T^3
a_3 &= p_3T^3
:math:`a_0`, :math:`a_1`, :math:`a_2` and :math:`a_3` are 16, 32, 48
and 48 bits in width respectively. See :meth:`shuttler_volt_to_mu` for
machine unit conversion.
.. note::
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 a1: The :math:`a_1` coefficient in machine unit.
:param a2: The :math:`a_2` coefficient in machine unit.
:param a3: The :math:`a_3` coefficient in machine unit.
"""
coef_words = [
a0,
a1,
a1 >> 16,
a2 & 0xFFFF,
(a2 >> 16) & 0xFFFF,
(a2 >> 32) & 0xFFFF,
a3 & 0xFFFF,
(a3 >> 16) & 0xFFFF,
(a3 >> 32) & 0xFFFF,
]
for i in range(len(coef_words)):
rtio_output(self.target_o | i, coef_words[i])
delay_mu(int64(self.core.ref_multiplier))
class DDS:
"""Shuttler Core DDS spline.
A Shuttler channel can generate a waveform `w(t)` that is the sum of a
cubic spline `a(t)` and a sinusoid modulated in amplitude by a cubic
spline `b(t)` and in phase/frequency by a quadratic spline `c(t)`, where
.. math::
w(t) = a(t) + b(t) * cos(c(t))
and `t` corresponds to time in seconds.
This class controls the cubic spline `b(t)` and quadratic spline `c(t)`,
in which
.. math::
b(t) &= g * (q_0 + q_1t + \\frac{q_2t^2}{2} + \\frac{q_3t^3}{6})
c(t) &= r_0 + r_1t + \\frac{r_2t^2}{2}
`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`.
:param channel: RTIO channel number of this DC-bias spline interface.
:param core_device: Core device name.
"""
kernel_invariants = {"core", "channel", "target_o"}
def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device)
self.channel = channel
self.target_o = channel << 8
@kernel
def set_waveform(self, b0: TInt32, b1: TInt32, b2: TInt64, b3: TInt64,
c0: TInt32, c1: TInt32, c2: TInt32):
"""Set the DDS spline waveform.
Given `b(t)` and `c(t)` as defined in :class:`DDS`, the coefficients
should be configured by the following formulae.
.. math::
T &= 8*10^{-9}
b_0 &= q_0
b_1 &= q_1T + \\frac{q_2T^2}{2} + \\frac{q_3T^3}{6}
b_2 &= q_2T^2 + q_3T^3
b_3 &= q_3T^3
c_0 &= r_0
c_1 &= r_1T + \\frac{r_2T^2}{2}
c_2 &= r_2T^2
:math:`b_0`, :math:`b_1`, :math:`b_2` and :math:`b_3` are 16, 32, 48
and 48 bits in width respectively. See :meth:`shuttler_volt_to_mu` for
machine unit conversion. :math:`c_0`, :math:`c_1` and :math:`c_2` are
16, 32 and 32 bits in width respectively.
Note: The waveform is not updated to the Shuttler Core until
triggered. See :class:`Trigger` for the update triggering mechanism.
:param b0: The :math:`b_0` coefficient in machine units.
:param b1: The :math:`b_1` coefficient in machine units.
:param b2: The :math:`b_2` coefficient in machine units.
:param b3: The :math:`b_3` coefficient in machine units.
:param c0: The :math:`c_0` coefficient in machine units.
:param c1: The :math:`c_1` coefficient in machine units.
:param c2: The :math:`c_2` coefficient in machine units.
"""
coef_words = [
b0,
b1,
b1 >> 16,
b2 & 0xFFFF,
(b2 >> 16) & 0xFFFF,
(b2 >> 32) & 0xFFFF,
b3 & 0xFFFF,
(b3 >> 16) & 0xFFFF,
(b3 >> 32) & 0xFFFF,
c0,
c1,
c1 >> 16,
c2,
c2 >> 16,
]
for i in range(len(coef_words)):
rtio_output(self.target_o | i, coef_words[i])
delay_mu(int64(self.core.ref_multiplier))
class Trigger:
"""Shuttler Core spline coefficients update trigger.
:param channel: RTIO channel number of the trigger interface.
:param core_device: Core device name.
"""
kernel_invariants = {"core", "channel", "target_o"}
def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device)
self.channel = channel
self.target_o = channel << 8
@kernel
def trigger(self, trig_out):
"""Triggers coefficient update of (a) Shuttler Core channel(s).
Each bit corresponds to a Shuttler waveform generator core. Setting
``trig_out`` bits commits the pending coefficient update (from
``set_waveform`` in :class:`DCBias` and :class:`DDS`) to the Shuttler Core
synchronously.
:param trig_out: Coefficient update trigger bits. The MSB corresponds
to Channel 15, LSB corresponds to Channel 0.
"""
rtio_output(self.target_o, trig_out)
RELAY_SPI_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
ADC_SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
1*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
# SPI clock write and read dividers
# CS should assert at least 9.5 ns after clk pulse
SPIT_RELAY_WR = 4
# 25 ns high/low pulse hold (limiting for write)
SPIT_ADC_WR = 4
SPIT_ADC_RD = 16
# SPI CS line
CS_RELAY = 1 << 0
CS_LED = 1 << 1
CS_ADC = 1 << 0
# Referenced AD4115 registers
_AD4115_REG_STATUS = 0x00
_AD4115_REG_ADCMODE = 0x01
_AD4115_REG_DATA = 0x04
_AD4115_REG_ID = 0x07
_AD4115_REG_CH0 = 0x10
_AD4115_REG_SETUPCON0 = 0x20
class Relay:
"""Shuttler AFE relay switches.
This class controls the AFE relay switches and the LEDs. Switch the relay on to
enable AFE output; off to disable the output. The LEDs indicate the
relay status.
.. note::
The relay does not disable ADC measurements. Voltage of any channels
can still be read by the ADC even after switching off the relays.
:param spi_device: SPI bus device name.
:param core_device: Core device name.
"""
kernel_invariant = {"core", "bus"}
def __init__(self, dmgr, spi_device, core_device="core"):
self.core = dmgr.get(core_device)
self.bus = dmgr.get(spi_device)
@kernel
def init(self):
"""Initialize SPI device.
Configures the SPI bus to 16 bits, write-only, simultaneous relay
switches and LED control.
"""
self.bus.set_config_mu(
RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED)
@kernel
def enable(self, en: TInt32):
"""Enable/disable relay switches of corresponding channels.
Each bit corresponds to the relay switch of a channel. Asserting a bit
turns on the corresponding relay switch; deasserting the same bit
turns off the switch instead.
:param en: Switch enable bits. The MSB corresponds to Channel 15, LSB
corresponds to Channel 0.
"""
self.bus.write(en << 16)
class ADC:
"""Shuttler AFE ADC (AD4115) driver.
:param spi_device: SPI bus device name.
:param core_device: Core device name.
"""
kernel_invariant = {"core", "bus"}
def __init__(self, dmgr, spi_device, core_device="core"):
self.core = dmgr.get(core_device)
self.bus = dmgr.get(spi_device)
@kernel
def read_id(self) -> TInt32:
"""Read the product ID of the ADC.
The expected return value is 0x38DX, the 4 LSbs are don't cares.
:return: The read-back product ID.
"""
return self.read16(_AD4115_REG_ID)
@kernel
def reset(self):
"""AD4115 reset procedure.
Performs a write operation of 96 serial clock cycles with DIN
held at high. This resets the entire device, including the register
contents.
.. note::
The datasheet only requires 64 cycles, but reasserting ``CS_n`` right
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.write(0xffffffff)
self.bus.write(0xffffffff)
self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC)
self.bus.write(0xffffffff)
@kernel
def read8(self, addr: TInt32) -> TInt32:
"""Read from 8-bit register.
:param addr: Register address.
:return: Read-back register content.
"""
self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
16, SPIT_ADC_RD, CS_ADC)
self.bus.write((addr | 0x40) << 24)
return self.bus.read() & 0xff
@kernel
def read16(self, addr: TInt32) -> TInt32:
"""Read from 16-bit register.
:param addr: Register address.
:return: Read-back register content.
"""
self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
24, SPIT_ADC_RD, CS_ADC)
self.bus.write((addr | 0x40) << 24)
return self.bus.read() & 0xffff
@kernel
def read24(self, addr: TInt32) -> TInt32:
"""Read from 24-bit register.
:param addr: Register address.
:return: Read-back register content.
"""
self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
32, SPIT_ADC_RD, CS_ADC)
self.bus.write((addr | 0x40) << 24)
return self.bus.read() & 0xffffff
@kernel
def write8(self, addr: TInt32, data: TInt32):
"""Write to 8-bit register.
:param addr: Register address.
:param data: Data to be written.
"""
self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 16, SPIT_ADC_WR, CS_ADC)
self.bus.write(addr << 24 | (data & 0xff) << 16)
@kernel
def write16(self, addr: TInt32, data: TInt32):
"""Write to 16-bit register.
:param addr: Register address.
:param data: Data to be written.
"""
self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 24, SPIT_ADC_WR, CS_ADC)
self.bus.write(addr << 24 | (data & 0xffff) << 8)
@kernel
def write24(self, addr: TInt32, data: TInt32):
"""Write to 24-bit register.
:param addr: Register address.
:param data: Data to be written.
"""
self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC)
self.bus.write(addr << 24 | (data & 0xffffff))
@kernel
def read_ch(self, channel: TInt32) -> TFloat:
"""Sample a Shuttler channel on the AFE.
Performs a single conversion using profile 0 and setup 0 on the
selected channel. The sample is then recovered and converted to volts.
:param channel: Shuttler channel to be sampled.
:return: Voltage sample in volts.
"""
# Always configure Profile 0 for single conversion
self.write16(_AD4115_REG_CH0, 0x8000 | ((channel * 2 + 1) << 4))
self.write16(_AD4115_REG_SETUPCON0, 0x1300)
self.single_conversion()
delay(100*us)
adc_code = self.read24(_AD4115_REG_DATA)
return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1
@kernel
def single_conversion(self):
"""Place the ADC in single conversion mode.
The ADC returns to standby mode after the conversion is complete.
"""
self.write16(_AD4115_REG_ADCMODE, 0x8010)
@kernel
def standby(self):
"""Place the ADC in standby mode and disable power down the clock.
The ADC can be returned to single conversion mode by calling
:meth:`single_conversion`.
"""
# Selecting internal XO (0b00) also disables clock during standby
self.write16(_AD4115_REG_ADCMODE, 0x8020)
@kernel
def power_down(self):
"""Place the ADC in power-down mode.
The ADC must be reset before returning to other modes.
.. note::
The AD4115 datasheet suggests placing the ADC in standby mode
before power-down. This is to prevent accidental entry into the
power-down mode. See also :meth:`standby` and :meth:`power_up`.
"""
self.write16(_AD4115_REG_ADCMODE, 0x8030)
@kernel
def power_up(self):
"""Exit the ADC power-down mode.
The ADC should be in power-down mode before calling this method.
See also :meth:`power_down`.
"""
self.reset()
# Although the datasheet claims 500 us reset wait time, only waiting
# for ~500 us can result in DOUT pin stuck in high
delay(2500*us)
@kernel
def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]):
"""Calibrate the Shuttler waveform generator using the ADC on the AFE.
Finds the average slope rate and average offset by samples, and
compensates by writing the pre-DAC gain and offset registers in the
configuration registers.
.. note::
If the pre-calibration slope rate is less than 1, the calibration
procedure will introduce a pre-DAC gain compensation. However, this
may saturate the pre-DAC voltage code (see :class:`Config` notes).
Shuttler cannot cover the entire +/- 10 V range in this case.
See also :meth:`Config.set_gain` and :meth:`Config.set_offset`.
:param volts: A list of all 16 cubic DC-bias splines.
(See :class:`DCBias`)
:param trigger: The Shuttler spline coefficient update trigger.
:param config: The Shuttler Core configuration registers.
:param samples: A list of sample voltages for calibration. There must
be at least 2 samples to perform slope rate calculation.
"""
assert len(volts) == 16
assert len(samples) > 1
measurements = [0.0] * len(samples)
for ch in range(16):
# Find the average slope rate and offset
for i in range(len(samples)):
self.core.break_realtime()
volts[ch].set_waveform(
shuttler_volt_to_mu(samples[i]), 0, 0, 0)
trigger.trigger(1 << ch)
measurements[i] = self.read_ch(ch)
# Find the average output slope
slope_sum = 0.0
for i in range(len(samples) - 1):
slope_sum += (measurements[i+1] - measurements[i])/(samples[i+1] - samples[i])
slope_avg = slope_sum / (len(samples) - 1)
gain_code = int32(1 / slope_avg * (2 ** 16)) & 0xffff
# Scale the measurements by 1/slope, find average offset
offset_sum = 0.0
for i in range(len(samples)):
offset_sum += (measurements[i] / slope_avg) - samples[i]
offset_avg = offset_sum / len(samples)
offset_code = shuttler_volt_to_mu(-offset_avg)
self.core.break_realtime()
config.set_gain(ch, gain_code)
delay_mu(int64(self.core.ref_multiplier))
config.set_offset(ch, offset_code)

View File

@ -4,7 +4,7 @@ Driver for generic SPI on RTIO.
This ARTIQ coredevice driver corresponds to the "new" MiSoC SPI core (v2).
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
@ -51,7 +51,7 @@ class SPIMaster:
event (``SPI_INPUT`` set), then :meth:`read` the ``data``.
* 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
last transfer are submitted with ``SPI_END`` cleared in the configuration
register.
@ -72,6 +72,10 @@ class SPIMaster:
self.channel = channel
self.update_xfer_duration_mu(div, length)
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@portable
def frequency_to_div(self, f):
"""Convert a SPI clock frequency to the closest SPI clock divider."""
@ -134,10 +138,10 @@ class SPIMaster:
* :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)
: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.
(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.
Or number of the chip select to assert if ``cs`` is decoded
downstream. (reset=0)
@ -148,16 +152,15 @@ class SPIMaster:
def set_config_mu(self, flags, length, div, cs):
"""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 length: Number of bits to write during the next transfer.
(reset=1)
:param div: Counter load value to divide the RTIO
clock by to generate the SPI clock. (minimum=2, reset=2)
``f_rtio_clk/f_spi == div``. If ``div`` is odd,
the setup phase of the SPI clock is one coarse RTIO clock cycle
longer than the hold phase.
clock by to generate the SPI clock; ``f_rtio_clk/f_spi == div``.
If ``div`` is odd, the setup phase of the SPI clock is one
coarse RTIO clock cycle longer than the hold phase. (minimum=2, reset=2)
:param cs: Bit pattern of chip selects to assert.
Or number of the chip select to assert if ``cs`` is decoded
downstream. (reset=0)
@ -184,7 +187,7 @@ class SPIMaster:
experiments and are known.
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
sequence, the playback of the sequence will not update the
@ -204,7 +207,7 @@ class SPIMaster:
* The ``data`` register and the shift register are 32 bits wide.
* Data writes take one ``ref_period`` cycle.
* 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.
* Transfers in a multi-transfer transaction take up to one SPI clock
cycle less time depending on multiple parameters. Advanced users may
@ -273,9 +276,8 @@ class NRTSPIMaster:
def set_config_mu(self, flags=0, length=8, div=6, cs=1):
"""Set the ``config`` register.
Note that the non-realtime SPI cores are usually clocked by the system
clock and not the RTIO clock. In many cases, the SPI configuration is
already set by the firmware and you do not need to call this method.
In many cases, the SPI configuration is already set by the firmware
and you do not need to call this method.
"""
spi_set_config(self.busno, flags, length, div, cs)

View File

@ -1,228 +0,0 @@
from numpy import int32, int64
from artiq.language.core import kernel, portable, delay
from artiq.coredevice.rtio import rtio_output, rtio_output_wide
from artiq.language.types import TInt32, TInt64, TFloat
class Spline:
r"""Spline interpolating RTIO channel.
One knot of a polynomial basis spline (B-spline) :math:`u(t)`
is defined by the coefficients :math:`u_n` up to order :math:`n = k`.
If the coefficients are evaluated starting at time :math:`t_0`,
the output :math:`u(t)` for :math:`t > t_0, t_0` is:
.. math::
u(t) &= \sum_{n=0}^k \frac{u_n}{n!} (t - t_0)^n \\
&= u_0 + u_1 (t - t_0) + \frac{u_2}{2} (t - t_0)^2 + \dots
This class contains multiple methods to convert spline knot data from SI
to machine units and multiple methods that set the current spline
coefficient data. None of these advance the timeline. The :meth:`smooth`
method is the only method that advances the timeline.
:param width: Width in bits of the quantity that this spline controls
:param time_width: Width in bits of the time counter of this spline
:param channel: RTIO channel number
:param core_device: Core device that this spline is attached to
:param scale: Scale for conversion between machine units and physical
units; to be given as the "full scale physical value".
"""
kernel_invariants = {"channel", "core", "scale", "width",
"time_width", "time_scale"}
def __init__(self, width, time_width, channel, core_device, scale=1.):
self.core = core_device
self.channel = channel
self.width = width
self.scale = float((int64(1) << width) / scale)
self.time_width = time_width
self.time_scale = float((1 << time_width) *
core_device.coarse_ref_period)
@portable(flags={"fast-math"})
def to_mu(self, value: TFloat) -> TInt32:
"""Convert floating point ``value`` from physical units to 32 bit
integer machine units."""
return int32(round(value*self.scale))
@portable(flags={"fast-math"})
def from_mu(self, value: TInt32) -> TFloat:
"""Convert 32 bit integer ``value`` from machine units to floating
point physical units."""
return value/self.scale
@portable(flags={"fast-math"})
def to_mu64(self, value: TFloat) -> TInt64:
"""Convert floating point ``value`` from physical units to 64 bit
integer machine units."""
return int64(round(value*self.scale))
@kernel
def set_mu(self, value: TInt32):
"""Set spline value (machine units).
:param value: Spline value in integer machine units.
"""
rtio_output(self.channel << 8, value)
@kernel(flags={"fast-math"})
def set(self, value: TFloat):
"""Set spline value.
:param value: Spline value relative to full-scale.
"""
if self.width > 32:
l = [int32(0)] * 2
self.pack_coeff_mu([self.to_mu64(value)], l)
rtio_output_wide(self.channel << 8, l)
else:
rtio_output(self.channel << 8, self.to_mu(value))
@kernel
def set_coeff_mu(self, value): # TList(TInt32)
"""Set spline raw values.
:param value: Spline packed raw values.
"""
rtio_output_wide(self.channel << 8, value)
@portable(flags={"fast-math"})
def pack_coeff_mu(self, coeff, packed): # TList(TInt64), TList(TInt32)
"""Pack coefficients into RTIO data
:param coeff: TList(TInt64) list of machine units spline coefficients.
Lowest (zeroth) order first. The coefficient list is zero-extended
by the RTIO gateware.
:param packed: TList(TInt32) list for packed RTIO data. Must be
pre-allocated. Length in bits is
``n*width + (n - 1)*n//2*time_width``
"""
pos = 0
for i in range(len(coeff)):
wi = self.width + i*self.time_width
ci = coeff[i]
while wi != 0:
j = pos//32
used = pos - 32*j
avail = 32 - used
if avail > wi:
avail = wi
cij = int32(ci)
if avail != 32:
cij &= (1 << avail) - 1
packed[j] |= cij << used
ci >>= avail
wi -= avail
pos += avail
@portable(flags={"fast-math"})
def coeff_to_mu(self, coeff, coeff64): # TList(TFloat), TList(TInt64)
"""Convert a floating point list of coefficients into a 64 bit
integer (preallocated).
:param coeff: TList(TFloat) list of coefficients in physical units.
:param coeff64: TList(TInt64) preallocated list of coefficients in
machine units.
"""
for i in range(len(coeff)):
vi = coeff[i] * self.scale
for j in range(i):
vi *= self.time_scale
ci = int64(round(vi))
coeff64[i] = ci
# artiq.wavesynth.coefficients.discrete_compensate:
if i == 2:
coeff64[1] += ci >> self.time_width + 1
elif i == 3:
coeff64[2] += ci >> self.time_width
coeff64[1] += ci // 6 >> 2*self.time_width
def coeff_as_packed_mu(self, coeff64):
"""Pack 64 bit integer machine units coefficients into 32 bit integer
RTIO data list.
This is a host-only method that can be used to generate packed
spline coefficient data to be frozen into kernels at compile time.
"""
n = len(coeff64)
width = n*self.width + (n - 1)*n//2*self.time_width
packed = [int32(0)] * ((width + 31)//32)
self.pack_coeff_mu(coeff64, packed)
return packed
def coeff_as_packed(self, coeff):
"""Convert floating point spline coefficients into 32 bit integer
packed data.
This is a host-only method that can be used to generate packed
spline coefficient data to be frozen into kernels at compile time.
"""
coeff64 = [int64(0)] * len(coeff)
self.coeff_to_mu(coeff, coeff64)
return self.coeff_as_packed_mu(coeff64)
@kernel(flags={"fast-math"})
def set_coeff(self, coeff): # TList(TFloat)
"""Set spline coefficients.
Missing coefficients (high order) are zero-extended byt the RTIO
gateware.
If more coefficients are supplied than the gateware supports the extra
coefficients are ignored.
:param value: List of floating point spline coefficients,
lowest order (constant) coefficient first. Units are the
unit of this spline's value times increasing powers of 1/s.
"""
n = len(coeff)
coeff64 = [int64(0)] * n
self.coeff_to_mu(coeff, coeff64)
width = n*self.width + (n - 1)*n//2*self.time_width
packed = [int32(0)] * ((width + 31)//32)
self.pack_coeff_mu(coeff64, packed)
self.set_coeff_mu(packed)
@kernel(flags={"fast-math"})
def smooth(self, start: TFloat, stop: TFloat, duration: TFloat,
order: TInt32):
"""Initiate an interpolated value change.
For zeroth order (step) interpolation, the step is at
``start + duration/2``.
First order interpolation corresponds to a linear value ramp from
``start`` to ``stop`` over ``duration``.
The third order interpolation is constrained to have zero first
order derivative at both `start` and `stop`.
For first order and third order interpolation (linear and cubic)
the interpolator needs to be stopped explicitly at the stop time
(e.g. by setting spline coefficient data or starting a new
:meth:`smooth` interpolation).
This method advances the timeline by ``duration``.
:param start: Initial value of the change. In physical units.
:param stop: Final value of the change. In physical units.
:param duration: Duration of the interpolation. In physical units.
:param order: Order of the interpolation. Only 0, 1,
and 3 are valid: step, linear, cubic.
"""
if order == 0:
delay(duration/2.)
self.set_coeff([stop])
delay(duration/2.)
elif order == 1:
self.set_coeff([start, (stop - start)/duration])
delay(duration)
elif order == 3:
v2 = 6.*(stop - start)/(duration*duration)
self.set_coeff([start, 0., v2, -2.*v2/duration])
delay(duration)
else:
raise ValueError("Invalid interpolation order. "
"Supported orders are: 0, 1, 3.")

View File

@ -23,12 +23,12 @@ def y_mu_to_full_scale(y):
@portable
def adc_mu_to_volts(x, gain):
"""Convert servo ADC data from machine units to Volt."""
def adc_mu_to_volts(x, gain, corrected_fs=True):
"""Convert servo ADC data from machine units to volts."""
val = (x >> 1) & 0xffff
mask = 1 << 15
val = -(val & mask) + (val & ~mask)
return sampler.adc_mu_to_volt(val, gain)
return sampler.adc_mu_to_volt(val, gain, corrected_fs)
class SUServo:
@ -62,14 +62,15 @@ class SUServo:
:param gains: Initial value for PGIA gains shift register
(default: 0x0000). Knowledge of this state is not transferred
between experiments.
:param sampler_hw_rev: Sampler's revision string
:param core_device: Core device name
"""
kernel_invariants = {"channel", "core", "pgia", "cplds", "ddses",
"ref_period_mu"}
"ref_period_mu", "corrected_fs"}
def __init__(self, dmgr, channel, pgia_device,
cpld_devices, dds_devices,
gains=0x0000, core_device="core"):
gains=0x0000, sampler_hw_rev="v2.2", core_device="core"):
self.core = dmgr.get(core_device)
self.pgia = dmgr.get(pgia_device)
@ -81,8 +82,13 @@ class SUServo:
self.gains = gains
self.ref_period_mu = self.core.seconds_to_mu(
self.core.coarse_ref_period)
self.corrected_fs = sampler.Sampler.use_corrected_fs(sampler_hw_rev)
assert self.ref_period_mu == self.core.ref_multiplier
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel
def init(self):
"""Initialize the servo, Sampler and both Urukuls.
@ -149,7 +155,7 @@ class SUServo:
This method advances the timeline by one servo memory access.
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
update will happen about two servo cycles (~2.3 µs) after enabling
the servo. The delay is deterministic.
@ -192,7 +198,7 @@ class SUServo:
consistent and valid data, stop the servo before using this method.
: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
# range, X0/X1/OFFSET are only 24 bits. Finally, the RTIO interface
@ -234,7 +240,7 @@ class SUServo:
"""
val = self.get_adc_mu(channel)
gain = (self.gains >> (channel*2)) & 0b11
return adc_mu_to_volts(val, gain)
return adc_mu_to_volts(val, gain, self.corrected_fs)
class Channel:
@ -255,6 +261,10 @@ class Channel:
self.servo.channel)
self.dds = self.servo.ddses[self.servo_channel // 4]
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel
def set(self, en_out, en_iir=0, profile=0):
"""Operate channel.
@ -278,12 +288,12 @@ class Channel:
def set_dds_mu(self, profile, ftw, offs, pow_=0):
"""Set profile DDS coefficients in machine units.
.. seealso:: :meth:`set_amplitude`
See also :meth:`Channel.set_dds`.
:param profile: Profile number (0-31)
:param ftw: Frequency tuning word (32 bit unsigned)
:param offs: IIR offset (17 bit signed)
:param pow_: Phase offset word (16 bit)
:param ftw: Frequency tuning word (32-bit unsigned)
:param offs: IIR offset (17-bit signed)
:param pow_: Phase offset word (16-bit)
"""
base = (self.servo_channel << 8) | (profile << 3)
self.servo.write(base + 0, ftw >> 16)
@ -317,7 +327,7 @@ class Channel:
See :meth:`set_dds_mu` for setting the complete DDS profile.
: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)
self.servo.write(base + 4, offs)
@ -365,15 +375,15 @@ class Channel:
* :math:`b_0` and :math:`b_1` are the feedforward gains for the two
delays
.. seealso:: :meth:`set_iir`
See also :meth:`Channel.set_iir`.
:param profile: Profile number (0-31)
: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)
:param b0: 18 bit signed B0 coefficient (recent,
:param b0: 18-bit signed B0 coefficient (recent,
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)
:param dly: IIR update suppression time. In units of IIR cycles
(~1.2 µs, 0-255).
@ -489,7 +499,7 @@ class Channel:
consistent and valid data, stop the servo before using this method.
: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)
@ -525,7 +535,7 @@ class Channel:
This method advances the timeline by one servo memory access.
: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.
# Reads interact with the 18 MSBs (coefficient memory width)

View File

@ -27,7 +27,7 @@ class TTLOut:
This should be used with output-only channels.
:param channel: channel number
:param channel: Channel number
"""
kernel_invariants = {"core", "channel", "target_o"}
@ -36,6 +36,10 @@ class TTLOut:
self.channel = channel
self.target_o = channel << 8
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel
def output(self):
pass
@ -105,7 +109,7 @@ class TTLInOut:
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.
:param channel: channel number
:param channel: Channel number
"""
kernel_invariants = {"core", "channel", "gate_latency_mu",
"target_o", "target_oe", "target_sens", "target_sample"}
@ -128,6 +132,10 @@ class TTLInOut:
self.target_sens = (channel << 8) + 2
self.target_sample = (channel << 8) + 3
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel
def set_oe(self, oe):
rtio_output(self.target_oe, 1 if oe else 0)
@ -137,7 +145,7 @@ class TTLInOut:
"""Set the direction to output at the current position of the time
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.
This method only configures the direction at the FPGA. When using
@ -150,7 +158,7 @@ class TTLInOut:
"""Set the direction to input at the current position of the time
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.
This method only configures the direction at the FPGA. When using
@ -318,17 +326,18 @@ class TTLInOut:
:return: The number of events before the timeout elapsed (0 if none
observed).
Examples:
**Examples:**
To count events on channel ``ttl_input``, up to the current timeline
position::
position: ::
ttl_input.count(now_mu())
If other events are scheduled between the end of the input gate
period and when the number of events is counted, using ``now_mu()``
as timeout consumes an unnecessary amount of timeline slack. In
such cases, it can be beneficial to pass a more precise timestamp,
for example::
period and when the number of events is counted, using
:meth:`~artiq.language.core.now_mu()` as timeout consumes an
unnecessary amount of timeline slack. In such cases, it can be
beneficial to pass a more precise timestamp, for example: ::
gate_end_mu = ttl_input.gate_rising(100 * us)
@ -342,7 +351,7 @@ class TTLInOut:
num_rising_edges = ttl_input.count(gate_end_mu)
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))
"""
@ -433,7 +442,7 @@ class TTLInOut:
was being watched.
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)
success = True
@ -465,6 +474,10 @@ class TTLClockGen:
self.acc_width = numpy.int64(acc_width)
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@portable
def frequency_to_ftw(self, frequency):
"""Returns the frequency tuning word corresponding to the given

View File

@ -130,7 +130,7 @@ class CPLD:
:param spi_device: SPI bus device name
:param io_update_device: IO update 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)
frequency in Hz
: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.
On Urukul boards with CPLD gateware before v1.3.1 only the default
(0, i.e. variant dependent divider) is valid.
:param sync_sel: SYNC (multi-chip synchronisation) signal source selection.
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
:param sync_sel: ``SYNC`` (multi-chip synchronisation) signal source selection.
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
other chips.
:param rf_sw: Initial CPLD RF switch register setting (default: 0x0).
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
state without side effects. Knowledge of this state is not transferred
between experiments.
:param sync_div: SYNC_IN generator divider. The ratio between the coarse
RTIO frequency and the SYNC_IN generator frequency (default: 2 if
:param sync_div: ``SYNC_IN`` generator divider. The ratio between the coarse
RTIO frequency and the ``SYNC_IN`` generator frequency (default: 2 if
`sync_device` was specified).
:param core_device: Core device name
@ -204,7 +204,7 @@ class CPLD:
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`.
"""
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
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.
"""
@ -283,7 +283,7 @@ class CPLD:
def cfg_switches(self, state: TInt32):
"""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)
@ -327,10 +327,9 @@ class CPLD:
@kernel
def set_all_att_mu(self, att_reg: TInt32):
"""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,
SPIT_ATT_WR, CS_ATT)
@ -342,8 +341,7 @@ class CPLD:
"""Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels.
.. seealso:: :meth:`set_att_mu`
See also :meth:`set_att_mu`.
:param channel: Attenuator channel (0-3).
: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
: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,
SPIT_ATT_RD, CS_ATT)
@ -380,7 +378,7 @@ class CPLD:
The result is stored and will be used in future calls of
: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).
:return: 8-bit digital attenuation setting:
@ -392,7 +390,7 @@ class CPLD:
def get_channel_att(self, channel: TInt32) -> TFloat:
"""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).
:return: Attenuation setting in dB. Higher value is more
@ -403,14 +401,14 @@ class CPLD:
@kernel
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.
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.
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.
"""
ftw_max = 1 << 4

View File

@ -1,7 +1,7 @@
"""RTIO driver for the Zotino 32-channel, 16-bit 1MSPS DAC.
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

View File

@ -1,7 +1,7 @@
import asyncio
import logging
from PyQt5 import QtCore, QtWidgets
from PyQt6 import QtCore, QtGui, QtWidgets
from artiq.gui import applets
@ -13,58 +13,58 @@ class AppletsCCBDock(applets.AppletsDock):
def __init__(self, *args, **kwargs):
applets.AppletsDock.__init__(self, *args, **kwargs)
sep = QtWidgets.QAction(self.table)
sep = QtGui.QAction(self.table)
sep.setSeparator(True)
self.table.addAction(sep)
ccbp_group_menu = QtWidgets.QMenu()
actiongroup = QtWidgets.QActionGroup(self.table)
ccbp_group_menu = QtWidgets.QMenu(self.table)
actiongroup = QtGui.QActionGroup(self.table)
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.triggered.connect(lambda: self.set_ccbp(""))
ccbp_group_menu.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.triggered.connect(lambda: self.set_ccbp("ignore"))
ccbp_group_menu.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.triggered.connect(lambda: self.set_ccbp("create"))
ccbp_group_menu.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.ccbp_group_enable.setCheckable(True)
self.ccbp_group_enable.triggered.connect(lambda: self.set_ccbp("enable"))
ccbp_group_menu.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.table.addAction(self.ccbp_group_action)
self.table.itemSelectionChanged.connect(self.update_group_ccbp_menu)
self.update_group_ccbp_menu()
ccbp_global_menu = QtWidgets.QMenu()
actiongroup = QtWidgets.QActionGroup(self.table)
ccbp_global_menu = QtWidgets.QMenu(self.table)
actiongroup = QtGui.QActionGroup(self.table)
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)
ccbp_global_menu.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.setChecked(True)
ccbp_global_menu.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.ccbp_global_enable.setCheckable(True)
ccbp_global_menu.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)
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)
if ccbp == "enable":
applet.setCheckState(0, QtCore.Qt.Checked)
applet.setCheckState(0, QtCore.Qt.CheckState.Checked)
def ccb_disable_applet(self, name, group=None):
"""Disables an applet.
@ -216,7 +216,7 @@ class AppletsCCBDock(applets.AppletsDock):
return
parent, applet = self.locate_applet(name, group, False)
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):
"""Disables all the applets in a group.
@ -246,7 +246,7 @@ class AppletsCCBDock(applets.AppletsDock):
return
else:
wi = nwi
wi.setCheckState(0, QtCore.Qt.Unchecked)
wi.setCheckState(0, QtCore.Qt.CheckState.Unchecked)
def ccb_notify(self, message):
try:

View File

@ -2,104 +2,29 @@ import asyncio
import logging
import numpy as np
from PyQt5 import QtCore, QtWidgets
from PyQt6 import QtCore, QtGui, QtWidgets
from sipyco import pyon
from artiq.tools import short_format, exc_to_warning
from artiq.gui.tools import LayoutWidget, QRecursiveFilterProxyModel
from artiq.tools import scale_from_metadata, short_format, exc_to_warning
from artiq.gui.tools import LayoutWidget
from artiq.gui.models import DictSyncTreeSepModel
from artiq.gui.scientific_spinbox import ScientificSpinBox
logger = logging.getLogger(__name__)
async def rename(key, newkey, value, dataset_ctl):
if key != newkey:
async def rename(key, new_key, value, metadata, persist, dataset_ctl):
if key != new_key:
await dataset_ctl.delete(key)
await dataset_ctl.set(newkey, value)
await dataset_ctl.set(new_key, value, metadata=metadata, persist=persist)
class Editor(QtWidgets.QDialog):
def __init__(self, parent, dataset_ctl, key, value):
QtWidgets.QDialog.__init__(self, parent=parent)
self.dataset_ctl = dataset_ctl
self.key = key
self.initial_type = type(value)
self.setWindowTitle("Edit dataset")
grid = QtWidgets.QGridLayout()
self.setLayout(grid)
grid.addWidget(QtWidgets.QLabel("Name:"), 0, 0)
self.name_widget = QtWidgets.QLineEdit()
self.name_widget.setText(key)
grid.addWidget(self.name_widget, 0, 1)
grid.addWidget(QtWidgets.QLabel("Value:"), 1, 0)
grid.addWidget(self.get_edit_widget(value), 1, 1)
buttons = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
grid.setRowStretch(2, 1)
grid.addWidget(buttons, 3, 0, 1, 2)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
def accept(self):
newkey = self.name_widget.text()
value = self.initial_type(self.get_edit_widget_value())
asyncio.ensure_future(rename(self.key, newkey, value, self.dataset_ctl))
QtWidgets.QDialog.accept(self)
def get_edit_widget(self, initial_value):
raise NotImplementedError
def get_edit_widget_value(self):
raise NotImplementedError
class NumberEditor(Editor):
def get_edit_widget(self, initial_value):
self.edit_widget = ScientificSpinBox()
self.edit_widget.setDecimals(13)
self.edit_widget.setPrecision()
self.edit_widget.setRelativeStep()
self.edit_widget.setValue(float(initial_value))
return self.edit_widget
def get_edit_widget_value(self):
return self.edit_widget.value()
class BoolEditor(Editor):
def get_edit_widget(self, initial_value):
self.edit_widget = QtWidgets.QCheckBox()
self.edit_widget.setChecked(bool(initial_value))
return self.edit_widget
def get_edit_widget_value(self):
return self.edit_widget.isChecked()
class StringEditor(Editor):
def get_edit_widget(self, initial_value):
self.edit_widget = QtWidgets.QLineEdit()
self.edit_widget.setText(initial_value)
return self.edit_widget
def get_edit_widget_value(self):
return self.edit_widget.text()
class Creator(QtWidgets.QDialog):
def __init__(self, parent, dataset_ctl):
class CreateEditDialog(QtWidgets.QDialog):
def __init__(self, parent, dataset_ctl, key=None, value=None, metadata=None, persist=False):
QtWidgets.QDialog.__init__(self, parent=parent)
self.dataset_ctl = dataset_ctl
self.setWindowTitle("Create dataset")
self.setWindowTitle("Create dataset" if key is None else "Edit dataset")
grid = QtWidgets.QGridLayout()
grid.setRowMinimumHeight(1, 40)
grid.setColumnMinimumWidth(2, 60)
@ -117,44 +42,125 @@ class Creator(QtWidgets.QDialog):
grid.addWidget(self.data_type, 1, 2)
self.value_widget.textChanged.connect(self.dtype)
grid.addWidget(QtWidgets.QLabel("Persist:"), 2, 0)
grid.addWidget(QtWidgets.QLabel("Unit:"), 2, 0)
self.unit_widget = QtWidgets.QLineEdit()
grid.addWidget(self.unit_widget, 2, 1)
grid.addWidget(QtWidgets.QLabel("Scale:"), 3, 0)
self.scale_widget = QtWidgets.QLineEdit()
grid.addWidget(self.scale_widget, 3, 1)
grid.addWidget(QtWidgets.QLabel("Precision:"), 4, 0)
self.precision_widget = QtWidgets.QLineEdit()
grid.addWidget(self.precision_widget, 4, 1)
grid.addWidget(QtWidgets.QLabel("Persist:"), 5, 0)
self.box_widget = QtWidgets.QCheckBox()
grid.addWidget(self.box_widget, 2, 1)
grid.addWidget(self.box_widget, 5, 1)
self.ok = QtWidgets.QPushButton('&Ok')
self.ok.setEnabled(False)
self.cancel = QtWidgets.QPushButton('&Cancel')
self.buttons = QtWidgets.QDialogButtonBox(self)
self.buttons.addButton(
self.ok, QtWidgets.QDialogButtonBox.AcceptRole)
self.ok, QtWidgets.QDialogButtonBox.ButtonRole.AcceptRole)
self.buttons.addButton(
self.cancel, QtWidgets.QDialogButtonBox.RejectRole)
grid.setRowStretch(3, 1)
grid.addWidget(self.buttons, 4, 0, 1, 3)
self.cancel, QtWidgets.QDialogButtonBox.ButtonRole.RejectRole)
grid.setRowStretch(6, 1)
grid.addWidget(self.buttons, 7, 0, 1, 3, alignment=QtCore.Qt.AlignmentFlag.AlignHCenter)
self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject)
self.key = key
self.name_widget.setText(key)
value_edit_string = self.value_to_edit_string(value)
if metadata is not None:
scale = scale_from_metadata(metadata)
t = value.dtype if value is np.ndarray else type(value)
if scale != 1 and np.issubdtype(t, np.number):
# degenerates to float type
value_edit_string = self.value_to_edit_string(value / scale)
self.unit_widget.setText(metadata.get('unit', ''))
self.scale_widget.setText(str(metadata.get('scale', '')))
self.precision_widget.setText(str(metadata.get('precision', '')))
self.value_widget.setText(value_edit_string)
self.box_widget.setChecked(persist)
def accept(self):
key = self.name_widget.text()
value = self.value_widget.text()
persist = self.box_widget.isChecked()
asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set(
key, pyon.decode(value), persist)))
unit = self.unit_widget.text()
scale = self.scale_widget.text()
precision = self.precision_widget.text()
metadata = {}
if unit != "":
metadata['unit'] = unit
if scale != "":
metadata['scale'] = float(scale)
if precision != "":
metadata['precision'] = int(precision)
scale = scale_from_metadata(metadata)
value = self.parse_edit_string(value)
t = value.dtype if value is np.ndarray else type(value)
if scale != 1 and np.issubdtype(t, np.number):
# degenerates to float type
value = float(value * scale)
if self.key and self.key != key:
asyncio.ensure_future(exc_to_warning(rename(self.key, key, value, metadata, persist,
self.dataset_ctl)))
else:
asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set(key, value, metadata=metadata,
persist=persist)))
self.key = key
QtWidgets.QDialog.accept(self)
def dtype(self):
txt = self.value_widget.text()
try:
result = pyon.decode(txt)
result = self.parse_edit_string(txt)
# ensure only pyon compatible types are permissable
pyon.encode(result)
except:
pixmap = self.style().standardPixmap(
QtWidgets.QStyle.SP_MessageBoxWarning)
QtWidgets.QStyle.StandardPixmap.SP_MessageBoxWarning)
self.data_type.setPixmap(pixmap)
self.ok.setEnabled(False)
else:
self.data_type.setText(type(result).__name__)
self.ok.setEnabled(True)
@staticmethod
def parse_edit_string(s):
if s == "":
raise TypeError
_eval_dict = {
"__builtins__": {},
"array": np.array,
"null": np.nan,
"inf": np.inf
}
for t_ in pyon._numpy_scalar:
_eval_dict[t_] = eval("np.{}".format(t_), {"np": np})
return eval(s, _eval_dict, {})
@staticmethod
def value_to_edit_string(v):
t = type(v)
r = ""
if isinstance(v, np.generic):
r += t.__name__
r += "("
r += repr(v)
r += ")"
elif v is None:
return r
else:
r += repr(v)
return r
class Model(DictSyncTreeSepModel):
def __init__(self, init):
@ -166,17 +172,17 @@ class Model(DictSyncTreeSepModel):
if column == 1:
return "Y" if v[0] else "N"
elif column == 2:
return short_format(v[1])
return short_format(v[1], v[2])
else:
raise ValueError
class DatasetsDock(QtWidgets.QDockWidget):
def __init__(self, datasets_sub, dataset_ctl):
def __init__(self, dataset_sub, dataset_ctl):
QtWidgets.QDockWidget.__init__(self, "Datasets")
self.setObjectName("Datasets")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable)
self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
self.dataset_ctl = dataset_ctl
grid = LayoutWidget()
@ -188,31 +194,31 @@ class DatasetsDock(QtWidgets.QDockWidget):
grid.addWidget(self.search, 0, 0)
self.table = QtWidgets.QTreeView()
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self.table.setSelectionMode(
QtWidgets.QAbstractItemView.SingleSelection)
QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
grid.addWidget(self.table, 1, 0)
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
create_action = QtWidgets.QAction("New dataset", self.table)
self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
create_action = QtGui.QAction("New dataset", self.table)
create_action.triggered.connect(self.create_clicked)
create_action.setShortcut("CTRL+N")
create_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
create_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
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.setShortcut("RETURN")
edit_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
edit_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.table.doubleClicked.connect(self.edit_clicked)
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.setShortcut("DELETE")
delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
delete_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.table.addAction(delete_action)
self.table_model = Model(dict())
datasets_sub.add_setmodel_callback(self.set_model)
dataset_sub.add_setmodel_callback(self.set_model)
def _search_datasets(self):
if hasattr(self, "table_model_filter"):
@ -221,12 +227,13 @@ class DatasetsDock(QtWidgets.QDockWidget):
def set_model(self, 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.setModel(self.table_model_filter)
def create_clicked(self):
Creator(self, self.dataset_ctl).open()
CreateEditDialog(self, self.dataset_ctl).open()
def edit_clicked(self):
idx = self.table.selectedIndexes()
@ -234,19 +241,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
idx = self.table_model_filter.mapToSource(idx[0])
key = self.table_model.index_to_key(idx)
if key is not None:
persist, value = self.table_model.backing_store[key]
t = type(value)
if np.issubdtype(t, np.number):
dialog_cls = NumberEditor
elif np.issubdtype(t, np.bool_):
dialog_cls = BoolEditor
elif np.issubdtype(t, np.unicode_):
dialog_cls = StringEditor
else:
logger.error("Cannot edit dataset %s: "
"type %s is not supported", key, t)
return
dialog_cls(self, self.dataset_ctl, key, value).open()
persist, value, metadata = self.table_model.backing_store[key]
CreateEditDialog(self, self.dataset_ctl, key, value, metadata, persist).open()
def delete_clicked(self):
idx = self.table.selectedIndexes()

View File

@ -4,14 +4,15 @@ import os
from functools import partial
from collections import OrderedDict
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt6 import QtCore, QtGui, QtWidgets
import h5py
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.tools import LayoutWidget, log_level_to_name, get_open_file_name
from artiq.gui.tools import (LayoutWidget, log_level_to_name, get_open_file_name)
from artiq.tools import parse_devarg_override, unparse_devarg_override
logger = logging.getLogger(__name__)
@ -23,117 +24,32 @@ logger = logging.getLogger(__name__)
# 2. file:<class name>@<file name>
class _WheelFilter(QtCore.QObject):
def eventFilter(self, obj, event):
if (event.type() == QtCore.QEvent.Wheel and
event.modifiers() != QtCore.Qt.NoModifier):
event.ignore()
return True
return False
class _ArgumentEditor(QtWidgets.QTreeWidget):
class _ArgumentEditor(EntryTreeWidget):
def __init__(self, manager, dock, expurl):
self.manager = manager
self.expurl = expurl
QtWidgets.QTreeWidget.__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()))
self._groups = dict()
self._arg_to_widgets = dict()
EntryTreeWidget.__init__(self)
arguments = self.manager.get_submission_arguments(self.expurl)
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():
widgets = dict()
self._arg_to_widgets[name] = widgets
self.set_argument(name, argument)
entry = procdesc_to_entry(argument["desc"])(argument)
widget_item = QtWidgets.QTreeWidgetItem([name])
if argument["tooltip"]:
widget_item.setToolTip(0, argument["tooltip"])
widgets["entry"] = entry
widgets["widget_item"] = widget_item
self.quickStyleClicked.connect(dock.submit_clicked)
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.setIcon(
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_BrowserReload))
QtWidgets.QStyle.StandardPixmap.SP_BrowserReload))
recompute_arguments.clicked.connect(dock._recompute_arguments_clicked)
load_hdf5 = QtWidgets.QPushButton("Load HDF5")
load_hdf5.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOpenButton))
QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton))
load_hdf5.clicked.connect(dock._load_hdf5_clicked)
buttons = LayoutWidget()
@ -143,24 +59,10 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
buttons.layout.setColumnStretch(1, 0)
buttons.layout.setColumnStretch(2, 0)
buttons.layout.setColumnStretch(3, 1)
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 _recompute_argument_clicked(self, name):
asyncio.ensure_future(self._recompute_argument(name))
def reset_entry(self, key):
asyncio.ensure_future(self._recompute_argument(key))
async def _recompute_argument(self, name):
try:
@ -175,46 +77,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
state = procdesc_to_entry(procdesc).default_state(procdesc)
argument["desc"] = procdesc
argument["state"] = state
# Qt needs a setItemWidget() to handle layout correctly,
# simply replacing the entry inside the LayoutWidget
# results in a bug.
widgets = self._arg_to_widgets[name]
widgets["entry"].deleteLater()
widgets["entry"] = procdesc_to_entry(procdesc)(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 _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"])
self.update_argument(name, argument)
# Hooks that allow user-supplied argument editors to react to imminent user
# actions. Here, we always keep the manager-stored submission arguments
@ -235,10 +98,10 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
def __init__(self, manager, expurl):
QtWidgets.QMdiSubWindow.__init__(self)
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.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogContentsView))
QtWidgets.QStyle.StandardPixmap.SP_FileDialogContentsView))
self.layout = QtWidgets.QGridLayout()
top_widget = QtWidgets.QWidget()
@ -268,17 +131,17 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
datetime.setDate(QtCore.QDate.currentDate())
else:
datetime.setDateTime(QtCore.QDateTime.fromMSecsSinceEpoch(
scheduling["due_date"]*1000))
int(scheduling["due_date"] * 1000)))
datetime_en.setChecked(scheduling["due_date"] is not None)
def update_datetime(dt):
scheduling["due_date"] = dt.toMSecsSinceEpoch()/1000
scheduling["due_date"] = dt.toMSecsSinceEpoch() / 1000
datetime_en.setChecked(True)
datetime.dateTimeChanged.connect(update_datetime)
def update_datetime_en(checked):
if checked:
due_date = datetime.dateTime().toMSecsSinceEpoch()/1000
due_date = datetime.dateTime().toMSecsSinceEpoch() / 1000
else:
due_date = None
scheduling["due_date"] = due_date
@ -311,7 +174,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
flush = self.flush
flush.setToolTip("Flush the pipeline (of current- and higher-priority "
"experiments) before starting the experiment")
self.layout.addWidget(flush, 2, 2, 1, 2)
self.layout.addWidget(flush, 2, 2)
flush.setChecked(scheduling["flush"])
@ -319,6 +182,20 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
scheduling["flush"] = bool(checked)
flush.stateChanged.connect(update_flush)
devarg_override = QtWidgets.QComboBox()
devarg_override.setEditable(True)
devarg_override.lineEdit().setPlaceholderText("Override device arguments")
devarg_override.lineEdit().setClearButtonEnabled(True)
devarg_override.insertItem(0, "core:analyze_at_run_end=True")
self.layout.addWidget(devarg_override, 2, 3)
devarg_override.setCurrentText(options["devarg_override"])
def update_devarg_override(text):
options["devarg_override"] = text
devarg_override.editTextChanged.connect(update_devarg_override)
self.devarg_override = devarg_override
log_level = QtWidgets.QComboBox()
log_level.addItems(log_levels)
log_level.setCurrentIndex(1)
@ -339,9 +216,11 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
if "repo_rev" in options:
repo_rev = QtWidgets.QLineEdit()
repo_rev.setPlaceholderText("current")
repo_rev_label = QtWidgets.QLabel("Revision:")
repo_rev.setClearButtonEnabled(True)
repo_rev_label = QtWidgets.QLabel("Rev / ref:")
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, 3, 3)
@ -358,21 +237,21 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton))
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
submit.setToolTip("Schedule the experiment (Ctrl+Return)")
submit.setShortcut("CTRL+RETURN")
submit.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
submit.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(submit, 1, 4, 2, 1)
submit.clicked.connect(self.submit_clicked)
reqterm = QtWidgets.QPushButton("Terminate instances")
reqterm.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogCancelButton))
QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton))
reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)")
reqterm.setShortcut("CTRL+BACKSPACE")
reqterm.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
QtWidgets.QSizePolicy.Expanding)
reqterm.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(reqterm, 3, 4)
reqterm.clicked.connect(self.reqterm_clicked)
@ -410,8 +289,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
arginfo = expdesc["arginfo"]
for k, v in overrides.items():
# Some values (e.g. scans) may have multiple defaults in a list
if ("default" in arginfo[k][0]
and isinstance(arginfo[k][0]["default"], list)):
if ("default" in arginfo[k][0] and isinstance(arginfo[k][0]["default"], list)):
arginfo[k][0]["default"].insert(0, v)
else:
arginfo[k][0]["default"] = v
@ -422,13 +300,13 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
editor_class = self.manager.get_argument_editor_class(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.argeditor.restore_state(argeditor_state)
def contextMenuEvent(self, event):
menu = QtWidgets.QMenu(self)
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:
asyncio.ensure_future(self._recompute_sched_options_task())
@ -471,11 +349,14 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
return
try:
if "devarg_override" in expid:
self.devarg_override.setCurrentText(
unparse_devarg_override(expid["devarg_override"]))
self.log_level.setCurrentIndex(log_levels.index(
log_level_to_name(expid["log_level"])))
if ("repo_rev" in expid and
expid["repo_rev"] != "N/A" and
hasattr(self, "repo_rev")):
if "repo_rev" in expid and \
expid["repo_rev"] != "N/A" and \
hasattr(self, "repo_rev"):
self.repo_rev.setText(expid["repo_rev"])
except:
logger.error("Could not set submission options from HDF5 expid",
@ -542,7 +423,7 @@ class _QuickOpenDialog(QtWidgets.QDialog):
QtWidgets.QDialog.done(self, r)
def _open_experiment(self, exp_name, modifiers):
if modifiers & QtCore.Qt.ControlModifier:
if modifiers & QtCore.Qt.KeyboardModifier.ControlModifier:
try:
self.manager.submit(exp_name)
except:
@ -586,10 +467,10 @@ class ExperimentManager:
self.open_experiments = dict()
self.is_quick_open_shown = False
quick_open_shortcut = QtWidgets.QShortcut(
QtCore.Qt.CTRL + QtCore.Qt.Key_P,
quick_open_shortcut = QtGui.QShortcut(
QtGui.QKeySequence("Ctrl+P"),
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)
def set_dataset_model(self, model):
@ -644,7 +525,8 @@ class ExperimentManager:
else:
# mutated by _ExperimentDock
options = {
"log_level": logging.WARNING
"log_level": logging.WARNING,
"devarg_override": ""
}
if expurl[:5] == "repo:":
options["repo_rev"] = None
@ -665,6 +547,21 @@ class ExperimentManager:
self.argument_ui_names[expurl] = ui_name
return arguments
def set_argument_value(self, expurl, name, value):
try:
argument = self.submission_arguments[expurl][name]
if argument["desc"]["ty"] == "Scannable":
ty = value["ty"]
argument["state"]["selected"] = ty
argument["state"][ty] = value
else:
argument["state"] = value
if expurl in self.open_experiments.keys():
self.open_experiments[expurl].argeditor.update_argument(name, argument)
except:
logger.warn("Failed to set value for argument \"{}\" in experiment: {}."
.format(name, expurl), exc_info=1)
def get_submission_arguments(self, expurl):
if expurl in self.submission_arguments:
return self.submission_arguments[expurl]
@ -673,8 +570,8 @@ class ExperimentManager:
raise ValueError("Submission arguments must be preinitialized "
"when not using repository")
class_desc = self.explist[expurl[5:]]
return self.initialize_submission_arguments(expurl,
class_desc["arginfo"], class_desc.get("argument_ui", None))
return self.initialize_submission_arguments(expurl, class_desc["arginfo"],
class_desc.get("argument_ui", None))
def open_experiment(self, expurl):
if expurl in self.open_experiments:
@ -692,7 +589,7 @@ class ExperimentManager:
del self.submission_arguments[expurl]
dock = _ExperimentDock(self, expurl)
self.open_experiments[expurl] = dock
dock.setAttribute(QtCore.Qt.WA_DeleteOnClose)
dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
self.main_window.centralWidget().addSubWindow(dock)
dock.show()
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
@ -711,7 +608,12 @@ class ExperimentManager:
del self.open_experiments[expurl]
async def _submit_task(self, expurl, *args):
try:
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):
@ -725,7 +627,14 @@ class ExperimentManager:
entry_cls = procdesc_to_entry(argument["desc"])
argument_values[name] = entry_cls.state_to_value(argument["state"])
try:
devarg_override = parse_devarg_override(options["devarg_override"])
except:
logger.error("Failed to parse device argument overrides for %s", expurl)
return
expid = {
"devarg_override": devarg_override,
"log_level": options["log_level"],
"file": file,
"class_name": class_name,
@ -762,9 +671,9 @@ class ExperimentManager:
repo_match = "repo_rev" in expid
else:
repo_match = "repo_rev" not in expid
if (repo_match and
("file" in expid and expid["file"] == file) and
expid["class_name"] == class_name):
if repo_match and \
("file" in expid and expid["file"] == file) and \
expid["class_name"] == class_name:
rids.append(rid)
asyncio.ensure_future(self._request_term_multiple(rids))
@ -818,6 +727,7 @@ class ExperimentManager:
self.is_quick_open_shown = True
dialog = _QuickOpenDialog(self)
def closed():
self.is_quick_open_shown = False
dialog.closed.connect(closed)

View File

@ -3,7 +3,7 @@ import logging
import re
from functools import partial
from PyQt5 import QtCore, QtWidgets
from PyQt6 import QtCore, QtGui, QtWidgets
from artiq.gui.tools import LayoutWidget
from artiq.gui.models import DictSyncTreeSepModel
@ -37,7 +37,8 @@ class _OpenFileDialog(QtWidgets.QDialog):
self.file_list.doubleClicked.connect(self.accept)
buttons = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
QtWidgets.QDialogButtonBox.StandardButton.Ok |
QtWidgets.QDialogButtonBox.StandardButton.Cancel)
grid.addWidget(buttons, 2, 0, 1, 2)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
@ -52,7 +53,7 @@ class _OpenFileDialog(QtWidgets.QDialog):
item = QtWidgets.QListWidgetItem()
item.setText("..")
item.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogToParent))
QtWidgets.QStyle.StandardPixmap.SP_FileDialogToParent))
self.file_list.addItem(item)
try:
@ -64,9 +65,9 @@ class _OpenFileDialog(QtWidgets.QDialog):
return
for name in sorted(contents, key=lambda x: (x[-1] not in "\\/", x)):
if name[-1] in "\\/":
icon = QtWidgets.QStyle.SP_DirIcon
icon = QtWidgets.QStyle.StandardPixmap.SP_DirIcon
else:
icon = QtWidgets.QStyle.SP_FileIcon
icon = QtWidgets.QStyle.StandardPixmap.SP_FileIcon
if name[-3:] != ".py":
continue
item = QtWidgets.QListWidgetItem()
@ -94,7 +95,7 @@ class _OpenFileDialog(QtWidgets.QDialog):
else:
break
self.explorer.current_directory = \
self.explorer.current_directory[:idx+1]
self.explorer.current_directory[:idx + 1]
if self.explorer.current_directory == "/":
self.explorer.current_directory = ""
asyncio.ensure_future(self.refresh_view())
@ -103,6 +104,7 @@ class _OpenFileDialog(QtWidgets.QDialog):
asyncio.ensure_future(self.refresh_view())
else:
file = self.explorer.current_directory + selected
async def open_task():
try:
await self.exp_manager.open_file(file)
@ -162,8 +164,8 @@ class ExplorerDock(QtWidgets.QDockWidget):
schedule_ctl, experiment_db_ctl, device_db_ctl):
QtWidgets.QDockWidget.__init__(self, "Explorer")
self.setObjectName("Explorer")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable)
self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
top_widget = LayoutWidget()
self.setWidget(top_widget)
@ -174,7 +176,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
top_widget.addWidget(QtWidgets.QLabel("Revision:"), 0, 0)
self.revision = QtWidgets.QLabel()
self.revision.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse)
self.revision.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse)
top_widget.addWidget(self.revision, 0, 1)
self.stack = QtWidgets.QStackedWidget()
@ -186,14 +188,14 @@ class ExplorerDock(QtWidgets.QDockWidget):
self.el = QtWidgets.QTreeView()
self.el.setHeaderHidden(True)
self.el.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectItems)
self.el.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems)
self.el.doubleClicked.connect(
partial(self.expname_action, "open_experiment"))
self.el_buttons.addWidget(self.el, 0, 0, colspan=2)
open = QtWidgets.QPushButton("Open")
open.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOpenButton))
QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton))
open.setToolTip("Open the selected experiment (Return)")
self.el_buttons.addWidget(open, 1, 0)
open.clicked.connect(
@ -201,7 +203,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton))
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
submit.setToolTip("Schedule the selected experiment (Ctrl+Return)")
self.el_buttons.addWidget(submit, 1, 1)
submit.clicked.connect(
@ -210,55 +212,56 @@ class ExplorerDock(QtWidgets.QDockWidget):
self.explist_model = Model(dict())
explist_sub.add_setmodel_callback(self.set_model)
self.el.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
open_action = QtWidgets.QAction("Open", self.el)
self.el.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
open_action = QtGui.QAction("Open", self.el)
open_action.triggered.connect(
partial(self.expname_action, "open_experiment"))
open_action.setShortcut("RETURN")
open_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
open_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.el.addAction(open_action)
submit_action = QtWidgets.QAction("Submit", self.el)
submit_action = QtGui.QAction("Submit", self.el)
submit_action.triggered.connect(
partial(self.expname_action, "submit"))
submit_action.setShortcut("CTRL+RETURN")
submit_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
submit_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
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(
partial(self.expname_action, "request_inst_term"))
reqterm_action.setShortcut("CTRL+BACKSPACE")
reqterm_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
reqterm_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.el.addAction(reqterm_action)
set_shortcut_menu = QtWidgets.QMenu()
set_shortcut_menu = QtWidgets.QMenu(self.el)
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))
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)
self.el.addAction(set_shortcut_action)
sep = QtWidgets.QAction(self.el)
sep = QtGui.QAction(self.el)
sep.setSeparator(True)
self.el.addAction(sep)
scan_repository_action = QtWidgets.QAction("Scan repository HEAD",
scan_repository_action = QtGui.QAction("Scan repository HEAD",
self.el)
def scan_repository():
asyncio.ensure_future(experiment_db_ctl.scan_repository_async())
scan_repository_action.triggered.connect(scan_repository)
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():
asyncio.ensure_future(device_db_ctl.scan())
scan_ddb_action.triggered.connect(scan_ddb)
self.el.addAction(scan_ddb_action)
self.current_directory = ""
open_file_action = QtWidgets.QAction("Open file outside repository",
open_file_action = QtGui.QAction("Open file outside repository",
self.el)
open_file_action.triggered.connect(
lambda: _OpenFileDialog(self, self.exp_manager,
@ -292,7 +295,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
if expname is not None:
expurl = "repo:" + expname
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):
if scanning:

View File

@ -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)

View File

@ -2,58 +2,66 @@ import asyncio
import logging
import textwrap
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 *
from artiq.coredevice.ad9910 import _AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7, _AD9910_REG_FTW
from artiq.coredevice.ad9912_reg import AD9912_POW1
from artiq.gui.tools import LayoutWidget
from artiq.gui.flowlayout import FlowLayout
from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe
from artiq.coredevice.ad9912_reg import AD9912_SER_CONF
from artiq.gui.tools import LayoutWidget, QDockWidgetCloseDetect, DoubleClickLineEdit
from artiq.gui.dndwidgets import VDragScrollArea, DragDropFlowLayoutWidget
from artiq.gui.models import DictSyncTreeSepModel
from artiq.tools import elide
logger = logging.getLogger(__name__)
class _CancellableLineEdit(QtWidgets.QLineEdit):
def escapePressedConnect(self, cb):
self.esc_cb = cb
def keyPressEvent(self, event):
key = event.key()
if key == QtCore.Qt.Key_Escape:
if key == QtCore.Qt.Key.Key_Escape:
self.esc_cb(event)
QtWidgets.QLineEdit.keyPressEvent(self, event)
class _TTLWidget(QtWidgets.QFrame):
def __init__(self, dm, channel, force_out, title):
class _MoninjWidget(QtWidgets.QFrame):
def __init__(self, title):
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.set_mode = dm.ttl_set_mode
self.force_out = force_out
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.title = title
self.stack = QtWidgets.QStackedWidget()
grid.addWidget(self.stack, 2, 1)
self.grid.addWidget(self.stack, 2, 1)
self.direction = QtWidgets.QLabel()
self.direction.setAlignment(QtCore.Qt.AlignCenter)
self.direction.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.stack.addWidget(self.direction)
grid_cb = LayoutWidget()
@ -73,13 +81,13 @@ class _TTLWidget(QtWidgets.QFrame):
self.stack.addWidget(grid_cb)
self.value = QtWidgets.QLabel()
self.value.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(self.value, 3, 1)
self.value.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
self.grid.addWidget(self.value, 3, 1)
grid.setRowStretch(1, 1)
grid.setRowStretch(2, 0)
grid.setRowStretch(3, 0)
grid.setRowStretch(4, 1)
self.grid.setRowStretch(1, 1)
self.grid.setRowStretch(2, 0)
self.grid.setRowStretch(3, 0)
self.grid.setRowStretch(4, 1)
self.programmatic_change = False
self.override.clicked.connect(self.override_toggled)
@ -145,40 +153,13 @@ class _TTLWidget(QtWidgets.QFrame):
self.programmatic_change = False
def sort_key(self):
return self.channel
return (0, self.channel, 0)
def uid(self):
return self.title
class _SimpleDisplayWidget(QtWidgets.QFrame):
def __init__(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
def to_model_path(self):
return "ttl/{}".format(self.title)
class _DDSModel:
@ -212,28 +193,18 @@ class _DDSModel:
return ftw / self.ftw_per_hz
class _DDSWidget(QtWidgets.QFrame):
def __init__(self, dm, title, bus_channel=0, channel=0, dds_model=None):
class _DDSWidget(_MoninjWidget):
def __init__(self, dm, title, bus_channel, channel,
dds_type, ref_clk, cpld=None, pll=1, clk_div=0):
self.dm = dm
self.bus_channel = bus_channel
self.channel = channel
self.dds_name = title
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)
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)
_MoninjWidget.__init__(self, title)
# FREQ DATA/EDIT FIELD
self.data_stack = QtWidgets.QStackedWidget()
@ -245,11 +216,11 @@ class _DDSWidget(QtWidgets.QFrame):
grid_disp.layout.setVerticalSpacing(0)
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)
unit = QtWidgets.QLabel("MHz")
unit.setAlignment(QtCore.Qt.AlignCenter)
unit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
grid_disp.addWidget(unit, 0, 3, 1, 1)
self.data_stack.addWidget(grid_disp)
@ -261,14 +232,14 @@ class _DDSWidget(QtWidgets.QFrame):
grid_edit.layout.setVerticalSpacing(0)
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)
unit = QtWidgets.QLabel("MHz")
unit.setAlignment(QtCore.Qt.AlignCenter)
unit.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
grid_edit.addWidget(unit, 0, 3, 1, 1)
self.data_stack.addWidget(grid_edit)
grid.addWidget(self.data_stack, 2, 1)
self.grid.addWidget(self.data_stack, 2, 1)
# BUTTONS
self.button_stack = QtWidgets.QStackedWidget()
@ -301,16 +272,20 @@ class _DDSWidget(QtWidgets.QFrame):
cancel.setToolTip("Cancel changes")
apply_grid.addWidget(cancel, 0, 2, 1, 1)
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)
grid.setRowStretch(2, 1)
grid.setRowStretch(3, 1)
self.grid.setRowStretch(1, 1)
self.grid.setRowStretch(2, 1)
self.grid.setRowStretch(3, 1)
set_btn.clicked.connect(self.set_clicked)
apply.clicked.connect(self.apply_changes)
if self.dds_model.is_urukul:
off_btn.clicked.connect(self.off_clicked)
off_btn.setToolTip(textwrap.dedent(
"""Note: If TTL RTIO sw for the channel is switched high,
this button will not disable the channel.
Use the TTL override instead."""))
self.value_edit.returnPressed.connect(lambda: self.apply_changes(None))
self.value_edit.escapePressedConnect(self.cancel_changes)
cancel.clicked.connect(self.cancel_changes)
@ -320,8 +295,7 @@ class _DDSWidget(QtWidgets.QFrame):
def set_clicked(self, set):
self.data_stack.setCurrentIndex(1)
self.button_stack.setCurrentIndex(1)
self.value_edit.setText("{:.7f}"
.format(self.cur_frequency/1e6))
self.value_edit.setText("{:.7f}".format(self.cur_frequency / 1e6))
self.value_edit.setFocus()
self.value_edit.selectAll()
@ -331,7 +305,7 @@ class _DDSWidget(QtWidgets.QFrame):
def apply_changes(self, apply):
self.data_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)
def cancel_changes(self, cancel):
@ -340,28 +314,53 @@ class _DDSWidget(QtWidgets.QFrame):
def refresh_display(self):
self.cur_frequency = self.dds_model.cur_frequency
self.value_label.setText("<font size=\"4\">{:.7f}</font>"
.format(self.cur_frequency/1e6))
self.value_edit.setText("{:.7f}"
.format(self.cur_frequency/1e6))
self.value_label.setText("<font size=\"4\">{:.7f}</font>".format(self.cur_frequency / 1e6))
self.value_edit.setText("{:.7f}".format(self.cur_frequency / 1e6))
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):
def __init__(self, dm, spi_channel, channel, title):
class _DACWidget(_MoninjWidget):
def __init__(self, dm, spi_channel, channel, title, vref, offset_dacs):
_MoninjWidget.__init__(self, "{}_ch{}".format(title, channel))
self.spi_channel = spi_channel
self.channel = channel
self.cur_value = 0
_SimpleDisplayWidget.__init__(self, "{} ch{}".format(title, channel))
self.cur_value = 0x8000
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):
self.value.setText("<font size=\"4\">{:.3f}</font><font size=\"2\"> %</font>"
.format(self.cur_value*100/2**16))
self.value.setText("<font size=\"4\">{:+.3f} V</font>"
.format(self.mu_to_voltage(self.cur_value)))
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")
@ -379,24 +378,19 @@ def setup_from_ddb(ddb):
comment = v.get("comment")
if v["type"] == "local":
if v["module"] == "artiq.coredevice.ttl":
if "ttl_urukul" in k:
continue
channel = v["arguments"]["channel"]
force_out = v["class"] == "TTLOut"
widget = _WidgetDesc(k, comment, _TTLWidget, (channel, force_out, k))
description.add(widget)
elif (v["module"] == "artiq.coredevice.ad9914"
and v["class"] == "AD9914"):
elif (v["module"] == "artiq.coredevice.ad9914" and v["class"] == "AD9914"):
bus_channel = v["arguments"]["bus_channel"]
channel = v["arguments"]["channel"]
dds_sysclk = v["arguments"]["sysclk"]
model = _DDSModel(v["class"], dds_sysclk)
widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model))
widget = _WidgetDesc(k, comment, _DDSWidget,
(k, bus_channel, channel, v["class"], dds_sysclk))
description.add(widget)
elif (v["module"] == "artiq.coredevice.ad9910"
and v["class"] == "AD9910") or \
(v["module"] == "artiq.coredevice.ad9912"
and v["class"] == "AD9912"):
elif (v["module"] == "artiq.coredevice.ad9910" and v["class"] == "AD9910") or \
(v["module"] == "artiq.coredevice.ad9912" and v["class"] == "AD9912"):
channel = v["arguments"]["chip_select"] - 4
if channel < 0:
continue
@ -406,18 +400,28 @@ def setup_from_ddb(ddb):
pll = v["arguments"]["pll_n"]
refclk = ddb[dds_cpld]["arguments"]["refclk"]
clk_div = v["arguments"].get("clk_div", 0)
model = _DDSModel( v["class"], refclk, dds_cpld, pll, clk_div)
widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model))
widget = _WidgetDesc(k, comment, _DDSWidget,
(k, bus_channel, channel, v["class"],
refclk, dds_cpld, pll, clk_div))
description.add(widget)
elif ( (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx")
or (v["module"] == "artiq.coredevice.zotino" and v["class"] == "Zotino")):
elif (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx") or \
(v["module"] == "artiq.coredevice.zotino" and v["class"] == "Zotino"):
spi_device = v["arguments"]["spi_device"]
spi_device = ddb[spi_device]
while isinstance(spi_device, str):
spi_device = ddb[spi_device]
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):
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)
elif v["type"] == "controller" and k == "core_moninj":
mi_addr = v["host"]
@ -442,18 +446,15 @@ class _DeviceManager:
self.widgets_by_uid = dict()
self.dds_sysclk = 0
self.ttl_cb = lambda: None
self.ttl_widgets = dict()
self.dds_cb = lambda: None
self.dds_widgets = dict()
self.dac_cb = lambda: None
self.dac_widgets = dict()
self.channels_cb = lambda: None
def init_ddb(self, 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)
if (mi_addr, mi_port) != (self.mi_addr, self.mi_port):
@ -464,24 +465,8 @@ class _DeviceManager:
for to_remove in self.description - description:
widget = self.widgets_by_uid[to_remove.uid]
del self.widgets_by_uid[to_remove.uid]
if isinstance(widget, _TTLWidget):
self.setup_ttl_monitoring(False, widget.channel)
self.setup_monitoring(False, widget)
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:
widget = to_add.cls(self, *to_add.arguments)
@ -489,26 +474,15 @@ class _DeviceManager:
widget.setToolTip(to_add.comment)
self.widgets_by_uid[to_add.uid] = widget
if isinstance(widget, _TTLWidget):
self.ttl_widgets[widget.channel] = widget
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
if description != self.description:
self.channels_cb()
self.description = description
def ttl_set_mode(self, channel, mode):
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":
widget.cur_override = True
widget.cur_level = False
@ -534,7 +508,7 @@ class _DeviceManager:
"log_level": logging.WARNING,
"content": content,
"class_name": class_name,
"arguments": []
"arguments": {}
}
scheduling = {
"pipeline_name": "main",
@ -549,33 +523,55 @@ class _DeviceManager:
scheduling["flush"])
logger.info("Submitted '%s', RID is %d", title, rid)
def dds_set_frequency(self, dds_channel, dds_model, freq):
def _dds_faux_injection(self, dds_channel, dds_model, action, title, log_msg):
# create kernel and fill it in and send-by-content
# initialize CPLD (if applicable)
if dds_model.is_urukul:
# urukuls need CPLD init and switch to on
# keep previous config if it was set already
cpld_dev = """self.setattr_device("core_cache")
self.setattr_device("{}")""".format(dds_model.cpld)
cpld_init = """cfg = self.core_cache.get("_{cpld}_cfg")
if len(cfg) > 0:
self.{cpld}.cfg_reg = cfg[0]
else:
# `sta`/`rf_sw`` variables are guaranteed for urukuls
# so {action} can use it
# if there's no RF enabled, CPLD may have not been initialized
# but if there is, it has been initialised - no need to do again
cpld_init = """delay(15*ms)
was_init = self.core_cache.get("_{cpld}_init")
sta = self.{cpld}.sta_read()
rf_sw = urukul_sta_rf_sw(sta)
if rf_sw == 0 and len(was_init) == 0:
delay(15*ms)
self.{cpld}.init()
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
cfg = self.core_cache.get("_{cpld}_cfg")
self.core_cache.put("_{cpld}_init", [1])
""".format(cpld=dds_model.cpld)
cfg_sw = """self.{}.cfg_sw(True)
cfg[0] = self.{}.cfg_reg
""".format(dds_channel, dds_model.cpld)
else:
cpld_dev = ""
cpld_init = ""
cfg_sw = ""
# AD9912/9910: init channel (if uninitialized)
if dds_model.dds_type == "AD9912":
# 0xFF before init, 0x99 after
channel_init = """
if self.{dds_channel}.read({cfgreg}, length=1) == 0xFF:
delay(10*ms)
self.{dds_channel}.init()
""".format(dds_channel=dds_channel, cfgreg=AD9912_SER_CONF)
elif dds_model.dds_type == "AD9910":
# -1 before init, 2 after
channel_init = """
if self.{dds_channel}.read32({cfgreg}) == -1:
delay(10*ms)
self.{dds_channel}.init()
""".format(dds_channel=dds_channel, cfgreg=AD9912_SER_CONF)
else:
channel_init = "self.{dds_channel}.init()".format(dds_channel=dds_channel)
dds_exp = textwrap.dedent("""
from artiq.experiment import *
from artiq.coredevice.urukul import *
class SetDDS(EnvExperiment):
class {title}(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("{dds_channel}")
@ -583,55 +579,82 @@ class _DeviceManager:
@kernel
def run(self):
self.core.reset()
self.core.break_realtime()
{cpld_init}
delay(5*ms)
self.{dds_channel}.init()
self.{dds_channel}.set({freq})
{cfg_sw}
""".format(dds_channel=dds_channel, freq=freq,
delay(10*ms)
{channel_init}
delay(15*ms)
{action}
""".format(title=title, action=action,
dds_channel=dds_channel,
cpld_dev=cpld_dev, cpld_init=cpld_init,
cfg_sw=cfg_sw))
channel_init=channel_init))
asyncio.ensure_future(
self._submit_by_content(
dds_exp,
title,
log_msg))
def dds_set_frequency(self, dds_channel, dds_model, freq):
action = "self.{ch}.set({freq})".format(
freq=freq, ch=dds_channel)
if dds_model.is_urukul:
action += """
ch_no = self.{ch}.chip_select - 4
self.{cpld}.cfg_switches(rf_sw | 1 << ch_no)
""".format(ch=dds_channel, cpld=dds_model.cpld)
self._dds_faux_injection(
dds_channel,
dds_model,
action,
"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):
# urukul only
toggle_exp = textwrap.dedent("""
from artiq.experiment import *
class ToggleDDS(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("{ch}")
self.setattr_device("core_cache")
self.setattr_device("{cpld}")
@kernel
def run(self):
self.core.reset()
cfg = self.core_cache.get("_{cpld}_cfg")
if len(cfg) > 0:
self.{cpld}.cfg_reg = cfg[0]
if sw:
switch = "| 1 << ch_no"
else:
delay(15*ms)
self.{cpld}.init()
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
cfg = self.core_cache.get("_{cpld}_cfg")
delay(5*ms)
self.{ch}.init()
self.{ch}.cfg_sw({sw})
cfg[0] = self.{cpld}.cfg_reg
""".format(ch=dds_channel, cpld=dds_model.cpld, sw=sw))
asyncio.ensure_future(
self._submit_by_content(
toggle_exp,
switch = "& ~(1 << ch_no)"
action = """
ch_no = self.{dds_channel}.chip_select - 4
self.{cpld}.cfg_switches(rf_sw {switch})
""".format(
dds_channel=dds_channel,
cpld=dds_model.cpld,
switch=switch
)
self._dds_faux_injection(
dds_channel,
dds_model,
action,
"ToggleDDS",
"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):
if self.mi_connection is not None:
@ -652,24 +675,28 @@ class _DeviceManager:
def monitor_cb(self, channel, probe, value):
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:
widget.cur_level = bool(value)
elif probe == TTLProbe.oe.value:
widget.cur_oe = bool(value)
widget.refresh_display()
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.refresh_display()
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.refresh_display()
def injection_status_cb(self, channel, override, value):
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:
widget.cur_override = bool(value)
if override == TTLOverride.level.value:
@ -691,15 +718,13 @@ class _DeviceManager:
self.disconnect_cb)
try:
await new_mi_connection.connect(self.mi_addr, self.mi_port)
except asyncio.CancelledError:
logger.info("cancelled connection to moninj")
break
except:
logger.error("failed to connect to moninj", exc_info=True)
except Exception:
logger.error("failed to connect to moninj. Is aqctl_moninj_proxy running?",
exc_info=True)
await asyncio.sleep(10.)
self.reconnect_mi.set()
else:
logger.info("ARTIQ dashboard connected to moninj proxy (%s)",
logger.info("ARTIQ dashboard connected to moninj (%s)",
self.mi_addr)
self.mi_connection = new_mi_connection
for ttl_channel in self.ttl_widgets.keys():
@ -719,48 +744,237 @@ class _DeviceManager:
await self.mi_connection.close()
class _MonInjDock(QtWidgets.QDockWidget):
def __init__(self, name):
QtWidgets.QDockWidget.__init__(self, name)
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[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.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable)
self.setFeatures(self.DockWidgetFeature.DockWidgetMovable |
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):
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()):
grid.addWidget(widget)
self.manager.dm.setup_monitoring(True, widget)
self.flow.addWidget(widget)
widget.show()
scroll_area.setWidgetResizable(True)
scroll_area.setWidget(grid_widget)
def restore_widgets(self):
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:
def __init__(self, schedule_ctl):
self.ttl_dock = _MonInjDock("TTL")
self.dds_dock = _MonInjDock("DDS")
self.dac_dock = _MonInjDock("DAC")
def __init__(self, schedule_ctl, main_window):
self.docks = dict()
self.main_window = main_window
self.dm = _DeviceManager(schedule_ctl)
self.dm.ttl_cb = lambda: self.ttl_dock.layout_widgets(
self.dm.ttl_widgets.values())
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.dm.channels_cb = self.add_channels
self.channel_model = Model({})
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):
await self.subscriber.connect(server, port)
def create_new_dock(self, add_to_area=True):
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):
await self.subscriber.close()
if self.dm is not None:
await self.dm.close()

View File

@ -3,7 +3,7 @@ import time
from functools import partial
import logging
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt6 import QtCore, QtWidgets, QtGui
from artiq.gui.models import DictSyncModel
from artiq.tools import elide
@ -16,8 +16,7 @@ class Model(DictSyncModel):
def __init__(self, init):
DictSyncModel.__init__(self,
["RID", "Pipeline", "Status", "Prio", "Due date",
"Revision", "File", "Class name"],
init)
"Revision", "File", "Class name"], init)
def sort_key(self, k, v):
# 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):
QtWidgets.QDockWidget.__init__(self, "Schedule")
self.setObjectName("Schedule")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable)
self.setFeatures(QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
self.schedule_ctl = schedule_ctl
self.table = QtWidgets.QTableView()
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection)
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self.table.setSelectionMode(QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
self.table.verticalHeader().setSectionResizeMode(
QtWidgets.QHeaderView.ResizeToContents)
QtWidgets.QHeaderView.ResizeMode.ResizeToContents)
self.table.verticalHeader().hide()
self.setWidget(self.table)
self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
request_termination_action = QtWidgets.QAction("Request termination", self.table)
self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
request_termination_action = QtGui.QAction("Request termination", self.table)
request_termination_action.triggered.connect(partial(self.delete_clicked, True))
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)
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.setShortcut("SHIFT+DELETE")
delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut)
delete_action.setShortcutContext(QtCore.Qt.ShortcutContext.WidgetShortcut)
self.table.addAction(delete_action)
terminate_pipeline = QtWidgets.QAction(
terminate_pipeline = QtGui.QAction(
"Gracefully terminate all in pipeline", self.table)
terminate_pipeline.triggered.connect(self.terminate_pipeline_clicked)
self.table.addAction(terminate_pipeline)
@ -96,14 +95,14 @@ class ScheduleDock(QtWidgets.QDockWidget):
cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
h = self.table.horizontalHeader()
h.resizeSection(0, 7*cw)
h.resizeSection(1, 12*cw)
h.resizeSection(2, 16*cw)
h.resizeSection(3, 6*cw)
h.resizeSection(4, 16*cw)
h.resizeSection(5, 30*cw)
h.resizeSection(6, 20*cw)
h.resizeSection(7, 20*cw)
h.resizeSection(0, 7 * cw)
h.resizeSection(1, 12 * cw)
h.resizeSection(2, 16 * cw)
h.resizeSection(3, 6 * cw)
h.resizeSection(4, 16 * cw)
h.resizeSection(5, 30 * cw)
h.resizeSection(6, 20 * cw)
h.resizeSection(7, 20 * cw)
def set_model(self, model):
self.table_model = model
@ -151,7 +150,6 @@ class ScheduleDock(QtWidgets.QDockWidget):
rids.add(rid)
asyncio.ensure_future(self.request_term_multiple(rids))
def save_state(self):
return bytes(self.table.horizontalHeader().saveState())

View File

@ -1,9 +1,7 @@
import logging
from functools import partial
from PyQt5 import QtCore, QtWidgets
from artiq.gui.tools import LayoutWidget
from PyQt6 import QtCore, QtGui, QtWidgets
logger = logging.getLogger(__name__)
@ -13,8 +11,8 @@ class ShortcutsDock(QtWidgets.QDockWidget):
def __init__(self, main_window, exp_manager):
QtWidgets.QDockWidget.__init__(self, "Shortcuts")
self.setObjectName("Shortcuts")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable)
self.setFeatures(self.DockWidgetFeature.DockWidgetMovable |
self.DockWidgetFeature.DockWidgetFloatable)
layout = QtWidgets.QGridLayout()
top_widget = QtWidgets.QWidget()
@ -35,28 +33,28 @@ class ShortcutsDock(QtWidgets.QDockWidget):
for i in range(12):
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.setSizePolicy(QtWidgets.QSizePolicy.Ignored,
QtWidgets.QSizePolicy.Ignored)
label.setSizePolicy(QtWidgets.QSizePolicy.Policy.Ignored,
QtWidgets.QSizePolicy.Policy.Ignored)
layout.addWidget(label, row, 1)
clear = QtWidgets.QToolButton()
clear.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogDiscardButton))
QtWidgets.QStyle.StandardPixmap.SP_DialogDiscardButton))
layout.addWidget(clear, row, 2)
clear.clicked.connect(partial(self.set_shortcut, i, ""))
open = QtWidgets.QToolButton()
open.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOpenButton))
QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton))
layout.addWidget(open, row, 3)
open.clicked.connect(partial(self._open_experiment, i))
submit = QtWidgets.QPushButton("Submit")
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOkButton))
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
layout.addWidget(submit, row, 4)
submit.clicked.connect(partial(self._activated, i))
@ -70,8 +68,8 @@ class ShortcutsDock(QtWidgets.QDockWidget):
"open": open,
"submit": submit
}
shortcut = QtWidgets.QShortcut("F" + str(i+1), main_window)
shortcut.setContext(QtCore.Qt.ApplicationShortcut)
shortcut = QtGui.QShortcut("F" + str(i+1), main_window)
shortcut.setContext(QtCore.Qt.ShortcutContext.ApplicationShortcut)
shortcut.activated.connect(partial(self._activated, i))
def _activated(self, nr):

926
artiq/dashboard/waveform.py Normal file
View File

@ -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()

View File

@ -127,7 +127,7 @@
"# let's connect to the master\n",
"\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",
"\n",
"print(\"current schedule\")\n",

View File

@ -7,7 +7,11 @@ device_db = {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {"host": core_addr, "ref_period": 1e-9}
"arguments": {
"host": core_addr,
"ref_period": 1e-9,
"analyzer_proxy": "core_analyzer"
}
},
"core_log": {
"type": "controller",
@ -22,6 +26,13 @@ device_db = {
"port": 1384,
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_analyzer": {
"type": "controller",
"host": "::1",
"port_proxy": 1385,
"port": 1386,
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",

View File

@ -5,7 +5,11 @@ device_db = {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {"host": core_addr, "ref_period": 1/(8*150e6)}
"arguments": {
"host": core_addr,
"ref_period": 1e-9,
"analyzer_proxy": "core_analyzer"
}
},
"core_log": {
"type": "controller",
@ -20,6 +24,13 @@ device_db = {
"port": 1384,
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_analyzer": {
"type": "controller",
"host": "::1",
"port_proxy": 1385,
"port": 1386,
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",

View File

@ -1,184 +0,0 @@
core_addr = "192.168.1.70"
device_db = {
"core": {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {"host": core_addr, "ref_period": 1/(8*150e6)}
},
"core_log": {
"type": "controller",
"host": "::1",
"port": 1068,
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
},
"core_moninj": {
"type": "controller",
"host": "::1",
"port_proxy": 1383,
"port": 1384,
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",
"class": "CoreCache"
},
"core_dma": {
"type": "local",
"module": "artiq.coredevice.dma",
"class": "CoreDMA"
},
}
device_db.update(
spi_urukul0={
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 0}
},
ttl_urukul0_io_update={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 1}
},
ttl_urukul0_sw0={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 2}
},
ttl_urukul0_sw1={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 3}
},
ttl_urukul0_sw2={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 4}
},
ttl_urukul0_sw3={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 5}
},
urukul0_cpld={
"type": "local",
"module": "artiq.coredevice.urukul",
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul0",
"io_update_device": "ttl_urukul0_io_update",
"refclk": 150e6,
"clk_sel": 2
}
}
)
for i in range(4):
device_db["urukul0_ch" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 16, # 600MHz sample rate
"pll_vco": 2,
"chip_select": 4 + i,
"cpld_device": "urukul0_cpld",
"sw_device": "ttl_urukul0_sw" + str(i)
}
}
"""
artiq_route routing.bin init
artiq_route routing.bin set 0 0
artiq_route routing.bin set 1 1 0
artiq_route routing.bin set 2 1 1 0
artiq_route routing.bin set 3 2 0
artiq_route routing.bin set 4 2 1 0
artiq_coremgmt -D kasli config write -f routing_table routing.bin
"""
for sayma in range(2):
amc_base = 0x010000 + sayma*0x020000
rtm_base = 0x020000 + sayma*0x020000
for i in range(4):
device_db["led" + str(4*sayma+i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + i}
}
for i in range(2):
device_db["ttl_mcx" + str(2*sayma+i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLInOut",
"arguments": {"channel": amc_base + 4 + i}
}
for i in range(8):
device_db["sawg" + str(8*sayma+i)] = {
"type": "local",
"module": "artiq.coredevice.sawg",
"class": "SAWG",
"arguments": {"channel_base": amc_base + 6 + i*10, "parallelism": 4}
}
for basemod in range(2):
for i in range(4):
device_db["sawg_sw" + str(8*sayma+4*basemod+i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": rtm_base + basemod*9 + i}
}
att_idx = 2*sayma + basemod
device_db["basemod_att_rst_n"+str(att_idx)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": rtm_base + basemod*9 + 4}
}
device_db["basemod_att_clk"+str(att_idx)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": rtm_base + basemod*9 + 5}
}
device_db["basemod_att_le"+str(att_idx)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": rtm_base + basemod*9 + 6}
}
device_db["basemod_att_mosi"+str(att_idx)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": rtm_base + basemod*9 + 7}
}
device_db["basemod_att_miso"+str(att_idx)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLInOut",
"arguments": {"channel": rtm_base + basemod*9 + 8}
}
device_db["basemod_att"+str(att_idx)] = {
"type": "local",
"module": "artiq.coredevice.basemod_att",
"class": "BaseModAtt",
"arguments": {
"rst_n": "basemod_att_rst_n"+str(att_idx),
"clk": "basemod_att_clk"+str(att_idx),
"le": "basemod_att_le"+str(att_idx),
"mosi": "basemod_att_mosi"+str(att_idx),
"miso": "basemod_att_miso"+str(att_idx),
}
}

View File

@ -1,25 +0,0 @@
from artiq.experiment import *
class BaseMod(EnvExperiment):
def build(self):
self.setattr_device("core")
self.basemods = [self.get_device("basemod_att0"), self.get_device("basemod_att1")]
self.rfsws = [self.get_device("sawg_sw"+str(i)) for i in range(8)]
@kernel
def run(self):
self.core.reset()
for basemod in self.basemods:
self.core.break_realtime()
delay(10*ms)
basemod.reset()
delay(10*ms)
basemod.set(0.0, 0.0, 0.0, 0.0)
delay(10*ms)
print(basemod.get_mu())
self.core.break_realtime()
for rfsw in self.rfsws:
rfsw.on()
delay(1*ms)

View File

@ -1,37 +0,0 @@
from artiq.experiment import *
class Sines2Sayma(EnvExperiment):
def build(self):
self.setattr_device("core")
self.sawgs = [self.get_device("sawg"+str(i)) for i in range(16)]
@kernel
def drtio_is_up(self):
for i in range(5):
if not self.core.get_rtio_destination_status(i):
return False
return True
@kernel
def run(self):
while True:
print("waiting for DRTIO ready...")
while not self.drtio_is_up():
pass
print("OK")
self.core.reset()
for sawg in self.sawgs:
delay(1*ms)
sawg.reset()
for sawg in self.sawgs:
delay(1*ms)
sawg.amplitude1.set(.4)
# Do not use a sub-multiple of oscilloscope sample rates.
sawg.frequency0.set(9*MHz)
while self.drtio_is_up():
pass

View File

@ -1,89 +0,0 @@
from artiq.experiment import *
class SinesUrukulSayma(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("urukul0_cpld")
# Urukul clock output syntonized to the RTIO clock.
# Can be used as HMC830 reference on Sayma RTM.
# When using this reference, Sayma must be recalibrated every time Urukul
# is rebooted, as Urukul is not synchronized to the Kasli.
self.urukul_hmc_ref = self.get_device("urukul0_ch3")
# Urukul measurement channels - compare with SAWG outputs.
# When testing sync, do not reboot Urukul, as it is not
# synchronized to the Kasli.
self.urukul_meas = [self.get_device("urukul0_ch" + str(i)) for i in range(3)]
# The same waveform is output on all first 4 SAWG channels (first DAC).
self.sawgs = [self.get_device("sawg"+str(i)) for i in range(4)]
self.basemod = self.get_device("basemod_att0")
self.rfsws = [self.get_device("sawg_sw"+str(i)) for i in range(4)]
# DRTIO destinations:
# 0: local
# 1: Sayma AMC
# 2: Sayma RTM
@kernel
def drtio_is_up(self):
for i in range(3):
if not self.core.get_rtio_destination_status(i):
return False
return True
@kernel
def run(self):
f = 9*MHz
dds_ftw = self.urukul_meas[0].frequency_to_ftw(f)
sawg_ftw = self.sawgs[0].frequency0.to_mu(f)
if dds_ftw != sawg_ftw:
print("DDS and SAWG FTWs do not match:", dds_ftw, sawg_ftw)
return
self.core.reset()
self.urukul0_cpld.init()
delay(1*ms)
self.urukul_hmc_ref.init()
self.urukul_hmc_ref.set_mu(0x40000000, asf=self.urukul_hmc_ref.amplitude_to_asf(0.6))
self.urukul_hmc_ref.set_att(6.)
self.urukul_hmc_ref.sw.on()
for urukul_ch in self.urukul_meas:
delay(1*ms)
urukul_ch.init()
urukul_ch.set_mu(dds_ftw, asf=urukul_ch.amplitude_to_asf(0.5))
urukul_ch.set_att(6.)
urukul_ch.sw.on()
while True:
print("waiting for DRTIO ready...")
while not self.drtio_is_up():
pass
print("OK")
self.core.reset()
delay(10*ms)
self.basemod.reset()
delay(10*ms)
self.basemod.set(3.0, 3.0, 3.0, 3.0)
delay(10*ms)
for rfsw in self.rfsws:
delay(1*ms)
rfsw.on()
for sawg in self.sawgs:
delay(1*ms)
sawg.reset()
for sawg in self.sawgs:
delay(1*ms)
sawg.amplitude1.set(.4)
sawg.frequency0.set_mu(sawg_ftw)
sawg.phase0.set_mu(sawg_ftw*now_mu() >> 17)
while self.drtio_is_up():
pass

View File

@ -0,0 +1,19 @@
{
"target": "kasli",
"variant": "shuttlerdemo",
"hw_rev": "v2.0",
"drtio_role": "master",
"peripherals": [
{
"type": "shuttler",
"hw_rev": "v1.1",
"ports": [0]
},
{
"type": "dio",
"ports": [1],
"bank_direction_low": "input",
"bank_direction_high": "output"
}
]
}

View File

@ -0,0 +1,330 @@
from artiq.experiment import *
from artiq.coredevice.shuttler import shuttler_volt_to_mu
DAC_Fs_MHZ = 125
CORDIC_GAIN = 1.64676
@portable
def shuttler_phase_offset(offset_degree):
return round(offset_degree / 360 * (2 ** 16))
@portable
def shuttler_freq_mu(freq_mhz):
return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz)
@portable
def shuttler_chirp_rate_mu(freq_mhz_per_us):
return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2))
@portable
def shuttler_freq_sweep(start_f_MHz, end_f_MHz, time_us):
return shuttler_chirp_rate_mu((end_f_MHz - start_f_MHz)/(time_us))
@portable
def shuttler_volt_amp_mu(volt):
return shuttler_volt_to_mu(volt)
@portable
def shuttler_volt_damp_mu(volt_per_us):
return round(float(2) ** 32 * (volt_per_us / 20) / DAC_Fs_MHZ)
@portable
def shuttler_volt_ddamp_mu(volt_per_us_square):
return round(float(2) ** 48 * (volt_per_us_square / 20) * 2 / (DAC_Fs_MHZ ** 2))
@portable
def shuttler_volt_dddamp_mu(volt_per_us_cube):
return round(float(2) ** 48 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3))
@portable
def shuttler_dds_amp_mu(volt):
return shuttler_volt_amp_mu(volt / CORDIC_GAIN)
@portable
def shuttler_dds_damp_mu(volt_per_us):
return shuttler_volt_damp_mu(volt_per_us / CORDIC_GAIN)
@portable
def shuttler_dds_ddamp_mu(volt_per_us_square):
return shuttler_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN)
@portable
def shuttler_dds_dddamp_mu(volt_per_us_cube):
return shuttler_volt_dddamp_mu(volt_per_us_cube / CORDIC_GAIN)
class Shuttler(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("core_dma")
self.setattr_device("scheduler")
self.shuttler0_leds = [ self.get_device("shuttler0_led{}".format(i)) for i in range(2) ]
self.setattr_device("shuttler0_config")
self.setattr_device("shuttler0_trigger")
self.shuttler0_dcbias = [ self.get_device("shuttler0_dcbias{}".format(i)) for i in range(16) ]
self.shuttler0_dds = [ self.get_device("shuttler0_dds{}".format(i)) for i in range(16) ]
self.setattr_device("shuttler0_relay")
self.setattr_device("shuttler0_adc")
@kernel
def record(self):
with self.core_dma.record("example_waveform"):
self.example_waveform()
@kernel
def init(self):
self.led()
self.relay_init()
self.adc_init()
self.shuttler_reset()
@kernel
def run(self):
self.core.reset()
self.core.break_realtime()
self.init()
self.record()
example_waveform_handle = self.core_dma.get_handle("example_waveform")
print("Example Waveforms are on OUT0 and OUT1")
self.core.break_realtime()
while not(self.scheduler.check_termination()):
delay(1*s)
self.core_dma.playback_handle(example_waveform_handle)
@kernel
def shuttler_reset(self):
for i in range(16):
self.shuttler_channel_reset(i)
# To avoid RTIO Underflow
delay(50*us)
@kernel
def shuttler_channel_reset(self, ch):
self.shuttler0_dcbias[ch].set_waveform(
a0=0,
a1=0,
a2=0,
a3=0,
)
self.shuttler0_dds[ch].set_waveform(
b0=0,
b1=0,
b2=0,
b3=0,
c0=0,
c1=0,
c2=0,
)
self.shuttler0_trigger.trigger(1 << ch)
@kernel
def example_waveform(self):
# Equation of Output Waveform
# w(t_us) = a(t_us) + b(t_us) * cos(c(t_us))
# Step 1:
# Enable the Output Relay of OUT0 and OUT1
# Step 2: Cosine Wave Frequency Sweep from 10kHz to 50kHz in 500us
# OUT0: b(t_us) = 1
# c(t_us) = 2 * pi * (0.08 * t_us ^ 2 + 0.01 * t_us)
# OUT1: b(t_us) = 1
# c(t_us) = 2 * pi * (0.05 * t_us)
# Step 3(after 500us): Cosine Wave with 180 Degree Phase Offset
# OUT0: b(t_us) = 1
# c(t_us) = 2 * pi * (0.05 * t_us) + pi
# OUT1: b(t_us) = 1
# c(t_us) = 2 * pi * (0.05 * t_us)
# Step 4(after 500us): Cosine Wave with Amplitude Envelop
# OUT0: b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us
# c(t_us) = 2 * pi * (0.05 * t_us)
# OUT1: b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us
# c(t_us) = 0
# Step 5(after 500us): Sawtooth Wave Modulated with 50kHz Cosine Wave
# OUT0: a(t_us) = 0.01 * t_us - 5
# b(t_us) = 1
# c(t_us) = 2 * pi * (0.05 * t_us)
# OUT1: a(t_us) = 0.01 * t_us - 5
# Step 6(after 1000us): A Combination of Previous Waveforms
# OUT0: a(t_us) = 0.01 * t_us - 5
# b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us
# c(t_us) = 2 * pi * (0.08 * t_us ^ 2 + 0.01 * t_us)
# Step 7(after 500us): Mirrored Waveform in Step 6
# OUT0: a(t_us) = 2.5 + -0.01 * (1000 ^ 2) * t_us
# b(t_us) = 0.0001367187 * t_us ^ 2 - 0.06835937 * t_us
# c(t_us) = 2 * pi * (-0.08 * t_us ^ 2 + 0.05 * t_us) + pi
# Step 8(after 500us):
# Disable Output Relay of OUT0 and OUT1
# Reset OUT0 and OUT1
## Step 1 ##
self.shuttler0_relay.enable(0b11)
## Step 2 ##
start_f_MHz = 0.01
end_f_MHz = 0.05
duration_us = 500
# OUT0 and OUT1 have their frequency and phase aligned at 500us
self.shuttler0_dds[0].set_waveform(
b0=shuttler_dds_amp_mu(1.0),
b1=0,
b2=0,
b3=0,
c0=0,
c1=shuttler_freq_mu(start_f_MHz),
c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us),
)
self.shuttler0_dds[1].set_waveform(
b0=shuttler_dds_amp_mu(1.0),
b1=0,
b2=0,
b3=0,
c0=0,
c1=shuttler_freq_mu(end_f_MHz),
c2=0,
)
self.shuttler0_trigger.trigger(0b11)
delay(500*us)
## Step 3 ##
# OUT0 and OUT1 has 180 degree phase difference
self.shuttler0_dds[0].set_waveform(
b0=shuttler_dds_amp_mu(1.0),
b1=0,
b2=0,
b3=0,
c0=shuttler_phase_offset(180.0),
c1=shuttler_freq_mu(end_f_MHz),
c2=0,
)
# Phase and Output Setting of OUT1 is retained
# if the channel is not triggered or config is not cleared
self.shuttler0_trigger.trigger(0b1)
delay(500*us)
## Step 4 ##
# b(0) = 0, b(250) = 8.545, b(500) = 0
self.shuttler0_dds[0].set_waveform(
b0=0,
b1=shuttler_dds_damp_mu(0.06835937),
b2=shuttler_dds_ddamp_mu(-0.0001367187),
b3=0,
c0=0,
c1=shuttler_freq_mu(end_f_MHz),
c2=0,
)
self.shuttler0_dds[1].set_waveform(
b0=0,
b1=shuttler_dds_damp_mu(0.06835937),
b2=shuttler_dds_ddamp_mu(-0.0001367187),
b3=0,
c0=0,
c1=0,
c2=0,
)
self.shuttler0_trigger.trigger(0b11)
delay(500*us)
## Step 5 ##
self.shuttler0_dcbias[0].set_waveform(
a0=shuttler_volt_amp_mu(-5.0),
a1=int32(shuttler_volt_damp_mu(0.01)),
a2=0,
a3=0,
)
self.shuttler0_dds[0].set_waveform(
b0=shuttler_dds_amp_mu(1.0),
b1=0,
b2=0,
b3=0,
c0=0,
c1=shuttler_freq_mu(end_f_MHz),
c2=0,
)
self.shuttler0_dcbias[1].set_waveform(
a0=shuttler_volt_amp_mu(-5.0),
a1=int32(shuttler_volt_damp_mu(0.01)),
a2=0,
a3=0,
)
self.shuttler0_dds[1].set_waveform(
b0=0,
b1=0,
b2=0,
b3=0,
c0=0,
c1=0,
c2=0,
)
self.shuttler0_trigger.trigger(0b11)
delay(1000*us)
## Step 6 ##
self.shuttler0_dcbias[0].set_waveform(
a0=shuttler_volt_amp_mu(-2.5),
a1=int32(shuttler_volt_damp_mu(0.01)),
a2=0,
a3=0,
)
self.shuttler0_dds[0].set_waveform(
b0=0,
b1=shuttler_dds_damp_mu(0.06835937),
b2=shuttler_dds_ddamp_mu(-0.0001367187),
b3=0,
c0=0,
c1=shuttler_freq_mu(start_f_MHz),
c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us),
)
self.shuttler0_trigger.trigger(0b1)
self.shuttler_channel_reset(1)
delay(500*us)
## Step 7 ##
self.shuttler0_dcbias[0].set_waveform(
a0=shuttler_volt_amp_mu(2.5),
a1=int32(shuttler_volt_damp_mu(-0.01)),
a2=0,
a3=0,
)
self.shuttler0_dds[0].set_waveform(
b0=0,
b1=shuttler_dds_damp_mu(-0.06835937),
b2=shuttler_dds_ddamp_mu(0.0001367187),
b3=0,
c0=shuttler_phase_offset(180.0),
c1=shuttler_freq_mu(end_f_MHz),
c2=shuttler_freq_sweep(end_f_MHz, start_f_MHz, duration_us),
)
self.shuttler0_trigger.trigger(0b1)
delay(500*us)
## Step 8 ##
self.shuttler0_relay.enable(0)
self.shuttler_channel_reset(0)
self.shuttler_channel_reset(1)
@kernel
def led(self):
for i in range(2):
for j in range(3):
self.shuttler0_leds[i].pulse(.1*s)
delay(.1*s)
@kernel
def relay_init(self):
self.shuttler0_relay.init()
self.shuttler0_relay.enable(0x0000)
@kernel
def adc_init(self):
delay_mu(int64(self.core.ref_multiplier))
self.shuttler0_adc.power_up()
delay_mu(int64(self.core.ref_multiplier))
assert self.shuttler0_adc.read_id() >> 4 == 0x038d
delay_mu(int64(self.core.ref_multiplier))
# The actual output voltage is limited by the hardware, the calculated calibration gain and offset.
# For example, if the system has a calibration gain of 1.06, then the max output voltage = 10 / 1.06 = 9.43V.
# Setting a value larger than 9.43V will result in overflow.
self.shuttler0_adc.calibrate(self.shuttler0_dcbias, self.shuttler0_trigger, self.shuttler0_config)

View File

@ -5,7 +5,11 @@ device_db = {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {"host": core_addr, "ref_period": 1e-9}
"arguments": {
"host": core_addr,
"ref_period": 1e-9,
"analyzer_proxy": "core_analyzer"
}
},
"core_log": {
"type": "controller",
@ -20,6 +24,13 @@ device_db = {
"port": 1384,
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_analyzer": {
"type": "controller",
"host": "::1",
"port_proxy": 1385,
"port": 1386,
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",

View File

@ -9,7 +9,11 @@ device_db = {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {"host": core_addr, "ref_period": 1e-9}
"arguments": {
"host": core_addr,
"ref_period": 1e-9,
"analyzer_proxy": "core_analyzer"
}
},
"core_log": {
"type": "controller",
@ -24,6 +28,13 @@ device_db = {
"port": 1384,
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_analyzer": {
"type": "controller",
"host": "::1",
"port_proxy": 1385,
"port": 1386,
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",

View File

@ -20,7 +20,7 @@ class DDSSetter(EnvExperiment):
"driver": self.get_device(k),
"frequency": self.get_argument(
"{}_frequency".format(k),
NumberValue(100e6, scale=1e6, unit="MHz", ndecimals=6))
NumberValue(100e6, scale=1e6, unit="MHz", precision=6))
}
@kernel

View File

@ -12,8 +12,8 @@ class PhotonHistogram(EnvExperiment):
self.setattr_device("bdd_sw")
self.setattr_device("pmt")
self.setattr_argument("nbins", NumberValue(100, ndecimals=0, step=1))
self.setattr_argument("repeats", NumberValue(100, ndecimals=0, step=1))
self.setattr_argument("nbins", NumberValue(100, precision=0, step=1))
self.setattr_argument("repeats", NumberValue(100, precision=0, step=1))
self.setattr_dataset("cool_f", 230*MHz)
self.setattr_dataset("detect_f", 220*MHz)

View File

@ -79,7 +79,7 @@ class SpeedBenchmark(EnvExperiment):
"CoreSend1MB",
"CorePrimes"]))
self.setattr_argument("nruns", NumberValue(10, min=1, max=1000,
ndecimals=0, step=1))
precision=0, step=1))
self.setattr_device("core")
self.setattr_device("scheduler")

View File

@ -1,102 +0,0 @@
core_addr = "192.168.1.65"
device_db = {
"core": {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {"host": core_addr, "ref_period": 1/(8*150e6)}
},
"core_log": {
"type": "controller",
"host": "::1",
"port": 1068,
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
},
"core_moninj": {
"type": "controller",
"host": "::1",
"port_proxy": 1383,
"port": 1384,
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",
"class": "CoreCache"
},
"core_dma": {
"type": "local",
"module": "artiq.coredevice.dma",
"class": "CoreDMA"
}
}
# master peripherals
for i in range(4):
device_db["led" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": i},
}
# DEST#1 peripherals
amc_base = 0x070000
rtm_base = 0x020000
for i in range(4):
device_db["led" + str(4+i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + i},
}
#DIO (EEM0) starting at RTIO channel 0x000056
for i in range(8):
device_db["ttl" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + 0x000056 + i},
}
#DIO (EEM1) starting at RTIO channel 0x00005e
for i in range(8):
device_db["ttl" + str(8+i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + 0x00005e + i},
}
device_db["fmcdio_dirctl_clk"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + 0x000066}
}
device_db["fmcdio_dirctl_ser"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + 0x000067}
}
device_db["fmcdio_dirctl_latch"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + 0x000068}
}
device_db["fmcdio_dirctl"] = {
"type": "local",
"module": "artiq.coredevice.shiftreg",
"class": "ShiftReg",
"arguments": {"clk": "fmcdio_dirctl_clk",
"ser": "fmcdio_dirctl_ser",
"latch": "fmcdio_dirctl_latch"}
}

View File

@ -1,129 +0,0 @@
import sys
import os
import select
from artiq.experiment import *
from artiq.coredevice.fmcdio_vhdci_eem import *
def chunker(seq, size):
res = []
for el in seq:
res.append(el)
if len(res) == size:
yield res
res = []
if res:
yield res
def is_enter_pressed() -> TBool:
if os.name == "nt":
if msvcrt.kbhit() and msvcrt.getch() == b"\r":
return True
else:
return False
else:
if select.select([sys.stdin, ], [], [], 0.0)[0]:
sys.stdin.read(1)
return True
else:
return False
class Demo(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("fmcdio_dirctl")
self.leds = dict()
self.ttl_outs = dict()
ddb = self.get_device_db()
for name, desc in ddb.items():
if isinstance(desc, dict) and desc["type"] == "local":
module, cls = desc["module"], desc["class"]
if (module, cls) == ("artiq.coredevice.ttl", "TTLOut"):
dev = self.get_device(name)
if "led" in name: # guess
self.leds[name] = dev
elif "ttl" in name: # to exclude fmcdio_dirctl
self.ttl_outs[name] = dev
self.leds = sorted(self.leds.items(), key=lambda x: x[1].channel)
self.ttl_outs = sorted(self.ttl_outs.items(), key=lambda x: x[1].channel)
self.dirctl_word = (
shiftreg_bits(0, dio_bank0_out_pins | dio_bank1_out_pins) |
shiftreg_bits(1, dio_bank0_out_pins | dio_bank1_out_pins)
)
@kernel
def init(self):
self.core.break_realtime()
print("*** Waiting for DRTIO ready...")
drtio_indices = [7]
for i in drtio_indices:
while not self.drtio_is_up(i):
pass
self.fmcdio_dirctl.set(self.dirctl_word)
@kernel
def drtio_is_up(self, drtio_index):
if not self.core.get_rtio_destination_status(drtio_index):
return False
print("DRTIO #", drtio_index, "is ready\n")
return True
@kernel
def test_led(self, led):
while not is_enter_pressed():
self.core.break_realtime()
# do not fill the FIFOs too much to avoid long response times
t = now_mu() - self.core.seconds_to_mu(0.2)
while self.core.get_rtio_counter_mu() < t:
pass
for i in range(3):
led.pulse(100*ms)
delay(100*ms)
def test_leds(self):
print("*** Testing LEDs.")
print("Check for blinking. Press ENTER when done.")
for led_name, led_dev in self.leds:
print("Testing LED: {}".format(led_name))
self.test_led(led_dev)
@kernel
def test_ttl_out_chunk(self, ttl_chunk):
while not is_enter_pressed():
self.core.break_realtime()
for _ in range(50000):
i = 0
for ttl in ttl_chunk:
i += 1
for _ in range(i):
ttl.pulse(1*us)
delay(1*us)
delay(10*us)
def test_ttl_outs(self):
print("*** Testing TTL outputs.")
print("Outputs are tested in groups of 4. Touch each TTL connector")
print("with the oscilloscope probe tip, and check that the number of")
print("pulses corresponds to its number in the group.")
print("Press ENTER when done.")
for ttl_chunk in chunker(self.ttl_outs, 4):
print("Testing TTL outputs: {}.".format(", ".join(name for name, dev in ttl_chunk)))
self.test_ttl_out_chunk([dev for name, dev in ttl_chunk])
def run(self):
self.core.reset()
if self.leds:
self.test_leds()
if self.ttl_outs:
self.test_ttl_outs()

View File

@ -45,13 +45,13 @@ class ArgumentsDemo(EnvExperiment):
PYONValue(self.get_dataset("foo", default=42)))
self.setattr_argument("number", NumberValue(42e-6,
unit="us",
ndecimals=4))
precision=4))
self.setattr_argument("integer", NumberValue(42,
step=1, ndecimals=0))
step=1, precision=0))
self.setattr_argument("string", StringValue("Hello World"))
self.setattr_argument("scan", Scannable(global_max=400,
default=NoScan(325),
ndecimals=6))
precision=6))
self.setattr_argument("boolean", BooleanValue(True), "Group")
self.setattr_argument("enum", EnumerationValue(
["foo", "bar", "quux"], "foo"), "Group")

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