1
0
forked from M-Labs/artiq

Compare commits

...

300 Commits

Author SHA1 Message Date
63db4af1fc doc: Fix example flake 2025-01-09 11:16:25 +08:00
626c709b4e doc: Warning on Urukul cycle alignment in DMA 2025-01-08 10:30:33 +08:00
8ff433596b flake: update dependencies 2025-01-08 10:27:57 +08:00
dc21f0b6dd update copyright year 2025-01-04 10:45:34 +08:00
087eb514c1 flake: update dependencies 2024-12-30 19:24:25 +08:00
newell
7534a8fe04
Update AD9834 coredevice driver 2024-12-30 13:40:13 +08:00
7669cfce3d doc: Clarify Nix installation 2024-12-30 13:35:17 +08:00
99fe642cab Fix sloppy mistake in PR 2061 2024-12-30 13:34:07 +08:00
d8184cfb56
do not fail on exception message formatting, add tests 2024-12-30 13:16:16 +08:00
5b52f187d0 analyzer: increase thread stack size 2024-12-25 09:48:23 +08:00
9c99d116bb Fix StoppedMessage has no attribute "channel" error for DRTIO setups
Since DRTIO setups now have sequences from multiple cores, it can have more than one StoppedMessage.

Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-12-21 09:43:31 +08:00
366bb0fc59 Fix moninj showing wrong frequency for urukuls with non-default clk_div
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-12-17 15:00:48 +08:00
c65520ab01 Fix suservo example
Signed-off-by: Egor Savkin <es@m-labs.hk>
2024-12-16 15:13:03 +08:00
f5bbc688f0 flake: update dependencies 2024-12-15 21:20:24 +08:00
598046b2e6 doc: Add NixOS/preinstall handbook 2024-12-15 21:13:12 +08:00
Charlie Root
9a8338d71b flake: format using alejandra 2024-12-15 20:40:08 +08:00
Charlie Root
db293d5ecd flake: add alejandra as a formatter 2024-12-15 20:40:08 +08:00
Charlie Root
67dde30625 flake: clean up qt paths 2024-12-11 23:17:41 +08:00
Charlie Root
2508ff4812 flake: clean up devShells
Move both devShells into devshells.<...>,
switch from buildInputs to packages
2024-12-11 22:12:46 +08:00
3db8d2310c flake: update dependencies 2024-12-11 13:29:46 +08:00
Charlie Root
6097a32f4a flake: move defaultPackage.<system> to packages.<system>.default
change deprecated defaultPackage.x86_64-linux to
packages.x86_64-linux.default,
remove unneeded ps
2024-12-02 12:09:58 +08:00
592f0a7708 afws_client: sync 2024-11-28 18:55:19 +08:00
de8f8af3dd drtio: add InjectionRequest to expects_response 2024-11-26 14:57:20 +08:00
145a973213 flake: factor out Qt paths into exported variable
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-11-25 11:45:32 +08:00
fd664e82d1 doc: Windows netstat fix 2024-11-22 20:24:09 +08:00
07ba7a075f RELEASE_NOTES: formatting 2024-11-22 14:23:31 +08:00
d5020f205e doc: config over DRTIO changes 2024-11-22 14:21:21 +08:00
f4e3b3a0a5 doc: cli argument submission format 2024-11-22 14:18:05 +08:00
9b5b4c07ea doc: note use of scheduler attributes 2024-11-22 14:18:05 +08:00
61c311fc54 doc: Add FAQ on port conflicts 2024-11-22 10:30:12 +08:00
270a417a28 Correctly handle try/catch try/finally blocks
If we have a try/catch block nested inside a try/finally, then the
finally block would not be executed in the event of the exception.
This is because the landing pad of the inner try is marked as having no
cleanup clause.

We now set has_cleanup to False only if there is no outer try block.

Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2024-11-20 09:38:36 +08:00
e1aa8a5a8c Remove final_branch from ARTIQ IR transform
As of da4ff44377 this is always None, and
so can be skipped.

Signed-off-by: Jonathan Coates <jonathan.coates@oxionics.com>
2024-11-20 09:38:36 +08:00
8b335c4459
Support CoreMgmt over DRTIO 2024-11-20 08:25:06 +08:00
df1a389007
flash_tools: cleanup import 2024-11-20 08:23:51 +08:00
1ed04ebc91
Merge branch 'master' into drtio-coremgmt 2024-11-20 08:22:51 +08:00
cb836cd4a0 fmcdio_vhdci_eem: remove 2024-11-20 08:20:26 +08:00
90764b04f9 both ... not -> neither ... nor 2024-11-18 16:04:44 +08:00
6251e73459 board_misoc: enable efc rebooting 2024-11-18 09:56:09 +08:00
e36916b931 coremgmt flashing: detect risc-v or zynq targets 2024-11-18 09:56:09 +08:00
de349e4c39 bit2bin, fetch_bin -> flash_tools 2024-11-18 09:56:09 +08:00
f2c13a5041 drtio_proto: add allocate step for flashing
This avoids reallocation when transfering binaries. Reallocation during flash handshakes could cause DRTIO timeouts.
2024-11-18 09:56:09 +08:00
1583debfe7 feature gate txenable with has_drtio_eem 2024-11-18 09:56:09 +08:00
a643da2c5e fix frontend args reference 2024-11-18 09:56:09 +08:00
f5ff908098 artiq_flash/coremgmt flash -> fetch_bin 2024-11-18 09:56:09 +08:00
e8bd99048e coremgmt frontend: fix arg name 2024-11-18 09:56:09 +08:00
21944ff865 coremgmt flash: update crc mismatch message 2024-11-18 09:56:09 +08:00
9882e16216 RELEASE_NOTES: fix typo 2024-11-18 09:56:07 +08:00
ffcf79b74e satman: support coremgmt logging 2024-11-18 09:55:10 +08:00
ea61d9bd39 satman: uart_logger -> buffer_logger 2024-11-18 09:55:10 +08:00
cc40313501 flake8 2024-11-18 09:55:10 +08:00
2e9622b9d6 update RELEASE_NOTES 2024-11-18 09:55:08 +08:00
9d3204d019 runtim mgmt: fix pull log message relaying 2024-11-18 09:53:43 +08:00
6b6bcdb6d6 runtime mgmt: avoid passing incomplete message to corelog 2024-11-18 09:53:43 +08:00
28654501af runtime mgmt: minor fix 2024-11-18 09:53:43 +08:00
a2d341f4d6 satman mgmt: get_slice_sat -> get_slice_satellite 2024-11-18 09:53:43 +08:00
5c21649d10 frontend: make coremgmt flash zynq-compatible 2024-11-18 09:53:43 +08:00
2b73d5a4c6 drtio-proto: avoid expecting drop link ack response 2024-11-18 09:53:43 +08:00
4a54241e1b runtime mgmt: reorganize uses 2024-11-18 09:53:43 +08:00
f2f27e2d30 runtime mgmt: remove cursor 2024-11-18 09:53:43 +08:00
a56294f9de satman coremgmt: impl config erase 2024-11-18 09:53:43 +08:00
e85b640a83 drtop-proto: rearrange packet assignment 2024-11-18 09:53:43 +08:00
1874438890 coremgmt frontend: add artiq flash like source tree support 2024-11-18 09:53:43 +08:00
d935c22aad remove dead commented code 2024-11-18 09:53:43 +08:00
2c0a4c0bae drtio_proto: implement reboot init handshake 2024-11-18 09:53:43 +08:00
bb8148e554 mgmt: implement flash via drtio 2024-11-18 09:53:43 +08:00
c5988ab48b mgmt flash: simplify protocol 2024-11-18 09:53:43 +08:00
045ebd53c4 coremgmt: implement flash 2024-11-18 09:53:43 +08:00
5502aefa39 drtio-proto: merge coremgmt ACK adn NACK 2024-11-18 09:53:43 +08:00
7af8511de3 drtio-proto: remove reboot imminent message 2024-11-18 09:53:43 +08:00
f6cf66966d remote coremgmt: restart device gracefully after flashing 2024-11-18 09:53:43 +08:00
644e24be34 local coremgmt: return the whole config after read 2024-11-18 09:53:43 +08:00
455e2a8f9d satman coremgmt: EOF line 2024-11-18 09:53:43 +08:00
cbcf2bd84a frontend: pass drtio destination during communication 2024-11-18 09:53:43 +08:00
05578b282e satman: support remote drtio instruction for core mgmt 2024-11-18 09:53:43 +08:00
e57d18a41f runtime: support mgmt over drtio 2024-11-18 09:53:43 +08:00
b60a616e78 drtio: add new messages for remote mgmt 2024-11-18 09:53:43 +08:00
12682a277e config: impl flashing over mgmt 2024-11-18 09:53:43 +08:00
newell
1836ab5196
doc: Add note that 150 MHz internal reference is not supported for EBAZ4205 (#2617) 2024-11-17 15:33:38 +08:00
673fe29a12 RELEASE_NOTES: update 2024-11-16 17:43:56 +08:00
52c07a2b14 gui/experiments: add custom colors for experiment windows
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-11-16 17:42:29 +08:00
292a07d830 doc: EBAZ4205 SD Boot 2024-11-16 17:39:26 +08:00
bd7b07b0fd master: implement DRTIO-EEM satellite disconnection 2024-11-16 17:35:54 +08:00
40ab4fee5b drtio_eem: move rx_ready out of init 2024-11-16 17:35:54 +08:00
architeuthidae
27d54cb8f3
doc: Add FAQ on rollbacks/releases (#2609) 2024-11-16 13:19:04 +08:00
9aae89be69 flake: update dependencies 2024-11-15 10:12:48 +08:00
82505a2203
firmware: Disable the Nagle algorithm on sockets
Signed-off-by: Michael Birtwell <michael.birtwell@oxionics.com>
2024-11-13 18:43:52 +00:00
Charlie Root
bca30becbf flake.nix: clean up inputs
- Put all inputs into a single attrSet to improve readabiliy
- Fix deprecated links since links without quotes surrounding them have
been deprecated for quite some time.
2024-11-09 15:01:20 +08:00
83254d13da doc: Mock platformdirs 2024-11-04 23:43:32 +00:00
e336e33a46 artiq.svg: clean up
* Remove several broken open paths that do not add visible geometry
  (AI/Inkscape residue)
* Ungroup a spurious group
* Convert two remaining objects from polygon to paths such that
  the file only contains paths. Increases compatibility with other tools
  (CAD, PCB layout, conversion tools)
2024-11-03 22:29:07 +08:00
b4085312b0 flake: add paramiko ed25519 dependencies 2024-10-31 11:35:08 +08:00
65d20b7857 flake: update dependencies 2024-10-30 13:31:16 +08:00
56bd975a34 doc: Typo 2024-10-25 10:32:27 +08:00
4bf2331d6a doc: Elaboration on Git workflows 2024-10-23 11:40:04 +08:00
architeuthidae
02235b2d80
doc: Add section on management system communications (#2564)
Co-authored-by: architeuthidae <am@m-labs.hk>
2024-10-18 18:59:22 +08:00
58ea3b5bcc Fix some typos 2024-10-18 14:57:38 +08:00
fd2df7ce68 docs: mention that subkernels pass now_mu 2024-10-18 14:43:27 +08:00
4178fed3f7 subkernels: send now_mu when starting a subkernel 2024-10-18 14:43:27 +08:00
33b81d0e2e
Merge pull request #2598 from eduardotenholder/master
fix typo
2024-10-17 17:25:23 +02:00
Phillip Klein
d602cdbc1f fix typo 2024-10-17 15:38:07 +02:00
newell
30b9479437
add AD9834 core device driver (#2596) 2024-10-17 14:14:45 +08:00
newell
700812471c
doc: Additonal EBAZ4205 documentation, fix JSON description terminology (#2588) 2024-10-16 14:12:40 +08:00
fcac2ea20e flake: update dependencies 2024-10-15 15:57:09 +08:00
1fc6ab8c57 doc: Fix Python indentation 2024-10-15 14:41:12 +08:00
thatschatt
a570e6fd87
doc: Add TTuple to compiler section (#2591) 2024-10-14 18:47:02 +08:00
e06534913c satman: gate gt_drtio against having drtio-eem 2024-10-11 17:30:30 +08:00
65843696cd doc: Attribute writeback / data transfer FAQ 2024-10-09 14:03:40 +08:00
architeuthidae
a761b9cf9c
doc: Additional Nix config details (#2584)
Co-authored-by: architeuthidae <am@m-labs.hk>
2024-10-09 13:25:52 +08:00
2fa60cc084 doc: couple typos in extending rtio 2024-10-09 12:38:08 +08:00
cdcaee80d1 Point to Hydra server for EBAZ4205 2024-10-08 15:20:28 +08:00
6afbd90c59 update dependencies 2024-10-08 15:11:34 +08:00
049ef90220 Update RELEASE_NOTES.rst 2024-10-07 10:55:44 +08:00
b751e5455b ebaz4205 docs 2024-10-07 10:55:44 +08:00
333623e24b flake: update asyncserial
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-10-01 14:45:17 +08:00
21cc0f7273 flake: update dependencies 2024-09-30 14:58:58 +08:00
5b63cbe986 flake: update qasync, sync with nixpkgs 2024-09-30 14:58:21 +08:00
5b72a1faa5 Fix typo 2024-09-30 14:10:31 +08:00
1d093d0bce RELEASE_NOTES: restart idle kernels on flash 2024-09-14 11:37:22 +08:00
40227421a8 run idle kernel on flash 2024-09-14 11:37:22 +08:00
41203df735 almazny: fix missing delay import 2024-09-13 14:48:21 +08:00
38e0d6b953 moninj: fix pyqt6 context menu exec 2024-09-10 15:31:32 +08:00
Adam Chatterley
d8a3da449e RELEASE_NOTES: invariant detection 2024-09-10 07:58:01 +08:00
Adam Chatterley
6f70d629cf doc: Add kernel invariant detection description
Signed-off-by: Adam Chatterley <aschatterley@phys.au.dk>
2024-09-10 07:58:01 +08:00
Adam Chatterley
03606f4d7e core: Add option to detect kernel invariants
There is already invariant detection in the the compiler, this just
exposes it by adding a flag to Core

Signed-off-by: Adam Chatterley <aschatterley@phys.au.dk>
2024-09-10 07:58:01 +08:00
David Nadlinger
ed3b60b270 RELEASE_NOTES: group dashboard changes, mention Schedule column hiding 2024-09-09 22:00:46 +08:00
David Nadlinger
6a7926ddeb dashboard: Allow moving/hiding of Schedule view columns
This is very handy to make good use of screen real estate, as
e.g. file path and Git revision are often not as important to
judge the state of the system at a glance – particularly, as
the schedule entries can then fit on a single line each.
2024-09-09 22:00:46 +08:00
David Nadlinger
4e654011c3 gui/models: Add DictSyncModel default args to match C++ [nfc] 2024-09-09 22:00:46 +08:00
Charles Baynham
5912142836 nix: add paramiko to boards environment for lightweight flashing 2024-09-06 20:46:46 +08:00
0c1ffa9f4f doc: Add 'Adding custom EEM' section 2024-09-03 22:19:14 +08:00
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
00f2e3ae93 doc: Dashboard 'Load HDF5' button + nitpicks 2024-08-28 11:20:02 +08:00
9aaec5db67 update default version 2024-08-27 13:59:22 +08:00
583a4ceadd
switch from appdirs to platformdirs 2024-08-27 13:59:05 +08:00
dba877471f flake: update dependencies 2024-08-24 10:51:34 +08:00
457b3edd44 test_client: make master termination easier to debug 2024-08-24 10:47:01 +08:00
fbb1a2c25d master: migrate deprecated pygit2 commit.hex attr 2024-08-24 10:47:01 +08:00
352cf907ee compiler: add UnwrapNoneError exception 2024-08-23 19:12:52 +08:00
7a2b11cc54 firmware: add UnwrapNoneError exception 2024-08-23 19:12:52 +08:00
792f3d1ca8 doc: Fix TikZ arrow types (again) 2024-08-23 17:22:24 +08:00
f6bc4d559a doc: Fix TikZ arrow types 2024-08-23 16:44:56 +08:00
6b570c0484 doc: Additional keyboard shortcuts 2024-08-23 16:44:56 +08:00
70dce7c1dd doc: Minor fixes 2024-08-23 16:44:56 +08:00
e38dc59656 doc: Add ZC706 to core device page 2024-08-22 13:52:16 +08:00
70d0f930c6 doc: More FAQs 2024-08-22 13:51:39 +08:00
9bfac74c3f doc: Add 'Overview' diagram 2024-08-22 13:44:20 +08:00
3d125e76b3 firmware: add LinAlgError exception 2024-08-22 13:19:36 +08:00
8e6fe41dc4 compiler: add LinAlgError exception 2024-08-22 13:19:36 +08:00
61e96b37f9 doc: remove section on idle kernel info after demotion 2024-08-21 11:26:53 +08:00
788e0cb3da session: demote idle/startup kernel messages to debug 2024-08-21 11:26:53 +08:00
62afcdaaf6
gui/state: implement backup state file for last successful load (#2538) 2024-08-21 00:39:24 +08:00
83bf984216 doc: Management expansion, suggested edits 2024-08-20 16:29:06 +08:00
020fe6caf0 doc: Add Waveform/RTIO analyzer 2024-08-20 16:29:06 +08:00
9e557cdbf9 doc: Add reference descriptions 2024-08-20 16:29:06 +08:00
b5787ac8f4 doc: GUI applet groups 2024-08-20 16:29:06 +08:00
e4b4657a6d doc: using_data_interfaces associated changes 2024-08-20 16:29:06 +08:00
25b3553469 doc: Add 'Data and user interfaces' page 2024-08-20 16:29:06 +08:00
7e32f00121 firmware/ksupport: improve comments and syscall name 2024-08-20 15:21:27 +08:00
76ead047bf coredevice/test: add unittests for exceptions 2024-08-20 15:21:27 +08:00
cd4a0bb39e firmware/ksupport: add exception unittests 2024-08-20 15:21:27 +08:00
09128f87e6 coredevice/comm_kernel: map exceptions to correct names 2024-08-20 15:21:27 +08:00
33d5002f39 sync exception names and ids 2024-08-20 15:21:27 +08:00
0eddd2bbaa doc: More FAQs 2024-08-19 13:03:46 +08:00
d28355541a flake: update dependencies, use rust overlay snapshot 2024-08-14 16:52:46 +08:00
00b429b468 doc: Refactor management system reference 2024-08-13 14:53:13 +08:00
1b75bd1448 doc: Extending RTIO, fixes 2024-08-13 14:50:13 +08:00
83922cce8b doc: Add 'Extending RTIO' page 2024-08-13 14:50:13 +08:00
775aff730e flake/sphinx: add tikz for diagrams 2024-08-13 14:50:13 +08:00
c0805b9cb9 doc: Minor link fixes 2024-08-12 16:52:45 +08:00
322f9f6e55 setup: minimum Python version is 3.11 (StreamWriter.start_tls) 2024-08-12 11:40:34 +08:00
9f9acb3528 gui: force xcb instead of wayland 2024-08-07 16:01:51 +08:00
2241a32c9a afws_client: report error on JSON data length mismatch 2024-08-02 14:26:58 +08:00
e627aaeda0 CONTRIBUTING: Doc section 2024-08-01 19:29:53 +08:00
cec24feac8 doc: Refactor FAQ slightly 2024-08-01 19:28:42 +08:00
c8b797c5ac doc: Add helpdesk instructions to FAQ 2024-08-01 19:28:42 +08:00
a547fac41b doc: Add extra resources to FAQ 2024-08-01 19:28:42 +08:00
55a89b1dbb doc: Add note on nix profile deletion, garbage collect 2024-08-01 18:57:34 +08:00
477320d72c doc: reST formatting 2024-08-01 18:52:11 +08:00
1b28e38d51 flake/sphinx: fix errors in manual nix build 2024-08-01 18:49:21 +08:00
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
4fcb7cc408 big_number: fix port to Qt6 2024-07-31 15:06:47 +02:00
0623480c82 doc: Subkernel exception fix 2024-07-31 17:17:13 +08:00
e63ac3435f satman: pass exceptions from one subkernel to another 2024-07-31 17:17:01 +08:00
02479e4fb3 subkernels: separate error messages 2024-07-31 17:17:01 +08:00
c5c5708f49 compiler: add builtinInvoke for subkernel raising functions 2024-07-31 17:17:01 +08:00
fb8dd01e8d subkernel: pass exceptions to kernel 2024-07-31 17:17:01 +08:00
e12bc586a5 doc: Rewrite FAQ 2024-07-31 17:15:27 +08:00
d69c2b6aa2 git(hub): update gitignore, pull request template 2024-07-31 13:24:40 +08:00
994a936f26 doc: Document core_log() in manual 2024-07-31 13:20:15 +08:00
75ffbeba4d
flake: avoid permissions race window when setting up HITL SSH key 2024-07-30 11:24:21 +08:00
fbf11ca002 afws_client: fix unicode error in json handling 2024-07-29 18:05:15 +08:00
61ac6da547 doc: Add references to command line tools 2024-07-29 13:40:11 +08:00
2ec01a3c45 doc: Add artiq_session and artiq_ctlmgr to front-end tools 2024-07-29 13:40:11 +08:00
bac22b7163 doc: Add automodule to command line references 2024-07-29 13:40:11 +08:00
937f3811d1 doc: Rename main frontend tools page 2024-07-29 13:40:11 +08:00
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
191494e430 RELEASE_NOTES: update 2024-07-27 22:10:06 +08:00
b588295063 flake: switch to nixpkgs unstable (py3.12), update nix deps 2024-07-27 21:49:33 +08:00
e7f906e47a applets: fix initial embedded widget size 2024-07-27 21:37:19 +08:00
d6bcc64518 flake: update qasync to support pyqt6 2024-07-27 21:37:19 +08:00
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
378d962edb afws_client: fix JSON parsing in read_json method 2024-07-26 10:55:40 +08:00
11c5f537bb RELEASE_NOTES: added fastino monitor 2024-07-23 10:52:24 +08:00
ee3d93ce6a moninj: add fastino support 2024-07-23 10:52:24 +08:00
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
e67bcfb36e doc: Add links for ddb_template and afws_client 2024-07-22 18:36:10 +08:00
52b0f30216 artiq_dashboard: Replace --port-notify 2024-07-22 16:57:38 +08:00
b0d2705c38 doc: link fixes 2024-07-19 18:35:21 +08:00
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
8a6a6042fd comm_analyzer: avoid unnecessary copy 2024-07-19 16:15:37 +08:00
7b13ffd9f3 doc: Fix Sphinx error in AFWS argparser 2024-07-19 11:21:36 +08:00
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
c05b109d5f afws_client: extract get_argparser() for modular arg parsing 2024-07-18 12:45:49 +08:00
5dbf870e68 doc: Reword heading 2024-07-17 12:12:47 +08:00
4cd9b8a8cf doc: rename release_notes -> releases 2024-07-17 12:12:47 +08:00
c9a4f3b9ee doc: Refactor manual index, make sections 2024-07-17 12:12:47 +08:00
bff46fcf1e doc: Add explanation of event spreading 2024-07-17 12:05:41 +08:00
a5327e383c doc: Reference link fixes 2024-07-17 11:54:50 +08:00
088ea36d53 doc: Sphinx configuration nitpicky 2024-07-17 11:54:50 +08:00
a4bfb0d5dd doc: Formatting and link fixes in docstrings 2024-07-17 11:39:08 +08:00
0e1b29c5d9 doc: Assorted typos and dead links 2024-07-17 11:36:59 +08:00
81106f3567 doc: Rewrite and overhaul of 'Compiler' page 2024-07-15 19:24:48 +08:00
9087c8698d ad9912: use pll doubler for refclk <11mhz 2024-07-15 19:21:58 +08:00
c2e323662b doc: Put back telnet exit hint 2024-07-15 10:54:38 +08:00
26483d5daf doc: Environment, suggested changes 2024-07-12 10:53:04 +02:00
d5f11387e8 doc: Mention artiq_ddb_template in Environment manual page 2024-07-12 10:53:04 +02:00
158789b2e5 doc: Improve description of environment in tutorials 2024-07-12 10:53:04 +02:00
e0e96fb08b doc: Update + expansion of Environment manual page 2024-07-12 10:53:04 +02:00
96e4949c37 doc: NDSP page edit, fixes 2024-07-12 09:09:59 +02:00
8cfae86634 doc: Mention ping() in NDSP tutorial 2024-07-12 09:09:59 +02:00
b2955f2bbe doc: Edit of 'Developing an NDSP' page 2024-07-12 09:09:59 +02:00
1032b9accf doc: resynchronize introduction to readme 2024-07-11 10:00:41 +02:00
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
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
e9f1b9d4ff browser: fix ctrl scroll type error 2024-07-10 10:40:23 +02:00
5ab2602802 tests: fix sed lane distributor test, force enable spread 2024-07-10 14:36:41 +08:00
86c6d11ed4 repeater: handle async packets on forwarding 2024-07-09 17:03:44 +02:00
7a50afd9a9 flake: update dependencies 2024-07-09 10:33:45 +02:00
af99a06919 allow toggling SED spread with flash config key 2024-07-09 10:32:01 +02:00
8a178a628a doc: Change references to rtio_clock/now to _mu 2024-07-08 23:10:27 +02:00
810ab20425 doc: Add warning on sequence error nondeterminism, for now 2024-07-08 23:10:27 +02:00
a5437dd4f5 doc: RTIO manual page edit, suggested changes 2024-07-08 23:10:27 +02:00
de5cd99787 docs: RTIO manual page edit 2024-07-08 23:10:27 +02:00
c785c763fe afws_client: asyncio fixes 2024-07-08 22:55:02 +02:00
8f0147f029 doc: Fix sphinx 'duplicated reference' warnings 2024-07-08 22:42:05 +02:00
9c1482aa69 moninj: adjust DAC widget label alignment 2024-07-08 22:40:49 +02:00
6044d810ca moninj: fixed size widgets 2024-07-08 22:40:49 +02:00
dcc5307771 firmware/ksupport: add rint api 2024-07-05 08:52:53 +02:00
d8cf91bfb2 RELEASE_NOTES: update 2024-07-05 08:42:34 +02:00
24133ca04d moninj: display voltage in DACWidget 2024-07-05 08:41:32 +02:00
868e4defda moninj: restore urukul TTL control 2024-07-04 13:43:43 +02:00
e7faca81fc repeater: clear buffer after ping 2024-07-04 13:38:14 +02:00
b8c12976db math_fns: Delete duplicated definition 2024-07-04 09:00:40 +02:00
91a4315386 RELEASE_NOTES: update 2024-07-03 09:15:43 +02:00
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
fdb0668c8a moninj: set parent to main window on widget delete 2024-07-02 14:21:50 +08:00
f1e8b8772a moninj: fix visual bug 2024-07-02 14:21:50 +08:00
9386a7a16f doc: Fix heading levels in Releases page 2024-06-27 15:52:48 +08:00
bcc760a3fb doc: Add description of release model to manual 2024-06-27 14:43:10 +08:00
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
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
bd7daa5247 flake: use upstream nixpkgs openocd
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2024-06-26 12:38:02 +08:00
48cdf42016 docs: Add details of shallow 'with parallel' to manual 2024-06-25 17:47:33 +08:00
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
81cda2380d doc: Management system overhaul, further corrections 2024-06-25 17:30:31 +08:00
ea22f67c4c doc: Clearer+more accurate overview of mgmt system 2024-06-25 17:30:31 +08:00
aa21b78681 doc: Management system update, corrections 2024-06-25 17:30:31 +08:00
de1df88dcd doc: Split running/management tool reference into separate page 2024-06-25 17:30:31 +08:00
0dc727c1fd doc: 'Management system' manual page copy+clarity edit 2024-06-25 17:30:31 +08:00
45ef4d18d7 doc: Elaboration on interactive args & controllers 2024-06-25 17:30:31 +08:00
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
e09fde6f51 doc: minor correction 2024-06-20 17:34:58 +08:00
02c1d2514f doc: Add DRTIO-over-EEM to manual + phrasing fixes 2024-06-20 17:34:08 +08:00
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
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
85545a8447 flake: update dependencies 2024-06-19 12:43:36 +08:00
77580b5bf6 kasli: raise error when enabling WRPLL with v1.x 2024-06-19 12:40:52 +08:00
84b97976c0 kasli: fix v1.0 & v1.1 compilation error 2024-06-19 12:40:26 +08:00
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
a167cc6043 doc: fix formatting in Installing page 2024-06-13 12:14:53 +08:00
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
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
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
ebc1e3fb76 bump major version number 2024-06-06 09:45:54 +08:00
180 changed files with 9206 additions and 4514 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

4
.gitignore vendored
View File

@ -11,6 +11,7 @@ __pycache__/
.ipynb_checkpoints
/doc/manual/_build
/build
/result
/dist
/*.egg-info
/.coverage
@ -23,7 +24,8 @@ __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/

View File

@ -8,27 +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``)
* 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
@ -38,8 +38,8 @@ 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 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).
@ -51,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.
@ -63,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 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.
@ -108,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

@ -7,29 +7,25 @@
ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is a leading-edge control and data acquisition system for quantum information experiments.
It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Many laboratories around the world have adopted ARTIQ as their control system and some have `contributed <https://m-labs.hk/experiment-control/funding/>`_ to it.
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 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 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-2024 M-Labs Limited.
Copyright (C) 2014-2025 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,9 +3,32 @@
Release notes
=============
ARTIQ-8 (Unreleased)
ARTIQ-9 (Unreleased)
--------------------
* Dashboard:
- Experiment windows can have different colors, selected by the user.
- Zotino monitoring now displays the values in volts.
- Schedule display columns can now be reordered and shown/hidden using the table
header context menu.
- State files are now automatically backed up upon successful loading.
* afws_client now uses the "happy eyeballs" algorithm (RFC 6555) for a faster and more
reliable connection to the server.
* The Zadig driver installer was added to the MSYS2 offline installer.
* Fastino monitoring with Moninj is now supported.
* Qt6 support.
* Python 3.12 support.
* Compiler can now give automatic suggestions for ``kernel_invariants``.
* Idle kernels now restart when written with ``artiq_coremgmt`` and stop when erased/removed from config.
* New support for the EBAZ4205 Zynq-SoC control card.
* New core device driver for the AD9834 DDS, tested with the ZonRi Technology Co., Ltd. AD9834-Module.
* Support for coredevice reflashing through the new ``flash`` tool in ``artiq_coremgmt``.
* ``artiq_coremgmt`` now supports configuring satellites.
* ``artiq.coredevice.fmcdio_vhdci_eem`` has been removed.
ARTIQ-8
-------
Highlights:
* New hardware support:

View File

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

View File

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

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python3
from PyQt5 import QtWidgets, QtCore, QtGui
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
@ -17,7 +17,7 @@ class QCancellableLineEdit(QtWidgets.QLineEdit):
editCancelled = QtCore.pyqtSignal()
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Escape:
if event.key() == QtCore.Qt.Key.Key_Escape:
self.editCancelled.emit()
else:
super().keyPressEvent(event)
@ -34,7 +34,7 @@ class NumberWidget(LayoutWidget):
self.addWidget(self.number_area, 0, 0)
self.unit_area = QtWidgets.QLabel()
self.unit_area.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop)
self.unit_area.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignTop)
self.addWidget(self.unit_area, 0, 1)
self.lcd_widget = QResponsiveLCDNumber()
@ -44,7 +44,7 @@ class NumberWidget(LayoutWidget):
self.edit_widget = QCancellableLineEdit()
self.edit_widget.setValidator(QtGui.QDoubleValidator())
self.edit_widget.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
self.edit_widget.setAlignment(QtCore.Qt.AlignmentFlag.AlignRight | QtCore.Qt.AlignmentFlag.AlignVCenter)
self.edit_widget.editCancelled.connect(self.cancel_edit)
self.edit_widget.returnPressed.connect(self.confirm_edit)
self.number_area.addWidget(self.edit_widget)

View File

@ -1,6 +1,6 @@
#!/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

View File

@ -1,7 +1,7 @@
#!/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

View File

@ -1,8 +1,8 @@
#!/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
@ -24,11 +24,11 @@ class XYPlot(pyqtgraph.PlotWidget):
y = value[self.args.y]
except KeyError:
return
x = value.get(self.args.x, (False, None))
x = value.get(self.args.x)
if x is None:
x = np.arange(len(y))
error = value.get(self.args.error, (False, None))
fit = value.get(self.args.fit, (False, None))
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

View File

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

View File

@ -24,21 +24,21 @@ class _AppletRequestInterface:
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
"""
Set a dataset.
See documentation of ``artiq.language.environment.set_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 ``artiq.language.environment.mutate_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 ``artiq.language.environment.append_to_dataset``.
See documentation of :meth:`~artiq.language.environment.HasEnvironment.append_to_dataset`.
"""
raise NotImplementedError
@ -49,8 +49,9 @@ class _AppletRequestInterface:
: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 ``Scannable`` arguments, this parameter
should be a ``ScanObject``. The type of the ``ScanObject`` will be set as the selected type when this function is called.
: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
@ -136,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
@ -272,7 +272,7 @@ class SimpleApplet:
# 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))
@ -285,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()

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
@ -62,8 +62,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
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)
@ -74,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()
@ -85,13 +85,13 @@ 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)
@ -112,7 +112,8 @@ 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)

View File

@ -4,7 +4,7 @@ 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
@ -33,13 +33,13 @@ class _ArgumentEditor(EntryTreeWidget):
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()
@ -86,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()
@ -126,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)
@ -316,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):
@ -406,7 +406,7 @@ 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))

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,35 +69,35 @@ 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:
@ -114,7 +114,7 @@ class Hdf5FileSystemModel(QtWidgets.QFileSystemModel):
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):
@ -125,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)
@ -147,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)
@ -252,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

@ -88,7 +88,10 @@ class EmbeddingMap:
self.subkernel_message_map[msg_type.name] = msg_id
self.object_reverse_map[obj_id] = msg_id
self.preallocate_runtime_exception_names(["RuntimeError",
# Keep this list of exceptions in sync with `EXCEPTION_ID_LOOKUP` in `artiq::firmware::ksupport::eh_artiq`
# 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",
@ -96,10 +99,23 @@ class EmbeddingMap:
"I2CError",
"CacheError",
"SPIError",
"0:ZeroDivisionError",
"SubkernelError",
"0:AssertionError",
"0:AttributeError",
"0:IndexError",
"0:IOError",
"0:KeyError",
"0:NotImplementedError",
"0:OverflowError",
"0:RuntimeError",
"0:TimeoutError",
"0:TypeError",
"0:ValueError",
"0:ZeroDivisionError",
"0:LinAlgError",
"UnwrapNoneError",
"SubkernelError"])
])
def preallocate_runtime_exception_names(self, names):
for i, name in enumerate(names):
@ -747,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

View File

@ -1047,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.

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

@ -65,10 +65,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
:ivar catch_clauses: (list of (:class:`ir.BasicBlock`, :class:`types.Type` or None))
a list of catch clauses that should be appended to inner try block
landingpad
:ivar final_branch: (function (target: :class:`ir.BasicBlock`, block: :class:`ir.BasicBlock)
or None)
the function that appends to ``block`` a jump through the ``finally`` statement
to ``target``
There is, additionally, some global state that is used to translate
the results of analyses on AST level to IR level:
@ -114,7 +110,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.return_target = None
self.unwind_target = None
self.catch_clauses = []
self.final_branch = None
self.function_map = dict()
self.variable_map = dict()
self.method_map = defaultdict(lambda: [])
@ -635,11 +630,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.append(ir.Branch(self.continue_target))
def raise_exn(self, exn=None, loc=None):
if self.final_branch is not None:
raise_proxy = self.add_block("try.raise")
self.final_branch(raise_proxy, self.current_block)
self.current_block = raise_proxy
if exn is not None:
# if we need to raise the exception in a final body, we have to
# lazy-evaluate the exception object to make sure that we generate
@ -713,7 +703,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
return_action.append(ir.Return(value))
final_branch(return_action, return_proxy)
else:
landingpad.has_cleanup = False
landingpad.has_cleanup = self.unwind_target is not None
# we should propagate the clauses to nested try catch blocks
# so nested try catch will jump to our clause if the inner one does not
@ -777,7 +767,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.continue_target = old_continue
self.return_target = old_return
if any(node.finalbody):
# create new unwind target for cleanup
final_dispatcher = self.add_block("try.final.dispatch")
final_landingpad = ir.LandingPad(cleanup)
@ -786,7 +775,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
# make sure that exception clauses are unwinded to the finally block
old_unwind, self.unwind_target = self.unwind_target, final_dispatcher
if any(node.finalbody):
# if we have a while:try/finally continue must execute finally
# before continuing the while
redirect = final_branch
@ -1114,13 +1102,11 @@ class ARTIQIRGenerator(algorithm.Visitor):
entry = self.add_block("entry")
old_block, self.current_block = self.current_block, entry
old_final_branch, self.final_branch = self.final_branch, None
old_unwind, self.unwind_target = self.unwind_target, None
self.raise_exn(lambda: exn_gen(*args[1:]), loc=loc)
finally:
self.current_function = old_func
self.current_block = old_block
self.final_branch = old_final_branch
self.unwind_target = old_unwind
# cond: bool Value, condition
@ -1539,7 +1525,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
entry = self.add_block("entry")
old_block, self.current_block = self.current_block, entry
old_final_branch, self.final_branch = self.final_branch, None
old_unwind, self.unwind_target = self.unwind_target, None
shape = self.append(ir.GetAttr(arg, "shape"))
@ -1565,7 +1550,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.current_loc = old_loc
self.current_function = old_func
self.current_block = old_block
self.final_branch = old_final_branch
self.unwind_target = old_unwind
def _get_array_unaryop(self, name, make_op, result_type, arg_type):
@ -1666,7 +1650,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
entry = self.add_block("entry")
old_block, self.current_block = self.current_block, entry
old_final_branch, self.final_branch = self.final_branch, None
old_unwind, self.unwind_target = self.unwind_target, None
body_gen(result, lhs, rhs)
@ -1677,7 +1660,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
self.current_loc = old_loc
self.current_function = old_func
self.current_block = old_block
self.final_branch = old_final_branch
self.unwind_target = old_unwind
def _make_array_elementwise_binop(self, name, result_type, lhs_type,
@ -2544,10 +2526,22 @@ class ARTIQIRGenerator(algorithm.Visitor):
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:
@ -2594,7 +2588,14 @@ class ARTIQIRGenerator(algorithm.Visitor):
{"name": name, "recv": vartype, "send": msg.value_type},
node.loc)
self.engine.process(diag)
return self.append(ir.Builtin("subkernel_recv", [msg_id, timeout], vartype))
if self.unwind_target is None:
ret = self.append(ir.Builtin("subkernel_recv", [msg_id, timeout], vartype))
else:
after_invoke = self.add_block("invoke")
ret = self.append(ir.BuiltinInvoke("subkernel_recv", [msg_id, timeout],
vartype, after_invoke, self.unwind_target))
self.current_block = after_invoke
return ret
elif types.is_exn_constructor(typ):
return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args])
elif types.is_constructor(typ):
@ -2801,7 +2802,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
entry = self.add_block("entry")
old_block, self.current_block = self.current_block, entry
old_final_branch, self.final_branch = self.final_branch, None
old_unwind, self.unwind_target = self.unwind_target, None
exn = self.alloc_exn(builtins.TException("AssertionError"),
@ -2814,7 +2814,6 @@ class ARTIQIRGenerator(algorithm.Visitor):
finally:
self.current_function = old_func
self.current_block = old_block
self.final_branch = old_final_branch
self.unwind_target = old_unwind
self.raise_assert_func = func
@ -2946,7 +2945,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

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

@ -1437,6 +1437,44 @@ class LLVMIRGenerator:
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])

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,
@ -309,7 +309,7 @@ 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.
@ -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) - (

437
artiq/coredevice/ad9834.py Normal file
View File

@ -0,0 +1,437 @@
"""
RTIO Driver for the Analog Devices AD9834 DDS via 3-wire SPI interface.
"""
# https://www.analog.com/media/en/technical-documentation/data-sheets/AD9834.pdf
# https://www.analog.com/media/en/technical-documentation/app-notes/an-1070.pdf
from numpy import int32
from artiq.coredevice import spi2 as spi
from artiq.experiment import *
from artiq.language.core import *
from artiq.language.types import *
from artiq.language.units import *
AD9834_B28 = 1 << 13
AD9834_HLB = 1 << 12
AD9834_FSEL = 1 << 11
AD9834_PSEL = 1 << 10
AD9834_PIN_SW = 1 << 9
AD9834_RESET = 1 << 8
AD9834_SLEEP1 = 1 << 7
AD9834_SLEEP12 = 1 << 6
AD9834_OPBITEN = 1 << 5
AD9834_SIGN_PIB = 1 << 4
AD9834_DIV2 = 1 << 3
AD9834_MODE = 1 << 1
AD9834_FREQ_REG_0 = 0b01 << 14
AD9834_FREQ_REG_1 = 0b10 << 14
FREQ_REGS = [AD9834_FREQ_REG_0, AD9834_FREQ_REG_1]
AD9834_PHASE_REG = 0b11 << 14
AD9834_PHASE_REG_0 = AD9834_PHASE_REG | (0 << 13)
AD9834_PHASE_REG_1 = AD9834_PHASE_REG | (1 << 13)
PHASE_REGS = [AD9834_PHASE_REG_0, AD9834_PHASE_REG_1]
class AD9834:
"""
AD9834 DDS driver.
This class provides control for the DDS AD9834.
The driver utilizes bit-controlled :const:`AD9834_FSEL`, :const:`AD9834_PSEL`, and
:const:`AD9834_RESET`. To pin control ``FSELECT``, ``PSELECT``, and ``RESET`` set
:const:`AD9834_PIN_SW`. The ``ctrl_reg`` attribute is used to maintain the state of
the control register, enabling persistent management of various configurations.
:param spi_device: SPI bus device name.
:param spi_freq: SPI bus clock frequency (default: 10 MHz, max: 40 MHz).
:param clk_freq: DDS clock frequency (default: 75 MHz).
:param core_device: Core device name (default: "core").
"""
kernel_invariants = {"core", "bus", "spi_freq", "clk_freq"}
def __init__(
self, dmgr, spi_device, spi_freq=10 * MHz, clk_freq=75 * MHz, core_device="core"
):
self.core = dmgr.get(core_device)
self.bus = dmgr.get(spi_device)
assert spi_freq <= 40 * MHz, "SPI frequency exceeds maximum value of 40 MHz"
self.spi_freq = spi_freq
self.clk_freq = clk_freq
self.ctrl_reg = 0x0000 # Reset control register
@kernel
def write(self, data: TInt32):
"""
Write a 16-bit word to the AD9834.
This method sends a 16-bit data word to the AD9834 via the SPI bus. The input
data is left-shifted by 16 bits to ensure proper alignment for the SPI controller,
allowing for accurate processing of the command by the AD9834.
This method is used internally by other methods to update the control registers
and frequency settings of the AD9834. It should not be called directly unless
low-level register manipulation is required.
:param data: The 16-bit word to be sent to the AD9834.
"""
self.bus.write(data << 16)
@kernel
def enable_reset(self):
"""
Enable the DDS reset.
This method sets :const:`AD9834_RESET`, putting the AD9834 into a reset state.
While in this state, the digital-to-analog converter (DAC) is not operational.
This method should be called during initialization or when a reset is required
to reinitialize the device and ensure proper operation.
"""
self.ctrl_reg |= AD9834_RESET
self.write(self.ctrl_reg)
@kernel
def output_enable(self):
"""
Disable the DDS reset and start signal generation.
This method clears :const:`AD9834_RESET`, allowing the AD9834 to begin generating
signals. Once this method is called, the device will resume normal operation and
output the generated waveform.
This method should be called after configuration of the frequency and phase
settings to activate the output.
"""
self.ctrl_reg &= ~AD9834_RESET
self.write(self.ctrl_reg)
@kernel
def init(self):
"""
Initialize the AD9834: configure the SPI bus and reset the DDS.
This method performs the necessary setup for the AD9834 device, including:
- Configuring the SPI bus parameters (clock polarity, data width, and frequency).
- Putting the AD9834 into a reset state to ensure proper initialization.
The SPI bus is configured to use 16 bits of data width with the clock frequency
provided as a parameter when creating the AD9834 instance. After configuring
the SPI bus, the method invokes :meth:`enable_reset()` to reset the AD9834.
This is an essential step to prepare the device for subsequent configuration
of frequency and phase.
This method should be called before any other operations are performed
on the AD9834 to ensure that the device is in a known state.
"""
self.bus.set_config(spi.SPI_CLK_POLARITY | spi.SPI_END, 16, self.spi_freq, 1)
self.enable_reset()
@kernel
def set_frequency_reg_msb(self, freq_reg: TInt32, word: TInt32):
"""
Set the fourteen most significant bits MSBs of the specified frequency register.
This method updates the specified frequency register with the provided MSB value.
It configures the control register to indicate that the MSB is being set.
:param freq_reg: The frequency register to write to (0-1).
:param word: The value to be written to the fourteen MSBs of the frequency register.
The method first clears the appropriate control bits, sets :const:`AD9834_HLB` to
indicate that the MSB is being sent, and then writes the updated control register
followed by the MSB value to the specified frequency register.
"""
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
self.ctrl_reg &= ~AD9834_B28
self.ctrl_reg |= AD9834_HLB
self.write(self.ctrl_reg)
self.write(FREQ_REGS[freq_reg] | (word & 0x3FFF))
@kernel
def set_frequency_reg_lsb(self, freq_reg: TInt32, word: TInt32):
"""
Set the fourteen least significant bits LSBs of the specified frequency register.
This method updates the specified frequency register with the provided LSB value.
It configures the control register to indicate that the LSB is being set.
:param freq_reg: The frequency register to write to (0-1).
:param word: The value to be written to the fourteen LSBs of the frequency register.
The method first clears the appropriate control bits and writes the updated control
register followed by the LSB value to the specified frequency register.
"""
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
self.ctrl_reg &= ~AD9834_B28
self.ctrl_reg &= ~AD9834_HLB
self.write(self.ctrl_reg)
self.write(FREQ_REGS[freq_reg] | (word & 0x3FFF))
@kernel
def set_frequency_reg(self, freq_reg: TInt32, freq_word: TInt32):
"""
Set the frequency for the specified frequency register using a precomputed frequency word.
This writes to the 28-bit frequency register in one transfer.
:param freq_reg: The frequency register to write to (0-1).
:param freq_word: The precomputed frequency word.
"""
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
self.ctrl_reg |= AD9834_B28
self.write(self.ctrl_reg)
lsb = freq_word & 0x3FFF
msb = (freq_word >> 14) & 0x3FFF
self.write(FREQ_REGS[freq_reg] | lsb)
self.write(FREQ_REGS[freq_reg] | msb)
@portable(flags={"fast-math"})
def frequency_to_ftw(self, frequency: TFloat) -> TInt32:
"""Return the 28-bit frequency tuning word corresponding to the given
frequency.
"""
assert frequency <= 37.5 * MHz, "Frequency exceeds maximum value of 37.5 MHz"
return int((frequency * (1 << 28)) / self.clk_freq) & 0x0FFFFFFF
@portable(flags={"fast-math"})
def turns_to_pow(self, turns: TFloat) -> TInt32:
"""Return the 12-bit phase offset word corresponding to the given phase
in turns."""
assert 0.0 <= turns <= 1.0, "Turns exceeds range 0.0 - 1.0"
return int32(round(turns * 0x1000)) & int32(0x0FFF)
@kernel
def select_frequency_reg(self, freq_reg: TInt32):
"""
Select the active frequency register for the phase accumulator.
This method chooses between the two available frequency registers in the AD9834 to
control the frequency of the output waveform. The control register is updated
to reflect the selected frequency register.
:param freq_reg: The frequency register to write to (0-1).
"""
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
if freq_reg:
self.ctrl_reg |= AD9834_FSEL
else:
self.ctrl_reg &= ~AD9834_FSEL
self.ctrl_reg &= ~AD9834_PIN_SW
self.write(self.ctrl_reg)
@kernel
def set_phase_reg(self, phase_reg: TInt32, phase: TInt32):
"""
Set the phase for the specified phase register.
This method updates the specified phase register with the provided phase value.
:param phase_reg: The phase register to write to (0-1).
:param phase: The value to be written to the phase register.
The method masks the phase value to ensure it fits within the 12-bit limit
and writes it to the specified phase register.
"""
assert 0 <= phase_reg <= 1, "Invalid phase register index"
phase_word = phase & 0x0FFF
self.write(PHASE_REGS[phase_reg] | phase_word)
@kernel
def select_phase_reg(self, phase_reg: TInt32):
"""
Select the active phase register for the phase accumulator.
This method chooses between the two available phase registers in the AD9834 to
control the phase of the output waveform. The control register is updated
to reflect the selected phase register.
:param phase_reg: The phase register to write to (0-1).
"""
assert 0 <= phase_reg <= 1, "Invalid phase register index"
if phase_reg:
self.ctrl_reg |= AD9834_PSEL
else:
self.ctrl_reg &= ~AD9834_PSEL
self.ctrl_reg &= ~AD9834_PIN_SW
self.write(self.ctrl_reg)
@kernel
def sleep(self, dac_pd: bool = False, clk_dis: bool = False):
"""
Put the AD9834 into sleep mode by selectively powering down the DAC and/or disabling
the internal clock.
This method controls the sleep mode behavior of the AD9834 by setting or clearing the
corresponding bits in the control register. Two independent options can be specified:
:param dac_pd: Set to ``True`` to power down the DAC (:const:`AD9834_SLEEP12` is set).
``False`` will leave the DAC active.
:param clk_dis: Set to ``True`` to disable the internal clock (:const:`AD9834_SLEEP1` is set).
``False`` will keep the clock running.
Both options can be enabled independently, allowing the DAC and/or clock to be powered down as needed.
The method updates the control register and writes the changes to the AD9834 device.
"""
if dac_pd:
self.ctrl_reg |= AD9834_SLEEP12
else:
self.ctrl_reg &= ~AD9834_SLEEP12
if clk_dis:
self.ctrl_reg |= AD9834_SLEEP1
else:
self.ctrl_reg &= ~AD9834_SLEEP1
self.write(self.ctrl_reg)
@kernel
def awake(self):
"""
Exit sleep mode and restore normal operation.
This method brings the AD9834 out of sleep mode by clearing any DAC power-down or
internal clock disable settings. It calls :meth:`sleep()` with no arguments,
effectively setting both ``dac_powerdown`` and ``internal_clk_disable`` to ``False``.
The device will resume generating output based on the current frequency and phase
settings.
"""
self.sleep()
@kernel
def config_sign_bit_out(
self,
high_z: bool = False,
msb_2: bool = False,
msb: bool = False,
comp_out: bool = False,
):
"""
Configure the ``SIGN BIT OUT`` pin for various output modes.
This method sets the output mode for the ``SIGN BIT OUT`` pin of the AD9834 based on the provided flags.
The user can enable one of several modes, including high impedance, MSB/2 output, MSB output,
or comparator output. These modes are mutually exclusive, and passing ``True`` to one flag will
configure the corresponding mode, while other flags should be left as ``False``.
:param high_z: Set to ``True`` to place the ``SIGN BIT OUT`` pin in high impedance (disabled) mode.
:param msb_2: Set to ``True`` to output DAC Data MSB divided by 2 on the ``SIGN BIT OUT`` pin.
:param msb: Set to ``True`` to output DAC Data MSB on the ``SIGN BIT OUT`` pin.
:param comp_out: Set to ``True`` to output the comparator signal on the ``SIGN BIT OUT`` pin.
Only one flag should be set to ``True`` at a time. If no valid mode is selected, the ``SIGN BIT OUT``
pin will default to high impedance mode.
The method updates the control register with the appropriate configuration and writes it to the AD9834.
"""
if high_z:
self.ctrl_reg &= ~AD9834_OPBITEN
elif msb_2:
self.ctrl_reg |= AD9834_OPBITEN
self.ctrl_reg &= ~(AD9834_MODE | AD9834_SIGN_PIB | AD9834_DIV2)
elif msb:
self.ctrl_reg |= AD9834_OPBITEN | AD9834_DIV2
self.ctrl_reg &= ~(AD9834_MODE | AD9834_SIGN_PIB)
elif comp_out:
self.ctrl_reg |= AD9834_OPBITEN | AD9834_SIGN_PIB | AD9834_DIV2
self.ctrl_reg &= ~AD9834_MODE
else:
self.ctrl_reg &= ~AD9834_OPBITEN
self.write(self.ctrl_reg)
@kernel
def enable_triangular_waveform(self):
"""
Enable triangular waveform generation.
This method configures the AD9834 to output a triangular waveform. It does so
by clearing :const:`AD9834_OPBITEN` in the control register and setting :const:`AD9834_MODE`.
Once this method is called, the AD9834 will begin generating a triangular waveform
at the frequency set for the selected frequency register.
This method should be called when a triangular waveform is desired for signal
generation. Ensure that the frequency is set appropriately before invoking this method.
"""
self.ctrl_reg &= ~AD9834_OPBITEN
self.ctrl_reg |= AD9834_MODE
self.write(self.ctrl_reg)
@kernel
def disable_triangular_waveform(self):
"""
Disable triangular waveform generation.
This method disables the triangular waveform output by clearing :const:`AD9834_MODE`.
After invoking this method, the AD9834 will cease generating a triangular waveform.
The device can then be configured to output other waveform types if needed.
This method should be called when switching to a different waveform type or
when the triangular waveform is no longer required.
"""
self.ctrl_reg &= ~AD9834_MODE
self.write(self.ctrl_reg)
@kernel
def set_mu(
self,
freq_word: TInt32 = 0,
phase_word: TInt32 = 0,
freq_reg: TInt32 = 0,
phase_reg: TInt32 = 0,
):
"""
Set DDS frequency and phase in machine units.
This method updates the specified frequency and phase registers with the provided
machine units, selects the corresponding registers, and enables the output.
:param freq_word: Frequency tuning word (28-bit).
:param phase_word: Phase tuning word (12-bit).
:param freq_reg: Frequency register to write to (0 or 1).
:param phase_reg: Phase register to write to (0 or 1).
"""
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
assert 0 <= phase_reg <= 1, "Invalid phase register index"
self.set_frequency_reg_lsb(freq_reg, freq_word & 0x3FFF)
self.set_frequency_reg_msb(freq_reg, (freq_word >> 14) & 0x3FFF)
self.set_phase_reg(phase_reg, phase_word)
self.select_frequency_reg(freq_reg)
self.select_phase_reg(phase_reg)
self.output_enable()
@kernel
def set(
self,
frequency: TFloat = 0.0,
phase: TFloat = 0.0,
freq_reg: TInt32 = 0,
phase_reg: TInt32 = 0,
):
"""
Set DDS frequency in Hz and phase using fractional turns.
This method converts the specified frequency and phase to their corresponding
machine units, updates the selected registers, and enables the output.
:param frequency: Frequency in Hz.
:param phase: Phase in fractional turns (e.g., 0.5 for 180 degrees).
:param freq_reg: Frequency register to write to (0 or 1).
:param phase_reg: Phase register to write to (0 or 1).
"""
assert 0 <= freq_reg <= 1, "Invalid frequency register index"
assert 0 <= phase_reg <= 1, "Invalid phase register index"
freq_word = self.frequency_to_ftw(frequency)
phase_word = self.turns_to_pow(phase)
self.set_mu(freq_word, phase_word, freq_reg, phase_reg)

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,16 +188,14 @@ 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
when changing frequency or phase. The DDS phase is the sum of the
phase accumulator and the phase offset. The only discontinuous
changes in the DDS output phase come from changes to the phase
offset. This mode is also knows as "relative phase mode".
offset. This mode is also known as "relative phase mode".
:math:`\phi(t) = q(t^\prime) + p + (t - t^\prime) f`
* :const:`PHASE_MODE_ABSOLUTE`: the phase accumulator is reset when
@ -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,31 @@ 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.
.. warning::
Deterministic phase control depends on correct alignment of operations
to a 4ns grid (``SYNC_CLK``). This function uses :meth:`~artiq.language.core.now_mu()`
to ensure such alignment automatically. When replayed over DMA, however, the ensuing
event sequence *must* be started at the same offset relative to ``SYNC_CLK``, or
unstable ``SYNC_CLK`` cycle assignment (i.e. inconsistent delays of exactly 4ns) will
result.
: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 +606,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 +625,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 +858,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 +879,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 +895,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 +906,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 +915,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 +947,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 +969,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 +986,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,7 +997,7 @@ 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.
"""
@ -1040,16 +1044,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 +1085,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,9 +22,9 @@ 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.
"""
@ -44,7 +44,11 @@ class AD9912:
self.pll_en = pll_en
self.pll_n = pll_n
if pll_en:
sysclk = self.cpld.refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n
refclk = self.cpld.refclk
if refclk < 11e6:
# use SYSCLK PLL Doubler
refclk = refclk * 2
sysclk = refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n
else:
sysclk = self.cpld.refclk
assert sysclk <= 1e9
@ -97,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)
@ -115,6 +119,10 @@ class AD9912:
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)
@ -125,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)
@ -137,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.
"""
@ -147,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)
@ -157,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.
"""
@ -170,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,
@ -189,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
@ -239,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
@ -251,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.
@ -134,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

@ -112,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.
"""
@ -122,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)
@ -531,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])
@ -565,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)

View File

@ -1,4 +1,4 @@
from artiq.language.core import kernel, portable
from artiq.language.core import kernel, portable, delay
from artiq.language.units import us
from numpy import int32
@ -17,12 +17,12 @@ ALMAZNY_LEGACY_SPIT_WR = 32
class AlmaznyLegacy:
"""
Almazny (High frequency mezzanine board for Mirny)
Almazny (High-frequency mezzanine board for Mirny)
This applies to Almazny hardware v1.1 and earlier.
Use :class:`artiq.coredevice.almazny.AlmaznyChannel` for Almazny v1.2 and later.
Use :class:`~artiq.coredevice.almazny.AlmaznyChannel` for Almazny v1.2 and later.
:param host_mirny: Mirny device Almazny is connected to
:param host_mirny: :class:`~artiq.coredevice.mirny.Mirny` device Almazny is connected to
"""
def __init__(self, dmgr, host_mirny):
@ -121,12 +121,13 @@ class AlmaznyLegacy:
class AlmaznyChannel:
"""
One Almazny channel
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.
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)

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

@ -181,25 +181,25 @@ class AnalyzerProxyReceiver:
async def _receive_cr(self):
try:
while True:
endian_byte = await self.reader.read(1)
if endian_byte == b"E":
endian = '>'
elif endian_byte == b"e":
endian = '<'
elif endian_byte == b"":
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
payload_length_word = await self.reader.readexactly(4)
payload_length = struct.unpack(endian + "I", payload_length_word)[0]
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.
remaining_data = await self.reader.readexactly(payload_length + 11)
data = endian_byte + payload_length_word + remaining_data
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)
@ -728,15 +728,7 @@ def decoded_dump_to_target(manager, devices, dump, uniform_interval):
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")
messages = dump.messages
messages = sorted(messages, key=get_message_time)
messages = sorted(dump.messages, key=get_message_time)
channel_handlers = create_channel_handlers(
manager, devices, ref_period,
@ -752,6 +744,8 @@ def decoded_dump_to_target(manager, devices, dump, uniform_interval):
interval = manager.get_channel("interval", 64, ty=WaveformType.ANALOG)
slack = manager.get_channel("rtio_slack", 64, ty=WaveformType.ANALOG)
stopped_messages = []
manager.set_time(0)
start_time = 0
for m in messages:
@ -762,7 +756,10 @@ def decoded_dump_to_target(manager, devices, dump, uniform_interval):
manager.set_start_time(start_time)
t0 = start_time
for i, message in enumerate(messages):
if message.channel in channel_handlers:
if isinstance(message, StoppedMessage):
stopped_messages.append(message)
logger.debug(f"StoppedMessage at {get_message_time(message)}")
elif message.channel in channel_handlers:
t = get_message_time(message)
if t >= 0:
if uniform_interval:
@ -776,3 +773,9 @@ def decoded_dump_to_target(manager, devices, dump, uniform_interval):
if isinstance(message, OutputMessage):
slack.set_value_double(
(message.timestamp - message.rtio_counter)*ref_period)
if not stopped_messages:
logger.warning("StoppedMessage missing")
else:
end_time = get_message_time(stopped_messages[-1])
manager.set_end_time(end_time)

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
@ -465,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":
@ -479,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)
@ -616,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,
@ -703,8 +705,13 @@ class CommKernel:
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]))
message = nested_exceptions[0][1].format(*nested_exceptions[0][2])
except:
message = nested_exceptions[0][1]
logger.error("Couldn't format exception message", exc_info=True)
try:
python_exn = python_exn_type(message)
except Exception as ex:
python_exn = RuntimeError(
f"Exception type={python_exn_type}, which couldn't be "

View File

@ -1,5 +1,7 @@
from enum import Enum
import binascii
import logging
import io
import struct
from sipyco.keepalive import create_connection
@ -23,6 +25,8 @@ class Request(Enum):
DebugAllocator = 8
Flash = 9
class Reply(Enum):
Success = 1
@ -46,15 +50,17 @@ class LogLevel(Enum):
class CommMgmt:
def __init__(self, host, port=1380):
def __init__(self, host, port=1380, drtio_dest=0):
self.host = host
self.port = port
self.drtio_dest = drtio_dest
def open(self):
if hasattr(self, "socket"):
return
self.socket = create_connection(self.host, self.port)
self.socket.sendall(b"ARTIQ management\n")
self._write_int8(self.drtio_dest)
endian = self._read(1)
if endian == b"e":
self.endian = "<"
@ -194,3 +200,22 @@ class CommMgmt:
def debug_allocator(self):
self._write_header(Request.DebugAllocator)
def flash(self, bin_paths):
self._write_header(Request.Flash)
with io.BytesIO() as image_buf:
for filename in bin_paths:
with open(filename, "rb") as fi:
bin_ = fi.read()
if (len(bin_paths) > 1):
image_buf.write(
struct.pack(self.endian + "I", len(bin_)))
image_buf.write(bin_)
crc = binascii.crc32(image_buf.getvalue())
image_buf.write(struct.pack(self.endian + "I", crc))
self._write_bytes(image_buf.getvalue())
self._read_expect(Reply.RebootImminent)

View File

@ -53,6 +53,9 @@ 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":
@ -73,8 +76,8 @@ 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).
@ -82,6 +85,8 @@ class Core:
(optional).
:param analyze_at_run_end: automatically trigger the core device analyzer
proxy after the Experiment's run stage finishes.
:param report_invariants: report variables which are not changed inside
kernels and are thus candidates for inclusion in kernel_invariants
"""
kernel_invariants = {
@ -92,7 +97,8 @@ class Core:
host, ref_period,
analyzer_proxy=None, analyze_at_run_end=False,
ref_multiplier=8,
target="rv32g", satellite_cpu_targets={}):
target="rv32g", satellite_cpu_targets={},
report_invariants=False):
self.ref_period = ref_period
self.ref_multiplier = ref_multiplier
self.satellite_cpu_targets = satellite_cpu_targets
@ -104,6 +110,7 @@ class Core:
self.comm = CommKernel(host)
self.analyzer_proxy_name = analyzer_proxy
self.analyze_at_run_end = analyze_at_run_end
self.report_invariants = report_invariants
self.first_run = True
self.dmgr = dmgr
@ -116,6 +123,8 @@ class Core:
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,
@ -134,7 +143,8 @@ class Core:
module = Module(stitcher,
ref_period=self.ref_period,
attribute_writeback=attribute_writeback)
attribute_writeback=attribute_writeback,
remarks=self.report_invariants)
target = target if target is not None else self.target_cls()
library = target.compile_and_link([module])
@ -241,8 +251,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.
@ -273,7 +283,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.
"""
@ -281,7 +291,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.
"""
@ -296,7 +306,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()
@ -315,7 +325,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
@ -343,7 +353,7 @@ class Core:
Returns only after the dump has been retrieved from the device.
Raises IOError if no analyzer proxy has been configured, or if the
Raises :exc:`IOError` if no analyzer proxy has been configured, or if the
analyzer proxy fails. In the latter case, more details would be
available in the proxy log.
"""

View File

@ -76,11 +76,11 @@ class CoreDMA:
@kernel
def record(self, name, enable_ddma=False):
"""Returns a context manager that will record a DMA trace called ``name``.
"""Returns a context manager that will record a DMA trace called `name`.
Any previously recorded trace with the same name is overwritten.
The trace will persist across kernel switches.
In DRTIO context, distributed DMA can be toggled with ``enable_ddma``.
In DRTIO context, distributed DMA can be toggled with `enable_ddma`.
Enabling it allows running DMA on satellites, rather than sending all
events from the master.
@ -116,7 +116,7 @@ class CoreDMA:
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."""
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")

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.
"""
@ -176,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,20 +2,40 @@ 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:
"""Information about an exception raised or passed through the core device."""
"""Information about an exception raised or passed through the core device.
If the exception message contains positional format arguments, it
will attempt to substitute them with the provided parameters.
If the substitution fails, the original message will remain unchanged.
"""
def __init__(self, exceptions, exception_info, traceback, stack_pointers):
self.exceptions = exceptions
self.exception_info = exception_info
@ -77,7 +97,10 @@ class CoreException:
exn_id = int(exn_id)
else:
exn_id = 0
try:
lines.append("{}({}): {}".format(name, exn_id, message.format(*params)))
except:
lines.append("{}({}): {}".format(name, exn_id, message))
zipped.append(((exception[3], exception[4], exception[5], exception[6],
None, []), None))
@ -137,7 +160,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
@ -157,13 +180,18 @@ class SubkernelError(Exception):
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.
@ -63,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)
@ -115,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)
@ -124,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).
@ -137,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:
@ -149,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`.
"""
@ -185,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)
@ -193,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)
@ -214,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)
@ -245,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.
"""
@ -293,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

@ -1,51 +0,0 @@
# Definitions for using the "FMC DIO 32ch LVDS a" card with the VHDCI-EEM breakout v1.1
eem_fmc_connections = {
0: [0, 8, 2, 3, 4, 5, 6, 7],
1: [1, 9, 10, 11, 12, 13, 14, 15],
2: [17, 16, 24, 19, 20, 21, 22, 23],
3: [18, 25, 26, 27, 28, 29, 30, 31],
}
def shiftreg_bits(eem, out_pins):
"""
Returns the bits that have to be set in the FMC card direction
shift register for the given EEM.
Takes a set of pin numbers (0-7) at the EEM. Return values
of this function for different EEMs should be ORed together.
"""
r = 0
for i in range(8):
if i not in out_pins:
lvds_line = eem_fmc_connections[eem][i]
# lines are swapped in pairs to ease PCB routing
# at the shift register
shift = lvds_line ^ 1
r |= 1 << shift
return r
dio_bank0_out_pins = set(range(4))
dio_bank1_out_pins = set(range(4, 8))
urukul_out_pins = {
0, # clk
1, # mosi
3, 4, 5, # cs_n
6, # io_update
7, # dds_reset
}
urukul_aux_out_pins = {
4, # sw0
5, # sw1
6, # sw2
7, # sw3
}
zotino_out_pins = {
0, # clk
1, # mosi
3, 4, # cs_n
5, # ldac_n
7, # clr_n
}

View File

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

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
@ -176,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
@ -212,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

@ -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
@ -82,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
)
@ -91,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))
@ -101,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
@ -138,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))
@ -149,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
@ -160,7 +160,7 @@ 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)

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.

View File

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

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

@ -16,13 +16,13 @@ SPI_CS_PGIA = 1 # separate SPI bus, CS used as RCLK
@portable
def adc_mu_to_volt(data, gain=0, corrected_fs=True):
"""Convert ADC data in machine units to Volts.
"""Convert ADC data in machine units to volts.
:param data: 16 bit signed ADC word
:param data: 16-bit signed ADC word
:param gain: PGIA gain setting (0: 1, ..., 3: 1000)
:param corrected_fs: use corrected ADC FS reference.
Should be True for Samplers' revisions after v2.1. False for v2.1 and earlier.
:return: Voltage in Volts
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.48 / (1 << 16) if corrected_fs else 20. / (1 << 16)
@ -40,7 +40,7 @@ def adc_mu_to_volt(data, gain=0, corrected_fs=True):
class Sampler:
"""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
@ -119,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
@ -142,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.
"""

View File

@ -16,8 +16,8 @@ def shuttler_volt_to_mu(volt):
class Config:
"""Shuttler configuration registers interface.
The configuration registers control waveform phase auto-clear, and pre-DAC
gain & offset values for calibration with ADC on the Shuttler AFE card.
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.
@ -84,8 +84,7 @@ class Config:
def set_offset(self, channel, offset):
"""Set the 16-bits pre-DAC offset register of a Shuttler Core channel.
.. seealso::
:meth:`shuttler_volt_to_mu`
See also :meth:`shuttler_volt_to_mu`.
:param channel: Shuttler Core channel to be configured.
:param offset: Shuttler Core channel offset.
@ -114,13 +113,13 @@ class DCBias:
.. math::
w(t) = a(t) + b(t) * cos(c(t))
And `t` corresponds to time in seconds.
and `t` corresponds to time in seconds.
This class controls the cubic spline `a(t)`, in which
.. math::
a(t) = p_0 + p_1t + \\frac{p_2t^2}{2} + \\frac{p_3t^3}{6}
And `a(t)` is in Volt.
and `a(t)` is measured in volts.
:param channel: RTIO channel number of this DC-bias spline interface.
:param core_device: Core device name.
@ -137,7 +136,7 @@ class DCBias:
"""Set the DC-bias spline waveform.
Given `a(t)` as defined in :class:`DCBias`, the coefficients should be
configured by the following formulae.
configured by the following formulae:
.. math::
T &= 8*10^{-9}
@ -154,8 +153,10 @@ class DCBias:
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.
.. 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.
@ -189,7 +190,7 @@ class DDS:
.. math::
w(t) = a(t) + b(t) * cos(c(t))
And `t` corresponds to time in seconds.
and `t` corresponds to time in seconds.
This class controls the cubic spline `b(t)` and quadratic spline `c(t)`,
in which
@ -198,7 +199,7 @@ class DDS:
c(t) &= r_0 + r_1t + \\frac{r_2t^2}{2}
And `b(t)` is in Volt, `c(t)` is in number of turns. Note that `b(t)`
`b(t)` is in volts, `c(t)` is in number of turns. Note that `b(t)`
contributes to a constant gain of :math:`g=1.64676`.
:param channel: RTIO channel number of this DC-bias spline interface.
@ -244,13 +245,13 @@ class DDS:
Note: The waveform is not updated to the Shuttler Core until
triggered. See :class:`Trigger` for the update triggering mechanism.
:param b0: The :math:`b_0` coefficient in machine unit.
:param b1: The :math:`b_1` coefficient in machine unit.
:param b2: The :math:`b_2` coefficient in machine unit.
:param b3: The :math:`b_3` coefficient in machine unit.
:param c0: The :math:`c_0` coefficient in machine unit.
:param c1: The :math:`c_1` coefficient in machine unit.
:param c2: The :math:`c_2` coefficient in machine unit.
: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,
@ -292,8 +293,8 @@ class Trigger:
"""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
``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
@ -336,8 +337,8 @@ _AD4115_REG_SETUPCON0 = 0x20
class Relay:
"""Shuttler AFE relay switches.
It controls the AFE relay switches and the LEDs. Switch on the relay to
enable AFE output; And off to disable the output. The LEDs indicates the
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::
@ -357,7 +358,7 @@ class Relay:
def init(self):
"""Initialize SPI device.
Configures the SPI bus to 16-bits, write-only, simultaneous relay
Configures the SPI bus to 16 bits, write-only, simultaneous relay
switches and LED control.
"""
self.bus.set_config_mu(
@ -365,10 +366,10 @@ class Relay:
@kernel
def enable(self, en: TInt32):
"""Enable/Disable relay switches of corresponding channels.
"""Enable/disable relay switches of corresponding channels.
Each bit corresponds to the relay switch of a channel. Asserting a bit
turns on the corresponding relay switch; Deasserting the same bit
turns on the corresponding relay switch; deasserting the same bit
turns off the switch instead.
:param en: Switch enable bits. The MSB corresponds to Channel 15, LSB
@ -403,12 +404,12 @@ class ADC:
def reset(self):
"""AD4115 reset procedure.
This performs a write operation of 96 serial clock cycles with DIN
held at high. It resets the entire device, including the register
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
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)
@ -420,7 +421,7 @@ class ADC:
@kernel
def read8(self, addr: TInt32) -> TInt32:
"""Read from 8 bit register.
"""Read from 8-bit register.
:param addr: Register address.
:return: Read-back register content.
@ -433,7 +434,7 @@ class ADC:
@kernel
def read16(self, addr: TInt32) -> TInt32:
"""Read from 16 bit register.
"""Read from 16-bit register.
:param addr: Register address.
:return: Read-back register content.
@ -446,7 +447,7 @@ class ADC:
@kernel
def read24(self, addr: TInt32) -> TInt32:
"""Read from 24 bit register.
"""Read from 24-bit register.
:param addr: Register address.
:return: Read-back register content.
@ -459,7 +460,7 @@ class ADC:
@kernel
def write8(self, addr: TInt32, data: TInt32):
"""Write to 8 bit register.
"""Write to 8-bit register.
:param addr: Register address.
:param data: Data to be written.
@ -470,7 +471,7 @@ class ADC:
@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.
@ -481,7 +482,7 @@ class ADC:
@kernel
def write24(self, addr: TInt32, data: TInt32):
"""Write to 24 bit register.
"""Write to 24-bit register.
:param addr: Register address.
:param data: Data to be written.
@ -494,11 +495,11 @@ class ADC:
def read_ch(self, channel: TInt32) -> TFloat:
"""Sample a Shuttler channel on the AFE.
It performs a single conversion using profile 0 and setup 0, on the
selected channel. The sample is then recovered and converted to volt.
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 volt.
:return: Voltage sample in volts.
"""
# Always configure Profile 0 for single conversion
self.write16(_AD4115_REG_CH0, 0x8000 | ((channel * 2 + 1) << 4))
@ -519,7 +520,7 @@ class ADC:
@kernel
def standby(self):
"""Place the ADC in standby mode and disables power down the clock.
"""Place the ADC in standby mode and disable power down the clock.
The ADC can be returned to single conversion mode by calling
:meth:`single_conversion`.
@ -536,13 +537,7 @@ class ADC:
.. 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.
.. seealso::
:meth:`standby`
:meth:`power_up`
power-down mode. See also :meth:`standby` and :meth:`power_up`.
"""
self.write16(_AD4115_REG_ADCMODE, 0x8030)
@ -552,8 +547,7 @@ class ADC:
The ADC should be in power-down mode before calling this method.
.. seealso::
:meth:`power_down`
See also :meth:`power_down`.
"""
self.reset()
# Although the datasheet claims 500 us reset wait time, only waiting
@ -564,22 +558,18 @@ class ADC:
def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]):
"""Calibrate the Shuttler waveform generator using the ADC on the AFE.
It finds the average slope rate and average offset by samples, and
compensate by writing the pre-DAC gain and offset registers in the
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 < 1, the calibration procedure
will introduce a pre-DAC gain compensation. However, this may
saturate the pre-DAC voltage code. (See :class:`Config` notes).
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`.
.. seealso::
:meth:`Config.set_gain`
:meth:`Config.set_offset`
:param volts: A list of all 16 cubic DC-bias spline.
: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.

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.
@ -138,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)
@ -152,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)
@ -188,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
@ -208,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

View File

@ -24,7 +24,7 @@ def y_mu_to_full_scale(y):
@portable
def adc_mu_to_volts(x, gain, corrected_fs=True):
"""Convert servo ADC data from machine units to Volt."""
"""Convert servo ADC data from machine units to volts."""
val = (x >> 1) & 0xffff
mask = 1 << 15
val = -(val & mask) + (val & ~mask)
@ -155,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.
@ -198,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
@ -288,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)
@ -327,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)
@ -375,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 +489,7 @@ class Channel:
def get_y_mu(self, profile):
"""Get a profile's IIR state (filter output, Y0) in machine units.
The IIR state is also know as the "integrator", or the DDS amplitude
The IIR state is also known as the "integrator", or the DDS amplitude
scale factor. It is 17 bits wide and unsigned.
This method does not advance the timeline but consumes all slack.
@ -499,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)
@ -507,7 +507,7 @@ class Channel:
def get_y(self, profile):
"""Get a profile's IIR state (filter output, Y0).
The IIR state is also know as the "integrator", or the DDS amplitude
The IIR state is also known as the "integrator", or the DDS amplitude
scale factor. It is 17 bits wide and unsigned.
This method does not advance the timeline but consumes all slack.
@ -525,7 +525,7 @@ class Channel:
def set_y_mu(self, profile, y):
"""Set a profile's IIR state (filter output, Y0) in machine units.
The IIR state is also know as the "integrator", or the DDS amplitude
The IIR state is also known as the "integrator", or the DDS amplitude
scale factor. It is 17 bits wide and unsigned.
This method must not be used when the servo could be writing to the
@ -535,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)
@ -545,7 +545,7 @@ class Channel:
def set_y(self, profile, y):
"""Set a profile's IIR state (filter output, Y0).
The IIR state is also know as the "integrator", or the DDS amplitude
The IIR state is also known as the "integrator", or the DDS amplitude
scale factor. It is 17 bits wide and unsigned.
This method must not be used when the servo could be writing to the

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"}
@ -109,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"}
@ -145,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
@ -158,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
@ -326,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)
@ -350,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))
"""
@ -441,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

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,11 +2,11 @@ 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 scale_from_metadata, short_format, exc_to_warning
from artiq.gui.tools import LayoutWidget, QRecursiveFilterProxyModel
from artiq.gui.tools import LayoutWidget
from artiq.gui.models import DictSyncTreeSepModel
@ -63,11 +63,11 @@ class CreateEditDialog(QtWidgets.QDialog):
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)
self.cancel, QtWidgets.QDialogButtonBox.ButtonRole.RejectRole)
grid.setRowStretch(6, 1)
grid.addWidget(self.buttons, 7, 0, 1, 3, alignment=QtCore.Qt.AlignHCenter)
grid.addWidget(self.buttons, 7, 0, 1, 3, alignment=QtCore.Qt.AlignmentFlag.AlignHCenter)
self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject)
@ -125,7 +125,7 @@ class CreateEditDialog(QtWidgets.QDialog):
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:
@ -181,8 +181,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
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()
@ -194,27 +194,27 @@ 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())
@ -227,7 +227,8 @@ 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)

View File

@ -4,7 +4,7 @@ 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
@ -28,6 +28,7 @@ class _ArgumentEditor(EntryTreeWidget):
def __init__(self, manager, dock, expurl):
self.manager = manager
self.expurl = expurl
self.dock = dock
EntryTreeWidget.__init__(self)
@ -44,12 +45,12 @@ class _ArgumentEditor(EntryTreeWidget):
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()
@ -78,6 +79,18 @@ class _ArgumentEditor(EntryTreeWidget):
argument["desc"] = procdesc
argument["state"] = state
self.update_argument(name, argument)
self.dock.apply_colors()
def apply_color(self, palette, color):
self.setPalette(palette)
for child in self.findChildren(QtWidgets.QWidget):
child.setPalette(palette)
child.setAutoFillBackground(True)
items = self.findItems("*",
QtCore.Qt.MatchFlag.MatchWildcard | QtCore.Qt.MatchFlag.MatchRecursive)
for item in items:
for column in range(item.columnCount()):
item.setBackground(column, QtGui.QColor(color))
# Hooks that allow user-supplied argument editors to react to imminent user
# actions. Here, we always keep the manager-stored submission arguments
@ -92,6 +105,19 @@ class _ArgumentEditor(EntryTreeWidget):
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
class _ColoredTitleBar(QtWidgets.QProxyStyle):
def __init__(self, color, style=None):
super().__init__(style)
self.color = color
def drawComplexControl(self, control, option, painter, widget=None):
if control == QtWidgets.QStyle.ComplexControl.CC_TitleBar:
option = QtWidgets.QStyleOptionTitleBar(option)
option.palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColor(self.color))
option.palette.setColor(QtGui.QPalette.ColorRole.Highlight, QtGui.QColor(self.color))
self.baseStyle().drawComplexControl(control, option, painter, widget)
class _ExperimentDock(QtWidgets.QMdiSubWindow):
sigClosed = QtCore.pyqtSignal()
@ -101,7 +127,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.layout = QtWidgets.QGridLayout()
top_widget = QtWidgets.QWidget()
@ -187,6 +213,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
devarg_override.lineEdit().setPlaceholderText("Override device arguments")
devarg_override.lineEdit().setClearButtonEnabled(True)
devarg_override.insertItem(0, "core:analyze_at_run_end=True")
devarg_override.insertItem(1, "core:report_invariants=True")
self.layout.addWidget(devarg_override, 2, 3)
devarg_override.setCurrentText(options["devarg_override"])
@ -237,21 +264,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)
@ -302,14 +329,61 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
self.argeditor = editor_class(self.manager, self, self.expurl)
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
self.argeditor.restore_state(argeditor_state)
self.apply_colors()
def contextMenuEvent(self, event):
menu = QtWidgets.QMenu(self)
select_title_bar_color = menu.addAction("Select title bar color")
select_window_color = menu.addAction("Select window color")
reset_colors = menu.addAction("Reset to default colors")
menu.addSeparator()
reset_sched = menu.addAction("Reset scheduler settings")
action = menu.exec_(self.mapToGlobal(event.pos()))
if action == reset_sched:
action = menu.exec(self.mapToGlobal(event.pos()))
if action == select_title_bar_color:
self.select_color("title_bar")
elif action == select_window_color:
self.select_color("window")
elif action == reset_colors:
self.reset_colors()
elif action == reset_sched:
asyncio.ensure_future(self._recompute_sched_options_task())
def select_color(self, key):
color = QtWidgets.QColorDialog.getColor(
title=f"Select {key.replace('_', ' ').title()} color")
if color.isValid():
self.manager.set_color(self.expurl, key, color.name())
self.apply_colors()
def apply_colors(self):
colors = self.manager.get_colors(self.expurl)
if colors is None:
palette = QtWidgets.QApplication.palette()
colors = {
"window": palette.color(QtGui.QPalette.ColorRole.Window).name(),
"title_bar": palette.color(QtGui.QPalette.ColorRole.Highlight).name(),
}
self.manager.colors[self.expurl] = colors
colors["window_text"] = "#000000" if QtGui.QColor(
colors["window"]).lightness() > 128 else "#FFFFFF"
self.modify_palette(colors)
self.setStyle(_ColoredTitleBar(colors["title_bar"]))
self.argeditor.apply_color(self.palette(), (colors["window"]))
def modify_palette(self, colors):
palette = self.palette()
palette.setColor(QtGui.QPalette.ColorRole.Window, QtGui.QColor(colors["window"]))
palette.setColor(QtGui.QPalette.ColorRole.Base, QtGui.QColor(colors["window"]))
palette.setColor(QtGui.QPalette.ColorRole.Button, QtGui.QColor(colors["window"]))
palette.setColor(QtGui.QPalette.ColorRole.Text, QtGui.QColor(colors["window_text"]))
palette.setColor(QtGui.QPalette.ColorRole.ButtonText, QtGui.QColor(colors["window_text"]))
palette.setColor(QtGui.QPalette.ColorRole.WindowText, QtGui.QColor(colors["window_text"]))
self.setPalette(palette)
def reset_colors(self):
self.manager.reset_colors(self.expurl)
self.apply_colors()
async def _recompute_sched_options_task(self):
try:
expdesc, _ = await self.manager.compute_expdesc(self.expurl)
@ -423,7 +497,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:
@ -456,6 +530,7 @@ class ExperimentManager:
self.submission_options = dict()
self.submission_arguments = dict()
self.argument_ui_names = dict()
self.colors = dict()
self.datasets = dict()
dataset_sub.add_setmodel_callback(self.set_dataset_model)
@ -467,10 +542,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):
@ -482,6 +557,18 @@ class ExperimentManager:
def set_schedule_model(self, model):
self.schedule = model.backing_store
def set_color(self, expurl, key, value):
if expurl not in self.colors:
self.colors[expurl] = {}
self.colors[expurl][key] = value
def get_colors(self, expurl):
return self.colors.get(expurl)
def reset_colors(self, expurl):
if expurl in self.colors:
del self.colors[expurl]
def resolve_expurl(self, expurl):
if expurl[:5] == "repo:":
expinfo = self.explist[expurl[5:]]
@ -589,8 +676,9 @@ 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.apply_colors()
dock.show()
dock.sigClosed.connect(partial(self.on_dock_closed, expurl))
if expurl in self.dock_states:
@ -707,7 +795,8 @@ class ExperimentManager:
"arguments": self.submission_arguments,
"docks": self.dock_states,
"argument_uis": self.argument_ui_names,
"open_docks": set(self.open_experiments.keys())
"open_docks": set(self.open_experiments.keys()),
"colors": self.colors
}
def restore_state(self, state):
@ -718,6 +807,7 @@ class ExperimentManager:
self.submission_options = state["options"]
self.submission_arguments = state["arguments"]
self.argument_ui_names = state.get("argument_uis", {})
self.colors = state.get("colors", {})
for expurl in state["open_docks"]:
self.open_experiment(expurl)

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()
@ -163,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)
@ -175,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()
@ -187,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(
@ -202,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(
@ -211,41 +212,41 @@ 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():
@ -253,15 +254,14 @@ class ExplorerDock(QtWidgets.QDockWidget):
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,

View File

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

View File

@ -4,13 +4,14 @@ import textwrap
from collections import namedtuple
from functools import partial
from PyQt5 import QtCore, QtWidgets
from PyQt6 import QtCore, QtWidgets, QtGui
from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe
from artiq.coredevice.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__)
@ -22,39 +23,45 @@ class _CancellableLineEdit(QtWidgets.QLineEdit):
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.title = title
self.setFrameShape(QtWidgets.QFrame.Box)
self.setFrameShadow(QtWidgets.QFrame.Raised)
grid = QtWidgets.QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(0)
self.setLayout(grid)
label = QtWidgets.QLabel(title)
label.setAlignment(QtCore.Qt.AlignCenter)
label.setSizePolicy(QtWidgets.QSizePolicy.Ignored,
QtWidgets.QSizePolicy.Preferred)
grid.addWidget(label, 1, 1)
self.stack = QtWidgets.QStackedWidget()
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()
@ -74,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)
@ -186,7 +193,7 @@ class _DDSModel:
return ftw / self.ftw_per_hz
class _DDSWidget(QtWidgets.QFrame):
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
@ -197,19 +204,7 @@ class _DDSWidget(QtWidgets.QFrame):
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()
@ -221,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)
@ -237,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()
@ -277,11 +272,11 @@ 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)
@ -332,39 +327,31 @@ class _DDSWidget(QtWidgets.QFrame):
return "dds/{}".format(self.title)
class _DACWidget(QtWidgets.QFrame):
def __init__(self, dm, spi_channel, channel, title):
QtWidgets.QFrame.__init__(self)
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
self.cur_value = 0x8000
self.title = title
self.setFrameShape(QtWidgets.QFrame.Box)
self.setFrameShadow(QtWidgets.QFrame.Raised)
grid = QtWidgets.QGridLayout()
grid.setContentsMargins(0, 0, 0, 0)
grid.setHorizontalSpacing(0)
grid.setVerticalSpacing(0)
self.setLayout(grid)
label = QtWidgets.QLabel("{} ch{}".format(title, channel))
label.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(label, 1, 1)
self.vref = vref
self.offset_dacs = offset_dacs
self.value = QtWidgets.QLabel()
self.value.setAlignment(QtCore.Qt.AlignCenter)
grid.addWidget(self.value, 2, 1, 6, 1)
self.value.setAlignment(QtCore.Qt.AlignmentFlag.AlignHCenter | QtCore.Qt.AlignmentFlag.AlignTop)
self.grid.addWidget(self.value, 2, 1, 6, 1)
grid.setRowStretch(1, 1)
grid.setRowStretch(2, 0)
grid.setRowStretch(3, 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 (2, self.spi_channel, self.channel)
@ -373,7 +360,7 @@ class _DACWidget(QtWidgets.QFrame):
return (self.title, self.channel)
def to_model_path(self):
return "dac/{} ch{}".format(self.title, self.channel)
return "dac/{}_ch{}".format(self.title, self.channel)
_WidgetDesc = namedtuple("_WidgetDesc", "uid comment cls arguments")
@ -391,8 +378,6 @@ 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))
@ -414,7 +399,7 @@ def setup_from_ddb(ddb):
bus_channel = ddb[spi_dev]["arguments"]["channel"]
pll = v["arguments"]["pll_n"]
refclk = ddb[dds_cpld]["arguments"]["refclk"]
clk_div = v["arguments"].get("clk_div", 0)
clk_div = ddb[dds_cpld]["arguments"].get("clk_div", 0)
widget = _WidgetDesc(k, comment, _DDSWidget,
(k, bus_channel, channel, v["class"],
refclk, dds_cpld, pll, clk_div))
@ -426,9 +411,17 @@ def setup_from_ddb(ddb):
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))
(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"]
@ -768,7 +761,7 @@ class Model(DictSyncTreeSepModel):
class _AddChannelDialog(QtWidgets.QDialog):
def __init__(self, parent, model):
QtWidgets.QDialog.__init__(self, parent=parent)
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
self.setWindowTitle("Add channels")
layout = QtWidgets.QVBoxLayout()
@ -778,14 +771,15 @@ class _AddChannelDialog(QtWidgets.QDialog):
self._tree_view = QtWidgets.QTreeView()
self._tree_view.setHeaderHidden(True)
self._tree_view.setSelectionBehavior(
QtWidgets.QAbstractItemView.SelectItems)
QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems)
self._tree_view.setSelectionMode(
QtWidgets.QAbstractItemView.ExtendedSelection)
QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
self._tree_view.setModel(self._model)
layout.addWidget(self._tree_view)
self._button_box = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
QtWidgets.QDialogButtonBox.StandardButton.Ok | \
QtWidgets.QDialogButtonBox.StandardButton.Cancel
)
self._button_box.setCenterButtons(True)
self._button_box.accepted.connect(self.add_channels)
@ -807,8 +801,8 @@ 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
@ -817,7 +811,7 @@ class _MonInjDock(QDockWidgetCloseDetect):
newdock = QtWidgets.QToolButton()
newdock.setToolTip("Create new moninj dock")
newdock.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogNewFolder))
QtWidgets.QStyle.StandardPixmap.SP_FileDialogNewFolder))
newdock.clicked.connect(lambda: self.manager.create_new_dock())
grid.addWidget(newdock, 0, 0)
@ -828,7 +822,7 @@ class _MonInjDock(QDockWidgetCloseDetect):
dialog_btn.setToolTip("Add channels")
dialog_btn.setIcon(
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogListView))
QtWidgets.QStyle.StandardPixmap.SP_FileDialogListView))
dialog_btn.clicked.connect(self.channel_dialog.open)
grid.addWidget(dialog_btn, 0, 1)
@ -841,7 +835,8 @@ class _MonInjDock(QDockWidgetCloseDetect):
self.flow = DragDropFlowLayoutWidget()
scroll_area.setWidgetResizable(True)
scroll_area.setWidget(self.flow)
self.flow.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.flow.setContextMenuPolicy(
QtCore.Qt.ContextMenuPolicy.CustomContextMenu)
self.flow.customContextMenuRequested.connect(self.custom_context_menu)
def custom_context_menu(self, pos):
@ -849,10 +844,10 @@ class _MonInjDock(QDockWidgetCloseDetect):
if index == -1:
return
menu = QtWidgets.QMenu()
delete_action = QtWidgets.QAction("Delete widget", menu)
delete_action = QtGui.QAction("Delete widget", menu)
delete_action.triggered.connect(partial(self.delete_widget, index))
menu.addAction(delete_action)
menu.exec_(self.flow.mapToGlobal(pos))
menu.exec(self.flow.mapToGlobal(pos))
def delete_all_widgets(self):
for index in reversed(range(self.flow.count())):
@ -861,9 +856,10 @@ class _MonInjDock(QDockWidgetCloseDetect):
def delete_widget(self, index, checked):
widget = self.flow.itemAt(index).widget()
widget.hide()
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
@ -871,9 +867,9 @@ class _MonInjDock(QDockWidgetCloseDetect):
def layout_widgets(self, widgets):
for widget in sorted(widgets, key=lambda w: w.sort_key()):
widget.show()
self.manager.dm.setup_monitoring(True, widget)
self.flow.addWidget(widget)
widget.show()
def restore_widgets(self):
if self.widget_uids is not None:
@ -937,7 +933,7 @@ class MonInj:
dock = _MonInjDock(name, self)
self.docks[name] = dock
if add_to_area:
self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
self.main_window.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock)
dock.setFloating(True)
dock.sigClosed.connect(partial(self.on_dock_closed, name))
self.update_closable()
@ -948,13 +944,13 @@ class MonInj:
del self.docks[name]
self.update_closable()
dock.delete_all_widgets()
dock.hide() # dock may be parent, only delete on exit
dock.deleteLater()
def update_closable(self):
flags = (QtWidgets.QDockWidget.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFloatable)
flags = (QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetMovable |
QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetFloatable)
if len(self.docks) > 1:
flags |= QtWidgets.QDockWidget.DockWidgetClosable
flags |= QtWidgets.QDockWidget.DockWidgetFeature.DockWidgetClosable
for dock in self.docks.values():
dock.setFeatures(flags)
@ -974,7 +970,8 @@ class MonInj:
dock = _MonInjDock(name, self)
self.docks[name] = dock
dock.restore_state(dock_state)
self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock)
self.main_window.addDockWidget(
QtCore.Qt.DockWidgetArea.RightDockWidgetArea, dock)
dock.sigClosed.connect(partial(self.on_dock_closed, name))
self.update_closable()

View File

@ -3,9 +3,10 @@ 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.gui.tools import SelectableColumnTableView
from artiq.tools import elide
@ -61,31 +62,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 = SelectableColumnTableView()
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)
@ -104,6 +105,9 @@ class ScheduleDock(QtWidgets.QDockWidget):
h.resizeSection(6, 20 * cw)
h.resizeSection(7, 20 * cw)
# Allow user to reorder or disable columns.
h.setSectionsMovable(True)
def set_model(self, model):
self.table_model = model
self.table.setModel(self.table_model)
@ -154,4 +158,9 @@ class ScheduleDock(QtWidgets.QDockWidget):
return bytes(self.table.horizontalHeader().saveState())
def restore_state(self, state):
self.table.horizontalHeader().restoreState(QtCore.QByteArray(state))
h = self.table.horizontalHeader()
h.restoreState(QtCore.QByteArray(state))
# The state includes the sectionsMovable property, so set it again to be able to
# deal with pre-existing save files from when we used not to enable it.
h.setSectionsMovable(True)

View File

@ -1,7 +1,7 @@
import logging
from functools import partial
from PyQt5 import QtCore, QtWidgets
from PyQt6 import QtCore, QtGui, QtWidgets
logger = logging.getLogger(__name__)
@ -11,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()
@ -36,25 +36,25 @@ class ShortcutsDock(QtWidgets.QDockWidget):
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))
@ -68,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):

View File

@ -5,7 +5,7 @@ import bisect
import itertools
import math
from PyQt5 import QtCore, QtWidgets, QtGui
from PyQt6 import QtCore, QtWidgets, QtGui
import pyqtgraph as pg
import numpy as np
@ -130,7 +130,7 @@ class _BaseWaveform(pg.PlotWidget):
self.setMinimumHeight(WAVEFORM_MIN_HEIGHT)
self.setMaximumHeight(WAVEFORM_MAX_HEIGHT)
self.setMenuEnabled(False)
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
self.name = name
self.width = width
@ -200,24 +200,27 @@ class _BaseWaveform(pg.PlotWidget):
self.cursor_y = self.y_data[ind]
def mouseMoveEvent(self, e):
if e.buttons() == QtCore.Qt.LeftButton \
and e.modifiers() == QtCore.Qt.ShiftModifier:
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.SP_FileIcon)
QtWidgets.QStyle.StandardPixmap.SP_FileIcon)
drag.setPixmap(pixmapi.pixmap(32))
drag.exec_(QtCore.Qt.MoveAction)
drag.exec(QtCore.Qt.DropAction.MoveAction)
else:
super().mouseMoveEvent(e)
def wheelEvent(self, e):
if e.modifiers() & QtCore.Qt.ControlModifier:
if e.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
super().wheelEvent(e)
else:
e.ignore()
def mouseDoubleClickEvent(self, e):
pos = self.view_box.mapSceneToView(e.pos())
pos = self.view_box.mapSceneToView(e.position())
self.cursorMove.emit(pos.x())
@ -393,6 +396,13 @@ class LogWaveform(_BaseWaveform):
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)
@ -408,7 +418,7 @@ class _WaveformView(QtWidgets.QWidget):
layout.setSpacing(0)
self.setLayout(layout)
self._ref_axis = pg.PlotWidget()
self._ref_axis = _RefAxis()
self._ref_axis.hideAxis("bottom")
self._ref_axis.hideAxis("left")
self._ref_axis.hideButtons()
@ -428,8 +438,9 @@ class _WaveformView(QtWidgets.QWidget):
scroll_area = VDragScrollArea(self)
scroll_area.setWidgetResizable(True)
scroll_area.setContentsMargins(0, 0, 0, 0)
scroll_area.setFrameShape(QtWidgets.QFrame.NoFrame)
scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
scroll_area.setFrameShape(QtWidgets.QFrame.Shape.NoFrame)
scroll_area.setVerticalScrollBarPolicy(
QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
layout.addWidget(scroll_area)
self._splitter = VDragDropSplitter(parent=scroll_area)
@ -444,10 +455,11 @@ class _WaveformView(QtWidgets.QWidget):
)
self.confirm_delete_dialog.setText("Delete all waveforms?")
self.confirm_delete_dialog.setStandardButtons(
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel
QtWidgets.QMessageBox.StandardButton.Ok |
QtWidgets.QMessageBox.StandardButton.Cancel
)
self.confirm_delete_dialog.setDefaultButton(
QtWidgets.QMessageBox.Ok
QtWidgets.QMessageBox.StandardButton.Ok
)
def setModel(self, model):
@ -519,10 +531,10 @@ class _WaveformView(QtWidgets.QWidget):
w.setStoppedX(self._stopped_x)
w.cursorMove.connect(self.cursorMove)
w.onCursorMove(self._cursor_x)
action = QtWidgets.QAction("Delete waveform", w)
action = QtGui.QAction("Delete waveform", w)
action.triggered.connect(lambda: self._delete_waveform(w))
w.addAction(action)
action = QtWidgets.QAction("Delete all waveforms", w)
action = QtGui.QAction("Delete all waveforms", w)
action.triggered.connect(self.confirm_delete_dialog.open)
w.addAction(action)
return w
@ -548,7 +560,7 @@ class _WaveformModel(QtCore.QAbstractTableModel):
def columnCount(self, parent=QtCore.QModelIndex()):
return len(self.headers)
def data(self, index, role=QtCore.Qt.DisplayRole):
def data(self, index, role=QtCore.Qt.ItemDataRole.DisplayRole):
if index.isValid():
return self.backing_struct[index.row()][index.column()]
return None
@ -656,7 +668,7 @@ class Model(DictSyncTreeSepModel):
class _AddChannelDialog(QtWidgets.QDialog):
def __init__(self, parent, model):
QtWidgets.QDialog.__init__(self, parent=parent)
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
self.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
self.setWindowTitle("Add channels")
layout = QtWidgets.QVBoxLayout()
@ -666,14 +678,14 @@ class _AddChannelDialog(QtWidgets.QDialog):
self._tree_view = QtWidgets.QTreeView()
self._tree_view.setHeaderHidden(True)
self._tree_view.setSelectionBehavior(
QtWidgets.QAbstractItemView.SelectItems)
QtWidgets.QAbstractItemView.SelectionBehavior.SelectItems)
self._tree_view.setSelectionMode(
QtWidgets.QAbstractItemView.ExtendedSelection)
QtWidgets.QAbstractItemView.SelectionMode.ExtendedSelection)
self._tree_view.setModel(self._model)
layout.addWidget(self._tree_view)
self._button_box = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
QtWidgets.QDialogButtonBox.StandardButton.Ok | QtWidgets.QDialogButtonBox.StandardButton.Cancel
)
self._button_box.setCenterButtons(True)
self._button_box.accepted.connect(self.add_channels)
@ -696,7 +708,7 @@ class WaveformDock(QtWidgets.QDockWidget):
QtWidgets.QDockWidget.__init__(self, "Waveform")
self.setObjectName("Waveform")
self.setFeatures(
QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable)
self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable)
self._channel_model = Model({})
self._waveform_model = _WaveformModel()
@ -724,14 +736,14 @@ class WaveformDock(QtWidgets.QDockWidget):
self._menu_btn = QtWidgets.QPushButton()
self._menu_btn.setIcon(
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogStart))
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.SP_BrowserReload))
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)
@ -743,7 +755,7 @@ class WaveformDock(QtWidgets.QDockWidget):
self._add_btn.setToolTip("Add channels...")
self._add_btn.setIcon(
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_FileDialogListView))
QtWidgets.QStyle.StandardPixmap.SP_FileDialogListView))
self._add_btn.clicked.connect(self._add_channel_dialog.open)
grid.addWidget(self._add_btn, 0, 2)
@ -763,7 +775,7 @@ class WaveformDock(QtWidgets.QDockWidget):
self._reset_zoom_btn.setToolTip("Reset zoom")
self._reset_zoom_btn.setIcon(
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_TitleBarMaxButton))
QtWidgets.QStyle.StandardPixmap.SP_TitleBarMaxButton))
self._reset_zoom_btn.clicked.connect(self._waveform_view.resetZoom)
grid.addWidget(self._reset_zoom_btn, 0, 3)
@ -773,7 +785,7 @@ class WaveformDock(QtWidgets.QDockWidget):
grid.addWidget(self._cursor_control, 0, 4, colspan=6)
def _add_async_action(self, label, coro):
action = QtWidgets.QAction(label, self)
action = QtGui.QAction(label, self)
action.triggered.connect(
lambda: asyncio.ensure_future(exc_to_warning(coro())))
self._file_menu.addAction(action)

View File

@ -36,7 +36,7 @@ class SUServo(EnvExperiment):
self.suservo0.set_pgia_mu(i, 0)
delay(10*us)
# DDS attenuator
self.suservo0.cpld0.set_att(0, 10.)
self.suservo0.cplds[0].set_att(0, 10.)
delay(1*us)
# Servo is done and disabled
assert self.suservo0.get_status() & 0xff == 2

View File

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

View File

@ -513,6 +513,7 @@ dependencies = [
"board_misoc",
"build_misoc",
"byteorder",
"crc",
"cslice",
"dyld",
"eh",
@ -553,10 +554,13 @@ dependencies = [
"board_artiq",
"board_misoc",
"build_misoc",
"byteorder",
"crc",
"cslice",
"eh",
"io",
"log",
"logger_artiq",
"proto_artiq",
"riscv",
]

View File

@ -500,7 +500,7 @@ pub extern fn main() -> i32 {
println!(r"|_| |_|_|____/ \___/ \____|");
println!("");
println!("MiSoC Bootloader");
println!("Copyright (c) 2017-2024 M-Labs Limited");
println!("Copyright (c) 2017-2025 M-Labs Limited");
println!("");
#[cfg(has_ethmac)]

View File

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

View File

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

View File

@ -37,6 +37,14 @@ fn recv<R, F: FnOnce(&Message) -> R>(f: F) -> R {
result
}
fn try_recv<F: FnOnce(&Message)>(f: F) {
let msg_ptr = mailbox::receive();
if msg_ptr != 0 {
f(unsafe { &*(msg_ptr as *const Message) });
mailbox::acknowledge();
}
}
macro_rules! recv {
($p:pat => $e:expr) => {
recv(move |request| {
@ -473,7 +481,15 @@ extern "C-unwind" fn dma_playback(timestamp: i64, ptr: i32, _uses_ddma: bool) {
extern "C-unwind" fn subkernel_load_run(id: u32, destination: u8, run: bool) {
send(&SubkernelLoadRunRequest { id: id, destination: destination, run: run });
let timestamp = unsafe {
((csr::rtio::now_hi_read() as u64) << 32) | (csr::rtio::now_lo_read() as u64)
};
send(&SubkernelLoadRunRequest {
id: id,
destination: destination,
run: run,
timestamp: timestamp,
});
recv!(&SubkernelLoadRunReply { succeeded } => {
if !succeeded {
raise!("SubkernelError",
@ -484,9 +500,10 @@ extern "C-unwind" fn subkernel_load_run(id: u32, destination: u8, run: bool) {
extern "C-unwind" fn subkernel_await_finish(id: u32, timeout: i64) {
send(&SubkernelAwaitFinishRequest { id: id, timeout: timeout });
recv!(SubkernelAwaitFinishReply { status } => {
recv(move |request| {
if let SubkernelAwaitFinishReply = request { }
else if let SubkernelError(status) = request {
match status {
SubkernelStatus::NoError => (),
SubkernelStatus::IncorrectState => raise!("SubkernelError",
"Subkernel not running"),
SubkernelStatus::Timeout => raise!("SubkernelError",
@ -494,7 +511,12 @@ extern "C-unwind" fn subkernel_await_finish(id: u32, timeout: i64) {
SubkernelStatus::CommLost => raise!("SubkernelError",
"Lost communication with satellite"),
SubkernelStatus::OtherError => raise!("SubkernelError",
"An error occurred during subkernel operation")
"An error occurred during subkernel operation"),
SubkernelStatus::Exception(e) => unsafe { crate::eh_artiq::raise(e) },
}
} else {
send(&Log(format_args!("unexpected reply: {:?}\n", request)));
loop {}
}
})
}
@ -512,15 +534,15 @@ extern fn subkernel_send_message(id: u32, is_return: bool, destination: u8,
extern "C-unwind" fn subkernel_await_message(id: i32, timeout: i64, tags: &CSlice<u8>, min: u8, max: u8) -> u8 {
send(&SubkernelMsgRecvRequest { id: id, timeout: timeout, tags: tags.as_ref() });
recv!(SubkernelMsgRecvReply { status, count } => {
match status {
SubkernelStatus::NoError => {
recv(move |request| {
if let SubkernelMsgRecvReply { count } = request {
if count < &min || count > &max {
raise!("SubkernelError",
"Received less or more arguments than expected");
}
*count
}
} else if let SubkernelError(status) = request {
match status {
SubkernelStatus::IncorrectState => raise!("SubkernelError",
"Subkernel not running"),
SubkernelStatus::Timeout => raise!("SubkernelError",
@ -528,7 +550,12 @@ extern "C-unwind" fn subkernel_await_message(id: i32, timeout: i64, tags: &CSlic
SubkernelStatus::CommLost => raise!("SubkernelError",
"Lost communication with satellite"),
SubkernelStatus::OtherError => raise!("SubkernelError",
"An error occurred during subkernel operation")
"An error occurred during subkernel operation"),
SubkernelStatus::Exception(e) => unsafe { crate::eh_artiq::raise(e) },
}
} else {
send(&Log(format_args!("unexpected reply: {:?}\n", request)));
loop {}
}
})
// RpcRecvRequest should be called `count` times after this to receive message data
@ -590,6 +617,15 @@ pub unsafe fn main() {
},
Ok(library) => {
send(&LoadReply(Ok(())));
// Master kernel would just acknowledge kernel load
// Satellites may send UpdateNow
try_recv(move |msg| match msg {
UpdateNow(timestamp) => unsafe {
csr::rtio::now_hi_write((*timestamp >> 32) as u32);
csr::rtio::now_lo_write(*timestamp as u32);
}
_ => unreachable!()
});
library
}
}

View File

@ -187,6 +187,24 @@ unsafe fn align_comma() {
}
}
pub unsafe fn align_wordslip(trx_no: u8) -> bool {
csr::eem_transceiver::transceiver_sel_write(trx_no);
for slip in 0..=1 {
csr::eem_transceiver::wordslip_write(slip as u8);
clock::spin_us(1);
csr::eem_transceiver::comma_align_reset_write(1);
clock::spin_us(100);
if csr::eem_transceiver::comma_read() == 1 {
debug!("comma alignment completed with {} wordslip", slip);
return true;
}
}
false
}
pub fn init() {
for trx_no in 0..csr::CONFIG_EEM_DRTIO_COUNT {
unsafe {
@ -211,9 +229,6 @@ pub fn init() {
}
});
unsafe {
align_comma();
csr::eem_transceiver::rx_ready_write(1);
}
unsafe { align_comma(); }
}
}

View File

@ -114,7 +114,17 @@ pub unsafe fn write(mut addr: usize, mut data: &[u8]) {
}
}
#[cfg(any(soc_platform = "kasli", soc_platform = "kc705"))]
pub unsafe fn flash_binary(origin: usize, payload: &[u8]) {
assert!((origin & (SECTOR_SIZE - 1)) == 0);
let mut offset = 0;
while offset < payload.len() {
erase_sector(origin + offset);
offset += SECTOR_SIZE;
}
write(origin, payload);
}
#[cfg(any(soc_platform = "kasli", soc_platform = "kc705", soc_platform = "efc"))]
pub unsafe fn reload () -> ! {
csr::icap::iprog_write(1);
loop {}

View File

@ -120,13 +120,32 @@ pub enum Packet {
SubkernelAddDataRequest { destination: u8, id: u32, status: PayloadStatus, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
SubkernelAddDataReply { succeeded: bool },
SubkernelLoadRunRequest { source: u8, destination: u8, id: u32, run: bool },
SubkernelLoadRunRequest { source: u8, destination: u8, id: u32, run: bool, timestamp: u64 },
SubkernelLoadRunReply { destination: u8, succeeded: bool },
SubkernelFinished { destination: u8, id: u32, with_exception: bool, exception_src: u8 },
SubkernelExceptionRequest { destination: u8 },
SubkernelException { last: bool, length: u16, data: [u8; SAT_PAYLOAD_MAX_SIZE] },
SubkernelExceptionRequest { source: u8, destination: u8 },
SubkernelException { destination: u8, last: bool, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
SubkernelMessage { source: u8, destination: u8, id: u32, status: PayloadStatus, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
SubkernelMessageAck { destination: u8 },
CoreMgmtGetLogRequest { destination: u8, clear: bool },
CoreMgmtClearLogRequest { destination: u8 },
CoreMgmtSetLogLevelRequest { destination: u8, log_level: u8 },
CoreMgmtSetUartLogLevelRequest { destination: u8, log_level: u8 },
CoreMgmtConfigReadRequest { destination: u8, length: u16, key: [u8; MASTER_PAYLOAD_MAX_SIZE] },
CoreMgmtConfigReadContinue { destination: u8 },
CoreMgmtConfigWriteRequest { destination: u8, last: bool, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
CoreMgmtConfigRemoveRequest { destination: u8, length: u16, key: [u8; MASTER_PAYLOAD_MAX_SIZE] },
CoreMgmtConfigEraseRequest { destination: u8 },
CoreMgmtRebootRequest { destination: u8 },
CoreMgmtAllocatorDebugRequest { destination: u8 },
CoreMgmtFlashRequest { destination: u8, payload_length: u32 },
CoreMgmtFlashAddDataRequest { destination: u8, last: bool, length: u16, data: [u8; MASTER_PAYLOAD_MAX_SIZE] },
CoreMgmtDropLinkAck { destination: u8 },
CoreMgmtDropLink,
CoreMgmtGetLogReply { last: bool, length: u16, data: [u8; SAT_PAYLOAD_MAX_SIZE] },
CoreMgmtConfigReadReply { last: bool, length: u16, value: [u8; SAT_PAYLOAD_MAX_SIZE] },
CoreMgmtReply { succeeded: bool },
}
impl Packet {
@ -354,7 +373,8 @@ impl Packet {
source: reader.read_u8()?,
destination: reader.read_u8()?,
id: reader.read_u32()?,
run: reader.read_bool()?
run: reader.read_bool()?,
timestamp: reader.read_u64()?
},
0xc5 => Packet::SubkernelLoadRunReply {
destination: reader.read_u8()?,
@ -367,14 +387,17 @@ impl Packet {
exception_src: reader.read_u8()?
},
0xc9 => Packet::SubkernelExceptionRequest {
source: reader.read_u8()?,
destination: reader.read_u8()?
},
0xca => {
let destination = reader.read_u8()?;
let last = reader.read_bool()?;
let length = reader.read_u16()?;
let mut data: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut data[0..length as usize])?;
Packet::SubkernelException {
destination: destination,
last: last,
length: length,
data: data
@ -401,6 +424,115 @@ impl Packet {
destination: reader.read_u8()?
},
0xd0 => Packet::CoreMgmtGetLogRequest {
destination: reader.read_u8()?,
clear: reader.read_bool()?,
},
0xd1 => Packet::CoreMgmtClearLogRequest {
destination: reader.read_u8()?,
},
0xd2 => Packet::CoreMgmtSetLogLevelRequest {
destination: reader.read_u8()?,
log_level: reader.read_u8()?,
},
0xd3 => Packet::CoreMgmtSetUartLogLevelRequest {
destination: reader.read_u8()?,
log_level: reader.read_u8()?,
},
0xd4 => {
let destination = reader.read_u8()?;
let length = reader.read_u16()?;
let mut key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut key[0..length as usize])?;
Packet::CoreMgmtConfigReadRequest {
destination: destination,
length: length,
key: key,
}
},
0xd5 => Packet::CoreMgmtConfigReadContinue {
destination: reader.read_u8()?,
},
0xd6 => {
let destination = reader.read_u8()?;
let last = reader.read_bool()?;
let length = reader.read_u16()?;
let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut data[0..length as usize])?;
Packet::CoreMgmtConfigWriteRequest {
destination: destination,
last: last,
length: length,
data: data,
}
},
0xd7 => {
let destination = reader.read_u8()?;
let length = reader.read_u16()?;
let mut key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut key[0..length as usize])?;
Packet::CoreMgmtConfigRemoveRequest {
destination: destination,
length: length,
key: key,
}
},
0xd8 => Packet::CoreMgmtConfigEraseRequest {
destination: reader.read_u8()?,
},
0xd9 => Packet::CoreMgmtRebootRequest {
destination: reader.read_u8()?,
},
0xda => Packet::CoreMgmtAllocatorDebugRequest {
destination: reader.read_u8()?,
},
0xdb => Packet::CoreMgmtFlashRequest {
destination: reader.read_u8()?,
payload_length: reader.read_u32()?,
},
0xdc => {
let destination = reader.read_u8()?;
let last = reader.read_bool()?;
let length = reader.read_u16()?;
let mut data: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut data[0..length as usize])?;
Packet::CoreMgmtFlashAddDataRequest {
destination: destination,
last: last,
length: length,
data: data,
}
},
0xdd => Packet::CoreMgmtDropLinkAck {
destination: reader.read_u8()?,
},
0xde => Packet::CoreMgmtDropLink,
0xdf => {
let last = reader.read_bool()?;
let length = reader.read_u16()?;
let mut data: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut data[0..length as usize])?;
Packet::CoreMgmtGetLogReply {
last: last,
length: length,
data: data,
}
},
0xe0 => {
let last = reader.read_bool()?;
let length = reader.read_u16()?;
let mut value: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
reader.read_exact(&mut value[0..length as usize])?;
Packet::CoreMgmtConfigReadReply {
last: last,
length: length,
value: value,
}
},
0xe1 => Packet::CoreMgmtReply {
succeeded: reader.read_bool()?,
},
ty => return Err(Error::UnknownPacket(ty))
})
}
@ -644,12 +776,13 @@ impl Packet {
writer.write_u8(0xc1)?;
writer.write_bool(succeeded)?;
},
Packet::SubkernelLoadRunRequest { source, destination, id, run } => {
Packet::SubkernelLoadRunRequest { source, destination, id, run, timestamp } => {
writer.write_u8(0xc4)?;
writer.write_u8(source)?;
writer.write_u8(destination)?;
writer.write_u32(id)?;
writer.write_bool(run)?;
writer.write_u64(timestamp)?;
},
Packet::SubkernelLoadRunReply { destination, succeeded } => {
writer.write_u8(0xc5)?;
@ -663,12 +796,14 @@ impl Packet {
writer.write_bool(with_exception)?;
writer.write_u8(exception_src)?;
},
Packet::SubkernelExceptionRequest { destination } => {
Packet::SubkernelExceptionRequest { source, destination } => {
writer.write_u8(0xc9)?;
writer.write_u8(source)?;
writer.write_u8(destination)?;
},
Packet::SubkernelException { last, length, data } => {
Packet::SubkernelException { destination, last, length, data } => {
writer.write_u8(0xca)?;
writer.write_u8(destination)?;
writer.write_bool(last)?;
writer.write_u16(length)?;
writer.write_all(&data[0..length as usize])?;
@ -686,6 +821,108 @@ impl Packet {
writer.write_u8(0xcc)?;
writer.write_u8(destination)?;
},
Packet::CoreMgmtGetLogRequest { destination, clear } => {
writer.write_u8(0xd0)?;
writer.write_u8(destination)?;
writer.write_bool(clear)?;
},
Packet::CoreMgmtClearLogRequest { destination } => {
writer.write_u8(0xd1)?;
writer.write_u8(destination)?;
},
Packet::CoreMgmtSetLogLevelRequest { destination, log_level } => {
writer.write_u8(0xd2)?;
writer.write_u8(destination)?;
writer.write_u8(log_level)?;
},
Packet::CoreMgmtSetUartLogLevelRequest { destination, log_level } => {
writer.write_u8(0xd3)?;
writer.write_u8(destination)?;
writer.write_u8(log_level)?;
},
Packet::CoreMgmtConfigReadRequest {
destination,
length,
key,
} => {
writer.write_u8(0xd4)?;
writer.write_u8(destination)?;
writer.write_u16(length)?;
writer.write_all(&key[0..length as usize])?;
},
Packet::CoreMgmtConfigReadContinue { destination } => {
writer.write_u8(0xd5)?;
writer.write_u8(destination)?;
},
Packet::CoreMgmtConfigWriteRequest {
destination,
last,
length,
data,
} => {
writer.write_u8(0xd6)?;
writer.write_u8(destination)?;
writer.write_bool(last)?;
writer.write_u16(length)?;
writer.write_all(&data[0..length as usize])?;
},
Packet::CoreMgmtConfigRemoveRequest {
destination,
length,
key,
} => {
writer.write_u8(0xd7)?;
writer.write_u8(destination)?;
writer.write_u16(length)?;
writer.write_all(&key[0..length as usize])?;
},
Packet::CoreMgmtConfigEraseRequest { destination } => {
writer.write_u8(0xd8)?;
writer.write_u8(destination)?;
},
Packet::CoreMgmtRebootRequest { destination } => {
writer.write_u8(0xd9)?;
writer.write_u8(destination)?;
},
Packet::CoreMgmtAllocatorDebugRequest { destination } => {
writer.write_u8(0xda)?;
writer.write_u8(destination)?;
},
Packet::CoreMgmtFlashRequest { destination, payload_length } => {
writer.write_u8(0xdb)?;
writer.write_u8(destination)?;
writer.write_u32(payload_length)?;
},
Packet::CoreMgmtFlashAddDataRequest { destination, last, length, data } => {
writer.write_u8(0xdc)?;
writer.write_u8(destination)?;
writer.write_bool(last)?;
writer.write_u16(length)?;
writer.write_all(&data[..length as usize])?;
},
Packet::CoreMgmtDropLinkAck { destination } => {
writer.write_u8(0xdd)?;
writer.write_u8(destination)?;
},
Packet::CoreMgmtDropLink =>
writer.write_u8(0xde)?,
Packet::CoreMgmtGetLogReply { last, length, data } => {
writer.write_u8(0xdf)?;
writer.write_bool(last)?;
writer.write_u16(length)?;
writer.write_all(&data[0..length as usize])?;
},
Packet::CoreMgmtConfigReadReply { last, length, value } => {
writer.write_u8(0xe0)?;
writer.write_bool(last)?;
writer.write_u16(length)?;
writer.write_all(&value[0..length as usize])?;
},
Packet::CoreMgmtReply { succeeded } => {
writer.write_u8(0xe1)?;
writer.write_bool(succeeded)?;
},
}
Ok(())
}
@ -703,6 +940,8 @@ impl Packet {
Packet::SubkernelLoadRunReply { destination, .. } => Some(*destination),
Packet::SubkernelMessage { destination, .. } => Some(*destination),
Packet::SubkernelMessageAck { destination, .. } => Some(*destination),
Packet::SubkernelExceptionRequest { destination, .. } => Some(*destination),
Packet::SubkernelException { destination, .. } => Some(*destination),
Packet::DmaPlaybackStatus { destination, .. } => Some(*destination),
Packet::SubkernelFinished { destination, .. } => Some(*destination),
_ => None
@ -717,7 +956,8 @@ impl Packet {
Packet::DmaAddTraceReply { .. } | Packet::DmaRemoveTraceReply { .. } |
Packet::DmaPlaybackReply { .. } | Packet::SubkernelLoadRunReply { .. } |
Packet::SubkernelMessageAck { .. } | Packet::DmaPlaybackStatus { .. } |
Packet::SubkernelFinished { .. } => false,
Packet::SubkernelFinished { .. } | Packet::CoreMgmtDropLinkAck { .. } |
Packet::InjectionRequest { .. } => false,
_ => true
}
}

View File

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

View File

@ -16,7 +16,9 @@ pub enum Error<T> {
#[fail(display = "invalid UTF-8: {}", _0)]
Utf8(Utf8Error),
#[fail(display = "{}", _0)]
Io(#[cause] IoError<T>)
Io(#[cause] IoError<T>),
#[fail(display = "drtio error")]
DrtioError,
}
impl<T> From<IoError<T>> for Error<T> {
@ -65,6 +67,8 @@ pub enum Request {
Reboot,
Flash { image: Vec<u8> },
DebugAllocator,
}
@ -123,6 +127,10 @@ impl Request {
8 => Request::DebugAllocator,
9 => Request::Flash {
image: reader.read_bytes()?,
},
ty => return Err(Error::UnknownPacket(ty))
})
}

View File

@ -16,6 +16,7 @@ build_misoc = { path = "../libbuild_misoc" }
failure = { version = "0.1", default-features = false }
failure_derive = { version = "0.1", default-features = false }
byteorder = { version = "1.0", default-features = false }
crc = { version = "1.7", default-features = false }
cslice = { version = "0.3" }
log = { version = "=0.4.14", default-features = false }
managed = { version = "^0.7.1", default-features = false, features = ["alloc", "map"] }

View File

@ -95,7 +95,9 @@ pub mod subkernel {
use board_artiq::drtio_routing::RoutingTable;
use board_misoc::clock;
use proto_artiq::{drtioaux_proto::{PayloadStatus, MASTER_PAYLOAD_MAX_SIZE}, rpc_proto as rpc};
use io::Cursor;
use io::{Cursor, ProtoRead};
use eh::eh_artiq::Exception;
use cslice::CSlice;
use rtio_mgt::drtio;
use sched::{Io, Mutex, Error as SchedError};
@ -192,14 +194,15 @@ pub mod subkernel {
}
pub fn load(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex, routing_table: &RoutingTable,
id: u32, run: bool) -> Result<(), Error> {
id: u32, run: bool, timestamp: u64) -> Result<(), Error> {
let _lock = subkernel_mutex.lock(io)?;
let subkernel = unsafe { SUBKERNELS.get_mut(&id).unwrap() };
if subkernel.state != SubkernelState::Uploaded {
error!("for id: {} expected Uploaded, got: {:?}", id, subkernel.state);
return Err(Error::IncorrectState);
}
drtio::subkernel_load(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, id, subkernel.destination, run)?;
drtio::subkernel_load(io, aux_mutex, ddma_mutex, subkernel_mutex,
routing_table, id, subkernel.destination, run, timestamp)?;
if run {
subkernel.state = SubkernelState::Running;
}
@ -256,6 +259,49 @@ pub mod subkernel {
}
}
fn read_exception_string<'a>(reader: &mut Cursor<&[u8]>) -> Result<CSlice<'a, u8>, Error> {
let len = reader.read_u32()? as usize;
if len == usize::MAX {
let data = reader.read_u32()?;
Ok(unsafe { CSlice::new(data as *const u8, len) })
} else {
let pos = reader.position();
let slice = unsafe {
let ptr = reader.get_ref().as_ptr().offset(pos as isize);
CSlice::new(ptr, len)
};
reader.set_position(pos + len);
Ok(slice)
}
}
pub fn read_exception(buffer: &[u8]) -> Result<Exception, Error>
{
let mut reader = Cursor::new(buffer);
let mut byte = reader.read_u8()?;
// to sync
while byte != 0x5a {
byte = reader.read_u8()?;
}
// skip sync bytes, 0x09 indicates exception
while byte != 0x09 {
byte = reader.read_u8()?;
}
let _len = reader.read_u32()?;
// ignore the remaining exceptions, stack traces etc. - unwinding from another device would be unwise anyway
Ok(Exception {
id: reader.read_u32()?,
message: read_exception_string(&mut reader)?,
param: [reader.read_u64()? as i64, reader.read_u64()? as i64, reader.read_u64()? as i64],
file: read_exception_string(&mut reader)?,
line: reader.read_u32()?,
column: reader.read_u32()?,
function: read_exception_string(&mut reader)?
})
}
pub fn retrieve_finish_status(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, id: u32) -> Result<SubkernelFinished, Error> {
let _lock = subkernel_mutex.lock(io)?;

View File

@ -1,6 +1,7 @@
#![feature(lang_items, panic_info_message, const_btree_new, iter_advance_by, never_type)]
#![no_std]
extern crate crc;
extern crate dyld;
extern crate eh;
#[macro_use]
@ -29,9 +30,10 @@ extern crate riscv;
extern crate tar_no_std;
use alloc::collections::BTreeMap;
use core::cell::RefCell;
use core::cell::{RefCell, Cell};
use core::convert::TryFrom;
use smoltcp::wire::HardwareAddress;
use urc::Urc;
use board_misoc::{csr, ident, clock, spiflash, config, net_settings, pmp, boot};
#[cfg(has_ethmac)]
@ -196,6 +198,7 @@ fn startup() {
let ddma_mutex = sched::Mutex::new();
let subkernel_mutex = sched::Mutex::new();
let restart_idle = Urc::new(Cell::new(false));
let mut scheduler = sched::Scheduler::new(interface);
let io = scheduler.io();
@ -205,15 +208,22 @@ fn startup() {
}
rtio_mgt::startup(&io, &aux_mutex, &drtio_routing_table, &up_destinations, &ddma_mutex, &subkernel_mutex);
io.spawn(4096, mgmt::thread);
{
let restart_idle = restart_idle.clone();
let aux_mutex = aux_mutex.clone();
let ddma_mutex = ddma_mutex.clone();
let subkernel_mutex = subkernel_mutex.clone();
let drtio_routing_table = drtio_routing_table.clone();
io.spawn(4096, move |io| { mgmt::thread(io, &restart_idle, &aux_mutex, &ddma_mutex, &subkernel_mutex, &drtio_routing_table) });
}
{
let aux_mutex = aux_mutex.clone();
let drtio_routing_table = drtio_routing_table.clone();
let up_destinations = up_destinations.clone();
let ddma_mutex = ddma_mutex.clone();
let subkernel_mutex = subkernel_mutex.clone();
io.spawn(32768, move |io| { session::thread(io, &aux_mutex, &drtio_routing_table, &up_destinations, &ddma_mutex, &subkernel_mutex) });
let restart_idle = restart_idle.clone();
io.spawn(32768, move |io| { session::thread(io, &aux_mutex, &drtio_routing_table, &up_destinations, &ddma_mutex, &subkernel_mutex, &restart_idle) });
}
#[cfg(any(has_rtio_moninj, has_drtio))]
{
@ -230,7 +240,7 @@ fn startup() {
let subkernel_mutex = subkernel_mutex.clone();
let drtio_routing_table = drtio_routing_table.clone();
let up_destinations = up_destinations.clone();
io.spawn(8192, move |io| { analyzer::thread(io, &aux_mutex, &ddma_mutex, &subkernel_mutex, &drtio_routing_table, &up_destinations) });
io.spawn(16384, move |io| { analyzer::thread(io, &aux_mutex, &ddma_mutex, &subkernel_mutex, &drtio_routing_table, &up_destinations) });
}
#[cfg(has_grabber)]

View File

@ -1,10 +1,10 @@
use log::{self, LevelFilter};
use core::cell::{Cell, RefCell};
use io::{Write, ProtoWrite, Error as IoError};
use board_misoc::{config, spiflash};
use logger_artiq::BufferLogger;
use board_artiq::drtio_routing::RoutingTable;
use io::{ProtoRead, Write, Error as IoError};
use mgmt_proto::*;
use sched::{Io, TcpListener, TcpStream, Error as SchedError};
use sched::{Io, Mutex, TcpListener, TcpStream, Error as SchedError};
use urc::Urc;
impl From<SchedError> for Error<SchedError> {
fn from(value: SchedError) -> Error<SchedError> {
@ -12,29 +12,39 @@ impl From<SchedError> for Error<SchedError> {
}
}
fn worker(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
read_magic(stream)?;
Write::write_all(stream, "e".as_bytes())?;
info!("new connection from {}", stream.remote_endpoint());
mod local_coremgmt {
use alloc::{string::String, vec::Vec};
use byteorder::{ByteOrder, NativeEndian};
use crc::crc32;
use log::LevelFilter;
loop {
match Request::read_from(stream)? {
Request::GetLog => {
use board_misoc::{config, mem, spiflash};
use io::ProtoWrite;
use logger_artiq::BufferLogger;
use super::*;
pub fn get_log(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
BufferLogger::with(|logger| {
let mut buffer = io.until_ok(|| logger.buffer())?;
Reply::LogContent(buffer.extract()).write_to(stream)
})?;
Ok(())
}
Request::ClearLog => {
BufferLogger::with(|logger| -> Result<(), Error<SchedError>> {
pub fn clear_log(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
BufferLogger::with(|logger| -> Result<(), IoError<SchedError>> {
let mut buffer = io.until_ok(|| logger.buffer())?;
Ok(buffer.clear())
})?;
Reply::Success.write_to(stream)?;
Ok(())
}
Request::PullLog => {
BufferLogger::with(|logger| -> Result<(), Error<SchedError>> {
pub fn pull_log(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
BufferLogger::with(|logger| -> Result<(), IoError<SchedError>> {
loop {
// Do this *before* acquiring the buffer, since that sets the log level
// to OFF.
@ -61,48 +71,75 @@ fn worker(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
buffer.clear();
}
})?;
Ok(())
}
Request::SetLogFilter(level) => {
pub fn set_log_filter(_io: &Io, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error<SchedError>> {
info!("changing log level to {}", level);
log::set_max_level(level);
Reply::Success.write_to(stream)?;
Ok(())
}
Request::SetUartLogFilter(level) => {
pub fn set_uart_log_filter(_io: &Io, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error<SchedError>> {
info!("changing UART log level to {}", level);
BufferLogger::with(|logger|
logger.set_uart_log_level(level));
Reply::Success.write_to(stream)?;
Ok(())
}
Request::ConfigRead { ref key } => {
pub fn config_read(_io: &Io, stream: &mut TcpStream, key: &String) -> Result<(), Error<SchedError>>{
config::read(key, |result| {
match result {
Ok(value) => Reply::ConfigData(&value).write_to(stream),
Err(_) => Reply::Error.write_to(stream)
}
})?;
Ok(())
}
Request::ConfigWrite { ref key, ref value } => {
pub fn config_write(io: &Io, stream: &mut TcpStream, key: &String, value: &Vec<u8>, restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
match config::write(key, value) {
Ok(_) => Reply::Success.write_to(stream),
Ok(_) => {
if key == "idle_kernel" {
io.until(|| !restart_idle.get())?;
restart_idle.set(true);
}
Reply::Success.write_to(stream)
},
Err(_) => Reply::Error.write_to(stream)
}?;
Ok(())
}
Request::ConfigRemove { ref key } => {
pub fn config_remove(io: &Io, stream: &mut TcpStream, key: &String, restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
match config::remove(key) {
Ok(()) => Reply::Success.write_to(stream),
Ok(()) => {
if key == "idle_kernel" {
io.until(|| !restart_idle.get())?;
restart_idle.set(true);
}
Reply::Success.write_to(stream)
},
Err(_) => Reply::Error.write_to(stream)
}?;
Ok(())
}
Request::ConfigErase => {
pub fn config_erase(io: &Io, stream: &mut TcpStream, restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
match config::erase() {
Ok(()) => Reply::Success.write_to(stream),
Ok(()) => {
io.until(|| !restart_idle.get())?;
restart_idle.set(true);
Reply::Success.write_to(stream)
},
Err(_) => Reply::Error.write_to(stream)
}?;
Ok(())
}
Request::Reboot => {
pub fn reboot(_io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
Reply::RebootImminent.write_to(stream)?;
stream.close()?;
stream.flush()?;
@ -111,22 +148,524 @@ fn worker(io: &Io, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
unsafe { spiflash::reload(); }
}
Request::DebugAllocator =>
unsafe { println!("{}", ::ALLOC) },
pub fn debug_allocator(_io: &Io, _stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
unsafe { println!("{}", ::ALLOC) }
Ok(())
}
pub fn flash(_io: &Io, stream: &mut TcpStream, image: &[u8]) -> Result<(), Error<SchedError>> {
let (expected_crc, mut image) = {
let (image, crc_slice) = image.split_at(image.len() - 4);
(NativeEndian::read_u32(crc_slice), image)
};
let actual_crc = crc32::checksum_ieee(image);
if actual_crc == expected_crc {
let bin_origins = [
("gateware" , 0 ),
("bootloader", mem::ROM_BASE ),
("firmware" , mem::FLASH_BOOT_ADDRESS),
];
for (name, origin) in bin_origins {
info!("Flashing {} binary...", name);
let size = NativeEndian::read_u32(&image[..4]) as usize;
image = &image[4..];
let (bin, remaining) = image.split_at(size);
image = remaining;
unsafe { spiflash::flash_binary(origin, bin) };
}
reboot(_io, stream)?;
} else {
error!("CRC failed, images have not been written to flash.\n(actual {:08x}, expected {:08x})", actual_crc, expected_crc);
Reply::Error.write_to(stream)?;
}
Ok(())
}
}
pub fn thread(io: Io) {
#[cfg(has_drtio)]
mod remote_coremgmt {
use alloc::{string::String, vec::Vec};
use log::LevelFilter;
use board_artiq::{drtioaux, drtioaux::Packet};
use io::ProtoWrite;
use rtio_mgt::drtio;
use proto_artiq::drtioaux_proto::MASTER_PAYLOAD_MAX_SIZE;
use super::*;
impl From<drtio::Error> for Error<SchedError> {
fn from(_value: drtio::Error) -> Error<SchedError> {
Error::DrtioError
}
}
pub fn get_log(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
let mut buffer = String::new();
loop {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtGetLogRequest { destination, clear: false }
);
match reply {
Ok(Packet::CoreMgmtGetLogReply { last, length, data }) => {
buffer.push_str(
core::str::from_utf8(&data[..length as usize]).map_err(|_| Error::DrtioError)?);
if last {
Reply::LogContent(&buffer).write_to(stream)?;
return Ok(());
}
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Reply::Error.write_to(stream)?;
return Err(drtio::Error::UnexpectedReply.into());
}
Err(e) => {
error!("aux packet error ({})", e);
Reply::Error.write_to(stream)?;
return Err(e.into());
}
}
}
}
pub fn clear_log(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtClearLogRequest { destination }
);
match reply {
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
Reply::Success.write_to(stream)?;
Ok(())
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Reply::Error.write_to(stream)?;
Err(drtio::Error::UnexpectedReply.into())
}
Err(e) => {
error!("aux packet error ({})", e);
Reply::Error.write_to(stream)?;
Err(e.into())
}
}
}
pub fn pull_log(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
let mut buffer = Vec::new();
loop {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtGetLogRequest { destination, clear: true }
);
match reply {
Ok(Packet::CoreMgmtGetLogReply { last, length, data }) => {
buffer.extend(&data[..length as usize]);
if last {
stream.write_bytes(&buffer)?;
buffer.clear();
}
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
return Err(drtio::Error::UnexpectedReply.into());
}
Err(e) => {
error!("aux packet error ({})", e);
return Err(e.into());
}
}
}
}
pub fn set_log_filter(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error<SchedError>> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtSetLogLevelRequest { destination, log_level: level as u8 }
);
match reply {
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
Reply::Success.write_to(stream)?;
Ok(())
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Reply::Error.write_to(stream)?;
Err(drtio::Error::UnexpectedReply.into())
}
Err(e) => {
error!("aux packet error ({})", e);
Reply::Error.write_to(stream)?;
Err(e.into())
}
}
}
pub fn set_uart_log_filter(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, stream: &mut TcpStream, level: LevelFilter) -> Result<(), Error<SchedError>> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtSetUartLogLevelRequest { destination, log_level: level as u8 }
);
match reply {
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
Reply::Success.write_to(stream)?;
Ok(())
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Reply::Error.write_to(stream)?;
Err(drtio::Error::UnexpectedReply.into())
}
Err(e) => {
error!("aux packet error ({})", e);
Reply::Error.write_to(stream)?;
Err(e.into())
}
}
}
pub fn config_read(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, stream: &mut TcpStream, key: &String) -> Result<(), Error<SchedError>> {
let mut config_key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
let len = key.len();
config_key[..len].clone_from_slice(key.as_bytes());
let mut reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtConfigReadRequest {
destination: destination,
length: len as u16,
key: config_key,
}
);
let mut buffer = Vec::<u8>::new();
loop {
match reply {
Ok(Packet::CoreMgmtConfigReadReply { length, last, value }) => {
buffer.extend(&value[..length as usize]);
if last {
Reply::ConfigData(&buffer).write_to(stream)?;
return Ok(());
}
reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtConfigReadContinue {
destination: destination,
}
);
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Reply::Error.write_to(stream)?;
return Err(drtio::Error::UnexpectedReply.into());
}
Err(e) => {
error!("aux packet error ({})", e);
Reply::Error.write_to(stream)?;
return Err(e.into());
}
}
}
}
pub fn config_write(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, stream: &mut TcpStream, key: &String, value: &Vec<u8>,
_restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
let mut message = Vec::with_capacity(key.len() + value.len() + 4 * 2);
message.write_string(key).unwrap();
message.write_bytes(value).unwrap();
match drtio::partition_data(&message, |slice, status, len: usize| {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtConfigWriteRequest {
destination: destination, length: len as u16, last: status.is_last(), data: *slice});
match reply {
Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()),
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Err(drtio::Error::UnexpectedReply)
}
Err(e) => {
error!("aux packet error ({})", e);
Err(e)
}
}
}) {
Ok(()) => {
Reply::Success.write_to(stream)?;
Ok(())
},
Err(e) => {
Reply::Error.write_to(stream)?;
Err(e.into())
},
}
}
pub fn config_remove(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, stream: &mut TcpStream, key: &String,
_restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
let mut config_key: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
let len = key.len();
config_key[..len].clone_from_slice(key.as_bytes());
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtConfigRemoveRequest {
destination: destination,
length: key.len() as u16,
key: config_key,
});
match reply {
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
Reply::Success.write_to(stream)?;
Ok(())
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Reply::Error.write_to(stream)?;
Err(drtio::Error::UnexpectedReply.into())
}
Err(e) => {
error!("aux packet error ({})", e);
Reply::Error.write_to(stream)?;
Err(e.into())
}
}
}
pub fn config_erase(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, stream: &mut TcpStream, _restart_idle: &Urc<Cell<bool>>) -> Result<(), Error<SchedError>> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtConfigEraseRequest {
destination: destination,
});
match reply {
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
Reply::Success.write_to(stream)?;
Ok(())
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Reply::Error.write_to(stream)?;
Err(drtio::Error::UnexpectedReply.into())
}
Err(e) => {
error!("aux packet error ({})", e);
Reply::Error.write_to(stream)?;
Err(e.into())
}
}
}
pub fn reboot(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtRebootRequest {
destination: destination,
});
match reply {
Ok(Packet::CoreMgmtReply { succeeded: true }) => {
Reply::RebootImminent.write_to(stream)?;
Ok(())
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Reply::Error.write_to(stream)?;
Err(drtio::Error::UnexpectedReply.into())
}
Err(e) => {
error!("aux packet error ({})", e);
Reply::Error.write_to(stream)?;
Err(e.into())
}
}
}
pub fn debug_allocator(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, _stream: &mut TcpStream) -> Result<(), Error<SchedError>> {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtAllocatorDebugRequest {
destination: destination,
});
match reply {
Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()),
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Err(drtio::Error::UnexpectedReply.into())
}
Err(e) => {
error!("aux packet error ({})", e);
Err(e.into())
}
}
}
pub fn flash(io: &Io, aux_mutex: &Mutex,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &RoutingTable, linkno: u8,
destination: u8, stream: &mut TcpStream, image: &[u8]) -> Result<(), Error<SchedError>> {
let alloc_reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtFlashRequest {
destination: destination,
payload_length: image.len() as u32,
});
match alloc_reply {
Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()),
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Reply::Error.write_to(stream)?;
Err(drtio::Error::UnexpectedReply)
}
Err(e) => {
error!("aux packet error ({})", e);
Reply::Error.write_to(stream)?;
Err(e)
}
}?;
match drtio::partition_data(&image, |slice, status, len: usize| {
let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&Packet::CoreMgmtFlashAddDataRequest {
destination: destination, length: len as u16, last: status.is_last(), data: *slice});
match reply {
Ok(Packet::CoreMgmtReply { succeeded: true }) => Ok(()),
Ok(Packet::CoreMgmtDropLink) => {
if status.is_last() {
drtioaux::send(
linkno, &Packet::CoreMgmtDropLinkAck { destination: destination }
).map_err(|_| drtio::Error::AuxError)
} else {
error!("received unexpected drop link packet");
Err(drtio::Error::UnexpectedReply)
}
}
Ok(packet) => {
error!("received unexpected aux packet: {:?}", packet);
Err(drtio::Error::UnexpectedReply)
}
Err(e) => {
error!("aux packet error ({})", e);
Err(e)
}
}
}) {
Ok(()) => {
Reply::RebootImminent.write_to(stream)?;
Ok(())
},
Err(e) => {
Reply::Error.write_to(stream)?;
Err(e.into())
},
}
}
}
#[cfg(has_drtio)]
macro_rules! process {
($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $subkernel_mutex:ident, $routing_table:ident, $tcp_stream:ident, $destination: ident, $func:ident $(, $param:expr)*) => {{
let hop = $routing_table.0[$destination as usize][0];
if hop == 0 {
local_coremgmt::$func($io, $tcp_stream, $($param, )*)
} else {
let linkno = hop - 1;
remote_coremgmt::$func($io, $aux_mutex, $ddma_mutex, $subkernel_mutex, $routing_table, linkno, $destination, $tcp_stream, $($param, )*)
}
}}
}
#[cfg(not(has_drtio))]
macro_rules! process {
($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $subkernel_mutex:ident, $routing_table:ident, $tcp_stream:ident, $_destination: ident, $func:ident $(, $param:expr)*) => {{
local_coremgmt::$func($io, $tcp_stream, $($param, )*)
}}
}
fn worker(io: &Io, stream: &mut TcpStream, restart_idle: &Urc<Cell<bool>>,
_aux_mutex: &Mutex, _ddma_mutex: &Mutex, _subkernel_mutex: &Mutex,
_routing_table: &RoutingTable) -> Result<(), Error<SchedError>> {
read_magic(stream)?;
let _destination = stream.read_u8()?;
Write::write_all(stream, "e".as_bytes())?;
info!("new connection from {}", stream.remote_endpoint());
loop {
match Request::read_from(stream)? {
Request::GetLog => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, get_log),
Request::ClearLog => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, clear_log),
Request::PullLog => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, pull_log),
Request::SetLogFilter(level) => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, set_log_filter, level),
Request::SetUartLogFilter(level) => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, set_uart_log_filter, level),
Request::ConfigRead { ref key } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_read, key),
Request::ConfigWrite { ref key, ref value } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_write, key, value, restart_idle),
Request::ConfigRemove { ref key } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_remove, key, restart_idle),
Request::ConfigErase => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, config_erase, restart_idle),
Request::Reboot => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, reboot),
Request::DebugAllocator => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, debug_allocator),
Request::Flash { ref image } => process!(io, _aux_mutex, _ddma_mutex, _subkernel_mutex, _routing_table, stream, _destination, flash, &image[..]),
}?;
}
}
pub fn thread(io: Io, restart_idle: &Urc<Cell<bool>>, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex, routing_table: &Urc<RefCell<RoutingTable>>) {
let listener = TcpListener::new(&io, 8192);
listener.listen(1380).expect("mgmt: cannot listen");
info!("management interface active");
loop {
let restart_idle = restart_idle.clone();
let aux_mutex = aux_mutex.clone();
let ddma_mutex = ddma_mutex.clone();
let subkernel_mutex = subkernel_mutex.clone();
let routing_table = routing_table.clone();
let stream = listener.accept().expect("mgmt: cannot accept").into_handle();
io.spawn(4096, move |io| {
io.spawn(16384, move |io| {
let routing_table = routing_table.borrow();
let mut stream = TcpStream::from_handle(&io, stream);
match worker(&io, &mut stream) {
match worker(&io, &mut stream, &restart_idle, &aux_mutex, &ddma_mutex, &subkernel_mutex, &routing_table) {
Ok(()) => (),
Err(Error::Io(IoError::UnexpectedEnd)) => (),
Err(err) => error!("aborted: {}", err)

View File

@ -16,6 +16,8 @@ const ASYNC_ERROR_SEQUENCE_ERROR: u8 = 1 << 2;
pub mod drtio {
use super::*;
use alloc::vec::Vec;
#[cfg(has_drtio_eem)]
use board_artiq::drtio_eem;
use drtioaux;
use proto_artiq::drtioaux_proto::{MASTER_PAYLOAD_MAX_SIZE, PayloadStatus};
use rtio_dma::remote_dma;
@ -24,6 +26,9 @@ pub mod drtio {
use kernel::subkernel;
use sched::Error as SchedError;
#[cfg(has_drtio_eem)]
const DRTIO_EEM_LINKNOS: core::ops::Range<usize> = (csr::DRTIO.len()-csr::CONFIG_EEM_DRTIO_COUNT as usize)..csr::DRTIO.len();
#[derive(Fail, Debug)]
pub enum Error {
#[fail(display = "timed out")]
@ -73,6 +78,18 @@ pub mod drtio {
fn link_rx_up(linkno: u8) -> bool {
let linkno = linkno as usize;
#[cfg(has_drtio_eem)]
if DRTIO_EEM_LINKNOS.contains(&linkno) {
let eem_trx_no = linkno - DRTIO_EEM_LINKNOS.start;
unsafe {
csr::eem_transceiver::transceiver_sel_write(eem_trx_no as u8);
csr::eem_transceiver::comma_align_reset_write(1);
}
clock::spin_us(100);
return unsafe {
csr::eem_transceiver::comma_read() == 1
};
}
unsafe {
(csr::DRTIO[linkno].rx_up_read)() == 1
}
@ -128,6 +145,8 @@ pub mod drtio {
drtioaux::Packet::SubkernelLoadRunReply { destination, .. } |
drtioaux::Packet::SubkernelMessage { destination, .. } |
drtioaux::Packet::SubkernelMessageAck { destination, .. } |
drtioaux::Packet::SubkernelExceptionRequest { destination, .. } |
drtioaux::Packet::SubkernelException { destination, .. } |
drtioaux::Packet::DmaPlaybackStatus { destination, .. } |
drtioaux::Packet::SubkernelFinished { destination, .. } => {
if *destination == 0 {
@ -412,9 +431,27 @@ pub mod drtio {
} else {
info!("[LINK#{}] link is down", linkno);
up_links[linkno as usize] = false;
#[cfg(has_drtio_eem)]
if DRTIO_EEM_LINKNOS.contains(&(linkno as usize)) {
unsafe { csr::eem_transceiver::rx_ready_write(0); }
// Clear DRTIOAUX buffer
while !matches!(drtioaux::recv(linkno), Ok(None)) {}
}
}
} else {
/* link was previously down */
#[cfg(has_drtio_eem)]
if DRTIO_EEM_LINKNOS.contains(&(linkno as usize)) {
let eem_trx_no = linkno - DRTIO_EEM_LINKNOS.start as u8;
if !unsafe { drtio_eem::align_wordslip(eem_trx_no) } {
continue;
}
unsafe {
csr::eem_transceiver::rx_ready_write(1);
}
}
if link_rx_up(linkno) {
info!("[LINK#{}] link RX became up, pinging", linkno);
let ping_count = ping_remote(&io, aux_mutex, linkno);
@ -469,7 +506,7 @@ pub mod drtio {
}
}
fn partition_data<F>(data: &[u8], send_f: F) -> Result<(), Error>
pub fn partition_data<F>(data: &[u8], send_f: F) -> Result<(), Error>
where F: Fn(&[u8; MASTER_PAYLOAD_MAX_SIZE], PayloadStatus, usize) -> Result<(), Error> {
let mut i = 0;
while i < data.len() {
@ -592,11 +629,14 @@ pub mod drtio {
}
pub fn subkernel_load(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, subkernel_mutex: &Mutex,
routing_table: &drtio_routing::RoutingTable, id: u32, destination: u8, run: bool
routing_table: &drtio_routing::RoutingTable, id: u32, destination: u8, run: bool, timestamp: u64
) -> Result<(), Error> {
let linkno = routing_table.0[destination as usize][0] - 1;
let reply = aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&drtioaux::Packet::SubkernelLoadRunRequest{ id: id, source: 0, destination: destination, run: run })?;
&drtioaux::Packet::SubkernelLoadRunRequest{
id: id, source: 0, destination: destination,
run: run, timestamp: timestamp
})?;
match reply {
drtioaux::Packet::SubkernelLoadRunReply { destination: 0, succeeded: true } => Ok(()),
drtioaux::Packet::SubkernelLoadRunReply { destination: 0, succeeded: false } =>
@ -612,9 +652,9 @@ pub mod drtio {
let mut remote_data: Vec<u8> = Vec::new();
loop {
let reply = aux_transact(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, linkno,
&drtioaux::Packet::SubkernelExceptionRequest { destination: destination })?;
&drtioaux::Packet::SubkernelExceptionRequest { source: 0, destination: destination })?;
match reply {
drtioaux::Packet::SubkernelException { last, length, data } => {
drtioaux::Packet::SubkernelException { destination: 0, last, length, data } => {
remote_data.extend(&data[0..length as usize]);
if last {
return Ok(remote_data);
@ -710,11 +750,30 @@ fn read_device_map() -> DeviceMap {
device_map
}
fn toggle_sed_spread(val: u8) {
unsafe { csr::rtio_core::sed_spread_enable_write(val); }
}
fn setup_sed_spread() {
config::read_str("sed_spread_enable", |r| {
match r {
Ok("1") => { info!("SED spreading enabled"); toggle_sed_spread(1); },
Ok("0") => { info!("SED spreading disabled"); toggle_sed_spread(0); },
Ok(_) => {
warn!("sed_spread_enable value not supported (only 1, 0 allowed), disabling by default");
toggle_sed_spread(0);
},
Err(_) => { info!("SED spreading disabled by default"); toggle_sed_spread(0) },
}
});
}
pub fn startup(io: &Io, aux_mutex: &Mutex,
routing_table: &Urc<RefCell<drtio_routing::RoutingTable>>,
up_destinations: &Urc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex) {
set_device_map(read_device_map());
setup_sed_spread();
drtio::startup(io, aux_mutex, routing_table, up_destinations, ddma_mutex, subkernel_mutex);
unsafe {
csr::rtio_core::reset_phy_write(1);

View File

@ -402,16 +402,18 @@ impl<'a> TcpListener<'a> {
socket.may_send() || socket.may_recv()
})?;
let accepted = self.handle.get();
let accepted = TcpStream {
io: self.io,
handle: self.handle.get(),
};
accepted.with_lower(|s| s.set_nagle_enabled(false));
self.handle.set(Self::new_lower(self.io, self.buffer_size.get()));
match self.listen(self.endpoint.get()) {
Ok(()) => (),
_ => unreachable!()
}
Ok(TcpStream {
io: self.io,
handle: accepted
})
Ok(accepted)
}
pub fn close(&self) {

View File

@ -126,19 +126,6 @@ macro_rules! unexpected {
($($arg:tt)*) => (return Err(Error::Unexpected(format!($($arg)*))));
}
#[cfg(has_drtio)]
macro_rules! propagate_subkernel_exception {
( $exception:ident, $stream:ident ) => {
error!("Exception in subkernel");
match $stream {
None => return Ok(true),
Some(ref mut $stream) => {
$stream.write_all($exception)?;
}
}
}
}
// Persistent state
#[derive(Debug)]
struct Congress {
@ -674,9 +661,9 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex,
}
}
#[cfg(has_drtio)]
&kern::SubkernelLoadRunRequest { id, destination: _, run } => {
&kern::SubkernelLoadRunRequest { id, destination: _, run, timestamp } => {
let succeeded = match subkernel::load(
io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, id, run) {
io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table, id, run, timestamp) {
Ok(()) => true,
Err(e) => { error!("Error loading subkernel: {}", e); false }
};
@ -686,23 +673,26 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex,
&kern::SubkernelAwaitFinishRequest{ id, timeout } => {
let res = subkernel::await_finish(io, aux_mutex, ddma_mutex, subkernel_mutex, routing_table,
id, timeout);
let status = match res {
let response = match res {
Ok(ref res) => {
if res.comm_lost {
kern::SubkernelStatus::CommLost
} else if let Some(exception) = &res.exception {
propagate_subkernel_exception!(exception, stream);
// will not be called after exception is served
kern::SubkernelStatus::OtherError
kern::SubkernelError(kern::SubkernelStatus::CommLost)
} else if let Some(raw_exception) = &res.exception {
let exception = subkernel::read_exception(raw_exception);
if let Ok(exception) = exception {
kern::SubkernelError(kern::SubkernelStatus::Exception(exception))
} else {
kern::SubkernelStatus::NoError
kern::SubkernelError(kern::SubkernelStatus::OtherError)
}
} else {
kern::SubkernelAwaitFinishReply
}
},
Err(SubkernelError::Timeout) => kern::SubkernelStatus::Timeout,
Err(SubkernelError::IncorrectState) => kern::SubkernelStatus::IncorrectState,
Err(_) => kern::SubkernelStatus::OtherError
Err(SubkernelError::Timeout) => kern::SubkernelError(kern::SubkernelStatus::Timeout),
Err(SubkernelError::IncorrectState) => kern::SubkernelError(kern::SubkernelStatus::IncorrectState),
Err(_) => kern::SubkernelError(kern::SubkernelStatus::OtherError)
};
kern_send(io, &kern::SubkernelAwaitFinishReply { status: status })
kern_send(io, &response)
}
#[cfg(has_drtio)]
&kern::SubkernelMsgSend { id, destination, count, tag, data } => {
@ -712,25 +702,34 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex,
#[cfg(has_drtio)]
&kern::SubkernelMsgRecvRequest { id, timeout, tags } => {
let message_received = subkernel::message_await(io, subkernel_mutex, id as u32, timeout);
let (status, count) = match message_received {
Ok(ref message) => (kern::SubkernelStatus::NoError, message.count),
Err(SubkernelError::Timeout) => (kern::SubkernelStatus::Timeout, 0),
Err(SubkernelError::IncorrectState) => (kern::SubkernelStatus::IncorrectState, 0),
Err(SubkernelError::SubkernelFinished) => {
if let Err(SubkernelError::SubkernelFinished) = message_received {
let res = subkernel::retrieve_finish_status(io, aux_mutex, ddma_mutex, subkernel_mutex,
routing_table, id as u32)?;
if res.comm_lost {
(kern::SubkernelStatus::CommLost, 0)
} else if let Some(exception) = &res.exception {
propagate_subkernel_exception!(exception, stream);
(kern::SubkernelStatus::OtherError, 0)
kern_send(io,
&kern::SubkernelError(kern::SubkernelStatus::CommLost))?;
} else if let Some(raw_exception) = &res.exception {
let exception = subkernel::read_exception(raw_exception);
if let Ok(exception) = exception {
kern_send(io,
&kern::SubkernelError(kern::SubkernelStatus::Exception(exception)))?;
} else {
(kern::SubkernelStatus::OtherError, 0)
kern_send(io,
&kern::SubkernelError(kern::SubkernelStatus::OtherError))?;
}
} else {
kern_send(io,
&kern::SubkernelError(kern::SubkernelStatus::OtherError))?;
}
Err(_) => (kern::SubkernelStatus::OtherError, 0)
} else {
let message = match message_received {
Ok(ref message) => kern::SubkernelMsgRecvReply { count: message.count },
Err(SubkernelError::Timeout) => kern::SubkernelError(kern::SubkernelStatus::Timeout),
Err(SubkernelError::IncorrectState) => kern::SubkernelError(kern::SubkernelStatus::IncorrectState),
Err(SubkernelError::SubkernelFinished) => unreachable!(), // taken care of above
Err(_) => kern::SubkernelError(kern::SubkernelStatus::OtherError)
};
kern_send(io, &kern::SubkernelMsgRecvReply { status: status, count: count})?;
kern_send(io, &message)?;
if let Ok(message) = message_received {
// receive code almost identical to RPC recv, except we are not reading from a stream
let mut reader = Cursor::new(message.data);
@ -773,11 +772,10 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex,
Err(_) => unexpected!("expected valid subkernel message data")
};
}
Ok(())
} else {
// if timed out, no data has been received, exception should be raised by kernel
Ok(())
}
// if timed out, no data has been received, exception should be raised by kernel
}
Ok(())
},
request => unexpected!("unexpected request {:?} from kernel CPU", request)
@ -841,7 +839,7 @@ fn flash_kernel_worker(io: &Io, aux_mutex: &Mutex,
routing_table: &drtio_routing::RoutingTable,
up_destinations: &Urc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex, congress: &mut Congress,
config_key: &str) -> Result<(), Error<SchedError>> {
config_key: &str, restart_idle: Option<&Urc<Cell<bool>>>) -> Result<(), Error<SchedError>> {
let mut session = Session::new(congress);
config::read(config_key, |result| {
@ -861,12 +859,17 @@ fn flash_kernel_worker(io: &Io, aux_mutex: &Mutex,
}
})?;
kern_run(&mut session)?;
loop {
if !rpc_queue::empty() {
unexpected!("unexpected background RPC in flash kernel")
}
if let Some(r_idle) = restart_idle {
if r_idle.get() {
return Err(Error::KernelNotFound)
}
}
if mailbox::receive() != 0 {
if process_kern_message(io, aux_mutex, routing_table, up_destinations, ddma_mutex, subkernel_mutex, None, &mut session)? {
return Ok(())
@ -899,7 +902,7 @@ fn respawn<F>(io: &Io, handle: &mut Option<ThreadHandle>, f: F)
pub fn thread(io: Io, aux_mutex: &Mutex,
routing_table: &Urc<RefCell<drtio_routing::RoutingTable>>,
up_destinations: &Urc<RefCell<[bool; drtio_routing::DEST_COUNT]>>,
ddma_mutex: &Mutex, subkernel_mutex: &Mutex) {
ddma_mutex: &Mutex, subkernel_mutex: &Mutex, restart_idle: &Urc<Cell<bool>>) {
let listener = TcpListener::new(&io, 65535);
listener.listen(1381).expect("session: cannot listen");
info!("accepting network sessions");
@ -912,11 +915,11 @@ pub fn thread(io: Io, aux_mutex: &Mutex,
let mut congress = congress.borrow_mut();
info!("running startup kernel");
match flash_kernel_worker(&io, &aux_mutex, &routing_table, &up_destinations,
ddma_mutex, subkernel_mutex, &mut congress, "startup_kernel") {
ddma_mutex, subkernel_mutex, &mut congress, "startup_kernel", None) {
Ok(()) =>
info!("startup kernel finished"),
Err(Error::KernelNotFound) =>
info!("no startup kernel found"),
debug!("no startup kernel found"),
Err(err) => {
congress.finished_cleanly.set(false);
error!("startup kernel aborted: {}", err);
@ -996,11 +999,12 @@ pub fn thread(io: Io, aux_mutex: &Mutex,
let congress = congress.clone();
let ddma_mutex = ddma_mutex.clone();
let subkernel_mutex = subkernel_mutex.clone();
let restart_idle = restart_idle.clone();
respawn(&io, &mut kernel_thread, move |io| {
let routing_table = routing_table.borrow();
let mut congress = congress.borrow_mut();
match flash_kernel_worker(&io, &aux_mutex, &routing_table, &up_destinations,
&ddma_mutex, &subkernel_mutex, &mut *congress, "idle_kernel") {
&ddma_mutex, &subkernel_mutex, &mut *congress, "idle_kernel", Some(&restart_idle)) {
Ok(()) =>
info!("idle kernel finished, standing by"),
Err(Error::Protocol(host::Error::Io(
@ -1011,8 +1015,9 @@ pub fn thread(io: Io, aux_mutex: &Mutex,
drtio::clear_buffers(&io, &aux_mutex);
}
Err(Error::KernelNotFound) => {
info!("no idle kernel found");
while io.relinquish().is_ok() {}
debug!("no idle kernel found");
while !restart_idle.get() && io.relinquish().is_ok() {}
restart_idle.set(false);
}
Err(err) => {
error!("idle kernel aborted: {}", err);

View File

@ -15,9 +15,12 @@ build_misoc = { path = "../libbuild_misoc" }
[dependencies]
log = { version = "0.4", default-features = false }
io = { path = "../libio", features = ["byteorder", "alloc"] }
byteorder = { version = "1.0", default-features = false }
crc = { version = "1.7", default-features = false }
cslice = { version = "0.3" }
board_misoc = { path = "../libboard_misoc", features = ["uart_console", "log"] }
board_artiq = { path = "../libboard_artiq", features = ["alloc"] }
logger_artiq = { path = "../liblogger_artiq" }
alloc_list = { path = "../liballoc_list" }
riscv = { version = "0.6.0", features = ["inline-asm"] }
proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] }

View File

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

View File

@ -6,21 +6,25 @@ extern crate log;
#[macro_use]
extern crate board_misoc;
extern crate board_artiq;
extern crate logger_artiq;
extern crate riscv;
extern crate alloc;
extern crate proto_artiq;
extern crate byteorder;
extern crate crc;
extern crate cslice;
extern crate io;
extern crate eh;
use core::convert::TryFrom;
use board_misoc::{csr, ident, clock, uart_logger, i2c, pmp};
use board_misoc::{csr, ident, clock, config, i2c, pmp};
#[cfg(has_si5324)]
use board_artiq::si5324;
#[cfg(has_si549)]
use board_artiq::si549;
#[cfg(soc_platform = "kasli")]
use board_misoc::irq;
use board_misoc::{boot, spiflash};
use board_artiq::{spi, drtioaux, drtio_routing};
#[cfg(soc_platform = "efc")]
use board_artiq::ad9117;
@ -30,6 +34,7 @@ use board_artiq::drtio_eem;
use riscv::register::{mcause, mepc, mtval};
use dma::Manager as DmaManager;
use kernel::Manager as KernelManager;
use mgmt::Manager as CoreManager;
use analyzer::Analyzer;
#[global_allocator]
@ -41,6 +46,7 @@ mod dma;
mod analyzer;
mod kernel;
mod cache;
mod mgmt;
fn drtiosat_reset(reset: bool) {
unsafe {
@ -70,6 +76,10 @@ fn drtiosat_tsc_loaded() -> bool {
}
}
fn toggle_sed_spread(val: u8) {
unsafe { csr::drtiosat::sed_spread_enable_write(val); }
}
#[derive(Clone, Copy)]
pub enum RtioMaster {
Drtio,
@ -100,13 +110,13 @@ pub fn cricon_read() -> RtioMaster {
#[cfg(has_drtio_routing)]
macro_rules! forward {
($routing_table:expr, $destination:expr, $rank:expr, $repeaters:expr, $packet:expr) => {{
($router:expr, $routing_table:expr, $destination:expr, $rank:expr, $self_destination:expr, $repeaters:expr, $packet:expr) => {{
let hop = $routing_table.0[$destination as usize][$rank as usize];
if hop != 0 {
let repno = (hop - 1) as usize;
if repno < $repeaters.len() {
if $packet.expects_response() {
return $repeaters[repno].aux_forward($packet);
return $repeaters[repno].aux_forward($packet, $router, $routing_table, $rank, $self_destination);
} else {
let res = $repeaters[repno].aux_send($packet);
// allow the satellite to parse the packet before next
@ -122,10 +132,10 @@ macro_rules! forward {
#[cfg(not(has_drtio_routing))]
macro_rules! forward {
($routing_table:expr, $destination:expr, $rank:expr, $repeaters:expr, $packet:expr) => {}
($router:expr, $routing_table:expr, $destination:expr, $rank:expr, $self_destination:expr, $repeaters:expr, $packet:expr) => {}
}
fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmgr: &mut KernelManager,
fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmgr: &mut KernelManager, coremgr: &mut CoreManager,
_repeaters: &mut [repeater::Repeater], _routing_table: &mut drtio_routing::RoutingTable, rank: &mut u8,
router: &mut routing::Router, self_destination: &mut u8, packet: drtioaux::Packet
) -> Result<(), drtioaux::Error<!>> {
@ -197,7 +207,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
let repno = hop - 1;
match _repeaters[repno].aux_forward(&drtioaux::Packet::DestinationStatusRequest {
destination: destination
}) {
}, router, _routing_table, *rank, *self_destination) {
Ok(()) => (),
Err(drtioaux::Error::LinkDown) => drtioaux::send(0, &drtioaux::Packet::DestinationDownReply)?,
Err(e) => {
@ -251,7 +261,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}
drtioaux::Packet::MonitorRequest { destination: _destination, channel, probe } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let value;
#[cfg(has_rtio_moninj)]
unsafe {
@ -268,7 +278,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
drtioaux::send(0, &reply)
},
drtioaux::Packet::InjectionRequest { destination: _destination, channel, overrd, value } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
#[cfg(has_rtio_moninj)]
unsafe {
csr::rtio_moninj::inj_chan_sel_write(channel as _);
@ -278,7 +288,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
Ok(())
},
drtioaux::Packet::InjectionStatusRequest { destination: _destination, channel, overrd } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let value;
#[cfg(has_rtio_moninj)]
unsafe {
@ -294,22 +304,22 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
},
drtioaux::Packet::I2cStartRequest { destination: _destination, busno } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = i2c::start(busno).is_ok();
drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded })
}
drtioaux::Packet::I2cRestartRequest { destination: _destination, busno } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = i2c::restart(busno).is_ok();
drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded })
}
drtioaux::Packet::I2cStopRequest { destination: _destination, busno } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = i2c::stop(busno).is_ok();
drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded })
}
drtioaux::Packet::I2cWriteRequest { destination: _destination, busno, data } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
match i2c::write(busno, data) {
Ok(ack) => drtioaux::send(0,
&drtioaux::Packet::I2cWriteReply { succeeded: true, ack: ack }),
@ -318,7 +328,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}
}
drtioaux::Packet::I2cReadRequest { destination: _destination, busno, ack } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
match i2c::read(busno, ack) {
Ok(data) => drtioaux::send(0,
&drtioaux::Packet::I2cReadReply { succeeded: true, data: data }),
@ -327,25 +337,25 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}
}
drtioaux::Packet::I2cSwitchSelectRequest { destination: _destination, busno, address, mask } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = i2c::switch_select(busno, address, mask).is_ok();
drtioaux::send(0, &drtioaux::Packet::I2cBasicReply { succeeded: succeeded })
}
drtioaux::Packet::SpiSetConfigRequest { destination: _destination, busno, flags, length, div, cs } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = spi::set_config(busno, flags, length, div, cs).is_ok();
drtioaux::send(0,
&drtioaux::Packet::SpiBasicReply { succeeded: succeeded })
},
drtioaux::Packet::SpiWriteRequest { destination: _destination, busno, data } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = spi::write(busno, data).is_ok();
drtioaux::send(0,
&drtioaux::Packet::SpiBasicReply { succeeded: succeeded })
}
drtioaux::Packet::SpiReadRequest { destination: _destination, busno } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
match spi::read(busno) {
Ok(data) => drtioaux::send(0,
&drtioaux::Packet::SpiReadReply { succeeded: true, data: data }),
@ -355,7 +365,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}
drtioaux::Packet::AnalyzerHeaderRequest { destination: _destination } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let header = analyzer.get_header();
drtioaux::send(0, &drtioaux::Packet::AnalyzerHeader {
total_byte_count: header.total_byte_count,
@ -365,7 +375,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}
drtioaux::Packet::AnalyzerDataRequest { destination: _destination } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let mut data_slice: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
let meta = analyzer.get_data(&mut data_slice);
drtioaux::send(0, &drtioaux::Packet::AnalyzerData {
@ -376,7 +386,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}
drtioaux::Packet::DmaAddTraceRequest { source, destination, id, status, length, trace } => {
forward!(_routing_table, destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, destination, *rank, *self_destination, _repeaters, &packet);
*self_destination = destination;
let succeeded = dmamgr.add(source, id, status, &trace, length as usize).is_ok();
router.send(drtioaux::Packet::DmaAddTraceReply {
@ -384,19 +394,19 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}, _routing_table, *rank, *self_destination)
}
drtioaux::Packet::DmaAddTraceReply { source, destination: _destination, id, succeeded } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
dmamgr.ack_upload(kernelmgr, source, id, succeeded, router, *rank, *self_destination, _routing_table);
Ok(())
}
drtioaux::Packet::DmaRemoveTraceRequest { source, destination: _destination, id } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = dmamgr.erase(source, id).is_ok();
router.send(drtioaux::Packet::DmaRemoveTraceReply {
destination: source, succeeded: succeeded
}, _routing_table, *rank, *self_destination)
}
drtioaux::Packet::DmaPlaybackRequest { source, destination: _destination, id, timestamp } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
// no DMA with a running kernel
let succeeded = !kernelmgr.is_running() && dmamgr.playback(source, id, timestamp).is_ok();
router.send(drtioaux::Packet::DmaPlaybackReply {
@ -404,27 +414,27 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}, _routing_table, *rank, *self_destination)
}
drtioaux::Packet::DmaPlaybackReply { destination: _destination, succeeded } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
if !succeeded {
kernelmgr.ddma_nack();
}
Ok(())
}
drtioaux::Packet::DmaPlaybackStatus { source: _, destination: _destination, id, error, channel, timestamp } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
dmamgr.remote_finished(kernelmgr, id, error, channel, timestamp);
Ok(())
}
drtioaux::Packet::SubkernelAddDataRequest { destination, id, status, length, data } => {
forward!(_routing_table, destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, destination, *rank, *self_destination, _repeaters, &packet);
*self_destination = destination;
let succeeded = kernelmgr.add(id, status, &data, length as usize).is_ok();
drtioaux::send(0,
&drtioaux::Packet::SubkernelAddDataReply { succeeded: succeeded })
}
drtioaux::Packet::SubkernelLoadRunRequest { source, destination: _destination, id, run } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
drtioaux::Packet::SubkernelLoadRunRequest { source, destination: _destination, id, run, timestamp } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let mut succeeded = kernelmgr.load(id).is_ok();
// allow preloading a kernel with delayed run
if run {
@ -432,7 +442,7 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
// cannot run kernel while DDMA is running
succeeded = false;
} else {
succeeded |= kernelmgr.run(source, id).is_ok();
succeeded |= kernelmgr.run(source, id, timestamp).is_ok();
}
}
router.send(drtioaux::Packet::SubkernelLoadRunReply {
@ -441,35 +451,41 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
_routing_table, *rank, *self_destination)
}
drtioaux::Packet::SubkernelLoadRunReply { destination: _destination, succeeded } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
// received if local subkernel started another, remote subkernel
kernelmgr.subkernel_load_run_reply(succeeded, *self_destination);
Ok(())
}
drtioaux::Packet::SubkernelFinished { destination: _destination, id, with_exception, exception_src } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
kernelmgr.remote_subkernel_finished(id, with_exception, exception_src);
Ok(())
}
drtioaux::Packet::SubkernelExceptionRequest { destination: _destination } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
let mut data_slice: [u8; SAT_PAYLOAD_MAX_SIZE] = [0; SAT_PAYLOAD_MAX_SIZE];
drtioaux::Packet::SubkernelExceptionRequest { source, destination: _destination } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let mut data_slice: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
let meta = kernelmgr.exception_get_slice(&mut data_slice);
drtioaux::send(0, &drtioaux::Packet::SubkernelException {
router.send(drtioaux::Packet::SubkernelException {
destination: source,
last: meta.status.is_last(),
length: meta.len,
data: data_slice,
})
}, _routing_table, *rank, *self_destination)
}
drtioaux::Packet::SubkernelException { destination: _destination, last, length, data } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
kernelmgr.received_exception(&data[..length as usize], last, router, _routing_table, *rank, *self_destination);
Ok(())
}
drtioaux::Packet::SubkernelMessage { source, destination: _destination, id, status, length, data } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
kernelmgr.message_handle_incoming(status, length as usize, id, &data);
router.send(drtioaux::Packet::SubkernelMessageAck {
destination: source
}, _routing_table, *rank, *self_destination)
}
drtioaux::Packet::SubkernelMessageAck { destination: _destination } => {
forward!(_routing_table, _destination, *rank, _repeaters, &packet);
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
if kernelmgr.message_ack_slice() {
let mut data_slice: [u8; MASTER_PAYLOAD_MAX_SIZE] = [0; MASTER_PAYLOAD_MAX_SIZE];
if let Some(meta) = kernelmgr.message_get_slice(&mut data_slice) {
@ -485,6 +501,167 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
Ok(())
}
drtioaux::Packet::CoreMgmtGetLogRequest { destination: _destination, clear } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let mut data_slice = [0; SAT_PAYLOAD_MAX_SIZE];
if let Ok(meta) = coremgr.log_get_slice(&mut data_slice, clear) {
drtioaux::send(
0,
&drtioaux::Packet::CoreMgmtGetLogReply {
last: meta.status.is_last(),
length: meta.len as u16,
data: data_slice,
},
)
} else {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
}
}
drtioaux::Packet::CoreMgmtClearLogRequest { destination: _destination } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: mgmt::clear_log().is_ok() })
}
drtioaux::Packet::CoreMgmtSetLogLevelRequest {destination: _destination, log_level } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
if let Ok(level_filter) = mgmt::byte_to_level_filter(log_level) {
info!("changing log level to {}", level_filter);
log::set_max_level(level_filter);
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
} else {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
}
}
drtioaux::Packet::CoreMgmtSetUartLogLevelRequest { destination: _destination, log_level } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
if let Ok(level_filter) = mgmt::byte_to_level_filter(log_level) {
info!("changing UART log level to {}", level_filter);
logger_artiq::BufferLogger::with(|logger|
logger.set_uart_log_level(level_filter));
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
} else {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
}
}
drtioaux::Packet::CoreMgmtConfigReadRequest {
destination: _destination,
length,
key,
} => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let mut value_slice = [0; SAT_PAYLOAD_MAX_SIZE];
let key_slice = &key[..length as usize];
if !key_slice.is_ascii() {
error!("invalid key");
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
} else {
let key = core::str::from_utf8(key_slice).unwrap();
if coremgr.fetch_config_value(key).is_ok() {
let meta = coremgr.get_config_value_slice(&mut value_slice);
drtioaux::send(
0,
&drtioaux::Packet::CoreMgmtConfigReadReply {
length: meta.len as u16,
last: meta.status.is_last(),
value: value_slice,
},
)
} else {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false })
}
}
}
drtioaux::Packet::CoreMgmtConfigReadContinue {
destination: _destination,
} => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let mut value_slice = [0; SAT_PAYLOAD_MAX_SIZE];
let meta = coremgr.get_config_value_slice(&mut value_slice);
drtioaux::send(
0,
&drtioaux::Packet::CoreMgmtConfigReadReply {
length: meta.len as u16,
last: meta.status.is_last(),
value: value_slice,
},
)
}
drtioaux::Packet::CoreMgmtConfigWriteRequest { destination: _destination, last, length, data } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
coremgr.add_config_data(&data, length as usize);
if last {
coremgr.write_config()
} else {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
}
}
drtioaux::Packet::CoreMgmtConfigRemoveRequest { destination: _destination, length, key } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let key = core::str::from_utf8(&key[..length as usize]).unwrap();
let succeeded = config::remove(key)
.map_err(|err| warn!("error on removing config: {:?}", err))
.is_ok();
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded })
}
drtioaux::Packet::CoreMgmtConfigEraseRequest { destination: _destination } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
let succeeded = config::erase()
.map_err(|err| warn!("error on erasing config: {:?}", err))
.is_ok();
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded })
}
drtioaux::Packet::CoreMgmtRebootRequest { destination: _destination } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })?;
warn!("restarting");
unsafe { spiflash::reload(); }
}
drtioaux::Packet::CoreMgmtFlashRequest { destination: _destination, payload_length } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
coremgr.allocate_image_buffer(payload_length as usize);
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
}
drtioaux::Packet::CoreMgmtFlashAddDataRequest { destination: _destination, last, length, data } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
coremgr.add_image_data(&data, length as usize);
if last {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtDropLink)
} else {
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: true })
}
}
drtioaux::Packet::CoreMgmtDropLinkAck { destination: _destination } => {
forward!(router, _routing_table, _destination, *rank, *self_destination, _repeaters, &packet);
#[cfg(not(has_drtio_eem))]
unsafe {
csr::gt_drtio::txenable_write(0);
}
#[cfg(has_drtio_eem)]
unsafe {
csr::eem_transceiver::txenable_write(0);
}
coremgr.flash_image();
warn!("restarting");
unsafe { spiflash::reload(); }
}
_ => {
warn!("received unexpected aux packet");
Ok(())
@ -493,13 +670,13 @@ fn process_aux_packet(dmamgr: &mut DmaManager, analyzer: &mut Analyzer, kernelmg
}
fn process_aux_packets(dma_manager: &mut DmaManager, analyzer: &mut Analyzer,
kernelmgr: &mut KernelManager, repeaters: &mut [repeater::Repeater],
kernelmgr: &mut KernelManager, coremgr: &mut CoreManager, repeaters: &mut [repeater::Repeater],
routing_table: &mut drtio_routing::RoutingTable, rank: &mut u8, router: &mut routing::Router,
destination: &mut u8) {
let result =
drtioaux::recv(0).and_then(|packet| {
if let Some(packet) = packet.or_else(|| router.get_local_packet()) {
process_aux_packet(dma_manager, analyzer, kernelmgr,
process_aux_packet(dma_manager, analyzer, kernelmgr, coremgr,
repeaters, routing_table, rank, router, destination, packet)
} else {
Ok(())
@ -654,6 +831,27 @@ fn sysclk_setup() {
}
}
fn setup_log_levels() {
match config::read_str("log_level", |r| r.map(|s| s.parse())) {
Ok(Ok(log_level_filter)) => {
info!("log level set to {} by `log_level` config key",
log_level_filter);
log::set_max_level(log_level_filter);
}
_ => info!("log level set to INFO by default")
}
match config::read_str("uart_log_level", |r| r.map(|s| s.parse())) {
Ok(Ok(uart_log_level_filter)) => {
info!("UART log level set to {} by `uart_log_level` config key",
uart_log_level_filter);
logger_artiq::BufferLogger::with(|logger|
logger.set_uart_log_level(uart_log_level_filter));
}
_ => info!("UART log level set to INFO by default")
}
}
static mut LOG_BUFFER: [u8; 1<<17] = [0; 1<<17];
#[no_mangle]
pub extern fn main() -> i32 {
@ -673,12 +871,21 @@ pub extern fn main() -> i32 {
irq::enable(csr::WRPLL_INTERRUPT);
clock::init();
uart_logger::ConsoleLogger::register();
unsafe {
logger_artiq::BufferLogger::new(&mut LOG_BUFFER[..]).register(||
boot::start_user(startup as usize));
}
0
}
fn startup() {
info!("ARTIQ satellite manager starting...");
info!("software ident {}", csr::CONFIG_IDENTIFIER_STR);
info!("gateware ident {}", ident::read(&mut [0; 64]));
setup_log_levels();
#[cfg(has_i2c)]
i2c::init().expect("I2C initialization failed");
#[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))]
@ -742,7 +949,7 @@ pub extern fn main() -> i32 {
io_expander.service().unwrap();
}
#[cfg(not(soc_platform = "efc"))]
#[cfg(not(has_drtio_eem))]
unsafe {
csr::gt_drtio::txenable_write(0xffffffffu32 as _);
}
@ -754,8 +961,25 @@ pub extern fn main() -> i32 {
init_rtio_crg();
config::read_str("sed_spread_enable", |r| {
match r {
Ok("1") => { info!("SED spreading enabled"); toggle_sed_spread(1); },
Ok("0") => { info!("SED spreading disabled"); toggle_sed_spread(0); },
Ok(_) => {
warn!("sed_spread_enable value not supported (only 1, 0 allowed), disabling by default");
toggle_sed_spread(0);
},
Err(_) => { info!("SED spreading disabled by default"); toggle_sed_spread(0) },
}
});
#[cfg(has_drtio_eem)]
{
drtio_eem::init();
unsafe {
csr::eem_transceiver::rx_ready_write(1)
}
}
#[cfg(has_drtio_routing)]
let mut repeaters = [repeater::Repeater::default(); csr::DRTIOREP.len()];
@ -807,6 +1031,7 @@ pub extern fn main() -> i32 {
let mut dma_manager = DmaManager::new();
let mut analyzer = Analyzer::new();
let mut kernelmgr = KernelManager::new();
let mut coremgr = CoreManager::new();
cricon_select(RtioMaster::Drtio);
drtioaux::reset(0);
@ -816,7 +1041,7 @@ pub extern fn main() -> i32 {
while drtiosat_link_rx_up() {
drtiosat_process_errors();
process_aux_packets(&mut dma_manager, &mut analyzer,
&mut kernelmgr, &mut repeaters, &mut routing_table,
&mut kernelmgr, &mut coremgr, &mut repeaters, &mut routing_table,
&mut rank, &mut router, &mut destination);
for rep in repeaters.iter_mut() {
rep.service(&routing_table, rank, destination, &mut router);

View File

@ -0,0 +1,149 @@
use alloc::vec::Vec;
use byteorder::{ByteOrder, NativeEndian};
use crc::crc32;
use routing::{Sliceable, SliceMeta};
use board_artiq::drtioaux;
use board_misoc::{mem, config, spiflash};
use log::LevelFilter;
use logger_artiq::BufferLogger;
use io::{Cursor, ProtoRead, ProtoWrite};
use proto_artiq::drtioaux_proto::SAT_PAYLOAD_MAX_SIZE;
pub fn clear_log() -> Result<(), ()> {
BufferLogger::with(|logger| {
let mut buffer = logger.buffer()?;
Ok(buffer.clear())
}).map_err(|()| error!("error on clearing log buffer"))
}
pub fn byte_to_level_filter(level_byte: u8) -> Result<LevelFilter, ()> {
Ok(match level_byte {
0 => LevelFilter::Off,
1 => LevelFilter::Error,
2 => LevelFilter::Warn,
3 => LevelFilter::Info,
4 => LevelFilter::Debug,
5 => LevelFilter::Trace,
lv => {
error!("unknown log level: {}", lv);
return Err(());
}
})
}
pub struct Manager {
config_payload: Cursor<Vec<u8>>,
image_payload: Cursor<Vec<u8>>,
last_value: Sliceable,
last_log: Sliceable,
}
impl Manager {
pub fn new() -> Manager {
Manager {
config_payload: Cursor::new(Vec::new()),
image_payload: Cursor::new(Vec::new()),
last_value: Sliceable::new(0, Vec::new()),
last_log: Sliceable::new(0, Vec::new()),
}
}
pub fn fetch_config_value(&mut self, key: &str) -> Result<(), ()> {
config::read(key, |result| result.map(
|value| self.last_value = Sliceable::new(0, value.to_vec())
)).map_err(|_err| warn!("read error: no such key"))
}
pub fn log_get_slice(&mut self, data_slice: &mut [u8; SAT_PAYLOAD_MAX_SIZE], consume: bool) -> Result<SliceMeta, ()> {
// Populate buffer if depleted
if self.last_log.at_end() {
BufferLogger::with(|logger| {
let mut buffer = logger.buffer()?;
self.last_log = Sliceable::new(0, buffer.extract().as_bytes().to_vec());
if consume {
buffer.clear();
}
Ok(())
}).map_err(|()| error!("error on getting log buffer"))?;
}
Ok(self.last_log.get_slice_satellite(data_slice))
}
pub fn get_config_value_slice(&mut self, data_slice: &mut [u8; SAT_PAYLOAD_MAX_SIZE]) -> SliceMeta {
self.last_value.get_slice_satellite(data_slice)
}
pub fn add_config_data(&mut self, data: &[u8], data_len: usize) {
self.config_payload.write_all(&data[..data_len]).unwrap();
}
pub fn clear_config_data(&mut self) {
self.config_payload.get_mut().clear();
self.config_payload.set_position(0);
}
pub fn write_config(&mut self) -> Result<(), drtioaux::Error<!>> {
let key = match self.config_payload.read_string() {
Ok(key) => key,
Err(err) => {
self.clear_config_data();
error!("error on reading key: {:?}", err);
return drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded: false });
}
};
let value = self.config_payload.read_bytes().unwrap();
let succeeded = config::write(&key, &value).map_err(|err| {
error!("error on writing config: {:?}", err);
}).is_ok();
self.clear_config_data();
drtioaux::send(0, &drtioaux::Packet::CoreMgmtReply { succeeded })
}
pub fn allocate_image_buffer(&mut self, image_size: usize) {
self.image_payload = Cursor::new(Vec::with_capacity(image_size));
}
pub fn add_image_data(&mut self, data: &[u8], data_len: usize) {
self.image_payload.write_all(&data[..data_len]).unwrap();
}
pub fn flash_image(&self) {
let image = &self.image_payload.get_ref()[..];
let (expected_crc, mut image) = {
let (image, crc_slice) = image.split_at(image.len() - 4);
(NativeEndian::read_u32(crc_slice), image)
};
let actual_crc = crc32::checksum_ieee(image);
if actual_crc == expected_crc {
let bin_origins = [
("gateware" , 0 ),
("bootloader", mem::ROM_BASE ),
("firmware" , mem::FLASH_BOOT_ADDRESS),
];
for (name, origin) in bin_origins {
info!("flashing {} binary...", name);
let size = NativeEndian::read_u32(&image[..4]) as usize;
image = &image[4..];
let (bin, remaining) = image.split_at(size);
image = remaining;
unsafe { spiflash::flash_binary(origin, bin) };
}
} else {
panic!("CRC failed, images have not been written to flash.\n(actual {:08x}, expected {:08x})", actual_crc, expected_crc);
}
}
}

View File

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

View File

@ -57,7 +57,7 @@ impl Sliceable {
self.data.extend(data);
}
get_slice_fn!(get_slice_sat, SAT_PAYLOAD_MAX_SIZE);
get_slice_fn!(get_slice_satellite, SAT_PAYLOAD_MAX_SIZE);
get_slice_fn!(get_slice_master, MASTER_PAYLOAD_MAX_SIZE);
}

View File

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

View File

@ -25,6 +25,9 @@ def get_argparser():
help="Simulation - does not connect to device")
parser.add_argument("core_addr", metavar="CORE_ADDR",
help="hostname or IP address of the core device")
parser.add_argument("-s", "--drtio-dest", default=0,
metavar="DRTIO_DEST", type=int,
help="specifies the DRTIO destination")
return parser
@ -39,7 +42,7 @@ async def get_logs_sim(host):
log_with_name("firmware.simulation", logging.INFO, "hello " + host)
async def get_logs(host):
async def get_logs(host, drtio_dest):
try:
reader, writer = await async_open_connection(
host,
@ -49,6 +52,7 @@ async def get_logs(host):
max_fails=3,
)
writer.write(b"ARTIQ management\n")
writer.write(drtio_dest.to_bytes(1))
endian = await reader.readexactly(1)
if endian == b"e":
endian = "<"
@ -96,7 +100,7 @@ def main():
signal_handler.setup()
try:
get_logs_task = asyncio.ensure_future(
get_logs_sim(args.core_addr) if args.simulation else get_logs(args.core_addr),
get_logs_sim(args.core_addr) if args.simulation else get_logs(args.core_addr, args.drtio_dest),
loop=loop)
try:
server = Server({"corelog": PingTarget()}, None, True)

View File

@ -7,7 +7,7 @@ import os
import logging
import sys
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt6 import QtCore, QtGui, QtWidgets
from qasync import QEventLoop
from sipyco.asyncio_tools import atexit_register_coroutine
@ -68,9 +68,9 @@ class Browser(QtWidgets.QMainWindow):
browse_root, dataset_sub)
smgr.register(self.experiments)
self.experiments.setHorizontalScrollBarPolicy(
QtCore.Qt.ScrollBarAsNeeded)
QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.experiments.setVerticalScrollBarPolicy(
QtCore.Qt.ScrollBarAsNeeded)
QtCore.Qt.ScrollBarPolicy.ScrollBarAsNeeded)
self.setCentralWidget(self.experiments)
self.files = files.FilesDock(dataset_sub, browse_root)
@ -91,29 +91,29 @@ class Browser(QtWidgets.QMainWindow):
self.log = log.LogDock(None, "log")
smgr.register(self.log)
self.log.setFeatures(self.log.DockWidgetMovable |
self.log.DockWidgetFloatable)
self.log.setFeatures(self.log.DockWidgetFeature.DockWidgetMovable |
self.log.DockWidgetFeature.DockWidgetFloatable)
self.addDockWidget(QtCore.Qt.LeftDockWidgetArea, self.files)
self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.applets)
self.addDockWidget(QtCore.Qt.RightDockWidgetArea, self.datasets)
self.addDockWidget(QtCore.Qt.BottomDockWidgetArea, self.log)
self.addDockWidget(QtCore.Qt.DockWidgetArea.LeftDockWidgetArea, self.files)
self.addDockWidget(QtCore.Qt.DockWidgetArea.BottomDockWidgetArea, self.applets)
self.addDockWidget(QtCore.Qt.DockWidgetArea.RightDockWidgetArea, self.datasets)
self.addDockWidget(QtCore.Qt.DockWidgetArea.BottomDockWidgetArea, self.log)
g = self.menuBar().addMenu("&Experiment")
a = QtWidgets.QAction("&Open", self)
a = QtGui.QAction("&Open", self)
a.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.SP_DialogOpenButton))
a.setShortcuts(QtGui.QKeySequence.Open)
QtWidgets.QStyle.StandardPixmap.SP_DialogOpenButton))
a.setShortcuts(QtGui.QKeySequence.StandardKey.Open)
a.setStatusTip("Open an experiment")
a.triggered.connect(self.experiments.select_experiment)
g.addAction(a)
g = self.menuBar().addMenu("&View")
a = QtWidgets.QAction("Cascade", self)
a = QtGui.QAction("Cascade", self)
a.setStatusTip("Cascade experiment windows")
a.triggered.connect(self.experiments.cascadeSubWindows)
g.addAction(a)
a = QtWidgets.QAction("Tile", self)
a = QtGui.QAction("Tile", self)
a.setStatusTip("Tile experiment windows")
a.triggered.connect(self.experiments.tileSubWindows)
g.addAction(a)
@ -140,7 +140,12 @@ def main():
args.db_file = os.path.join(get_user_config_dir(), "artiq_browser.pyon")
widget_log_handler = log.init_log(args, "browser")
app = QtWidgets.QApplication(["ARTIQ Browser"])
forced_platform = []
if (QtGui.QGuiApplication.platformName() == "wayland" and
not os.getenv("QT_QPA_PLATFORM")):
# force XCB instead of Wayland due to applets not embedding
forced_platform = ["-platform", "xcb"]
app = QtWidgets.QApplication(["ARTIQ Browser"] + forced_platform)
loop = QEventLoop(app)
asyncio.set_event_loop(loop)
atexit.register(loop.close)

View File

@ -1,10 +1,4 @@
#!/usr/bin/env python3
"""
Client to send commands to :mod:`artiq_master` and display results locally.
The client can perform actions such as accessing/setting datasets,
scanning devices, scheduling experiments, and looking for experiments/devices.
"""
import argparse
import logging
@ -40,10 +34,10 @@ def get_argparser():
parser = argparse.ArgumentParser(description="ARTIQ CLI client")
parser.add_argument(
"-s", "--server", default="::1",
help="hostname or IP of the master to connect to")
help="hostname or IP of the master to connect to (default: %(default)s)")
parser.add_argument(
"--port", default=None, type=int,
help="TCP port to use to connect to the master")
help="TCP port to use to connect to the master (default: %(default)s)")
parser.add_argument("--version", action="version",
version="ARTIQ v{}".format(artiq_version),
help="print the ARTIQ version number")
@ -59,7 +53,8 @@ def get_argparser():
help="priority (higher value means sooner "
"scheduling, default: %(default)s)")
parser_add.add_argument("-t", "--timed", default=None, type=str,
help="set a due date for the experiment")
help="set a due date for the experiment "
"(default: %(default)s)")
parser_add.add_argument("-f", "--flush", default=False,
action="store_true",
help="flush the pipeline before preparing "
@ -80,7 +75,7 @@ def get_argparser():
parser_add.add_argument("file", metavar="FILE",
help="file containing the experiment to run")
parser_add.add_argument("arguments", metavar="ARGUMENTS", nargs="*",
help="run arguments")
help="run arguments, use format KEY=VALUE")
parser_delete = subparsers.add_parser("delete",
help="delete an experiment "

View File

@ -1,7 +1,10 @@
#!/usr/bin/env python3
import argparse
import os
import struct
import tempfile
import atexit
from sipyco import common_args
@ -9,6 +12,7 @@ from artiq import __version__ as artiq_version
from artiq.master.databases import DeviceDB
from artiq.coredevice.comm_kernel import CommKernel
from artiq.coredevice.comm_mgmt import CommMgmt
from artiq.frontend.flash_tools import bit2bin, fetch_bin
def get_argparser():
@ -85,6 +89,20 @@ def get_argparser():
t_boot = tools.add_parser("reboot",
help="reboot the running system")
# flashing
t_flash = tools.add_parser("flash",
help="flash the running system")
p_directory = t_flash.add_argument("directory",
metavar="DIRECTORY", type=str,
help="directory that contains the "
"binaries")
p_srcbuild = t_flash.add_argument("--srcbuild",
help="board binaries directory is laid "
"out as a source build tree",
default=False, action="store_true")
# misc debug
t_debug = tools.add_parser("debug",
help="specialized debug functions")
@ -95,6 +113,12 @@ def get_argparser():
p_allocator = subparsers.add_parser("allocator",
help="show heap layout")
# manage target
p_drtio_dest = parser.add_argument("-s", "--drtio-dest", default=0,
metavar="DRTIO_DEST", type=int,
help="specify DRTIO destination that "
"receives this command")
return parser
@ -107,7 +131,7 @@ def main():
core_addr = ddb.get("core", resolve_alias=True)["arguments"]["host"]
else:
core_addr = args.device
mgmt = CommMgmt(core_addr)
mgmt = CommMgmt(core_addr, drtio_dest=args.drtio_dest)
if args.tool == "log":
if args.action == "set_level":
@ -138,6 +162,39 @@ def main():
if args.action == "erase":
mgmt.config_erase()
if args.tool == "flash":
retrieved_bins = []
bin_dict = {
"zynq":[
["boot"]
],
"riscv": [
["gateware"],
["bootloader"],
["runtime", "satman"],
],
}
for bin_list in bin_dict.values():
try:
bins = []
for bin_name in bin_list:
bins.append(fetch_bin(
args.directory, bin_name, args.srcbuild))
retrieved_bins.append(bins)
except FileNotFoundError:
pass
if retrieved_bins is None:
raise FileNotFoundError("neither risc-v nor zynq binaries were found")
if len(retrieved_bins) > 1:
raise ValueError("both risc-v and zynq binaries were found, "
"please clean up your build directory. ")
bins = retrieved_bins[0]
mgmt.flash(bins)
if args.tool == "reboot":
mgmt.reboot()

View File

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

View File

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

View File

@ -14,7 +14,7 @@ from sipyco import common_args
from artiq import __version__ as artiq_version
from artiq.remoting import SSHClient, LocalClient
from artiq.frontend.bit2bin import bit2bin
from artiq.frontend.flash_tools import artifact_path, bit2bin, fetch_bin
def get_argparser():
@ -30,7 +30,8 @@ Valid actions:
* firmware: write firmware to flash
* load: load main gateware bitstream into device (volatile but fast)
* erase: erase flash memory
* start: trigger the target to (re)load its gateware bitstream from flash
* start: trigger the target to (re)load its gateware bitstream from flash.
If your core device is reachable by network, prefer 'artiq_coremgmt reboot'.
Prerequisites:
@ -301,46 +302,19 @@ def main():
programmer = config["programmer"](client, preinit_script=args.preinit_command)
def artifact_path(this_binary_dir, *path_filename):
if args.srcbuild:
# source tree - use path elements to locate file
return os.path.join(this_binary_dir, *path_filename)
else:
# flat tree - all files in the same directory, discard path elements
*_, filename = path_filename
return os.path.join(this_binary_dir, filename)
def convert_gateware(bit_filename):
bin_handle, bin_filename = tempfile.mkstemp(
prefix="artiq_", suffix="_" + os.path.basename(bit_filename))
with open(bit_filename, "rb") as bit_file, open(bin_handle, "wb") as bin_file:
bit2bin(bit_file, bin_file)
atexit.register(lambda: os.unlink(bin_filename))
return bin_filename
for action in args.action:
if action == "gateware":
gateware_bin = convert_gateware(
artifact_path(binary_dir, "gateware", "top.bit"))
gateware_bin = fetch_bin(binary_dir, ["gateware"], args.srcbuild)
programmer.write_binary(*config["gateware"], gateware_bin)
elif action == "bootloader":
bootloader_bin = artifact_path(binary_dir, "software", "bootloader", "bootloader.bin")
bootloader_bin = fetch_bin(binary_dir, ["bootloader"], args.srcbuild)
programmer.write_binary(*config["bootloader"], bootloader_bin)
elif action == "storage":
storage_img = args.storage
programmer.write_binary(*config["storage"], storage_img)
elif action == "firmware":
firmware_fbis = []
for firmware in "satman", "runtime":
filename = artifact_path(binary_dir, "software", firmware, firmware + ".fbi")
if os.path.exists(filename):
firmware_fbis.append(filename)
if not firmware_fbis:
raise FileNotFoundError("no firmware found")
if len(firmware_fbis) > 1:
raise ValueError("more than one firmware file, please clean up your build directory. "
"Found firmware files: {}".format(" ".join(firmware_fbis)))
programmer.write_binary(*config["firmware"], firmware_fbis[0])
firmware_fbi = fetch_bin(binary_dir, ["satman", "runtime"], args.srcbuild)
programmer.write_binary(*config["firmware"], firmware_fbi)
elif action == "load":
gateware_bit = artifact_path(binary_dir, "gateware", "top.bit")
programmer.load(gateware_bit, 0)

View File

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

View File

@ -161,7 +161,7 @@ def get_argparser(with_file=True):
parser.add_argument("file", metavar="FILE",
help="file containing the experiment to run")
parser.add_argument("arguments", metavar="ARGUMENTS", nargs="*",
help="run arguments")
help="run arguments, use format KEY=VALUE")
return parser

View File

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

View File

@ -1,69 +0,0 @@
#!/usr/bin/env python3
# Copyright 2014-2017 Robert Jordens <jordens@gmail.com>
# after
# https://github.com/mfischer/fpgadev-zynq/blob/master/top/python/bit_to_zynq_bin.py
import struct
def flip32(data):
sl = struct.Struct("<I")
sb = struct.Struct(">I")
b = memoryview(data)
d = bytearray(len(data))
for offset in range(0, len(data), sl.size):
sb.pack_into(d, offset, *sl.unpack_from(b, offset))
return d
def bit2bin(bit, bin, flip=False):
l, = struct.unpack(">H", bit.read(2))
if l != 9:
raise ValueError("Missing <0009> header, not a bit file")
_ = bit.read(l) # unknown data
l, = struct.unpack(">H", bit.read(2))
if l != 1:
raise ValueError("Missing <0001> header, not a bit file")
while True:
key = bit.read(1).decode()
if not key:
break
if key in "abcd":
d = bit.read(*struct.unpack(">H", bit.read(2)))
assert d.endswith(b"\x00")
d = d[:-1].decode()
name = {
"a": "Design",
"b": "Part name",
"c": "Date",
"d": "Time"
}[key]
print("{}: {}".format(name, d))
elif key == "e":
l, = struct.unpack(">I", bit.read(4))
print("Bitstream payload length: {:#x}".format(l))
d = bit.read(l)
if flip:
d = flip32(d)
bin.write(d)
else:
d = bit.read(*struct.unpack(">H", bit.read(2)))
print("Unexpected key: {}: {}".format(key, d))
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(
description="Convert FPGA bit files to raw bin format "
"suitable for flashing")
parser.add_argument("-f", "--flip", dest="flip", action="store_true",
default=False, help="Flip 32-bit endianess (needed for Zynq)")
parser.add_argument("bitfile", metavar="BITFILE",
help="Input bit file name")
parser.add_argument("binfile", metavar="BINFILE",
help="Output bin file name")
args = parser.parse_args()
with open(args.bitfile, "rb") as f, open(args.binfile, "wb") as g:
bit2bin(f, g, args.flip)

View File

@ -0,0 +1,112 @@
import atexit
import os
import tempfile
import struct
def artifact_path(this_binary_dir, *path_filename, srcbuild=False):
if srcbuild:
# source tree - use path elements to locate file
return os.path.join(this_binary_dir, *path_filename)
else:
# flat tree - all files in the same directory, discard path elements
*_, filename = path_filename
return os.path.join(this_binary_dir, filename)
def fetch_bin(binary_dir, components, srcbuild=False):
def convert_gateware(bit_filename):
bin_handle, bin_filename = tempfile.mkstemp(
prefix="artiq_", suffix="_" + os.path.basename(bit_filename))
with open(bit_filename, "rb") as bit_file, open(bin_handle, "wb") as bin_file:
bit2bin(bit_file, bin_file)
atexit.register(lambda: os.unlink(bin_filename))
return bin_filename
if len(components) > 1:
bins = []
for option in components:
try:
bins.append(fetch_bin(binary_dir, [option], srcbuild))
except FileNotFoundError:
pass
if bins is None:
raise FileNotFoundError("multiple components not found: {}".format(
" ".join(components)))
if len(bins) > 1:
raise ValueError("more than one file, "
"please clean up your build directory. "
"Found files: {}".format(
" ".join(bins)))
return bins[0]
else:
component = components[0]
path = artifact_path(binary_dir, *{
"gateware": ["gateware", "top.bit"],
"boot": ["boot.bin"],
"bootloader": ["software", "bootloader", "bootloader.bin"],
"runtime": ["software", "runtime", "runtime.fbi"],
"satman": ["software", "satman", "satman.fbi"],
}[component], srcbuild=srcbuild)
if not os.path.exists(path):
raise FileNotFoundError("{} not found".format(component))
if component == "gateware":
path = convert_gateware(path)
return path
# Copyright 2014-2017 Robert Jordens <jordens@gmail.com>
# after
# https://github.com/mfischer/fpgadev-zynq/blob/master/top/python/bit_to_zynq_bin.py
def flip32(data):
sl = struct.Struct("<I")
sb = struct.Struct(">I")
b = memoryview(data)
d = bytearray(len(data))
for offset in range(0, len(data), sl.size):
sb.pack_into(d, offset, *sl.unpack_from(b, offset))
return d
def bit2bin(bit, bin, flip=False):
l, = struct.unpack(">H", bit.read(2))
if l != 9:
raise ValueError("Missing <0009> header, not a bit file")
_ = bit.read(l) # unknown data
l, = struct.unpack(">H", bit.read(2))
if l != 1:
raise ValueError("Missing <0001> header, not a bit file")
while True:
key = bit.read(1).decode()
if not key:
break
if key in "abcd":
d = bit.read(*struct.unpack(">H", bit.read(2)))
assert d.endswith(b"\x00")
d = d[:-1].decode()
name = {
"a": "Design",
"b": "Part name",
"c": "Date",
"d": "Time"
}[key]
print("{}: {}".format(name, d))
elif key == "e":
l, = struct.unpack(">I", bit.read(4))
print("Bitstream payload length: {:#x}".format(l))
d = bit.read(l)
if flip:
d = flip32(d)
bin.write(d)
else:
d = bit.read(*struct.unpack(">H", bit.read(2)))
print("Unexpected key: {}: {}".format(key, d))

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