forked from M-Labs/artiq
1
0
Fork 0

Compare commits

..

300 Commits
master ... nac3

Author SHA1 Message Date
Sebastien Bourdeauducq 5503c62cd2 flake: update dependencies 2023-12-12 16:14:35 +08:00
Sebastien Bourdeauducq 8b4572f9ca core: pass artiq_builtins to NAC3 2023-12-12 15:54:57 +08:00
Sebastien Bourdeauducq 089bc1f168 flake: update dependencies 2023-12-11 10:31:42 +08:00
Sebastien Bourdeauducq 4c189f8c05 core: make _ConstGenericMarker available to NAC3 2023-12-11 09:31:54 +08:00
Sebastien Bourdeauducq c618d5daa8 core: add ConstGeneric 2023-12-08 19:38:29 +08:00
Sebastien Bourdeauducq 985da815ae flake: update dependencies 2023-12-03 10:37:35 +08:00
Florian Agbuya bd61687e8f flake: fix ncurses on vivado
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2023-12-03 10:29:12 +08:00
Sebastien Bourdeauducq 8c520422f2 Revert "flake: fix vivado on recent nixpkgs (closes #2274)"
This reverts commit 931b3172c4.
2023-12-03 10:27:01 +08:00
Florian Agbuya 9a9b3b7858 flake: add new booktabs dependency for artiq-manual-pdf 2023-11-28 10:24:28 +08:00
Sebastien Bourdeauducq da510919f3 flake: update nac3 2023-11-25 20:17:36 +08:00
Sebastien Bourdeauducq e831cb47b8 windows: add jsonschema dependency 2023-11-25 16:14:22 +08:00
Sebastien Bourdeauducq 9f368ee610 update NAC3, switch Windows to CLANG64 and Python 3.11 2023-11-25 14:00:24 +08:00
Florian Agbuya 931b3172c4 flake: fix vivado on recent nixpkgs (closes #2274)
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2023-11-19 11:32:52 +08:00
Sebastien Bourdeauducq c3802108ba update NAC3 2023-11-11 16:50:26 +08:00
Sebastien Bourdeauducq 5bbac04bef coredevice: use new nac3 binary shift typing rules 2023-11-11 11:08:52 +08:00
Sebastien Bourdeauducq 42a9cc725b flake: update dependencies 2023-11-08 17:30:55 +08:00
David Mak 4fa0419c82 core: Add legacy_parallel
Signed-off-by: David Mak <csdavidmak@ust.hk>
2023-11-04 13:44:51 +08:00
Sebastien Bourdeauducq f3655aa084 flake: update nac3 2023-11-04 13:44:23 +08:00
Sebastien Bourdeauducq c1574ef7fe flake: update nixpkgs 2023-11-01 17:43:16 +08:00
David Mak 4784a42030 test_analyzer: Enable test_ttl_pulse test case
Now that the issue is fixed.

Signed-off-by: David Mak <csdavidmak@ust.hk>
2023-11-01 15:47:51 +08:00
David Mak c0d79a0ce2 embedding_map: Update exception list to match NAC3
Signed-off-by: David Mak <csdavidmak@ust.hk>
2023-11-01 13:29:47 +08:00
David Mak e60d5cab05 artiq_sinara_tester: Fix NAC3 compilation failures
- Fix incorrect types for bin-op/function call
- Explicitly invoke exception constructor for raise statement

Signed-off-by: David Mak <csdavidmak@ust.hk>
2023-10-30 08:15:36 +01:00
David Mak d0a3ba2bc0 artiq_sinara_tester: Refactor Almazny to AlmaznyLegacy
The class was renamed in 45cd438fb8.

Signed-off-by: David Mak <csdavidmak@ust.hk>
2023-10-30 08:15:36 +01:00
Florian Agbuya 92391c4763 flake: fix and upgrade wavedrom (closes #2266)
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2023-10-30 07:29:41 +01:00
Sebastien Bourdeauducq 700a20ad08 flake: update dependencies 2023-10-28 03:50:03 +08:00
David Mak fa5666fb0d docs: Add instructions to run with custom NAC3
Signed-off-by: David Mak <csdavidmak@ust.hk>
2023-10-16 09:07:27 +02:00
Sebastien Bourdeauducq 313449f2bd Merge branch 'master' into nac3 2023-10-06 15:15:27 +08:00
Sebastien Bourdeauducq 1fb5ecf7ed flake: update dependencies 2023-10-06 14:56:57 +08:00
Sebastien Bourdeauducq d96dc11b25 shuttler: use scheduler 2023-10-06 14:41:26 +08:00
Sebastien Bourdeauducq 24c3a2fd0a shuttler: port to NAC3 2023-10-06 14:40:53 +08:00
Sebastien Bourdeauducq fc082b62de Merge branch 'master' into nac3 2023-10-06 12:17:19 +08:00
Sebastien Bourdeauducq f89f775c91 flake: update dependencies 2023-10-05 18:03:34 +08:00
Sebastien Bourdeauducq 602b680b9d firmware: deal with rust nonsense
Fixes
"error: edition 2021 is unstable and only available with -Z unstable-options.
error: could not compile `alloc`"
2023-09-14 10:57:37 +08:00
Sebastien Bourdeauducq 24d2520655 flake: update dependencies 2023-09-13 19:22:41 +08:00
Sebastien Bourdeauducq 7cffa3f8ff Merge branch 'master' into nac3 2023-09-13 19:21:10 +08:00
Sebastien Bourdeauducq 4efb862280 flake: update dependencies 2023-07-17 15:54:39 +08:00
Sebastien Bourdeauducq 511f125c08 Merge branch 'master' into nac3 2023-07-17 15:45:54 +08:00
Sebastien Bourdeauducq caee570c48 Merge branch 'master' into nac3 2023-05-27 19:08:36 +08:00
Sebastien Bourdeauducq 0edcd53c24 artiq_ddb_template: fix almazny 2023-05-09 14:26:59 +08:00
Sebastien Bourdeauducq 250d7e8f0f examples/nac3devices: add almazny 2023-05-09 14:26:45 +08:00
Sebastien Bourdeauducq 60aa64b79e Merge branch 'master' into nac3 2023-05-09 14:26:29 +08:00
Sebastien Bourdeauducq cc41b70852 msys2: add lmdb dependency 2023-05-09 13:13:51 +08:00
Sebastien Bourdeauducq 42e42df43f Merge branch 'master' into nac3 2023-04-30 17:18:05 +08:00
Sebastien Bourdeauducq c139038022 flake: update dependencies 2023-04-30 17:17:39 +08:00
Sebastien Bourdeauducq 730a00d210 flake: update dependencies 2023-04-30 16:53:14 +08:00
Sebastien Bourdeauducq 5d2b3dafe3 remove lit from gitignore 2023-04-30 16:46:35 +08:00
Sebastien Bourdeauducq 825c49a911 artiq_ddb_template: fix pll_en typing 2023-04-30 16:43:52 +08:00
Sebastien Bourdeauducq 2b73ca862e Merge branch 'master' into nac3 2023-04-30 16:40:50 +08:00
Sebastien Bourdeauducq a43baa4cc4 Merge branch 'master' into nac3 2023-03-13 18:25:02 +08:00
Sebastien Bourdeauducq 3bfd743c47 flake: update dependencies 2023-03-13 18:24:33 +08:00
Sebastien Bourdeauducq 78daace19a ad9910: fix pll_en doc 2023-01-15 12:30:08 +08:00
Sebastien Bourdeauducq 9e8167e1fa artiq_ddb_template: fix mistake in 18524911 2023-01-15 12:26:52 +08:00
Sebastien Bourdeauducq 287e55c08a sampler: fix mistake in c591e7e3 2023-01-15 12:26:08 +08:00
Sebastien Bourdeauducq 7635b9ed92 Merge branch 'master' into nac3 2023-01-15 12:23:25 +08:00
Sebastien Bourdeauducq 844193148a msys2: add qt5-svg dependency 2022-11-18 18:19:58 +08:00
Sebastien Bourdeauducq f930b86dbe flake: cleanup 2022-11-18 18:12:38 +08:00
Sebastien Bourdeauducq 66daf3368e flake: remove libartiq-support leftover 2022-11-18 18:12:20 +08:00
Sebastien Bourdeauducq ece1269c2a flake: fix libcrypt.so.1 not found by vivado 2022-11-18 18:11:48 +08:00
Sebastien Bourdeauducq 826281a529 Merge branch 'master' into nac3 2022-11-18 17:10:12 +08:00
Sebastien Bourdeauducq c8f76e3899 flake: reenable tests 2022-08-18 14:37:51 +08:00
Sebastien Bourdeauducq e6067a218b versioneer: fix default 2022-08-18 14:35:23 +08:00
Sebastien Bourdeauducq 0953a07582 Merge branch 'master' into nac3 2022-08-18 14:35:09 +08:00
Sebastien Bourdeauducq c1d865c107 phaser: avoid OverflowError (2) 2022-08-05 22:59:47 +08:00
Sebastien Bourdeauducq a2d0ebabfe phaser: avoid OverflowError 2022-08-05 22:57:32 +08:00
Sebastien Bourdeauducq afd4f369d0 windows: fix python module installation paths 2022-08-05 22:44:09 +08:00
Sebastien Bourdeauducq 5ebccc2378 flake: disable failing sphinx-argparse tests 2022-08-05 22:28:07 +08:00
Sebastien Bourdeauducq d180af1deb flake: bump major version 2022-08-05 22:11:05 +08:00
Sebastien Bourdeauducq cc3eda8e91 flake: update dependencies 2022-08-05 22:08:31 +08:00
Sebastien Bourdeauducq 65300bcf92 flake: update dependencies 2022-07-04 18:08:56 +08:00
Sebastien Bourdeauducq 6483361e6a Merge branch 'master' into nac3 2022-07-02 19:20:15 +08:00
Sebastien Bourdeauducq 5261375301 update dependencies 2022-07-02 19:19:07 +08:00
Sebastien Bourdeauducq 8e206e92f5 test_pulse_rate: port to NAC3 2022-06-07 00:15:51 +08:00
Sebastien Bourdeauducq 85693e45e7 fix coredevice comm serve 2022-06-06 23:57:01 +08:00
Sebastien Bourdeauducq d15d922236 test_compile: port to NAC3 2022-06-06 23:56:14 +08:00
Sebastien Bourdeauducq cc3d86ff12 test_cache: port to NAC3 2022-06-06 23:43:43 +08:00
Sebastien Bourdeauducq dc006b1a40 test_analyzer: port to NAC3 2022-06-06 23:36:27 +08:00
Sebastien Bourdeauducq ba30705fa5 core: allow re-creation of Core object, do not use _allow_registration global variable 2022-06-06 23:13:46 +08:00
Sebastien Bourdeauducq b77f6886be test_i2c: port to NAC3 2022-06-06 22:55:34 +08:00
Sebastien Bourdeauducq cb68ed9f1d test: remove lit tests 2022-06-06 22:45:36 +08:00
Sebastien Bourdeauducq 0607743669 flake: update NAC3, move to LLVM 14, remove LLD 2022-06-06 18:49:49 +08:00
Sebastien Bourdeauducq 77fd47b1fb flake: remove libartiq-support 2022-06-06 18:49:23 +08:00
Sebastien Bourdeauducq ce027d9c51 Merge branch 'master' into nac3 2022-06-06 18:18:05 +08:00
occheung d924bfc958 dyld: handle rebind on symbols relocated by CALL_PLT 2022-06-01 12:42:54 +08:00
occheung 2cd43bf4f7 dyld: support additional RV32 reloc types
The support of LO12 type requires the runtime linker to find the corresponding HI20 symbol. resolve_rela needs the entire relocation section for that.
2022-06-01 12:42:54 +08:00
occheung 5d7b01bd3f dyld: rename pltrel to jmprel
nac3ld will not generate PLT & its relocation section. There might not be a pltrel in that case.
On the other hand, rebinding will not be limited to the symbols in the PLT when linked with nac3ld.
Thus the renaming.
2022-06-01 12:42:54 +08:00
Sebastien Bourdeauducq 7c3d9bb376 doc: remove unnecessary builtins.__in_sphinx__ hack 2022-05-27 15:35:01 +08:00
Sebastien Bourdeauducq 2c7c2d0e3d windows: add pybase64 dependency 2022-05-27 15:33:51 +08:00
Sebastien Bourdeauducq 6ac7aced34 windows: use upstream pyqtgraph and qasync 2022-05-27 15:32:47 +08:00
Sebastien Bourdeauducq c3ad77ec30 Merge branch 'master' into nac3 2022-05-27 15:29:55 +08:00
Sebastien Bourdeauducq c7b8dcac49 flake: update dependencies 2022-05-27 15:28:47 +08:00
Sebastien Bourdeauducq abe6b01191 flake: fix openocd build 2022-05-24 11:30:37 +08:00
Sebastien Bourdeauducq 09e786ed3f msys2: add python-tqdm dependency 2022-05-24 11:15:23 +08:00
Sebastien Bourdeauducq 32d4c520f7 flake: update dependencies 2022-05-24 11:12:30 +08:00
Sebastien Bourdeauducq 45edb9e0f7 Merge branch 'master' into nac3 2022-05-24 11:12:20 +08:00
Sebastien Bourdeauducq 1b348dc002 flake: update NAC3 2022-05-17 12:07:23 +08:00
Sebastien Bourdeauducq fb8c13b541 flake: update dependencies 2022-05-14 16:34:27 +08:00
Sebastien Bourdeauducq fc95dffd0b examples/no_hardware: port to NAC3 2022-04-28 15:38:19 +08:00
Sebastien Bourdeauducq 5504177d48 sim: fix imports 2022-04-28 15:37:44 +08:00
Sebastien Bourdeauducq 8e594d6666 language/core: forward kernel return value 2022-04-28 15:37:30 +08:00
Sebastien Bourdeauducq 49cb570290 examples/kasli_suservo: port to NAC3 2022-04-28 15:28:07 +08:00
Sebastien Bourdeauducq a1651d15f5 urukul: _RegIOUpdate fixes 2022-04-28 15:27:42 +08:00
Sebastien Bourdeauducq 13eb6e89f0 Merge branch 'master' into nac3 2022-04-28 15:01:17 +08:00
Sebastien Bourdeauducq 50767c0365 examples/kc705_nist_clock: port to NAC3 2022-04-27 18:41:59 +08:00
mwojcik 3160378614 almazny: fix missing (but unused) core 2022-04-27 17:31:44 +08:00
mwojcik d6f1c5984a artiq_sinara_tester: fix urukul eeprom_word type 2022-04-27 17:31:19 +08:00
Sebastien Bourdeauducq 614dfde33d flake: update dependencies 2022-04-27 15:43:16 +08:00
Sebastien Bourdeauducq 72aec819f7 revert incorrectly committed part of dd57fdc530 2022-04-27 13:46:44 +08:00
mwojcik 4c21d4a19c artiq_sinara_tester: fix almazny attenuation type 2022-04-27 11:57:20 +08:00
mwojcik 8c825505fd artiq_sinara_tester: fix mirny/almazny freq type 2022-04-26 12:52:58 +08:00
mwojcik 86142db56b artiq_sinara_tester: fix urukul rf switch 2022-04-22 11:03:57 +08:00
Sebastien Bourdeauducq 4e7b1c76cc flake: nix2.5 metadata 2022-04-19 00:10:39 +08:00
Sebastien Bourdeauducq 09f8f9f8bf flake: update NAC3 2022-04-19 00:04:33 +08:00
Sebastien Bourdeauducq efea248b69 flake: use NAC3 PGO build 2022-04-19 00:04:21 +08:00
Sebastien Bourdeauducq dd57fdc530 flake: update NAC3 2022-04-18 18:53:54 +08:00
Sebastien Bourdeauducq 89aa3a3d9d sim/devices: port to NAC3 2022-04-12 16:01:56 +08:00
Sebastien Bourdeauducq 125572b553 language: reimplement sim 2022-04-12 16:01:39 +08:00
Sebastien Bourdeauducq 83676927b5 examples/simple_simulation: fix 2022-04-12 15:59:20 +08:00
Sebastien Bourdeauducq c171deec51 sim: improve formatting 2022-04-12 15:58:54 +08:00
Sebastien Bourdeauducq 0978cd0e35 Merge branch 'master' into nac3 2022-04-12 09:19:59 +08:00
Sebastien Bourdeauducq 5041e5f6bd flake: update dependencies 2022-04-12 09:15:48 +08:00
Sebastien Bourdeauducq dc00f811e2 flake: update dependencies 2022-04-10 08:34:54 +08:00
Sebastien Bourdeauducq 9b910679f8 doc: MSYS2 2022-04-05 18:22:59 +08:00
Sebastien Bourdeauducq 176490a083 windows: fix more setuptools problems 2022-04-05 18:04:49 +08:00
Sebastien Bourdeauducq f704f3daa4 update sipyco 2022-04-05 17:35:58 +08:00
Sebastien Bourdeauducq 13e38913ea windows: work around cretinous setuptools problems (3) 2022-04-05 17:23:52 +08:00
Sebastien Bourdeauducq d75f41f8d2 windows: work around cretinous setuptools problems (2) 2022-04-05 17:13:28 +08:00
Sebastien Bourdeauducq a700c2c4e4 windows: depend on artiq-comtools 2022-04-05 17:10:57 +08:00
Sebastien Bourdeauducq ae5c64619a windows: work around cretinous setuptools problems 2022-04-05 17:07:26 +08:00
Sebastien Bourdeauducq 29c8e3958d windows: depend on qasync 2022-04-05 17:02:26 +08:00
Sebastien Bourdeauducq 113a36a9c0 windows: package qasync 2022-04-05 16:38:04 +08:00
Sebastien Bourdeauducq 64b56077cd windows: package pyqtgraph 2022-04-05 16:26:08 +08:00
Sebastien Bourdeauducq dfb2908d09 windows: clean up easy-install pollution 2022-04-05 11:32:00 +08:00
Sebastien Bourdeauducq 5bc2cf6cc2 windows: add NAC3 to MSYS2 repos 2022-04-05 10:55:52 +08:00
Sebastien Bourdeauducq 0f09bfc3c2 flake: add msys2-repos to hydra 2022-04-05 10:48:36 +08:00
Sebastien Bourdeauducq 73edc01fc6 windows: create msys2 repos 2022-04-05 10:40:52 +08:00
Sebastien Bourdeauducq 546c0ed2c5 windows: set package dependencies 2022-04-05 10:27:47 +08:00
Sebastien Bourdeauducq a4bdc6c9cb build MSYS2 packages 2022-04-05 10:12:29 +08:00
Sebastien Bourdeauducq 724fc62925 setup.py: stop checking for dependencies 2022-04-05 10:12:15 +08:00
Sebastien Bourdeauducq 34f3862558 update NAC3 2022-03-30 08:43:08 +08:00
Sebastien Bourdeauducq b1cfd343eb update NAC3 2022-03-29 08:49:43 +08:00
Sebastien Bourdeauducq 69cda517f6 Merge branch 'master' into nac3 2022-03-28 13:41:17 +08:00
Sebastien Bourdeauducq 1a3607564b urukul: support dds_reset and sync options 2022-03-27 18:02:36 +08:00
Sebastien Bourdeauducq 9d26f887ee update NAC3 2022-03-27 17:53:51 +08:00
Sebastien Bourdeauducq 8ea6a46ee6 update NAC3 2022-03-26 21:32:26 +08:00
Sebastien Bourdeauducq ec8ee1e934 language: clean up EmbeddingMap 2022-03-26 20:42:21 +08:00
Sebastien Bourdeauducq 0233e50d47 coredevice: fix execution of several kernels with attribute writeback 2022-03-26 20:41:37 +08:00
Sebastien Bourdeauducq 0190604ba0 comm_kernel: remove forgotten json import in 6aec423838 2022-03-26 20:22:58 +08:00
Sebastien Bourdeauducq 2bc770ec72 Revert "examples: work around NAC3 segfault"
This reverts commit 317c257778.
2022-03-26 20:21:01 +08:00
Sebastien Bourdeauducq 8e5fc599eb update NAC3 2022-03-26 20:20:50 +08:00
Sebastien Bourdeauducq 5c70b97850 runtime: fix EXCEPTION_ID_LOOKUP 2022-03-26 20:09:11 +08:00
pca006132 6aec423838 coredevice/comm_kernel: attributes writeback update 2022-03-26 19:22:39 +08:00
Sebastien Bourdeauducq bd3ab50a09 ad9910: cleanup import 2022-03-26 17:41:18 +08:00
Sebastien Bourdeauducq 72a6ff6e9c urukul: make RF switch TTL optional 2022-03-26 17:23:17 +08:00
Sebastien Bourdeauducq 317c257778 examples: work around NAC3 segfault 2022-03-26 16:01:03 +08:00
Sebastien Bourdeauducq d2add0a683 update NAC3, define option type 2022-03-26 16:00:49 +08:00
Sebastien Bourdeauducq a4c1f8a079 firmware: add UnwrapNoneError exception 2022-03-26 15:27:49 +08:00
pca006132 457f3c72ce coredevice/comm_kernel: implement attributes writeback 2022-03-25 23:42:24 +08:00
Sebastien Bourdeauducq aa8dfaf0f0 update NAC3 2022-03-24 22:33:24 +08:00
Sebastien Bourdeauducq 743b49e07e coredevice: remove exception workarounds 2022-03-17 19:15:27 +08:00
pca006132 1a71a45225 support builtin exceptions 2022-03-17 15:14:23 +08:00
Sebastien Bourdeauducq 2f55f57803 update dependencies 2022-03-10 17:31:02 +08:00
Sebastien Bourdeauducq 46fe507bd4 Merge branch 'master' into nac3 2022-03-10 17:30:26 +08:00
pca006132 1378cebe2c drivers: use print_rpc 2022-03-09 11:08:46 +08:00
pca006132 be3f05a4c0 artiq/language/core.py: define print_rpc 2022-03-09 11:00:20 +08:00
Sebastien Bourdeauducq 87154ea016 update nac3 2022-03-09 09:15:12 +08:00
Sebastien Bourdeauducq 25b1bbd10e Merge branch 'master' into nac3 2022-03-06 18:33:02 +08:00
Sebastien Bourdeauducq a7612b9736 artiq_sinara_tester: fix type error 2022-03-05 22:44:25 +08:00
Sebastien Bourdeauducq d5806fc959 artiq_sinara_tester: add test_ttl_in NAC3TODO 2022-03-05 22:44:02 +08:00
Sebastien Bourdeauducq c9fbb7024c artiq_flash: fix bit2bin 2022-03-05 22:43:25 +08:00
Sebastien Bourdeauducq 5e4ae4dfd9 ad9910: fix int64 operations 2022-03-05 22:43:01 +08:00
pca006132 af1b6d8d33 embedding_map: avoid key 0
Object key 0 is reserved for builtin exceptions.
2022-03-05 10:28:53 +08:00
Sebastien Bourdeauducq 76132c95c2 update nac3 2022-03-05 10:28:08 +08:00
Sebastien Bourdeauducq 9d82f968f9 artiq_sinara_tester: port to NAC3 2022-03-03 17:07:27 +08:00
Sebastien Bourdeauducq ba106de24f suservo: use bool for enable 2022-03-03 17:07:05 +08:00
Sebastien Bourdeauducq b7d35446e2 almazny: port to NAC3 2022-03-03 16:24:07 +08:00
Sebastien Bourdeauducq 8fc0e5d3aa suservo: port to NAC3 2022-03-02 08:55:27 +08:00
Sebastien Bourdeauducq be07481eb5 ad9910: port to NAC3 2022-03-01 18:48:26 +08:00
Sebastien Bourdeauducq 0256c91d53 ad9912: add missing NAC3TODO 2022-03-01 18:09:41 +08:00
Sebastien Bourdeauducq a3b55b6164 dma: tentative port to NAC3
will not work due to missing context manager and possibly memory management issues
2022-03-01 17:44:48 +08:00
Sebastien Bourdeauducq 156cf42f76 nac3devices: compile KasliEEPROM 2022-03-01 17:22:19 +08:00
Sebastien Bourdeauducq fb0b806f61 kasli_i2c: port to NAC3 2022-03-01 17:22:06 +08:00
Sebastien Bourdeauducq 01e55f5731 i2c: port to NAC3 2022-03-01 17:21:54 +08:00
Sebastien Bourdeauducq e6f26b5c74 Merge branch 'master' into nac3 2022-03-01 16:58:19 +08:00
Sebastien Bourdeauducq 5af22af5f6 examples/nac3devices: fix and reenable Phaser 2022-03-01 11:49:03 +08:00
Sebastien Bourdeauducq b66cce14fb update NAC3 2022-03-01 11:48:43 +08:00
Sebastien Bourdeauducq ea55c29568 phaser: port to NAC3 2022-02-28 17:00:24 +08:00
Sebastien Bourdeauducq a407007e0b fastino: port to NAC3 2022-02-28 13:34:55 +08:00
Sebastien Bourdeauducq 64a0c4b29a merge artiq.coredevice.runtime into comm_kernel 2022-02-28 12:04:46 +08:00
Sebastien Bourdeauducq 2e4233274f grabber: port to NAC3 2022-02-28 11:53:41 +08:00
Sebastien Bourdeauducq bf8e188868 ad9914: port to NAC3 2022-02-28 10:22:29 +08:00
Sebastien Bourdeauducq 0266d52497 flake: re-enable basic tests 2022-02-26 18:53:43 +08:00
Sebastien Bourdeauducq 5572f223d1 hardware_testbench: port imports to NAC3 2022-02-26 18:53:35 +08:00
Sebastien Bourdeauducq 158d65c822 test_spi: port imports to NAC3 2022-02-26 18:53:12 +08:00
Sebastien Bourdeauducq 5acf008be3 test_rtio: port imports to NAC3 2022-02-26 18:52:56 +08:00
Sebastien Bourdeauducq a606afa3bd test_phaser: port imports to NAC3 2022-02-26 18:52:40 +08:00
Sebastien Bourdeauducq 0e20058da3 test_performance: port imports and type annotations to NAC3 2022-02-26 18:52:15 +08:00
Sebastien Bourdeauducq 6eb1c4e138 sim: fix import for NAC3 2022-02-26 18:49:45 +08:00
Sebastien Bourdeauducq 9a05907b7a test_embedding: port imports and type annotations to NAC3 2022-02-26 18:47:59 +08:00
Sebastien Bourdeauducq 70531ae1e2 ad9910: port imports and type annotations to NAC3 2022-02-26 18:47:07 +08:00
Sebastien Bourdeauducq 2720bfa398 i2c: port syscalls to NAC3 2022-02-26 18:42:41 +08:00
Sebastien Bourdeauducq 645c4590b3 test: remove test_numpy (NAC3TODO) 2022-02-26 18:39:28 +08:00
Sebastien Bourdeauducq d7c915ff7b test_cache: partially port to NAC3 2022-02-26 17:56:02 +08:00
Sebastien Bourdeauducq b9a359a45b fix flake.lock 2022-02-26 17:50:41 +08:00
Sebastien Bourdeauducq d924d0cd20 flake: work around allowedUris problem 2022-02-26 17:43:27 +08:00
Sebastien Bourdeauducq bea7e952fa edge_counter: restore keyword arguments 2022-02-26 17:41:48 +08:00
Sebastien Bourdeauducq 7b02918a43 worker_impl: port to NAC3 2022-02-26 17:39:59 +08:00
Sebastien Bourdeauducq 892b96892f update nac3 2022-02-26 17:37:18 +08:00
Sebastien Bourdeauducq 2d5114f32d remove legacy compiler test 2022-02-26 16:45:12 +08:00
Sebastien Bourdeauducq 6388b82455 coredevice/cache: port to nac3 2022-02-26 16:28:17 +08:00
Sebastien Bourdeauducq 5db9bc9bd4 edge_counter: port to nac3 2022-02-26 08:55:08 +08:00
Sebastien Bourdeauducq 41c597a707 remove parts that won't initially be supported by nac3 2022-02-25 20:02:36 +08:00
Sebastien Bourdeauducq 404811cd5c Merge branch 'master' into nac3 2022-02-25 19:02:07 +08:00
Sebastien Bourdeauducq 808f968617 examples/nac3devices: add sampler 2022-02-25 18:27:40 +08:00
Sebastien Bourdeauducq 3d66a6be5d sampler: port to nac3 2022-02-25 18:26:18 +08:00
Sebastien Bourdeauducq 464818da34 ad9912: exception with string is supported by nac3 2022-02-25 18:26:01 +08:00
Sebastien Bourdeauducq e3a2825ae7 update nac3 2022-02-25 18:01:52 +08:00
Sebastien Bourdeauducq f7b315d661 Merge branch 'master' into nac3 2022-02-23 11:07:26 +08:00
Sebastien Bourdeauducq 9ab740d004 update nac3 and dependencies 2022-02-23 11:07:20 +08:00
Sebastien Bourdeauducq e76df491f2 coredevice/exceptions: nac3 no longer breaks because of docstrings 2022-02-14 16:39:26 +08:00
Sebastien Bourdeauducq 7b56a72da0 Merge branch 'master' into nac3 2022-02-14 16:38:09 +08:00
Sebastien Bourdeauducq ec6c6dd988 add comments about preallocate_runtime_exception_names/EXCEPTION_ID_LOOKUP syncing 2022-02-13 13:48:05 +08:00
Sebastien Bourdeauducq 9f620491a9 coredevice: fix exception return handling 2022-02-13 13:32:50 +08:00
Sebastien Bourdeauducq 8e1ac8b844 Revert "core: remove legacy synthesized filename"
This reverts commit 92c1bc2149.
2022-02-13 13:32:20 +08:00
Sebastien Bourdeauducq 8aa8647ba8 coredevice: use NAC3 exception support 2022-02-13 12:51:23 +08:00
Sebastien Bourdeauducq 207ff918c7 coredevice: define ValueError locally
work around M-Labs/nac3#189
2022-02-13 12:50:22 +08:00
Sebastien Bourdeauducq e8e1ccd4f1 coredevice/exceptions: port to NAC3 2022-02-13 12:49:09 +08:00
Sebastien Bourdeauducq 2616e1928d ttl: fix type error 2022-02-13 12:47:53 +08:00
Sebastien Bourdeauducq 4cd47fa935 update NAC3 2022-02-13 12:47:37 +08:00
Sebastien Bourdeauducq 92c1bc2149 core: remove legacy synthesized filename 2022-02-13 11:00:23 +08:00
pca006132 f2f2e12b91 language: implemented embedding map and exception 2022-02-13 10:59:18 +08:00
Sebastien Bourdeauducq ceceabbaf0 Merge branch 'master' into nac3 2022-01-26 07:25:20 +08:00
Sebastien Bourdeauducq c8ec2b0d7f flake: update dependencies 2022-01-26 07:23:57 +08:00
Sebastien Bourdeauducq 1c56b08a4b flake: update dependencies 2022-01-19 21:17:39 +08:00
Sebastien Bourdeauducq b452789f03 Merge branch 'master' into nac3 2022-01-19 21:07:10 +08:00
Sebastien Bourdeauducq 02555e48a0 update NAC3, use power operator 2022-01-09 11:45:10 +08:00
Sebastien Bourdeauducq 4ad8f5d6c7 flake: reexport and use mimalloc-enabled Python 2022-01-04 22:20:12 +08:00
Sebastien Bourdeauducq 59af28d6f7 flake: use LLVM 13 for consistency with NAC3 2022-01-04 22:11:19 +08:00
Sebastien Bourdeauducq 17d217d47d flake: update nac3 2022-01-04 22:09:52 +08:00
Sebastien Bourdeauducq c795cb40ea flake: update nac3 2021-12-28 11:49:01 +08:00
Sebastien Bourdeauducq 51cb8adba0 update NAC3 2021-12-26 08:50:02 +08:00
Sebastien Bourdeauducq 97365de104 flake: update nac3 2021-12-20 18:18:43 +08:00
Sebastien Bourdeauducq 243fe5ea88 flake: update nac3 2021-12-20 18:02:15 +08:00
Sebastien Bourdeauducq 088c3b470e update NAC3, use new Kernel type annotation 2021-12-20 17:56:40 +08:00
Sebastien Bourdeauducq d853604380 flake: update dependencies 2021-12-20 17:27:43 +08:00
Sebastien Bourdeauducq dad23b6981 coredevice/ad53xx: use len(list) 2021-12-09 12:40:01 +08:00
Sebastien Bourdeauducq 60cbde0820 flake: update dependencies 2021-12-09 12:35:33 +08:00
Sebastien Bourdeauducq 31ac6881df update NAC3, restore original delays 2021-12-06 12:21:52 +08:00
Sebastien Bourdeauducq 61d3e22ea8 flake: update nac3 2021-12-05 14:37:33 +08:00
Sebastien Bourdeauducq 3f3186005e flake: get rid of TARGET_AR 2021-12-05 14:31:49 +08:00
Sebastien Bourdeauducq e34f4cc99b language: add floor64 and ceil64 2021-12-04 20:13:00 +08:00
Sebastien Bourdeauducq 12c39aaaae coredevice/adf5356: use nac3 floor/ceil 2021-12-04 19:04:48 +08:00
Sebastien Bourdeauducq 2059fd375e language: add virtual 2021-12-04 19:03:39 +08:00
Sebastien Bourdeauducq 1bdf301e2a flake: update NAC3 2021-12-04 19:03:19 +08:00
Sebastien Bourdeauducq 6a09b92fb3 examples/nac3devices: fix mirny clocking 2021-11-29 18:15:06 +08:00
Sebastien Bourdeauducq 233c5f4bfb update NAC3 2021-11-29 18:14:47 +08:00
Sebastien Bourdeauducq b2f7022e0a examples/nac3devices: demonstrate new capabilities 2021-11-28 12:48:18 +08:00
Sebastien Bourdeauducq e45c194c49 coredevice/adf5356: port to NAC3 2021-11-28 12:38:23 +08:00
Sebastien Bourdeauducq 519e3d64d8 flake: update dependencies 2021-11-28 12:18:55 +08:00
Sebastien Bourdeauducq 5800496425 coredevice/adf5356_reg: port to NAC3 2021-11-24 16:54:58 +08:00
Sebastien Bourdeauducq 29f42ccd8a coredevice/mirny: port to NAC3 2021-11-23 17:13:43 +08:00
Sebastien Bourdeauducq f5a5b7a22a examples: add nac3devices 2021-11-23 16:41:29 +08:00
Sebastien Bourdeauducq 3a6fcd069d remove old examples 2021-11-23 16:37:40 +08:00
Sebastien Bourdeauducq d8e1a22bdf coredevice/ad53xx: remove problematic default param 2021-11-23 16:12:36 +08:00
Sebastien Bourdeauducq cbc767119d flake: update dependencies 2021-11-23 15:21:56 +08:00
Sebastien Bourdeauducq 34789767f0 firmware: fix compilation warning 2021-11-22 18:23:28 +08:00
Sebastien Bourdeauducq bd95d9cf3d coredevice/zotino: port to NAC3 2021-11-19 19:13:50 +08:00
Sebastien Bourdeauducq 64877c0588 fix Python 3.9 compatibility 2021-11-19 18:18:24 +08:00
Sebastien Bourdeauducq aa5f667ad8 ad9912: increase slack (no kernel invariants in NAC3 yet?) 2021-11-19 12:47:52 +08:00
Sebastien Bourdeauducq cc1080e055 ad9912: fix frequency_to_ftw 2021-11-19 12:42:08 +08:00
Sebastien Bourdeauducq f4acf04405 coredevice: fix run method 2021-11-16 18:32:14 +08:00
Sebastien Bourdeauducq 01f21f7545 flake: update dependencies 2021-11-16 17:51:18 +08:00
Sebastien Bourdeauducq 222968d68b coredevice: reinstate AugAssign methods 2021-11-13 12:42:13 +08:00
Sebastien Bourdeauducq 7a2428b6a7 flake: update nac3 2021-11-13 12:33:07 +08:00
Sebastien Bourdeauducq 3f807ad19e flake: update nac3 2021-11-12 20:07:29 +08:00
Sebastien Bourdeauducq 38e554fe98 artiq_run: fix ELF handling 2021-11-12 19:57:48 +08:00
Sebastien Bourdeauducq 75dad8090f Merge branch 'master' into nac3 2021-11-12 19:46:37 +08:00
Sebastien Bourdeauducq f07c747fa7 flake: update nac3, use patched nixpkgs from nac3 2021-11-12 16:27:08 +08:00
Sebastien Bourdeauducq 95eb218112 import_cache: read files only once 2021-11-11 20:42:49 +08:00
Sebastien Bourdeauducq 1ea3cf48d6 fix core.run invokation 2021-11-11 20:35:45 +08:00
Sebastien Bourdeauducq 5a3bf4894f reinstate import_cache hack (#416) 2021-11-11 20:35:06 +08:00
Sebastien Bourdeauducq 64c180c9e7 update NAC3 2021-11-11 16:51:56 +08:00
Sebastien Bourdeauducq c7cca11ad1 update NAC3 2021-11-11 16:32:37 +08:00
Sebastien Bourdeauducq a8129093df artiq_run: port to NAC3 (WIP) 2021-11-10 21:31:03 +08:00
Sebastien Bourdeauducq df87cc88d6 coredevice/ad9912: port to NAC3 2021-11-10 21:27:06 +08:00
Sebastien Bourdeauducq 93f24c9f94 coredevice/urukul: port to NAC3 2021-11-10 19:01:44 +08:00
Sebastien Bourdeauducq 31955d0c7a coredevice/spi2: port to NAC3 2021-11-10 17:23:44 +08:00
Sebastien Bourdeauducq b7313ddc32 coredevice/rtio: fix tuple annotation 2021-11-10 15:32:07 +08:00
Sebastien Bourdeauducq 262cd62544 examples/blink_forever: port to NAC3 2021-11-10 13:59:59 +08:00
Sebastien Bourdeauducq 2f031285a4 coredevice/ttl: port to NAC3 2021-11-10 13:58:54 +08:00
Sebastien Bourdeauducq deb8a77464 coredevice/rtio: port to NAC3 2021-11-10 13:57:03 +08:00
Sebastien Bourdeauducq 8f596ed04f coredevice: fix typing problems 2021-11-10 12:37:05 +08:00
Sebastien Bourdeauducq c8ebd80fe2 NAC3 integration WIP 2021-11-10 12:18:20 +08:00
Sebastien Bourdeauducq 2f60a38a9c flake: update dependencies 2021-11-10 12:16:29 +08:00
Sebastien Bourdeauducq e5620a6b7f language: remove old type annotations 2021-11-03 22:07:44 +08:00
Sebastien Bourdeauducq a3543a5527 bump ARTIQ version number 2021-11-03 21:51:16 +08:00
Sebastien Bourdeauducq 9a1a8e0e81 update dependencies 2021-11-03 21:49:21 +08:00
Sebastien Bourdeauducq 977543e05a Merge branch 'master' into nac3 2021-11-03 21:37:18 +08:00
Sebastien Bourdeauducq fbd5c70250 Revert "runtime: expose rint from libm"
Consistency with NAR3/Zynq where rint is not available.

This reverts commit f5100702f6.
2021-10-11 08:12:58 +08:00
Sebastien Bourdeauducq 17c283d091 runtime: expose rint from libm 2021-10-10 20:40:56 +08:00
Sebastien Bourdeauducq 97909d7619 remove old compiler, add nac3 dependency (WIP) 2021-10-08 00:30:27 +08:00
421 changed files with 3968 additions and 26186 deletions

1
.gitignore vendored
View File

@ -14,7 +14,6 @@ __pycache__/
/dist /dist
/*.egg-info /*.egg-info
/.coverage /.coverage
/artiq/test/lit/*/Output/
/artiq/binaries /artiq/binaries
/artiq/firmware/target/ /artiq/firmware/target/
/misoc_*/ /misoc_*/

View File

@ -26,6 +26,7 @@ report if possible:
* Operating System * Operating System
* ARTIQ version (with recent versions of ARTIQ, run ``artiq_client --version``) * 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``) * Version of the gateware and runtime loaded in the core device (in the output of ``artiq_coremgmt -D .... log``)
* If using MSYS2, output of `pacman -Q`
* Hardware involved * Hardware involved

View File

@ -8,79 +8,36 @@ ARTIQ-8 (Unreleased)
Highlights: Highlights:
* New hardware support: * Hardware support:
- Support for Shuttler, a 16-channel 125MSPS DAC card intended for ion transport.
Waveform generator and user API are similar to the NIST PDQ.
- Implemented Phaser-servo. This requires recent gateware on Phaser. - Implemented Phaser-servo. This requires recent gateware on Phaser.
- Almazny v1.2 with finer RF switch control. - Implemented Phaser-MIQRO support. This requires the Phaser MIQRO gateware
- Metlino and Sayma support has been dropped due to complications with synchronous RTIO clocking. variant.
- More user LEDs are exposed to RTIO on Kasli.
- Implemented Phaser-MIQRO support. This requires the proprietary Phaser MIQRO gateware
variant from QUARTIQ.
- Sampler: fixed ADC MU to Volt conversion factor for Sampler v2.2+. - Sampler: fixed ADC MU to Volt conversion factor for Sampler v2.2+.
For earlier hardware versions, specify the hardware version in the device For earlier hardware versions, specify the hardware version in the device
database file (e.g. ``"hw_rev": "v2.1"``) to use the correct conversion factor. database file (e.g. ``"hw_rev": "v2.1"``) to use the correct conversion factor.
* Support for distributed DMA, where DMA is run directly on satellites for corresponding - Almazny v1.2. It is incompatible with the legacy versions and is the default. To use legacy
RTIO events, increasing bandwidth in scenarios with heavy satellite usage. versions, specify ``almazny_hw_rev`` in the JSON description.
* Support for subkernels, where kernels are run on satellite device CPUs to offload some - Metlino and Sayma support has been dropped due to complications with synchronous RTIO clocking.
of the processing and RTIO operations.
* CPU (on softcore platforms) and AXI bus (on Zynq) are now clocked synchronously with the RTIO * CPU (on softcore platforms) and AXI bus (on Zynq) are now clocked synchronously with the RTIO
clock, to facilitate implementation of local processing on DRTIO satellites, and to slightly clock, to facilitate implementation of local processing on DRTIO satellites, and to slightly
reduce RTIO latency. reduce RTIO latency.
* Support for DRTIO-over-EEM, used with Shuttler. * MSYS2 packaging for Windows, which replaces Conda. Conda packages are still available to
* Added channel names to RTIO error messages. support legacy installations, but may be removed in a future release.
* GUI: * Added channel names to RTIO errors.
- Implemented Applet Request Interfaces which allow applets to modify datasets and set the * Full Python 3.10 support.
current values of widgets in the dashboard's experiment windows. * Python's built-in types (such as `float`, or `List[...]`) can now be used in type annotations on
- Implemented a new EntryArea widget which allows argument entry widgets to be used in applets. kernel functions.
* Distributed DMA is now supported, allowing DMA to be run directly on satellites for corresponding
RTIO events, increasing bandwidth in scenarios with heavy satellite usage.
* Applet Request Interfaces have been implemented, enabling applets to directly modify datasets
and temporarily set arguments in the dashboard.
* EntryArea widget has been implemented, allowing argument entry widgets to be used in applets.
* Dashboard:
- The "Close all applets" command (shortcut: Ctrl-Alt-W) now ignores docked applets, - The "Close all applets" command (shortcut: Ctrl-Alt-W) now ignores docked applets,
making it a convenient way to clean up after exploratory work without destroying a making it a convenient way to clean up after exploratory work without destroying a
carefully arranged default workspace. carefully arranged default workspace.
- Hotkeys now organize experiment windows in the order they were last interacted with: * Persistent datasets are now stored in a LMDB database for improved performance. PYON databases can
+ CTRL+SHIFT+T tiles experiment windows be converted with the script below.
+ CTRL+SHIFT+C cascades experiment windows
* Persistent datasets are now stored in a LMDB database for improved performance.
* Python's built-in types (such as ``float``, or ``List[...]``) can now be used in type annotations on
kernel functions.
* Full Python 3.10 support.
* MSYS2 packaging for Windows, which replaces Conda. Conda packages are still available to
support legacy installations, but may be removed in a future release.
Breaking changes:
* ``SimpleApplet`` now calls widget constructors with an additional ``ctl`` parameter for control
operations, which includes dataset operations. It can be ignored if not needed. For an example usage,
refer to the ``big_number.py`` applet.
* ``SimpleApplet`` and ``TitleApplet`` now call ``data_changed`` with additional parameters. Derived applets
should change the function signature as below:
::
# SimpleApplet
def data_changed(self, value, metadata, persist, mods)
# SimpleApplet (old version)
def data_changed(self, data, mods)
# TitleApplet
def data_changed(self, value, metadata, persist, mods, title)
# TitleApplet (old version)
def data_changed(self, data, mods, title)
Accesses to the data argument should be replaced as below:
::
data[key][0] ==> persist[key]
data[key][1] ==> value[key]
* The ``ndecimals`` parameter in ``NumberValue`` and ``Scannable`` has been renamed to ``precision``.
Parameters after and including ``scale`` in both constructors are now keyword-only.
Refer to the updated ``no_hardware/arguments_demo.py`` example for current usage.
* Almazny v1.2 is incompatible with the legacy versions and is the default.
To use legacy versions, specify ``almazny_hw_rev`` in the JSON description.
* kasli_generic.py has been merged into kasli.py, and the demonstration designs without JSON descriptions
have been removed. The base classes remain present in kasli.py to support third-party flows without
JSON descriptions.
* Legacy PYON databases should be converted to LMDB with the script below:
:: ::
@ -94,7 +51,35 @@ Accesses to the data argument should be replaced as below:
txn.put(key.encode(), pyon.encode((value, {})).encode()) txn.put(key.encode(), pyon.encode((value, {})).encode())
new.close() new.close()
* ``artiq.wavesynth`` has been removed. Breaking changes:
* ``SimpleApplet`` now calls widget constructors with an additional ``ctl`` parameter for control
operations, which includes dataset operations. It can be ignored if not needed. For an example usage,
refer to the ``big_number.py`` applet.
* ``SimpleApplet`` and ``TitleApplet`` now call ``data_changed`` with additional parameters. Wrapped widgets
should refactor the function signature as seen below:
::
# SimpleApplet
def data_changed(self, value, metadata, persist, mods)
# SimpleApplet (old version)
def data_changed(self, data, mods)
# TitleApplet
def data_changed(self, value, metadata, persist, mods, title)
# TitleApplet (old version)
def data_changed(self, data, mods, title)
Old syntax should be replaced with the form shown on the right.
::
data[key][0] ==> persist[key]
data[key][1] ==> value[key]
data[key][2] ==> metadata[key]
* The ``ndecimals`` parameter in ``NumberValue`` and ``Scannable`` has been renamed to ``precision``.
Parameters after and including ``scale`` in both constructors are now keyword-only.
Refer to the updated ``no_hardware/arguments_demo.py`` example for current usage.
ARTIQ-7 ARTIQ-7
------- -------

View File

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

View File

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

View File

@ -508,9 +508,5 @@ class ExperimentsArea(QtWidgets.QMdiArea):
self.open_experiments.append(dock) self.open_experiments.append(dock)
return dock return dock
def set_argument_value(self, expurl, name, value):
logger.warning("Unable to set argument '%s', dropping change. "
"'set_argument_value' not supported in browser.", name)
def on_dock_closed(self, dock): def on_dock_closed(self, dock):
self.open_experiments.remove(dock) self.open_experiments.remove(dock)

View File

@ -71,6 +71,7 @@ def build_artiq_soc(soc, argdict):
if not soc.config["DRTIO_ROLE"] == "satellite": if not soc.config["DRTIO_ROLE"] == "satellite":
builder.add_software_package("runtime", os.path.join(firmware_dir, "runtime")) builder.add_software_package("runtime", os.path.join(firmware_dir, "runtime"))
else: else:
# Assume DRTIO satellite.
builder.add_software_package("satman", os.path.join(firmware_dir, "satman")) builder.add_software_package("satman", os.path.join(firmware_dir, "satman"))
try: try:
builder.build() builder.build()

View File

@ -1,2 +0,0 @@
from .inline import inline
from .unroll import unroll

View File

@ -1,79 +0,0 @@
"""
:func:`inline` inlines a call instruction in ARTIQ IR.
The call instruction must have a statically known callee,
it must be second to last in the basic block, and the basic
block must have exactly one successor.
"""
from .. import types, builtins, iodelay, ir
def inline(call_insn):
assert isinstance(call_insn, ir.Call)
assert call_insn.static_target_function is not None
assert len(call_insn.basic_block.successors()) == 1
assert call_insn.basic_block.index(call_insn) == \
len(call_insn.basic_block.instructions) - 2
value_map = {}
source_function = call_insn.static_target_function
target_function = call_insn.basic_block.function
target_predecessor = call_insn.basic_block
target_successor = call_insn.basic_block.successors()[0]
if builtins.is_none(source_function.type.ret):
target_return_phi = None
else:
target_return_phi = target_successor.prepend(ir.Phi(source_function.type.ret))
closure = target_predecessor.insert(ir.GetAttr(call_insn.target_function(), '__closure__'),
before=call_insn)
for actual_arg, formal_arg in zip([closure] + call_insn.arguments(),
source_function.arguments):
value_map[formal_arg] = actual_arg
for source_block in source_function.basic_blocks:
target_block = ir.BasicBlock([], "i." + source_block.name)
target_function.add(target_block)
value_map[source_block] = target_block
def mapper(value):
if isinstance(value, ir.Constant):
return value
else:
return value_map[value]
for source_insn in source_function.instructions():
target_block = value_map[source_insn.basic_block]
if isinstance(source_insn, ir.Return):
if target_return_phi is not None:
target_return_phi.add_incoming(mapper(source_insn.value()), target_block)
target_insn = ir.Branch(target_successor)
elif isinstance(source_insn, ir.Phi):
target_insn = ir.Phi()
elif isinstance(source_insn, ir.Delay):
target_insn = source_insn.copy(mapper)
target_insn.interval = source_insn.interval.fold(call_insn.arg_exprs)
elif isinstance(source_insn, ir.Loop):
target_insn = source_insn.copy(mapper)
target_insn.trip_count = source_insn.trip_count.fold(call_insn.arg_exprs)
elif isinstance(source_insn, ir.Call):
target_insn = source_insn.copy(mapper)
target_insn.arg_exprs = \
{ arg: source_insn.arg_exprs[arg].fold(call_insn.arg_exprs)
for arg in source_insn.arg_exprs }
else:
target_insn = source_insn.copy(mapper)
target_insn.name = "i." + source_insn.name
value_map[source_insn] = target_insn
target_block.append(target_insn)
for source_insn in source_function.instructions():
if isinstance(source_insn, ir.Phi):
target_insn = value_map[source_insn]
for block, value in source_insn.incoming():
target_insn.add_incoming(value_map[value], value_map[block])
target_predecessor.terminator().replace_with(ir.Branch(value_map[source_function.entry()]))
if target_return_phi is not None:
call_insn.replace_all_uses_with(target_return_phi)
call_insn.erase()

View File

@ -1,97 +0,0 @@
"""
:func:`unroll` unrolls a loop instruction in ARTIQ IR.
The loop's trip count must be constant.
The loop body must not have any control flow instructions
except for one branch back to the loop head.
The loop body must be executed if the condition to which
the instruction refers is true.
"""
from .. import types, builtins, iodelay, ir
from ..analyses import domination
def _get_body_blocks(root, limit):
postorder = []
visited = set()
def visit(block):
visited.add(block)
for next_block in block.successors():
if next_block not in visited and next_block is not limit:
visit(next_block)
postorder.append(block)
visit(root)
postorder.reverse()
return postorder
def unroll(loop_insn):
loop_head = loop_insn.basic_block
function = loop_head.function
assert isinstance(loop_insn, ir.Loop)
assert len(loop_head.predecessors()) == 2
assert len(loop_insn.if_false().predecessors()) == 1
assert iodelay.is_const(loop_insn.trip_count)
trip_count = loop_insn.trip_count.fold().value
if trip_count == 0:
loop_insn.replace_with(ir.Branch(loop_insn.if_false()))
return
source_blocks = _get_body_blocks(loop_insn.if_true(), loop_head)
source_indvar = loop_insn.induction_variable()
source_tail = loop_insn.if_false()
unroll_target = loop_head
for n in range(trip_count):
value_map = {source_indvar: ir.Constant(n, source_indvar.type)}
for source_block in source_blocks:
target_block = ir.BasicBlock([], "u{}.{}".format(n, source_block.name))
function.add(target_block)
value_map[source_block] = target_block
def mapper(value):
if isinstance(value, ir.Constant):
return value
elif value in value_map:
return value_map[value]
else:
return value
for source_block in source_blocks:
target_block = value_map[source_block]
for source_insn in source_block.instructions:
if isinstance(source_insn, ir.Phi):
target_insn = ir.Phi()
else:
target_insn = source_insn.copy(mapper)
target_insn.name = "u{}.{}".format(n, source_insn.name)
target_block.append(target_insn)
value_map[source_insn] = target_insn
for source_block in source_blocks:
for source_insn in source_block.instructions:
if isinstance(source_insn, ir.Phi):
target_insn = value_map[source_insn]
for block, value in source_insn.incoming():
target_insn.add_incoming(value_map[value], value_map[block])
assert isinstance(unroll_target.terminator(), (ir.Branch, ir.Loop))
unroll_target.terminator().replace_with(ir.Branch(value_map[source_blocks[0]]))
unroll_target = value_map[source_blocks[-1]]
assert isinstance(unroll_target.terminator(), ir.Branch)
assert len(source_blocks[-1].successors()) == 1
unroll_target.terminator().replace_with(ir.Branch(source_tail))
for source_block in reversed(source_blocks):
for source_insn in reversed(source_block.instructions):
for use in set(source_insn.uses):
if isinstance(use, ir.Phi):
assert use.basic_block == loop_head
use.remove_incoming_value(source_insn)
source_insn.erase()
for source_block in reversed(source_blocks):
source_block.erase()

View File

@ -1,3 +0,0 @@
from .domination import DominatorTree
from .devirtualization import Devirtualization
from .invariant_detection import InvariantDetection

View File

@ -1,119 +0,0 @@
"""
:class:`Devirtualizer` performs method resolution at
compile time.
Devirtualization is implemented using a lattice
with three states: unknown assigned once diverges.
The lattice is computed individually for every
variable in scope as well as every
(instance type, field name) pair.
"""
from pythonparser import algorithm
from .. import asttyped, ir, types
def _advance(target_map, key, value):
if key not in target_map:
target_map[key] = value # unknown → assigned once
else:
target_map[key] = None # assigned once → diverges
class FunctionResolver(algorithm.Visitor):
def __init__(self, variable_map):
self.variable_map = variable_map
self.scope_map = dict()
self.queue = []
self.in_assign = False
self.current_scopes = []
def finalize(self):
for thunk in self.queue:
thunk()
def visit_scope(self, node):
self.current_scopes.append(node)
self.generic_visit(node)
self.current_scopes.pop()
def visit_in_assign(self, node):
self.in_assign = True
self.visit(node)
self.in_assign = False
def visit_Assign(self, node):
self.visit(node.value)
self.visit_in_assign(node.targets)
def visit_ForT(self, node):
self.visit(node.iter)
self.visit_in_assign(node.target)
self.visit(node.body)
self.visit(node.orelse)
def visit_withitemT(self, node):
self.visit(node.context_expr)
self.visit_in_assign(node.optional_vars)
def visit_comprehension(self, node):
self.visit(node.iter)
self.visit_in_assign(node.target)
self.visit(node.ifs)
def visit_ModuleT(self, node):
self.visit_scope(node)
def visit_FunctionDefT(self, node):
_advance(self.scope_map, (self.current_scopes[-1], node.name), node)
self.visit_scope(node)
def visit_NameT(self, node):
if self.in_assign:
# Just give up if we assign anything at all to a variable, and
# assume it diverges.
_advance(self.scope_map, (self.current_scopes[-1], node.id), None)
else:
# Look up the final value in scope_map and copy it into variable_map.
keys = [(scope, node.id) for scope in reversed(self.current_scopes)]
def thunk():
for key in keys:
if key in self.scope_map:
self.variable_map[node] = self.scope_map[key]
return
self.queue.append(thunk)
class MethodResolver(algorithm.Visitor):
def __init__(self, variable_map, method_map):
self.variable_map = variable_map
self.method_map = method_map
# embedding.Stitcher.finalize generates initialization statements
# of form "constructor.meth = meth_body".
def visit_Assign(self, node):
if node.value not in self.variable_map:
return
value = self.variable_map[node.value]
for target in node.targets:
if isinstance(target, asttyped.AttributeT):
if types.is_constructor(target.value.type):
instance_type = target.value.type.instance
elif types.is_instance(target.value.type):
instance_type = target.value.type
else:
continue
_advance(self.method_map, (instance_type, target.attr), value)
class Devirtualization:
def __init__(self):
self.variable_map = dict()
self.method_map = dict()
def visit(self, node):
function_resolver = FunctionResolver(self.variable_map)
function_resolver.visit(node)
function_resolver.finalize()
method_resolver = MethodResolver(self.variable_map, self.method_map)
method_resolver.visit(node)

View File

@ -1,144 +0,0 @@
"""
:class:`DominatorTree` computes the dominance relation over
control flow graphs.
See http://www.cs.rice.edu/~keith/EMBED/dom.pdf.
"""
class GenericDominatorTree:
def __init__(self):
self._assign_names()
self._compute()
def _traverse_in_postorder(self):
raise NotImplementedError
def _prev_block_names(self, block):
raise NotImplementedError
def _assign_names(self):
postorder = self._traverse_in_postorder()
self._start_name = len(postorder) - 1
self._block_of_name = postorder
self._name_of_block = {}
for block_name, block in enumerate(postorder):
self._name_of_block[block] = block_name
def _intersect(self, block_name_1, block_name_2):
finger_1, finger_2 = block_name_1, block_name_2
while finger_1 != finger_2:
while finger_1 < finger_2:
finger_1 = self._doms[finger_1]
while finger_2 < finger_1:
finger_2 = self._doms[finger_2]
return finger_1
def _compute(self):
self._doms = {}
# Start block dominates itself.
self._doms[self._start_name] = self._start_name
# We don't yet know what blocks dominate all other blocks.
for block_name in range(self._start_name):
self._doms[block_name] = None
changed = True
while changed:
changed = False
# For all blocks except start block, in reverse postorder...
for block_name in reversed(range(self._start_name)):
# Select a new immediate dominator from the blocks we have
# already processed, and remember all others.
# We've already processed at least one previous block because
# of the graph traverse order.
new_idom, prev_block_names = None, []
for prev_block_name in self._prev_block_names(block_name):
if new_idom is None and self._doms[prev_block_name] is not None:
new_idom = prev_block_name
else:
prev_block_names.append(prev_block_name)
# Find a common previous block
for prev_block_name in prev_block_names:
if self._doms[prev_block_name] is not None:
new_idom = self._intersect(prev_block_name, new_idom)
if self._doms[block_name] != new_idom:
self._doms[block_name] = new_idom
changed = True
def immediate_dominator(self, block):
return self._block_of_name[self._doms[self._name_of_block[block]]]
def dominators(self, block):
# Blocks that are statically unreachable from entry are considered
# dominated by every other block.
if block not in self._name_of_block:
yield from self._block_of_name
return
block_name = self._name_of_block[block]
yield self._block_of_name[block_name]
while block_name != self._doms[block_name]:
block_name = self._doms[block_name]
yield self._block_of_name[block_name]
class DominatorTree(GenericDominatorTree):
def __init__(self, function):
self.function = function
super().__init__()
def _traverse_in_postorder(self):
postorder = []
visited = set()
def visit(block):
visited.add(block)
for next_block in block.successors():
if next_block not in visited:
visit(next_block)
postorder.append(block)
visit(self.function.entry())
return postorder
def _prev_block_names(self, block_name):
for block in self._block_of_name[block_name].predecessors():
# Only return predecessors that are statically reachable from entry.
if block in self._name_of_block:
yield self._name_of_block[block]
class PostDominatorTree(GenericDominatorTree):
def __init__(self, function):
self.function = function
super().__init__()
def _traverse_in_postorder(self):
postorder = []
visited = set()
def visit(block):
visited.add(block)
for next_block in block.predecessors():
if next_block not in visited:
visit(next_block)
postorder.append(block)
for block in self.function.basic_blocks:
if not any(block.successors()):
visit(block)
postorder.append(None) # virtual exit block
return postorder
def _prev_block_names(self, block_name):
succ_blocks = self._block_of_name[block_name].successors()
if len(succ_blocks) > 0:
for block in succ_blocks:
yield self._name_of_block[block]
else:
yield self._start_name

View File

@ -1,49 +0,0 @@
"""
:class:`InvariantDetection` determines which attributes can be safely
marked kernel invariant.
"""
from pythonparser import diagnostic
from .. import ir, types
class InvariantDetection:
def __init__(self, engine):
self.engine = engine
def process(self, functions):
self.attr_locs = dict()
self.attr_written = set()
for func in functions:
self.process_function(func)
for key in self.attr_locs:
if key not in self.attr_written:
typ, attr = key
if attr in typ.constant_attributes:
continue
diag = diagnostic.Diagnostic("note",
"attribute '{attr}' of type '{type}' is never written to; " +
"it could be marked as kernel invariant to potentially increase performance",
{"attr": attr,
"type": typ.name},
self.attr_locs[key])
self.engine.process(diag)
def process_function(self, func):
for block in func.basic_blocks:
for insn in block.instructions:
if not isinstance(insn, (ir.GetAttr, ir.SetAttr)):
continue
if not types.is_instance(insn.object().type):
continue
key = (insn.object().type, insn.attr)
if isinstance(insn, ir.GetAttr):
if types.is_method(insn.type):
continue
if key not in self.attr_locs and insn.loc is not None:
self.attr_locs[key] = insn.loc
elif isinstance(insn, ir.SetAttr):
self.attr_written.add(key)

View File

@ -1,119 +0,0 @@
"""
The typedtree module exports the PythonParser AST enriched with
typing information.
"""
from pythonparser import ast
class commontyped(ast.commonloc):
"""A mixin for typed AST nodes."""
_types = ("type",)
def _reprfields(self):
return self._fields + self._locs + self._types
class scoped(object):
"""
:ivar typing_env: (dict with string keys and :class:`.types.Type` values)
map of variable names to variable types
:ivar globals_in_scope: (set of string keys)
set of variables resolved as globals
"""
class remote(object):
"""
:ivar remote_fn: (bool) whether function is ran on a remote device,
meaning arguments are received remotely and return is sent remotely
"""
# Typed versions of untyped nodes
class argT(ast.arg, commontyped):
pass
class ClassDefT(ast.ClassDef):
_types = ("constructor_type",)
class FunctionDefT(ast.FunctionDef, scoped, remote):
_types = ("signature_type",)
class QuotedFunctionDefT(FunctionDefT):
"""
:ivar flags: (set of str) Code generation flags (see :class:`ir.Function`).
"""
class ModuleT(ast.Module, scoped):
pass
class ExceptHandlerT(ast.ExceptHandler):
_fields = ("filter", "name", "body") # rename ast.ExceptHandler.type to filter
_types = ("name_type",)
class ForT(ast.For):
"""
:ivar trip_count: (:class:`iodelay.Expr`)
:ivar trip_interval: (:class:`iodelay.Expr`)
"""
class withitemT(ast.withitem):
_types = ("enter_type", "exit_type")
class SliceT(ast.Slice, commontyped):
pass
class AttributeT(ast.Attribute, commontyped):
pass
class BinOpT(ast.BinOp, commontyped):
pass
class BoolOpT(ast.BoolOp, commontyped):
pass
class CallT(ast.Call, commontyped, remote):
"""
:ivar iodelay: (:class:`iodelay.Expr`)
:ivar arg_exprs: (dict of str to :class:`iodelay.Expr`)
"""
class CompareT(ast.Compare, commontyped):
pass
class DictT(ast.Dict, commontyped):
pass
class DictCompT(ast.DictComp, commontyped, scoped):
pass
class EllipsisT(ast.Ellipsis, commontyped):
pass
class GeneratorExpT(ast.GeneratorExp, commontyped, scoped):
pass
class IfExpT(ast.IfExp, commontyped):
pass
class LambdaT(ast.Lambda, commontyped, scoped):
pass
class ListT(ast.List, commontyped):
pass
class ListCompT(ast.ListComp, commontyped, scoped):
pass
class NameT(ast.Name, commontyped):
pass
class NameConstantT(ast.NameConstant, commontyped):
pass
class NumT(ast.Num, commontyped):
pass
class SetT(ast.Set, commontyped):
pass
class SetCompT(ast.SetComp, commontyped, scoped):
pass
class StrT(ast.Str, commontyped):
pass
class StarredT(ast.Starred, commontyped):
pass
class SubscriptT(ast.Subscript, commontyped):
pass
class TupleT(ast.Tuple, commontyped):
pass
class UnaryOpT(ast.UnaryOp, commontyped):
pass
class YieldT(ast.Yield, commontyped):
pass
class YieldFromT(ast.YieldFrom, commontyped):
pass
# Novel typed nodes
class CoerceT(ast.expr, commontyped):
_fields = ('value',) # other_value deliberately not in _fields
class QuoteT(ast.expr, commontyped):
_fields = ('value',)

View File

@ -1,355 +0,0 @@
"""
The :mod:`builtins` module contains the builtin Python
and ARTIQ types, such as int or float.
"""
from collections import OrderedDict
from . import types
# Types
class TNone(types.TMono):
def __init__(self):
super().__init__("NoneType")
class TBool(types.TMono):
def __init__(self):
super().__init__("bool")
@staticmethod
def zero():
return False
@staticmethod
def one():
return True
class TInt(types.TMono):
def __init__(self, width=None):
if width is None:
width = types.TVar()
super().__init__("int", {"width": width})
@staticmethod
def zero():
return 0
@staticmethod
def one():
return 1
def TInt8():
return TInt(types.TValue(8))
def TInt32():
return TInt(types.TValue(32))
def TInt64():
return TInt(types.TValue(64))
def _int_printer(typ, printer, depth, max_depth):
if types.is_var(typ["width"]):
return "numpy.int?"
else:
return "numpy.int{}".format(types.get_value(typ.find()["width"]))
types.TypePrinter.custom_printers["int"] = _int_printer
class TFloat(types.TMono):
def __init__(self):
super().__init__("float")
@staticmethod
def zero():
return 0.0
@staticmethod
def one():
return 1.0
class TStr(types.TMono):
def __init__(self):
super().__init__("str")
class TBytes(types.TMono):
def __init__(self):
super().__init__("bytes")
class TByteArray(types.TMono):
def __init__(self):
super().__init__("bytearray")
class TList(types.TMono):
def __init__(self, elt=None):
if elt is None:
elt = types.TVar()
super().__init__("list", {"elt": elt})
class TArray(types.TMono):
def __init__(self, elt=None, num_dims=1):
if elt is None:
elt = types.TVar()
if isinstance(num_dims, int):
# Make TArray more convenient to instantiate from (ARTIQ) user code.
num_dims = types.TValue(num_dims)
# For now, enforce number of dimensions to be known, as we'd otherwise
# need to implement custom unification logic for the type of `shape`.
# Default to 1 to keep compatibility with old user code from before
# multidimensional array support.
assert isinstance(num_dims.value, int), "Number of dimensions must be resolved"
super().__init__("array", {"elt": elt, "num_dims": num_dims})
self.attributes = OrderedDict([
("buffer", types._TPointer(elt)),
("shape", types.TTuple([TInt32()] * num_dims.value)),
])
def _array_printer(typ, printer, depth, max_depth):
return "numpy.array(elt={}, num_dims={})".format(
printer.name(typ["elt"], depth, max_depth), typ["num_dims"].value)
types.TypePrinter.custom_printers["array"] = _array_printer
class TRange(types.TMono):
def __init__(self, elt=None):
if elt is None:
elt = types.TVar()
super().__init__("range", {"elt": elt})
self.attributes = OrderedDict([
("start", elt),
("stop", elt),
("step", elt),
])
class TException(types.TMono):
# All exceptions share the same internal layout:
# * Pointer to the unique global with the name of the exception (str)
# (which also serves as the EHABI type_info).
# * File, line and column where it was raised (str, int, int).
# * Message, which can contain substitutions {0}, {1} and {2} (str).
# * Three 64-bit integers, parameterizing the message (numpy.int64).
# These attributes are prefixed with `#` so that users cannot access them,
# and we don't have to do string allocation in the runtime.
# #__name__ is now a string key in the host. TStr may not be an actual
# CSlice in the runtime, they might be a CSlice with length = i32::MAX and
# ptr = string key in the host.
# Keep this in sync with the function ARTIQIRGenerator.alloc_exn.
attributes = OrderedDict([
("#__name__", TInt32()),
("#__file__", TStr()),
("#__line__", TInt32()),
("#__col__", TInt32()),
("#__func__", TStr()),
("#__message__", TStr()),
("#__param0__", TInt64()),
("#__param1__", TInt64()),
("#__param2__", TInt64()),
])
def __init__(self, name="Exception", id=0):
super().__init__(name)
self.id = id
def fn_bool():
return types.TConstructor(TBool())
def fn_int():
return types.TConstructor(TInt())
def fn_int32():
return types.TBuiltinFunction("int32")
def fn_int64():
return types.TBuiltinFunction("int64")
def fn_float():
return types.TConstructor(TFloat())
def fn_str():
return types.TConstructor(TStr())
def fn_bytes():
return types.TConstructor(TBytes())
def fn_bytearray():
return types.TConstructor(TByteArray())
def fn_list():
return types.TConstructor(TList())
def fn_array():
# numpy.array() is actually a "magic" macro that is expanded in-place, but
# just as for builtin functions, we do not want to quote it, etc.
return types.TBuiltinFunction("array")
def fn_Exception():
return types.TExceptionConstructor(TException("Exception"))
def fn_IndexError():
return types.TExceptionConstructor(TException("IndexError"))
def fn_ValueError():
return types.TExceptionConstructor(TException("ValueError"))
def fn_ZeroDivisionError():
return types.TExceptionConstructor(TException("ZeroDivisionError"))
def fn_RuntimeError():
return types.TExceptionConstructor(TException("RuntimeError"))
def fn_range():
return types.TBuiltinFunction("range")
def fn_len():
return types.TBuiltinFunction("len")
def fn_round():
return types.TBuiltinFunction("round")
def fn_abs():
return types.TBuiltinFunction("abs")
def fn_min():
return types.TBuiltinFunction("min")
def fn_max():
return types.TBuiltinFunction("max")
def fn_make_array():
return types.TBuiltinFunction("make_array")
def fn_print():
return types.TBuiltinFunction("print")
def fn_kernel():
return types.TBuiltinFunction("kernel")
def obj_parallel():
return types.TBuiltin("parallel")
def obj_interleave():
return types.TBuiltin("interleave")
def obj_sequential():
return types.TBuiltin("sequential")
def fn_delay():
return types.TBuiltinFunction("delay")
def fn_now_mu():
return types.TBuiltinFunction("now_mu")
def fn_delay_mu():
return types.TBuiltinFunction("delay_mu")
def fn_at_mu():
return types.TBuiltinFunction("at_mu")
def fn_rtio_log():
return types.TBuiltinFunction("rtio_log")
def fn_subkernel_await():
return types.TBuiltinFunction("subkernel_await")
def fn_subkernel_preload():
return types.TBuiltinFunction("subkernel_preload")
# Accessors
def is_none(typ):
return types.is_mono(typ, "NoneType")
def is_bool(typ):
return types.is_mono(typ, "bool")
def is_int(typ, width=None):
if width is not None:
return types.is_mono(typ, "int", width=width)
else:
return types.is_mono(typ, "int")
def is_int32(typ):
return is_int(typ, types.TValue(32))
def is_int64(typ):
return is_int(typ, types.TValue(64))
def get_int_width(typ):
if is_int(typ):
return types.get_value(typ.find()["width"])
def is_float(typ):
return types.is_mono(typ, "float")
def is_str(typ):
return types.is_mono(typ, "str")
def is_bytes(typ):
return types.is_mono(typ, "bytes")
def is_bytearray(typ):
return types.is_mono(typ, "bytearray")
def is_numeric(typ):
typ = typ.find()
return isinstance(typ, types.TMono) and \
typ.name in ('int', 'float')
def is_list(typ, elt=None):
if elt is not None:
return types.is_mono(typ, "list", elt=elt)
else:
return types.is_mono(typ, "list")
def is_array(typ, elt=None):
if elt is not None:
return types.is_mono(typ, "array", elt=elt)
else:
return types.is_mono(typ, "array")
def is_listish(typ, elt=None):
if is_list(typ, elt) or is_array(typ, elt):
return True
elif elt is None:
return is_str(typ) or is_bytes(typ) or is_bytearray(typ)
else:
return False
def is_range(typ, elt=None):
if elt is not None:
return types.is_mono(typ, "range", {"elt": elt})
else:
return types.is_mono(typ, "range")
def is_exception(typ, name=None):
if name is None:
return isinstance(typ.find(), TException)
else:
return isinstance(typ.find(), TException) and \
typ.name == name
def is_iterable(typ):
return is_listish(typ) or is_range(typ)
def get_iterable_elt(typ):
# TODO: Arrays count as listish, but this returns the innermost element type for
# n-dimensional arrays, rather than the n-1 dimensional result of iterating over
# the first axis, which makes the name a bit misleading.
if is_str(typ) or is_bytes(typ) or is_bytearray(typ):
return TInt8()
elif types._is_pointer(typ) or is_iterable(typ):
return typ.find()["elt"].find()
else:
assert False
def is_collection(typ):
typ = typ.find()
return isinstance(typ, types.TTuple) or \
types.is_mono(typ, "list")
def is_allocated(typ):
return not (is_none(typ) or is_bool(typ) or is_int(typ) or
is_float(typ) or is_range(typ) or
types._is_pointer(typ) or types.is_function(typ) or
types.is_external_function(typ) or types.is_rpc(typ) or
types.is_subkernel(typ) or types.is_method(typ) or
types.is_tuple(typ) or types.is_value(typ))

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +0,0 @@
import sys
import builtins
import linecache
import tokenize
import logging
import importlib.machinery as im
from artiq.experiment import kernel, portable
__all__ = ["install_hook"]
logger = logging.getLogger(__name__)
cache = dict()
im_exec_module = None
linecache_getlines = None
def hook_exec_module(self, module):
im_exec_module(self, module)
if (hasattr(module, "__file__")
# Heuristic to determine if the module may contain ARTIQ kernels.
# This breaks if kernel is not imported the usual way.
and ((getattr(module, "kernel", None) is kernel)
or (getattr(module, "portable", None) is portable))):
fn = module.__file__
try:
with tokenize.open(fn) as fp:
lines = fp.readlines()
if lines and not lines[-1].endswith("\n"):
lines[-1] += "\n"
cache[fn] = lines
except:
logger.warning("failed to add '%s' to cache", fn, exc_info=True)
else:
logger.debug("added '%s' to cache", fn)
def hook_getlines(filename, module_globals=None):
if filename in cache:
return cache[filename]
else:
return linecache_getlines(filename, module_globals)
def install_hook():
global im_exec_module, linecache_getlines
im_exec_module = im.SourceFileLoader.exec_module
im.SourceFileLoader.exec_module = hook_exec_module
linecache_getlines = linecache.getlines
linecache.getlines = hook_getlines
logger.debug("hook installed")

View File

@ -1,249 +0,0 @@
"""
The :mod:`iodelay` module contains the classes describing
the statically inferred RTIO delay arising from executing
a function.
"""
from functools import reduce
class Expr:
def __add__(lhs, rhs):
assert isinstance(rhs, Expr)
return Add(lhs, rhs)
__iadd__ = __add__
def __sub__(lhs, rhs):
assert isinstance(rhs, Expr)
return Sub(lhs, rhs)
__isub__ = __sub__
def __mul__(lhs, rhs):
assert isinstance(rhs, Expr)
return Mul(lhs, rhs)
__imul__ = __mul__
def __truediv__(lhs, rhs):
assert isinstance(rhs, Expr)
return TrueDiv(lhs, rhs)
__itruediv__ = __truediv__
def __floordiv__(lhs, rhs):
assert isinstance(rhs, Expr)
return FloorDiv(lhs, rhs)
__ifloordiv__ = __floordiv__
def __ne__(lhs, rhs):
return not (lhs == rhs)
def free_vars(self):
return set()
def fold(self, vars=None):
return self
class Const(Expr):
_priority = 1
def __init__(self, value):
assert isinstance(value, (int, float))
self.value = value
def __str__(self):
return str(self.value)
def __eq__(lhs, rhs):
return rhs.__class__ == lhs.__class__ and lhs.value == rhs.value
def eval(self, env):
return self.value
class Var(Expr):
_priority = 1
def __init__(self, name):
assert isinstance(name, str)
self.name = name
def __str__(self):
return self.name
def __eq__(lhs, rhs):
return rhs.__class__ == lhs.__class__ and lhs.name == rhs.name
def free_vars(self):
return {self.name}
def fold(self, vars=None):
if vars is not None and self.name in vars:
return vars[self.name]
else:
return self
class Conv(Expr):
_priority = 1
def __init__(self, operand, ref_period):
assert isinstance(operand, Expr)
assert isinstance(ref_period, float)
self.operand, self.ref_period = operand, ref_period
def __eq__(lhs, rhs):
return rhs.__class__ == lhs.__class__ and \
lhs.ref_period == rhs.ref_period and \
lhs.operand == rhs.operand
def free_vars(self):
return self.operand.free_vars()
class MUToS(Conv):
def __str__(self):
return "mu->s({})".format(self.operand)
def eval(self, env):
return self.operand.eval(env) * self.ref_period
def fold(self, vars=None):
operand = self.operand.fold(vars)
if isinstance(operand, Const):
return Const(operand.value * self.ref_period)
else:
return MUToS(operand, ref_period=self.ref_period)
class SToMU(Conv):
def __str__(self):
return "s->mu({})".format(self.operand)
def eval(self, env):
return int(self.operand.eval(env) / self.ref_period)
def fold(self, vars=None):
operand = self.operand.fold(vars)
if isinstance(operand, Const):
return Const(int(operand.value / self.ref_period))
else:
return SToMU(operand, ref_period=self.ref_period)
class BinOp(Expr):
def __init__(self, lhs, rhs):
self.lhs, self.rhs = lhs, rhs
def __str__(self):
lhs = "({})".format(self.lhs) if self.lhs._priority > self._priority else str(self.lhs)
rhs = "({})".format(self.rhs) if self.rhs._priority > self._priority else str(self.rhs)
return "{} {} {}".format(lhs, self._symbol, rhs)
def __eq__(lhs, rhs):
return rhs.__class__ == lhs.__class__ and lhs.lhs == rhs.lhs and lhs.rhs == rhs.rhs
def eval(self, env):
return self.__class__._op(self.lhs.eval(env), self.rhs.eval(env))
def free_vars(self):
return self.lhs.free_vars() | self.rhs.free_vars()
def _fold_binop(self, lhs, rhs):
if isinstance(lhs, Const) and lhs.__class__ == rhs.__class__:
return Const(self.__class__._op(lhs.value, rhs.value))
elif isinstance(lhs, (MUToS, SToMU)) and lhs.__class__ == rhs.__class__:
return lhs.__class__(self.__class__(lhs.operand, rhs.operand),
ref_period=lhs.ref_period).fold()
else:
return self.__class__(lhs, rhs)
def fold(self, vars=None):
return self._fold_binop(self.lhs.fold(vars), self.rhs.fold(vars))
class BinOpFixpoint(BinOp):
def _fold_binop(self, lhs, rhs):
if isinstance(lhs, Const) and lhs.value == self._fixpoint:
return rhs
elif isinstance(rhs, Const) and rhs.value == self._fixpoint:
return lhs
else:
return super()._fold_binop(lhs, rhs)
class Add(BinOpFixpoint):
_priority = 2
_symbol = "+"
_op = lambda a, b: a + b
_fixpoint = 0
class Mul(BinOpFixpoint):
_priority = 1
_symbol = "*"
_op = lambda a, b: a * b
_fixpoint = 1
class Sub(BinOp):
_priority = 2
_symbol = "-"
_op = lambda a, b: a - b
def _fold_binop(self, lhs, rhs):
if isinstance(rhs, Const) and rhs.value == 0:
return lhs
else:
return super()._fold_binop(lhs, rhs)
class Div(BinOp):
def _fold_binop(self, lhs, rhs):
if isinstance(rhs, Const) and rhs.value == 1:
return lhs
else:
return super()._fold_binop(lhs, rhs)
class TrueDiv(Div):
_priority = 1
_symbol = "/"
_op = lambda a, b: a / b if b != 0 else 0
class FloorDiv(Div):
_priority = 1
_symbol = "//"
_op = lambda a, b: a // b if b != 0 else 0
class Max(Expr):
_priority = 1
def __init__(self, operands):
assert isinstance(operands, list)
assert all([isinstance(operand, Expr) for operand in operands])
assert operands != []
self.operands = operands
def __str__(self):
return "max({})".format(", ".join([str(operand) for operand in self.operands]))
def __eq__(lhs, rhs):
return rhs.__class__ == lhs.__class__ and lhs.operands == rhs.operands
def free_vars(self):
return reduce(lambda a, b: a | b, [operand.free_vars() for operand in self.operands])
def eval(self, env):
return max([operand.eval() for operand in self.operands])
def fold(self, vars=None):
consts, exprs = [], []
for operand in self.operands:
operand = operand.fold(vars)
if isinstance(operand, Const):
consts.append(operand.value)
elif operand not in exprs:
exprs.append(operand)
if len(consts) > 0:
exprs.append(Const(max(consts)))
if len(exprs) == 1:
return exprs[0]
else:
return Max(exprs)
def is_const(expr, value=None):
expr = expr.fold()
if value is None:
return isinstance(expr, Const)
else:
return isinstance(expr, Const) and expr.value == value
def is_zero(expr):
return is_const(expr, 0)

File diff suppressed because it is too large Load Diff

View File

@ -1,70 +0,0 @@
/* Force ld to make the ELF header as loadable. */
PHDRS
{
headers PT_LOAD FILEHDR PHDRS ;
text PT_LOAD ;
data PT_LOAD ;
dynamic PT_DYNAMIC ;
eh_frame PT_GNU_EH_FRAME ;
}
SECTIONS
{
/* Push back .text section enough so that ld.lld not complain */
. = SIZEOF_HEADERS;
.text :
{
*(.text .text.*)
} : text
.rodata :
{
*(.rodata .rodata.*)
}
.eh_frame :
{
KEEP(*(.eh_frame))
} : text
.eh_frame_hdr :
{
KEEP(*(.eh_frame_hdr))
} : text : eh_frame
.got :
{
*(.got)
} : text
.got.plt :
{
*(.got.plt)
} : text
.data :
{
*(.data .data.*)
} : data
.dynamic :
{
*(.dynamic)
} : data : dynamic
.bss (NOLOAD) : ALIGN(4)
{
__bss_start = .;
*(.sbss .sbss.* .bss .bss.*);
. = ALIGN(4);
_end = .;
}
/* Kernel stack grows downward from end of memory, so put guard page after
* all the program contents. Note: This requires all loaded sections (at
* least those accessed) to be explicitly listed in the above!
*/
. = ALIGN(0x1000);
_sstack_guard = .;
}

View File

@ -1,132 +0,0 @@
r"""
The :mod:`math_fns` module lists math-related functions from NumPy recognized
by the ARTIQ compiler so host function objects can be :func:`match`\ ed to
the compiler type metadata describing their core device analogue.
"""
from collections import OrderedDict
import numpy
from . import builtins, types
# Some special mathematical functions are exposed via their scipy.special
# equivalents. Since the rest of the ARTIQ core does not depend on SciPy,
# gracefully handle it not being present, making the functions simply not
# available.
try:
import scipy.special as scipy_special
except ImportError:
scipy_special = None
#: float -> float numpy.* math functions for which llvm.* intrinsics exist.
unary_fp_intrinsics = [(name, "llvm." + name + ".f64") for name in [
"sin",
"cos",
"exp",
"exp2",
"log",
"log10",
"log2",
"fabs",
"floor",
"ceil",
"trunc",
"sqrt",
]] + [
# numpy.rint() seems to (NumPy 1.19.0, Python 3.8.5, Linux x86_64)
# implement round-to-even, but unfortunately, rust-lang/libm only
# provides round(), which always rounds away from zero.
#
# As there is no equivalent of the latter in NumPy (nor any other
# basic rounding function), expose round() as numpy.rint anyway,
# even if the rounding modes don't match up, so there is some way
# to do rounding on the core device. (numpy.round() has entirely
# different semantics; it rounds to a configurable number of
# decimals.)
("rint", "llvm.round.f64"),
]
#: 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"),
]
#: 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"),
("gamma", "tgamma"),
("gammaln", "lgamma"),
("j0", "j0"),
("j1", "j1"),
("y0", "y0"),
("y1", "y1"),
]
# Not mapped: jv/yv, libm only supports integer orders.
#: (float, float) -> float numpy.* math functions lowered to runtime calls.
binary_fp_runtime_calls = [
("arctan2", "atan2"),
("copysign", "copysign"),
("fmax", "fmax"),
("fmin", "fmin"),
# ("ldexp", "ldexp"), # One argument is an int; would need a bit more plumbing.
("hypot", "hypot"),
("nextafter", "nextafter"),
]
#: Array handling builtins (special treatment due to allocations).
numpy_builtins = ["transpose"]
def fp_runtime_type(name, arity):
args = [("arg{}".format(i), builtins.TFloat()) for i in range(arity)]
return types.TExternalFunction(
OrderedDict(args),
builtins.TFloat(),
name,
# errno isn't observable from ARTIQ Python.
flags={"nounwind", "nowrite"},
broadcast_across_arrays=True)
math_fn_map = {
getattr(numpy, symbol): fp_runtime_type(mangle, arity=1)
for symbol, mangle in (unary_fp_intrinsics + unary_fp_runtime_calls)
}
for symbol, mangle in binary_fp_runtime_calls:
math_fn_map[getattr(numpy, symbol)] = fp_runtime_type(mangle, arity=2)
for name in numpy_builtins:
math_fn_map[getattr(numpy, name)] = types.TBuiltinFunction("numpy." + name)
if scipy_special is not None:
for symbol, mangle in scipy_special_unary_runtime_calls:
math_fn_map[getattr(scipy_special, symbol)] = fp_runtime_type(mangle, arity=1)
def match(obj):
return math_fn_map.get(obj, None)

View File

@ -1,101 +0,0 @@
"""
The :class:`Module` class encapsulates a single Python module,
which corresponds to a single ARTIQ translation unit (one LLVM
bitcode file and one object file, unless LTO is used).
A :class:`Module` can be created from a typed AST.
The :class:`Source` class parses a single source file or
string and infers types for it using a trivial :module:`prelude`.
"""
import os
from pythonparser import source, diagnostic, parse_buffer
from . import prelude, types, transforms, analyses, validators, embedding
class Source:
def __init__(self, source_buffer, engine=None):
if engine is None:
self.engine = diagnostic.Engine(all_errors_are_fatal=True)
else:
self.engine = engine
self.embedding_map = embedding.EmbeddingMap()
self.name, _ = os.path.splitext(os.path.basename(source_buffer.name))
asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine,
prelude=prelude.globals())
inferencer = transforms.Inferencer(engine=engine)
self.parsetree, self.comments = parse_buffer(source_buffer, engine=engine)
self.typedtree = asttyped_rewriter.visit(self.parsetree)
self.globals = asttyped_rewriter.globals
inferencer.visit(self.typedtree)
@classmethod
def from_string(cls, source_string, name="input.py", first_line=1, engine=None):
return cls(source.Buffer(source_string + "\n", name, first_line), engine=engine)
@classmethod
def from_filename(cls, filename, engine=None):
with open(filename) as f:
return cls(source.Buffer(f.read(), filename, 1), engine=engine)
class Module:
def __init__(self, src, ref_period=1e-6, attribute_writeback=True, remarks=False):
self.attribute_writeback = attribute_writeback
self.engine = src.engine
self.embedding_map = src.embedding_map
self.name = src.name
self.globals = src.globals
int_monomorphizer = transforms.IntMonomorphizer(engine=self.engine)
cast_monomorphizer = transforms.CastMonomorphizer(engine=self.engine)
inferencer = transforms.Inferencer(engine=self.engine)
monomorphism_validator = validators.MonomorphismValidator(engine=self.engine)
escape_validator = validators.EscapeValidator(engine=self.engine)
iodelay_estimator = transforms.IODelayEstimator(engine=self.engine,
ref_period=ref_period)
constness_validator = validators.ConstnessValidator(engine=self.engine)
artiq_ir_generator = transforms.ARTIQIRGenerator(engine=self.engine,
module_name=src.name,
ref_period=ref_period,
embedding_map=self.embedding_map)
dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine)
local_access_validator = validators.LocalAccessValidator(engine=self.engine)
local_demoter = transforms.LocalDemoter()
constant_hoister = transforms.ConstantHoister()
devirtualization = analyses.Devirtualization()
interleaver = transforms.Interleaver(engine=self.engine)
invariant_detection = analyses.InvariantDetection(engine=self.engine)
int_monomorphizer.visit(src.typedtree)
cast_monomorphizer.visit(src.typedtree)
inferencer.visit(src.typedtree)
monomorphism_validator.visit(src.typedtree)
escape_validator.visit(src.typedtree)
iodelay_estimator.visit_fixpoint(src.typedtree)
constness_validator.visit(src.typedtree)
devirtualization.visit(src.typedtree)
self.artiq_ir = artiq_ir_generator.visit(src.typedtree)
artiq_ir_generator.annotate_calls(devirtualization)
dead_code_eliminator.process(self.artiq_ir)
interleaver.process(self.artiq_ir)
local_access_validator.process(self.artiq_ir)
local_demoter.process(self.artiq_ir)
constant_hoister.process(self.artiq_ir)
if remarks:
invariant_detection.process(self.artiq_ir)
# for subkernels: main kernel inferencer output, to be passed to further compilations
self.subkernel_arg_types = inferencer.subkernel_arg_types
def build_llvm_ir(self, target):
"""Compile the module to LLVM IR for the specified target."""
llvm_ir_generator = transforms.LLVMIRGenerator(
engine=self.engine, module_name=self.name, target=target,
embedding_map=self.embedding_map)
return llvm_ir_generator.process(self.artiq_ir,
attribute_writeback=self.attribute_writeback)
def __repr__(self):
printer = types.TypePrinter()
globals = ["%s: %s" % (var, printer.name(self.globals[var])) for var in self.globals]
return "<artiq.compiler.Module %s {\n %s\n}>" % (repr(self.name), ",\n ".join(globals))

View File

@ -1,62 +0,0 @@
"""
The :mod:`prelude` module contains the initial global environment
in which ARTIQ kernels are evaluated.
"""
from . import builtins
def globals():
return {
# Value constructors
"bool": builtins.fn_bool(),
"int": builtins.fn_int(),
"float": builtins.fn_float(),
"str": builtins.fn_str(),
"bytes": builtins.fn_bytes(),
"bytearray": builtins.fn_bytearray(),
"list": builtins.fn_list(),
"array": builtins.fn_array(),
"range": builtins.fn_range(),
"int32": builtins.fn_int32(),
"int64": builtins.fn_int64(),
# Exception constructors
"Exception": builtins.fn_Exception(),
"IndexError": builtins.fn_IndexError(),
"ValueError": builtins.fn_ValueError(),
"ZeroDivisionError": builtins.fn_ZeroDivisionError(),
"RuntimeError": builtins.fn_RuntimeError(),
# Built-in Python functions
"len": builtins.fn_len(),
"round": builtins.fn_round(),
"abs": builtins.fn_abs(),
"min": builtins.fn_min(),
"max": builtins.fn_max(),
"print": builtins.fn_print(),
# ARTIQ decorators
"kernel": builtins.fn_kernel(),
"subkernel": builtins.fn_kernel(),
"portable": builtins.fn_kernel(),
"rpc": builtins.fn_kernel(),
# ARTIQ context managers
"parallel": builtins.obj_parallel(),
"interleave": builtins.obj_interleave(),
"sequential": builtins.obj_sequential(),
# ARTIQ time management functions
"delay": builtins.fn_delay(),
"now_mu": builtins.fn_now_mu(),
"delay_mu": builtins.fn_delay_mu(),
"at_mu": builtins.fn_at_mu(),
# ARTIQ utility functions
"rtio_log": builtins.fn_rtio_log(),
"core_log": builtins.fn_print(),
# ARTIQ subkernel utility functions
"subkernel_await": builtins.fn_subkernel_await(),
"subkernel_preload": builtins.fn_subkernel_preload(),
}

View File

@ -1,309 +0,0 @@
import os, sys, tempfile, subprocess, io
from artiq.compiler import types, ir
from llvmlite import ir as ll, binding as llvm
llvm.initialize()
llvm.initialize_all_targets()
llvm.initialize_all_asmprinters()
class RunTool:
def __init__(self, pattern, **tempdata):
self._pattern = pattern
self._tempdata = tempdata
self._tempnames = {}
self._tempfiles = {}
def __enter__(self):
for key, data in self._tempdata.items():
if data is None:
fd, filename = tempfile.mkstemp()
os.close(fd)
self._tempnames[key] = filename
else:
with tempfile.NamedTemporaryFile(delete=False) as f:
f.write(data)
self._tempnames[key] = f.name
cmdline = []
for argument in self._pattern:
cmdline.append(argument.format(**self._tempnames))
# https://bugs.python.org/issue17023
windows = os.name == "nt"
process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, shell=windows)
stdout, stderr = process.communicate()
if process.returncode != 0:
raise Exception("{} invocation failed: {}".
format(cmdline[0], stderr))
self._tempfiles["__stdout__"] = io.StringIO(stdout)
for key in self._tempdata:
if self._tempdata[key] is None:
self._tempfiles[key] = open(self._tempnames[key], "rb")
return self._tempfiles
def __exit__(self, exc_typ, exc_value, exc_trace):
for file in self._tempfiles.values():
file.close()
for filename in self._tempnames.values():
os.unlink(filename)
def _dump(target, kind, suffix, content):
if target is not None:
print("====== {} DUMP ======".format(kind.upper()), file=sys.stderr)
content_value = content()
if isinstance(content_value, str):
content_value = bytes(content_value, 'utf-8')
if target == "":
file = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
else:
file = open(target + suffix, "wb")
file.write(content_value)
file.close()
print("{} dumped as {}".format(kind, file.name), file=sys.stderr)
class Target:
"""
A description of the target environment where the binaries
generated by the ARTIQ compiler will be deployed.
:var triple: (string)
LLVM target triple, e.g. ``"riscv32"``
:var data_layout: (string)
LLVM target data layout, e.g. ``"E-m:e-p:32:32-i64:32-f64:32-v64:32-v128:32-a:0:32-n32"``
:var features: (list of string)
LLVM target CPU features, e.g. ``["mul", "div", "ffl1"]``
:var additional_linker_options: (list of string)
Linker options for the target in addition to the target-independent ones, e.g. ``["--target2=rel"]``
:var print_function: (string)
Name of a formatted print functions (with the signature of ``printf``)
provided by the target, e.g. ``"printf"``.
:var now_pinning: (boolean)
Whether the target implements the now-pinning RTIO optimization.
"""
triple = "unknown"
data_layout = ""
features = []
additional_linker_options = []
print_function = "printf"
now_pinning = True
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"
def __init__(self, subkernel_id=None):
self.llcontext = ll.Context()
self.subkernel_id = subkernel_id
def target_machine(self):
lltarget = llvm.Target.from_triple(self.triple)
llmachine = lltarget.create_target_machine(
features=",".join(["+{}".format(f) for f in self.features]),
reloc="pic", codemodel="default",
abiname="ilp32d" if isinstance(self, RV32GTarget) else "")
llmachine.set_asm_verbosity(True)
return llmachine
def optimize(self, llmodule):
llpassmgr = llvm.create_module_pass_manager()
# Register our alias analysis passes.
llpassmgr.add_basic_alias_analysis_pass()
llpassmgr.add_type_based_alias_analysis_pass()
# Start by cleaning up after our codegen and exposing as much
# information to LLVM as possible.
llpassmgr.add_constant_merge_pass()
llpassmgr.add_cfg_simplification_pass()
llpassmgr.add_instruction_combining_pass()
llpassmgr.add_sroa_pass()
llpassmgr.add_dead_code_elimination_pass()
llpassmgr.add_function_attrs_pass()
llpassmgr.add_global_optimizer_pass()
# Now, actually optimize the code.
llpassmgr.add_function_inlining_pass(275)
llpassmgr.add_ipsccp_pass()
llpassmgr.add_instruction_combining_pass()
llpassmgr.add_gvn_pass()
llpassmgr.add_cfg_simplification_pass()
llpassmgr.add_licm_pass()
# Clean up after optimizing.
llpassmgr.add_dead_arg_elimination_pass()
llpassmgr.add_global_dce_pass()
llpassmgr.run(llmodule)
def compile(self, module):
"""Compile the module to a relocatable object for this target."""
if os.getenv("ARTIQ_DUMP_SIG"):
print("====== MODULE_SIGNATURE DUMP ======", file=sys.stderr)
print(module, file=sys.stderr)
if os.getenv("ARTIQ_IR_NO_LOC") is not None:
ir.BasicBlock._dump_loc = False
type_printer = types.TypePrinter()
suffix = "_subkernel_{}".format(self.subkernel_id) if self.subkernel_id is not None else ""
_dump(os.getenv("ARTIQ_DUMP_IR"), "ARTIQ IR", suffix + ".txt",
lambda: "\n".join(fn.as_entity(type_printer) for fn in module.artiq_ir))
llmod = module.build_llvm_ir(self)
try:
llparsedmod = llvm.parse_assembly(str(llmod))
llparsedmod.verify()
except RuntimeError:
_dump("", "LLVM IR (broken)", ".ll", lambda: str(llmod))
raise
_dump(os.getenv("ARTIQ_DUMP_UNOPT_LLVM"), "LLVM IR (generated)", suffix + "_unopt.ll",
lambda: str(llparsedmod))
self.optimize(llparsedmod)
_dump(os.getenv("ARTIQ_DUMP_LLVM"), "LLVM IR (optimized)", suffix + ".ll",
lambda: str(llparsedmod))
return llparsedmod
def assemble(self, llmodule):
llmachine = self.target_machine()
_dump(os.getenv("ARTIQ_DUMP_ASM"), "Assembly", ".s",
lambda: llmachine.emit_assembly(llmodule))
_dump(os.getenv("ARTIQ_DUMP_OBJ"), "Object file", ".o",
lambda: llmachine.emit_object(llmodule))
return llmachine.emit_object(llmodule)
def link(self, objects):
"""Link the relocatable objects into a shared library for this target."""
with RunTool([self.tool_ld, "-shared", "--eh-frame-hdr"] +
self.additional_linker_options +
["-T" + os.path.join(os.path.dirname(__file__), "kernel.ld")] +
["{{obj{}}}".format(index) for index in range(len(objects))] +
["-x"] +
["-o", "{output}"],
output=None,
**{"obj{}".format(index): obj for index, obj in enumerate(objects)}) \
as results:
library = results["output"].read()
_dump(os.getenv("ARTIQ_DUMP_ELF"), "Shared library", ".elf",
lambda: library)
return library
def compile_and_link(self, modules):
return self.link([self.assemble(self.compile(module)) for module in modules])
def strip(self, library):
with RunTool([self.tool_strip, "--strip-debug", "{library}", "-o", "{output}"],
library=library, output=None) \
as results:
return results["output"].read()
def symbolize(self, library, addresses):
if addresses == []:
return []
# We got a list of return addresses, i.e. addresses of instructions
# just after the call. Offset them back to get an address somewhere
# inside the call instruction (or its delay slot), since that's what
# the backtrace entry should point at.
last_inlined = None
offset_addresses = [hex(addr - 1) for addr in addresses]
with RunTool([self.tool_symbolizer, "--addresses", "--functions", "--inlines",
"--demangle", "--output-style=GNU", "--exe={library}"] + offset_addresses,
library=library) \
as results:
lines = iter(results["__stdout__"].read().rstrip().split("\n"))
backtrace = []
while True:
try:
address_or_function = next(lines)
except StopIteration:
break
if address_or_function[:2] == "0x":
address = int(address_or_function[2:], 16) + 1 # remove offset
function = next(lines)
inlined = False
else:
address = backtrace[-1][4] # inlined
function = address_or_function
inlined = True
location = next(lines)
filename, line = location.rsplit(":", 1)
if filename == "??" or filename == "<synthesized>":
continue
if line == "?":
line = -1
else:
line = int(line)
# can't get column out of addr2line D:
if inlined:
last_inlined.append((filename, line, -1, function, address))
else:
last_inlined = []
backtrace.append((filename, line, -1, function, address,
last_inlined))
return backtrace
def demangle(self, names):
if not any(names):
return names
with RunTool([self.tool_cxxfilt] + names) as results:
return results["__stdout__"].read().rstrip().split("\n")
class NativeTarget(Target):
def __init__(self):
super().__init__()
self.triple = llvm.get_default_triple()
self.data_layout = str(llvm.targets.Target.from_default_triple().create_target_machine().target_data)
class RV32IMATarget(Target):
triple = "riscv32-unknown-linux"
data_layout = "e-m:e-p:32:32-i64:64-n32-S128"
features = ["m", "a"]
additional_linker_options = ["-m", "elf32lriscv"]
print_function = "core_log"
now_pinning = True
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"
class RV32GTarget(Target):
triple = "riscv32-unknown-linux"
data_layout = "e-m:e-p:32:32-i64:64-n32-S128"
features = ["m", "a", "f", "d"]
additional_linker_options = ["-m", "elf32lriscv"]
print_function = "core_log"
now_pinning = True
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"
class CortexA9Target(Target):
triple = "armv7-unknown-linux-gnueabihf"
data_layout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
features = ["dsp", "fp16", "neon", "vfp3"]
additional_linker_options = ["-m", "armelf_linux_eabi", "--target2=rel"]
print_function = "core_log"
now_pinning = False
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"

View File

@ -1,21 +0,0 @@
import time, cProfile as profile, pstats
def benchmark(f, name):
profiler = profile.Profile()
profiler.enable()
start = time.perf_counter()
end = 0
runs = 0
while end - start < 5 or runs < 10:
f()
runs += 1
end = time.perf_counter()
profiler.create_stats()
print("{} {} runs: {:.2f}s, {:.2f}ms/run".format(
runs, name, end - start, (end - start) / runs * 1000))
stats = pstats.Stats(profiler)
stats.strip_dirs().sort_stats('time').print_stats(10)

View File

@ -1,46 +0,0 @@
import sys, os, tokenize
from artiq.master.databases import DeviceDB
from artiq.master.worker_db import DeviceManager
import artiq.coredevice.core
from artiq.coredevice.core import Core, CompileError
def _render_diagnostic(diagnostic, colored):
return "\n".join(diagnostic.render(only_line=True))
artiq.coredevice.core._render_diagnostic = _render_diagnostic
def main():
if len(sys.argv) > 1 and sys.argv[1] == "+diag":
del sys.argv[1]
diag = True
else:
diag = False
if len(sys.argv) > 1 and sys.argv[1] == "+compile":
del sys.argv[1]
compile_only = True
else:
compile_only = False
ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py")
dmgr = DeviceManager(DeviceDB(ddb_path))
with tokenize.open(sys.argv[1]) as f:
testcase_code = compile(f.read(), f.name, "exec")
testcase_vars = {'__name__': 'testbench', 'dmgr': dmgr}
exec(testcase_code, testcase_vars)
try:
core = dmgr.get("core")
if compile_only:
core.compile(testcase_vars["entrypoint"], (), {})
else:
core.run(testcase_vars["entrypoint"], (), {})
except CompileError as error:
if not diag:
exit(1)
if __name__ == "__main__":
main()

View File

@ -1,103 +0,0 @@
import sys, fileinput, os
from pythonparser import source, diagnostic, algorithm, parse_buffer
from .. import prelude, types
from ..transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer, CastMonomorphizer
from ..transforms import IODelayEstimator
from ..validators import ConstnessValidator
class Printer(algorithm.Visitor):
"""
:class:`Printer` prints ``:`` and the node type after every typed node,
and ``->`` and the node type before the colon in a function definition.
In almost all cases (except function definition) this does not result
in valid Python syntax.
:ivar rewriter: (:class:`pythonparser.source.Rewriter`) rewriter instance
"""
def __init__(self, buf):
self.rewriter = source.Rewriter(buf)
self.type_printer = types.TypePrinter()
def rewrite(self):
return self.rewriter.rewrite()
def visit_FunctionDefT(self, node):
super().generic_visit(node)
self.rewriter.insert_before(node.colon_loc,
"->{}".format(self.type_printer.name(node.return_type)))
def visit_ExceptHandlerT(self, node):
super().generic_visit(node)
if node.name_loc:
self.rewriter.insert_after(node.name_loc,
":{}".format(self.type_printer.name(node.name_type)))
def visit_ForT(self, node):
super().generic_visit(node)
if node.trip_count is not None and node.trip_interval is not None:
self.rewriter.insert_after(node.keyword_loc,
"[{} x {} mu]".format(node.trip_count.fold(),
node.trip_interval.fold()))
def generic_visit(self, node):
super().generic_visit(node)
if hasattr(node, "type"):
self.rewriter.insert_after(node.loc,
":{}".format(self.type_printer.name(node.type)))
def main():
if len(sys.argv) > 1 and sys.argv[1] == "+mono":
del sys.argv[1]
monomorphize = True
else:
monomorphize = False
if len(sys.argv) > 1 and sys.argv[1] == "+iodelay":
del sys.argv[1]
iodelay = True
else:
iodelay = False
if len(sys.argv) > 1 and sys.argv[1] == "+diag":
del sys.argv[1]
def process_diagnostic(diag):
print("\n".join(diag.render(only_line=True)))
if diag.level == "fatal":
exit()
else:
def process_diagnostic(diag):
print("\n".join(diag.render()))
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
buf = source.Buffer("".join(fileinput.input()).expandtabs(),
os.path.basename(fileinput.filename()))
parsed, comments = parse_buffer(buf, engine=engine)
typed = ASTTypedRewriter(engine=engine, prelude=prelude.globals()).visit(parsed)
Inferencer(engine=engine).visit(typed)
ConstnessValidator(engine=engine).visit(typed)
if monomorphize:
CastMonomorphizer(engine=engine).visit(typed)
IntMonomorphizer(engine=engine).visit(typed)
Inferencer(engine=engine).visit(typed)
if iodelay:
IODelayEstimator(engine=engine, ref_period=1e6).visit_fixpoint(typed)
printer = Printer(buf)
printer.visit(typed)
for comment in comments:
if comment.text.find("CHECK") >= 0:
printer.rewriter.remove(comment.loc)
print(printer.rewrite().source)
if __name__ == "__main__":
main()

View File

@ -1,23 +0,0 @@
import sys, os, fileinput
from pythonparser import diagnostic
from .. import ir
from ..module import Module, Source
def main():
if os.getenv("ARTIQ_IR_NO_LOC") is not None:
ir.BasicBlock._dump_loc = False
def process_diagnostic(diag):
print("\n".join(diag.render()))
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine))
for fn in mod.artiq_ir:
print(fn)
if __name__ == "__main__":
main()

View File

@ -1,35 +0,0 @@
import os, sys, fileinput, ctypes
from pythonparser import diagnostic
from llvmlite import binding as llvm
from ..module import Module, Source
from ..targets import NativeTarget
def main():
libartiq_support = os.getenv("LIBARTIQ_SUPPORT")
if libartiq_support is not None:
llvm.load_library_permanently(libartiq_support)
def process_diagnostic(diag):
print("\n".join(diag.render()))
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
source = "".join(fileinput.input())
source = source.replace("#ARTIQ#", "")
mod = Module(Source.from_string(source.expandtabs(), engine=engine))
target = NativeTarget()
llmod = mod.build_llvm_ir(target)
llparsedmod = llvm.parse_assembly(str(llmod))
llparsedmod.verify()
llmachine = llvm.Target.from_triple(target.triple).create_target_machine()
lljit = llvm.create_mcjit_compiler(llparsedmod, llmachine)
llmain = lljit.get_function_address(llmod.name + ".__modinit__")
ctypes.CFUNCTYPE(None)(llmain)()
if __name__ == "__main__":
main()

View File

@ -1,31 +0,0 @@
import sys, fileinput
from pythonparser import diagnostic
from llvmlite import ir as ll
from ..module import Module, Source
from ..targets import NativeTarget
def main():
def process_diagnostic(diag):
print("\n".join(diag.render()))
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine))
target = NativeTarget()
llmod = mod.build_llvm_ir(target=target)
# Add main so that the result can be executed with lli
llmain = ll.Function(llmod, ll.FunctionType(ll.VoidType(), []), "main")
llbuilder = ll.IRBuilder(llmain.append_basic_block("entry"))
llbuilder.call(llmod.get_global(llmod.name + ".__modinit__"), [
ll.Constant(ll.IntType(8).as_pointer(), None)])
llbuilder.ret_void()
print(llmod)
if __name__ == "__main__":
main()

View File

@ -1,37 +0,0 @@
import sys, os
from pythonparser import diagnostic
from ..module import Module, Source
from ..targets import RV32GTarget
from . import benchmark
def main():
if not len(sys.argv) == 2:
print("Expected exactly one module filename", file=sys.stderr)
exit(1)
def process_diagnostic(diag):
print("\n".join(diag.render()), file=sys.stderr)
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
# Make sure everything's valid
filename = sys.argv[1]
with open(filename) as f:
code = f.read()
source = Source.from_string(code, filename, engine=engine)
module = Module(source)
benchmark(lambda: Source.from_string(code, filename),
"ARTIQ parsing and inference")
benchmark(lambda: Module(source),
"ARTIQ transforms and validators")
benchmark(lambda: RV32GTarget().compile_and_link([module]),
"LLVM optimization and linking")
if __name__ == "__main__":
main()

View File

@ -1,75 +0,0 @@
import sys, os, tokenize
from pythonparser import diagnostic
from ...language.environment import ProcessArgumentManager
from ...master.databases import DeviceDB, DatasetDB
from ...master.worker_db import DeviceManager, DatasetManager
from ..module import Module
from ..embedding import Stitcher
from ..targets import RV32GTarget
from . import benchmark
def main():
if not len(sys.argv) == 2:
print("Expected exactly one module filename", file=sys.stderr)
exit(1)
def process_diagnostic(diag):
print("\n".join(diag.render()), file=sys.stderr)
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
with tokenize.open(sys.argv[1]) as f:
testcase_code = compile(f.read(), f.name, "exec")
testcase_vars = {'__name__': 'testbench'}
exec(testcase_code, testcase_vars)
device_db_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py")
device_mgr = DeviceManager(DeviceDB(device_db_path))
dataset_db_path = os.path.join(os.path.dirname(sys.argv[1]), "dataset_db.mdb")
dataset_db = DatasetDB(dataset_db_path)
dataset_mgr = DatasetManager()
argument_mgr = ProcessArgumentManager({})
def embed():
experiment = testcase_vars["Benchmark"]((device_mgr, dataset_mgr, argument_mgr))
stitcher = Stitcher(core=experiment.core, dmgr=device_mgr)
stitcher.stitch_call(experiment.run, (), {})
stitcher.finalize()
return stitcher
stitcher = embed()
module = Module(stitcher)
target = RV32GTarget()
llvm_ir = target.compile(module)
elf_obj = target.assemble(llvm_ir)
elf_shlib = target.link([elf_obj])
benchmark(lambda: embed(),
"ARTIQ embedding")
benchmark(lambda: Module(stitcher),
"ARTIQ transforms and validators")
benchmark(lambda: target.compile(module),
"LLVM optimizations")
benchmark(lambda: target.assemble(llvm_ir),
"LLVM machine code emission")
benchmark(lambda: target.link([elf_obj]),
"Linking")
benchmark(lambda: target.strip(elf_shlib),
"Stripping debug information")
dataset_db.close_db()
if __name__ == "__main__":
main()

View File

@ -1,30 +0,0 @@
import sys, os
from pythonparser import diagnostic
from ..module import Module, Source
from ..targets import RV32GTarget
def main():
if not len(sys.argv) > 1:
print("Expected at least one module filename", file=sys.stderr)
exit(1)
def process_diagnostic(diag):
print("\n".join(diag.render()), file=sys.stderr)
if diag.level in ("fatal", "error"):
exit(1)
engine = diagnostic.Engine()
engine.process = process_diagnostic
modules = []
for filename in sys.argv[1:]:
modules.append(Module(Source.from_filename(filename, engine=engine)))
llobj = RV32GTarget().compile_and_link(modules)
basename, ext = os.path.splitext(sys.argv[-1])
with open(basename + ".so", "wb") as f:
f.write(llobj)
if __name__ == "__main__":
main()

View File

@ -1,44 +0,0 @@
import sys, fileinput
from pythonparser import diagnostic
from ..module import Module, Source
from .. import types, iodelay
def main():
if len(sys.argv) > 1 and sys.argv[1] == "+diag":
del sys.argv[1]
diag = True
def process_diagnostic(diag):
print("\n".join(diag.render(only_line=True)))
if diag.level == "fatal":
exit()
else:
diag = False
def process_diagnostic(diag):
print("\n".join(diag.render(colored=False)))
if diag.level in ("fatal", "error"):
exit(1)
if len(sys.argv) > 1 and sys.argv[1] == "+delay":
del sys.argv[1]
force_delays = True
else:
force_delays = False
engine = diagnostic.Engine()
engine.process = process_diagnostic
try:
mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine))
if force_delays:
for var in mod.globals:
typ = mod.globals[var].find()
if types.is_function(typ) and types.is_indeterminate_delay(typ.delay):
process_diagnostic(typ.delay.find().cause)
print(repr(mod))
except:
if not diag: raise
if __name__ == "__main__":
main()

View File

@ -1,12 +0,0 @@
from .asttyped_rewriter import ASTTypedRewriter
from .inferencer import Inferencer
from .int_monomorphizer import IntMonomorphizer
from .cast_monomorphizer import CastMonomorphizer
from .iodelay_estimator import IODelayEstimator
from .artiq_ir_generator import ARTIQIRGenerator
from .dead_code_eliminator import DeadCodeEliminator
from .local_demoter import LocalDemoter
from .constant_hoister import ConstantHoister
from .interleaver import Interleaver
from .typedtree_printer import TypedtreePrinter
from .llvm_ir_generator import LLVMIRGenerator

File diff suppressed because it is too large Load Diff

View File

@ -1,527 +0,0 @@
"""
:class:`ASTTypedRewriter` rewrites a parsetree (:mod:`pythonparser.ast`)
to a typedtree (:mod:`..asttyped`).
"""
from collections import OrderedDict
from pythonparser import ast, algorithm, diagnostic
from .. import asttyped, types, builtins
# This visitor will be called for every node with a scope,
# i.e.: class, function, comprehension, lambda
class LocalExtractor(algorithm.Visitor):
def __init__(self, env_stack, engine):
super().__init__()
self.env_stack = env_stack
self.engine = engine
self.in_root = False
self.in_assign = False
self.typing_env = OrderedDict()
# which names are global have to be recorded in the current scope
self.global_ = set()
# which names are nonlocal only affects whether the current scope
# gets a new binding or not, so we throw this away
self.nonlocal_ = set()
# parameters can't be declared as global or nonlocal
self.params = set()
def visit_in_assign(self, node, in_assign):
try:
old_in_assign, self.in_assign = self.in_assign, in_assign
return self.visit(node)
finally:
self.in_assign = old_in_assign
def visit_Assign(self, node):
self.visit(node.value)
self.visit_in_assign(node.targets, in_assign=True)
def visit_For(self, node):
self.visit(node.iter)
self.visit_in_assign(node.target, in_assign=True)
self.visit(node.body)
self.visit(node.orelse)
def visit_withitem(self, node):
self.visit(node.context_expr)
self.visit_in_assign(node.optional_vars, in_assign=True)
def visit_comprehension(self, node):
self.visit(node.iter)
self.visit_in_assign(node.target, in_assign=True)
self.visit(node.ifs)
def visit_generator(self, node):
if self.in_root:
return
self.in_root = True
self.visit(list(reversed(node.generators)))
self.visit(node.elt)
visit_ListComp = visit_generator
visit_SetComp = visit_generator
visit_GeneratorExp = visit_generator
def visit_DictComp(self, node):
if self.in_root:
return
self.in_root = True
self.visit(list(reversed(node.generators)))
self.visit(node.key)
self.visit(node.value)
def visit_root(self, node):
if self.in_root:
return
self.in_root = True
self.generic_visit(node)
visit_Module = visit_root # don't look at inner scopes
visit_ClassDef = visit_root
visit_Lambda = visit_root
def visit_FunctionDef(self, node):
if self.in_root:
self._assignable(node.name)
self.visit_root(node)
def _assignable(self, name):
assert name is not None
if name not in self.typing_env and name not in self.nonlocal_:
self.typing_env[name] = types.TVar()
def visit_arg(self, node):
if node.arg in self.params:
diag = diagnostic.Diagnostic("error",
"duplicate parameter '{name}'", {"name": node.arg},
node.loc)
self.engine.process(diag)
return
self._assignable(node.arg)
self.params.add(node.arg)
def visit_Name(self, node):
if self.in_assign:
# code like:
# x = 1
# def f():
# x = 1
# creates a new binding for x in f's scope
self._assignable(node.id)
def visit_Attribute(self, node):
self.visit_in_assign(node.value, in_assign=False)
def visit_Subscript(self, node):
self.visit_in_assign(node.value, in_assign=False)
self.visit_in_assign(node.slice, in_assign=False)
def _check_not_in(self, name, names, curkind, newkind, loc):
if name in names:
diag = diagnostic.Diagnostic("error",
"name '{name}' cannot be {curkind} and {newkind} simultaneously",
{"name": name, "curkind": curkind, "newkind": newkind}, loc)
self.engine.process(diag)
return True
return False
def visit_Global(self, node):
for name, loc in zip(node.names, node.name_locs):
if self._check_not_in(name, self.nonlocal_, "nonlocal", "global", loc) or \
self._check_not_in(name, self.params, "a parameter", "global", loc):
continue
self.global_.add(name)
if len(self.env_stack) == 1:
self._assignable(name) # already in global scope
else:
if name not in self.env_stack[1]:
self.env_stack[1][name] = types.TVar()
self.typing_env[name] = self.env_stack[1][name]
def visit_Nonlocal(self, node):
for name, loc in zip(node.names, node.name_locs):
if self._check_not_in(name, self.global_, "global", "nonlocal", loc) or \
self._check_not_in(name, self.params, "a parameter", "nonlocal", loc):
continue
# nonlocal does not search prelude and global scopes
found = False
for outer_env in reversed(self.env_stack[2:]):
if name in outer_env:
found = True
break
if not found:
diag = diagnostic.Diagnostic("error",
"cannot declare name '{name}' as nonlocal: it is not bound in any outer scope",
{"name": name},
loc, [node.keyword_loc])
self.engine.process(diag)
continue
self.nonlocal_.add(name)
def visit_ExceptHandler(self, node):
self.visit(node.type)
if node.name is not None:
self._assignable(node.name)
for stmt in node.body:
self.visit(stmt)
class ASTTypedRewriter(algorithm.Transformer):
"""
:class:`ASTTypedRewriter` converts an untyped AST to a typed AST
where all type fields of non-literals are filled with fresh type variables,
and type fields of literals are filled with corresponding types.
:class:`ASTTypedRewriter` also discovers the scope of variable bindings
via :class:`LocalExtractor`.
"""
def __init__(self, engine, prelude):
self.engine = engine
self.globals = None
self.env_stack = [prelude]
self.in_class = None
def _try_find_name(self, name):
if self.in_class is not None:
typ = self.in_class.constructor_type.attributes.get(name)
if typ is not None:
return typ
for typing_env in reversed(self.env_stack):
if name in typing_env:
return typing_env[name]
def _find_name(self, name, loc):
typ = self._try_find_name(name)
if typ is not None:
return typ
diag = diagnostic.Diagnostic("fatal",
"undefined variable '{name}'", {"name":name}, loc)
self.engine.process(diag)
# Visitors that replace node with a typed node
#
def visit_Module(self, node):
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
node = asttyped.ModuleT(
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
body=node.body, loc=node.loc)
self.globals = node.typing_env
try:
self.env_stack.append(node.typing_env)
return self.generic_visit(node)
finally:
self.env_stack.pop()
def visit_FunctionDef(self, node):
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
signature_type = self._find_name(node.name, node.name_loc)
node = asttyped.FunctionDefT(
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
signature_type=signature_type, return_type=types.TVar(),
name=node.name, args=node.args, returns=node.returns,
body=node.body, decorator_list=node.decorator_list,
keyword_loc=node.keyword_loc, name_loc=node.name_loc,
arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs,
loc=node.loc, remote_fn=False)
try:
self.env_stack.append(node.typing_env)
return self.generic_visit(node)
finally:
self.env_stack.pop()
def visit_ClassDef(self, node):
if any(node.bases) or any(node.keywords) or \
node.starargs is not None or node.kwargs is not None:
diag = diagnostic.Diagnostic("error",
"inheritance is not supported", {},
node.lparen_loc.join(node.rparen_loc))
self.engine.process(diag)
for child in node.body:
if isinstance(child, (ast.Assign, ast.FunctionDef, ast.Pass)):
continue
diag = diagnostic.Diagnostic("fatal",
"class body must contain only assignments and function definitions", {},
child.loc)
self.engine.process(diag)
if node.name in self.env_stack[-1]:
diag = diagnostic.Diagnostic("fatal",
"variable '{name}' is already defined", {"name":node.name}, node.name_loc)
self.engine.process(diag)
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
# Now we create two types.
# The first type is the type of instances created by the constructor.
# Its attributes are those of the class environment, but wrapped
# appropriately so that they are linked to the class from which they
# originate.
instance_type = types.TInstance(node.name, OrderedDict())
# The second type is the type of the constructor itself (in other words,
# the class object): it is simply a singleton type that has the class
# environment as attributes.
constructor_type = types.TConstructor(instance_type)
constructor_type.attributes = extractor.typing_env
instance_type.constructor = constructor_type
self.env_stack[-1][node.name] = constructor_type
node = asttyped.ClassDefT(
constructor_type=constructor_type,
name=node.name,
bases=self.visit(node.bases), keywords=self.visit(node.keywords),
starargs=self.visit(node.starargs), kwargs=self.visit(node.kwargs),
body=node.body,
decorator_list=self.visit(node.decorator_list),
keyword_loc=node.keyword_loc, name_loc=node.name_loc,
lparen_loc=node.lparen_loc, star_loc=node.star_loc,
dstar_loc=node.dstar_loc, rparen_loc=node.rparen_loc,
colon_loc=node.colon_loc, at_locs=node.at_locs,
loc=node.loc)
try:
old_in_class, self.in_class = self.in_class, node
return self.generic_visit(node)
finally:
self.in_class = old_in_class
def visit_arg(self, node):
if node.annotation is not None:
diag = diagnostic.Diagnostic("fatal",
"type annotations are not supported here", {},
node.annotation.loc)
self.engine.process(diag)
return asttyped.argT(type=self._find_name(node.arg, node.loc),
arg=node.arg, annotation=None,
arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc)
def visit_Num(self, node):
if isinstance(node.n, int):
typ = builtins.TInt()
elif isinstance(node.n, float):
typ = builtins.TFloat()
else:
diag = diagnostic.Diagnostic("fatal",
"numeric type {type} is not supported", {"type": node.n.__class__.__name__},
node.loc)
self.engine.process(diag)
return asttyped.NumT(type=typ,
n=node.n, loc=node.loc)
def visit_Str(self, node):
if isinstance(node.s, str):
typ = builtins.TStr()
elif isinstance(node.s, bytes):
typ = builtins.TBytes()
else:
assert False
return asttyped.StrT(type=typ, s=node.s,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
def visit_Name(self, node):
return asttyped.NameT(type=self._find_name(node.id, node.loc),
id=node.id, ctx=node.ctx, loc=node.loc)
def visit_NameConstant(self, node):
if node.value is True or node.value is False:
typ = builtins.TBool()
elif node.value is None:
typ = builtins.TNone()
return asttyped.NameConstantT(type=typ, value=node.value, loc=node.loc)
def visit_Tuple(self, node):
node = self.generic_visit(node)
return asttyped.TupleT(type=types.TTuple([x.type for x in node.elts]),
elts=node.elts, ctx=node.ctx, loc=node.loc)
def visit_List(self, node):
node = self.generic_visit(node)
node = asttyped.ListT(type=builtins.TList(),
elts=node.elts, ctx=node.ctx,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
return self.visit(node)
def visit_Attribute(self, node):
node = self.generic_visit(node)
node = asttyped.AttributeT(type=types.TVar(),
value=node.value, attr=node.attr, ctx=node.ctx,
dot_loc=node.dot_loc, attr_loc=node.attr_loc, loc=node.loc)
return self.visit(node)
def visit_Slice(self, node):
node = self.generic_visit(node)
node = asttyped.SliceT(type=types.TVar(),
lower=node.lower, upper=node.upper, step=node.step,
bound_colon_loc=node.bound_colon_loc,
step_colon_loc=node.step_colon_loc,
loc=node.loc)
return self.visit(node)
def visit_Subscript(self, node):
node = self.generic_visit(node)
node = asttyped.SubscriptT(type=types.TVar(),
value=node.value, slice=node.slice, ctx=node.ctx,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
return self.visit(node)
def visit_BoolOp(self, node):
node = self.generic_visit(node)
node = asttyped.BoolOpT(type=types.TVar(),
op=node.op, values=node.values,
op_locs=node.op_locs, loc=node.loc)
return self.visit(node)
def visit_UnaryOp(self, node):
node = self.generic_visit(node)
node = asttyped.UnaryOpT(type=types.TVar(),
op=node.op, operand=node.operand,
loc=node.loc)
return self.visit(node)
def visit_BinOp(self, node):
node = self.generic_visit(node)
node = asttyped.BinOpT(type=types.TVar(),
left=node.left, op=node.op, right=node.right,
loc=node.loc)
return self.visit(node)
def visit_Compare(self, node):
node = self.generic_visit(node)
node = asttyped.CompareT(type=types.TVar(),
left=node.left, ops=node.ops, comparators=node.comparators,
loc=node.loc)
return self.visit(node)
def visit_IfExp(self, node):
node = self.generic_visit(node)
node = asttyped.IfExpT(type=types.TVar(),
test=node.test, body=node.body, orelse=node.orelse,
if_loc=node.if_loc, else_loc=node.else_loc, loc=node.loc)
return self.visit(node)
def visit_ListComp(self, node):
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
node = asttyped.ListCompT(
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
type=types.TVar(),
elt=node.elt, generators=node.generators,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
try:
self.env_stack.append(node.typing_env)
return self.generic_visit(node)
finally:
self.env_stack.pop()
def visit_Call(self, node):
node = self.generic_visit(node)
node = asttyped.CallT(type=types.TVar(), iodelay=None, arg_exprs={},
remote_fn=False, func=node.func,
args=node.args, keywords=node.keywords,
starargs=node.starargs, kwargs=node.kwargs,
star_loc=node.star_loc, dstar_loc=node.dstar_loc,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
return node
def visit_Lambda(self, node):
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
extractor.visit(node)
node = asttyped.LambdaT(
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
type=types.TVar(),
args=node.args, body=node.body,
lambda_loc=node.lambda_loc, colon_loc=node.colon_loc, loc=node.loc)
try:
self.env_stack.append(node.typing_env)
return self.generic_visit(node)
finally:
self.env_stack.pop()
def visit_ExceptHandler(self, node):
node = self.generic_visit(node)
if node.name is not None:
name_type = self._find_name(node.name, node.name_loc)
else:
name_type = types.TVar()
node = asttyped.ExceptHandlerT(
name_type=name_type,
filter=node.type, name=node.name, body=node.body,
except_loc=node.except_loc, as_loc=node.as_loc, name_loc=node.name_loc,
colon_loc=node.colon_loc, loc=node.loc)
return node
def visit_Raise(self, node):
node = self.generic_visit(node)
if node.cause:
diag = diagnostic.Diagnostic("error",
"'raise from' syntax is not supported", {},
node.from_loc)
self.engine.process(diag)
return node
def visit_For(self, node):
node = self.generic_visit(node)
node = asttyped.ForT(
target=node.target, iter=node.iter, body=node.body, orelse=node.orelse,
trip_count=None, trip_interval=None,
keyword_loc=node.keyword_loc, in_loc=node.in_loc, for_colon_loc=node.for_colon_loc,
else_loc=node.else_loc, else_colon_loc=node.else_colon_loc, loc=node.loc)
return node
def visit_withitem(self, node):
node = self.generic_visit(node)
node = asttyped.withitemT(
context_expr=node.context_expr, optional_vars=node.optional_vars,
enter_type=types.TVar(), exit_type=types.TVar(),
as_loc=node.as_loc, loc=node.loc)
return node
# Unsupported visitors
#
def visit_unsupported(self, node):
diag = diagnostic.Diagnostic("fatal",
"this syntax is not supported", {},
node.loc)
self.engine.process(diag)
# expr
visit_Dict = visit_unsupported
visit_DictComp = visit_unsupported
visit_Ellipsis = visit_unsupported
visit_GeneratorExp = visit_unsupported
# visit_Set = visit_unsupported
visit_SetComp = visit_unsupported
visit_Starred = visit_unsupported
visit_Yield = visit_unsupported
visit_YieldFrom = visit_unsupported
# stmt
visit_Delete = visit_unsupported
visit_Import = visit_unsupported
visit_ImportFrom = visit_unsupported

View File

@ -1,47 +0,0 @@
"""
:class:`CastMonomorphizer` uses explicit casts to monomorphize
expressions of undetermined integer type to either 32 or 64 bits.
"""
from pythonparser import algorithm, diagnostic
from .. import types, builtins, asttyped
class CastMonomorphizer(algorithm.Visitor):
def __init__(self, engine):
self.engine = engine
def visit_CallT(self, node):
if (types.is_builtin(node.func.type, "int") or
types.is_builtin(node.func.type, "int32") or
types.is_builtin(node.func.type, "int64")):
typ = node.type.find()
if (not types.is_var(typ["width"]) and
len(node.args) == 1 and
builtins.is_int(node.args[0].type) and
types.is_var(node.args[0].type.find()["width"])):
if isinstance(node.args[0], asttyped.BinOpT):
# Binary operations are a bit special: they can widen, and so their
# return type is indeterminate until both argument types are fully known.
# In case we first monomorphize the return type, and then some argument type,
# and argument type is wider than return type, we'll introduce a conflict.
return
node.args[0].type.unify(typ)
if types.is_builtin(node.func.type, "int") or \
types.is_builtin(node.func.type, "round"):
typ = node.type.find()
if types.is_var(typ["width"]):
typ["width"].unify(types.TValue(32))
self.generic_visit(node)
def visit_CoerceT(self, node):
if isinstance(node.value, asttyped.NumT) and \
builtins.is_int(node.type) and \
builtins.is_int(node.value.type) and \
not types.is_var(node.type["width"]) and \
types.is_var(node.value.type["width"]):
node.value.type.unify(node.type)
self.generic_visit(node)

View File

@ -1,43 +0,0 @@
"""
:class:`ConstantHoister` is a code motion transform:
it moves any invariant loads to the earliest point where
they may be executed.
"""
from .. import types, ir
class ConstantHoister:
def process(self, functions):
for func in functions:
self.process_function(func)
def process_function(self, func):
entry = func.entry()
worklist = set(func.instructions())
moved = set()
while len(worklist) > 0:
insn = worklist.pop()
if (isinstance(insn, ir.GetAttr) and insn not in moved and
types.is_instance(insn.object().type) and
insn.attr in insn.object().type.constant_attributes):
has_variant_operands = False
index_in_entry = 0
for operand in insn.operands:
if isinstance(operand, ir.Argument):
pass
elif isinstance(operand, ir.Instruction) and operand.basic_block == entry:
index_in_entry = entry.index(operand) + 1
else:
has_variant_operands = True
break
if has_variant_operands:
continue
insn.remove_from_parent()
entry.instructions.insert(index_in_entry, insn)
moved.add(insn)
for use in insn.uses:
worklist.add(use)

View File

@ -1,83 +0,0 @@
"""
:class:`DeadCodeEliminator` is a dead code elimination transform:
it only basic blocks with no predecessors as well as unused
instructions without side effects.
"""
from .. import ir
class DeadCodeEliminator:
def __init__(self, engine):
self.engine = engine
def process(self, functions):
for func in functions:
self.process_function(func)
def process_function(self, func):
# defer removing those blocks, so our use checks will ignore deleted blocks
preserve = [func.entry()]
work_list = [func.entry()]
while any(work_list):
block = work_list.pop()
for succ in block.successors():
if succ not in preserve:
preserve.append(succ)
work_list.append(succ)
to_be_removed = []
for block in func.basic_blocks:
if block not in preserve:
block.is_removed = True
to_be_removed.append(block)
for insn in block.instructions:
insn.is_removed = True
for block in to_be_removed:
self.remove_block(block)
modified = True
while modified:
modified = False
for insn in func.instructions():
# Note that GetLocal is treated as an impure operation:
# the local access validator has to observe it to emit
# a diagnostic for reads of uninitialized locals, and
# it also has to run after the interleaver, but interleaver
# doesn't like to work with IR before DCE.
if isinstance(insn, (ir.Phi, ir.Alloc, ir.GetAttr, ir.GetElem, ir.Coerce,
ir.Arith, ir.Compare, ir.Select, ir.Quote, ir.Closure,
ir.Offset)) \
and not any(insn.uses):
insn.erase()
modified = True
def remove_block(self, block):
# block.uses are updated while iterating
for use in set(block.uses):
if use.is_removed:
continue
if isinstance(use, ir.Phi):
use.remove_incoming_block(block)
if not any(use.operands):
self.remove_instruction(use)
elif isinstance(use, ir.SetLocal):
# setlocal %env, %block is only used for lowering finally
use.erase()
else:
assert False
block.erase()
def remove_instruction(self, insn):
for use in set(insn.uses):
if use.is_removed:
continue
if isinstance(use, ir.Phi):
use.remove_incoming_value(insn)
if not any(use.operands):
self.remove_instruction(use)
else:
assert False
insn.erase()

File diff suppressed because it is too large Load Diff

View File

@ -1,28 +0,0 @@
"""
:class:`IntMonomorphizer` collapses the integer literals of undetermined
width to 32 bits, assuming they fit into 32 bits, or 64 bits if they
do not.
"""
from pythonparser import algorithm, diagnostic
from .. import types, builtins, asttyped
class IntMonomorphizer(algorithm.Visitor):
def __init__(self, engine):
self.engine = engine
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:
width = 32
elif -2**63 < node.n < 2**63-1:
width = 64
else:
diag = diagnostic.Diagnostic("error",
"integer literal out of range for a signed 64-bit value", {},
node.loc)
self.engine.process(diag)
return
node.type["width"].unify(types.TValue(width))

View File

@ -1,185 +0,0 @@
"""
:class:`Interleaver` reorders requests to the RTIO core so that
the timestamp would always monotonically nondecrease.
"""
from pythonparser import diagnostic
from .. import types, builtins, ir, iodelay
from ..analyses import domination
from ..algorithms import inline, unroll
def delay_free_subgraph(root, limit):
visited = set()
queue = root.successors()
while len(queue) > 0:
block = queue.pop()
visited.add(block)
if block is limit:
continue
if isinstance(block.terminator(), ir.Delay):
return False
for successor in block.successors():
if successor not in visited:
queue.append(successor)
return True
def iodelay_of_block(block):
terminator = block.terminator()
if isinstance(terminator, ir.Delay):
# We should be able to fold everything without free variables.
folded_expr = terminator.interval.fold()
assert iodelay.is_const(folded_expr)
return folded_expr.value
else:
return 0
def is_pure_delay(insn):
return isinstance(insn, ir.Builtin) and insn.op in ("delay", "delay_mu")
def is_impure_delay_block(block):
terminator = block.terminator()
return isinstance(terminator, ir.Delay) and \
not is_pure_delay(terminator.decomposition())
class Interleaver:
def __init__(self, engine):
self.engine = engine
def process(self, functions):
for func in functions:
self.process_function(func)
def process_function(self, func):
for insn in func.instructions():
if isinstance(insn, ir.Delay):
if any(insn.interval.free_vars()):
# If a function has free variables in delay expressions,
# that means its IO delay depends on arguments.
# Do not change such functions in any way so that it will
# be successfully inlined and then removed by DCE.
return
postdom_tree = None
for insn in func.instructions():
if not isinstance(insn, ir.Interleave):
continue
# Lazily compute dominators.
if postdom_tree is None:
postdom_tree = domination.PostDominatorTree(func)
target_block = insn.basic_block
target_time = 0
source_blocks = insn.basic_block.successors()
source_times = [0 for _ in source_blocks]
if len(source_blocks) == 1:
# Immediate dominator for a interleave instruction with one successor
# is the first instruction in the body of the statement which created
# it, but below we expect that it would be the first instruction after
# the statement itself.
insn.replace_with(ir.Branch(source_blocks[0]))
continue
interleave_until = postdom_tree.immediate_dominator(insn.basic_block)
assert interleave_until is not None # no nonlocal flow in `with interleave`
assert interleave_until not in source_blocks
while len(source_blocks) > 0:
def time_after_block(pair):
index, block = pair
return source_times[index] + iodelay_of_block(block)
# Always prefer impure blocks (with calls) to pure blocks, because
# impure blocks may expand with smaller delays appearing, and in
# case of a tie, if a pure block is preferred, this would violate
# the timeline monotonicity.
available_source_blocks = list(filter(is_impure_delay_block, source_blocks))
if not any(available_source_blocks):
available_source_blocks = source_blocks
index, source_block = min(enumerate(available_source_blocks), key=time_after_block)
source_block_delay = iodelay_of_block(source_block)
new_target_time = source_times[index] + source_block_delay
target_time_delta = new_target_time - target_time
assert target_time_delta >= 0
target_terminator = target_block.terminator()
if isinstance(target_terminator, ir.Interleave):
target_terminator.replace_with(ir.Branch(source_block))
elif isinstance(target_terminator, (ir.Delay, ir.Branch)):
target_terminator.set_target(source_block)
else:
assert False
source_terminator = source_block.terminator()
if isinstance(source_terminator, ir.Interleave):
source_terminator.replace_with(ir.Branch(source_terminator.target()))
elif isinstance(source_terminator, ir.Branch):
pass
elif isinstance(source_terminator, ir.BranchIf):
# Skip a delay-free loop/conditional
source_block = postdom_tree.immediate_dominator(source_block)
assert (source_block is not None)
elif isinstance(source_terminator, ir.Return):
break
elif isinstance(source_terminator, ir.Delay):
old_decomp = source_terminator.decomposition()
if is_pure_delay(old_decomp):
if target_time_delta > 0:
new_decomp_expr = ir.Constant(int(target_time_delta), builtins.TInt64())
new_decomp = ir.Builtin("delay_mu", [new_decomp_expr], builtins.TNone())
new_decomp.loc = old_decomp.loc
source_terminator.basic_block.insert(new_decomp, before=source_terminator)
source_terminator.interval = iodelay.Const(target_time_delta)
source_terminator.set_decomposition(new_decomp)
else:
source_terminator.replace_with(ir.Branch(source_terminator.target()))
old_decomp.erase()
else: # It's a call.
need_to_inline = len(source_blocks) > 1
if need_to_inline:
if old_decomp.static_target_function is None:
diag = diagnostic.Diagnostic("fatal",
"it is not possible to interleave this function call within "
"a 'with interleave:' statement because the compiler could not "
"prove that the same function would always be called", {},
old_decomp.loc)
self.engine.process(diag)
inline(old_decomp)
postdom_tree = domination.PostDominatorTree(func)
continue
elif target_time_delta > 0:
source_terminator.interval = iodelay.Const(target_time_delta)
else:
source_terminator.replace_with(ir.Branch(source_terminator.target()))
elif isinstance(source_terminator, ir.Loop):
unroll(source_terminator)
postdom_tree = domination.PostDominatorTree(func)
continue
else:
assert False
target_block = source_block
target_time = new_target_time
new_source_block = postdom_tree.immediate_dominator(source_block)
assert (new_source_block is not None)
assert delay_free_subgraph(source_block, new_source_block)
if new_source_block == interleave_until:
# We're finished with this branch.
del source_blocks[index]
del source_times[index]
else:
source_blocks[index] = new_source_block
source_times[index] = new_target_time

View File

@ -1,334 +0,0 @@
"""
:class:`IODelayEstimator` calculates the amount of time
elapsed from the point of view of the RTIO core for
every function.
"""
from pythonparser import ast, algorithm, diagnostic
from .. import types, iodelay, builtins, asttyped
class _UnknownDelay(Exception):
pass
class _IndeterminateDelay(Exception):
def __init__(self, cause):
self.cause = cause
class IODelayEstimator(algorithm.Visitor):
def __init__(self, engine, ref_period):
self.engine = engine
self.ref_period = ref_period
self.changed = False
self.current_delay = iodelay.Const(0)
self.current_args = None
self.current_goto = None
self.current_return = None
def evaluate(self, node, abort, context):
if isinstance(node, asttyped.NumT):
return iodelay.Const(node.n)
elif isinstance(node, asttyped.CoerceT):
return self.evaluate(node.value, abort, context)
elif isinstance(node, asttyped.NameT):
if self.current_args is None:
note = diagnostic.Diagnostic("note",
"this variable is not an argument", {},
node.loc)
abort([note])
elif node.id in [arg.arg for arg in self.current_args.args]:
return iodelay.Var(node.id)
else:
notes = [
diagnostic.Diagnostic("note",
"this variable is not an argument of the innermost function", {},
node.loc),
diagnostic.Diagnostic("note",
"only these arguments are in scope of analysis", {},
self.current_args.loc)
]
abort(notes)
elif isinstance(node, asttyped.BinOpT):
lhs = self.evaluate(node.left, abort, context)
rhs = self.evaluate(node.right, abort, context)
if isinstance(node.op, ast.Add):
return lhs + rhs
elif isinstance(node.op, ast.Sub):
return lhs - rhs
elif isinstance(node.op, ast.Mult):
return lhs * rhs
elif isinstance(node.op, ast.Div):
return lhs / rhs
elif isinstance(node.op, ast.FloorDiv):
return lhs // rhs
else:
note = diagnostic.Diagnostic("note",
"this operator is not supported {context}",
{"context": context},
node.op.loc)
abort([note])
else:
note = diagnostic.Diagnostic("note",
"this expression is not supported {context}",
{"context": context},
node.loc)
abort([note])
def abort(self, message, loc, notes=[]):
diag = diagnostic.Diagnostic("error", message, {}, loc, notes=notes)
raise _IndeterminateDelay(diag)
def visit_fixpoint(self, node):
while True:
self.changed = False
self.visit(node)
if not self.changed:
return
def visit_ModuleT(self, node):
try:
for stmt in node.body:
try:
self.visit(stmt)
except _UnknownDelay:
pass # more luck next time?
except _IndeterminateDelay:
pass # we don't care; module-level code is never interleaved
def visit_function(self, args, body, typ, loc):
old_args, self.current_args = self.current_args, args
old_return, self.current_return = self.current_return, None
old_delay, self.current_delay = self.current_delay, iodelay.Const(0)
try:
self.visit(body)
if not iodelay.is_zero(self.current_delay) and self.current_return is not None:
self.abort("only return statement at the end of the function "
"can be interleaved", self.current_return.loc)
delay = types.TFixedDelay(self.current_delay.fold())
except _IndeterminateDelay as error:
delay = types.TIndeterminateDelay(error.cause)
self.current_delay = old_delay
self.current_return = old_return
self.current_args = old_args
if types.is_indeterminate_delay(delay) and types.is_indeterminate_delay(typ.delay):
# Both delays indeterminate; no point in unifying since that will
# replace the lazy and more specific error with an eager and more generic
# error (unification error of delay(?) with delay(?), which is useless).
return
try:
old_delay = typ.delay.find()
typ.delay.unify(delay)
if typ.delay.find() != old_delay:
self.changed = True
except types.UnificationError as e:
printer = types.TypePrinter()
diag = diagnostic.Diagnostic("fatal",
"delay {delaya} was inferred for this function, but its delay is already "
"constrained externally to {delayb}",
{"delaya": printer.name(delay), "delayb": printer.name(typ.delay)},
loc)
self.engine.process(diag)
def visit_FunctionDefT(self, node):
self.visit(node.args.defaults)
self.visit(node.args.kw_defaults)
# We can only handle return in tail position.
if isinstance(node.body[-1], ast.Return):
body = node.body[:-1]
else:
body = node.body
self.visit_function(node.args, body, node.signature_type.find(), node.loc)
visit_QuotedFunctionDefT = visit_FunctionDefT
def visit_LambdaT(self, node):
self.visit_function(node.args, node.body, node.type.find(), node.loc)
def get_iterable_length(self, node, context):
def abort(notes):
self.abort("for statement cannot be interleaved because "
"iteration count is indeterminate",
node.loc, notes)
def evaluate(node):
return self.evaluate(node, abort, context)
if isinstance(node, asttyped.CallT) and types.is_builtin(node.func.type, "range"):
range_min, range_max, range_step = iodelay.Const(0), None, iodelay.Const(1)
if len(node.args) == 3:
range_min, range_max, range_step = map(evaluate, node.args)
elif len(node.args) == 2:
range_min, range_max = map(evaluate, node.args)
elif len(node.args) == 1:
range_max, = map(evaluate, node.args)
return (range_max - range_min) // range_step
else:
note = diagnostic.Diagnostic("note",
"this value is not a constant range literal", {},
node.loc)
abort([note])
def visit_ForT(self, node):
self.visit(node.iter)
old_goto, self.current_goto = self.current_goto, None
old_delay, self.current_delay = self.current_delay, iodelay.Const(0)
self.visit(node.body)
if iodelay.is_zero(self.current_delay):
self.current_delay = old_delay
else:
if self.current_goto is not None:
self.abort("loop iteration count is indeterminate because of control flow",
self.current_goto.loc)
context = "in an iterable used in a for loop that is being interleaved"
node.trip_count = self.get_iterable_length(node.iter, context).fold()
node.trip_interval = self.current_delay.fold()
self.current_delay = old_delay + node.trip_interval * node.trip_count
self.current_goto = old_goto
self.visit(node.orelse)
def visit_goto(self, node):
self.current_goto = node
visit_Break = visit_goto
visit_Continue = visit_goto
def visit_control_flow(self, kind, node):
old_delay, self.current_delay = self.current_delay, iodelay.Const(0)
self.generic_visit(node)
if not iodelay.is_zero(self.current_delay):
self.abort("{} cannot be interleaved".format(kind), node.loc)
self.current_delay = old_delay
visit_If = lambda self, node: self.visit_control_flow("if statement", node)
visit_IfExpT = lambda self, node: self.visit_control_flow("if expression", node)
visit_Try = lambda self, node: self.visit_control_flow("try statement", node)
def visit_While(self, node):
old_goto, self.current_goto = self.current_goto, None
self.visit_control_flow("while statement", node)
self.current_goto = old_goto
def visit_Return(self, node):
self.current_return = node
def visit_With(self, node):
self.visit(node.items)
context_expr = node.items[0].context_expr
if len(node.items) == 1 and types.is_builtin(context_expr.type, "interleave"):
try:
delays = []
for stmt in node.body:
old_delay, self.current_delay = self.current_delay, iodelay.Const(0)
self.visit(stmt)
delays.append(self.current_delay)
self.current_delay = old_delay
if any(delays):
self.current_delay += iodelay.Max(delays)
except _IndeterminateDelay as error:
# Interleave failures inside `with` statements are hard failures,
# since there's no chance that the code will never actually execute
# inside a `with` statement after all.
note = diagnostic.Diagnostic("note",
"while interleaving this 'with interleave:' statement", {},
node.loc)
error.cause.notes += [note]
self.engine.process(error.cause)
flow_stmt = None
if self.current_goto is not None:
flow_stmt = self.current_goto
elif self.current_return is not None:
flow_stmt = self.current_return
if flow_stmt is not None:
note = diagnostic.Diagnostic("note",
"this '{kind}' statement transfers control out of "
"the 'with interleave:' statement",
{"kind": flow_stmt.keyword_loc.source()},
flow_stmt.loc)
diag = diagnostic.Diagnostic("error",
"cannot interleave this 'with interleave:' statement", {},
node.keyword_loc.join(node.colon_loc), notes=[note])
self.engine.process(diag)
elif len(node.items) == 1 and types.is_builtin(context_expr.type, "sequential"):
self.visit(node.body)
else:
self.abort("with statement cannot be interleaved", node.loc)
def visit_CallT(self, node):
typ = node.func.type.find()
def abort(notes):
self.abort("call cannot be interleaved because "
"an argument cannot be statically evaluated",
node.loc, notes)
if types.is_builtin(typ, "delay"):
value = self.evaluate(node.args[0], abort=abort,
context="as an argument for delay()")
call_delay = iodelay.SToMU(value, ref_period=self.ref_period)
elif types.is_builtin(typ, "delay_mu"):
value = self.evaluate(node.args[0], abort=abort,
context="as an argument for delay_mu()")
call_delay = value
elif not types.is_builtin(typ):
if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(typ):
offset = 0
elif types.is_method(typ):
offset = 1
typ = types.get_method_function(typ)
else:
assert False
if types.is_rpc(typ) or types.is_subkernel(typ):
call_delay = iodelay.Const(0)
else:
delay = typ.find().delay.find()
if types.is_var(delay):
raise _UnknownDelay()
elif delay.is_indeterminate():
note = diagnostic.Diagnostic("note",
"function called here", {},
node.loc)
cause = delay.cause
cause = diagnostic.Diagnostic(cause.level, cause.reason, cause.arguments,
cause.location, cause.highlights,
cause.notes + [note])
raise _IndeterminateDelay(cause)
elif delay.is_fixed():
args = {}
for kw_node in node.keywords:
args[kw_node.arg] = kw_node.value
for arg_name, arg_node in zip(list(typ.args)[offset:], node.args):
args[arg_name] = arg_node
free_vars = delay.duration.free_vars()
try:
node.arg_exprs = {
arg: self.evaluate(args[arg], abort=abort,
context="in the expression for argument '{}' "
"that affects I/O delay".format(arg))
for arg in free_vars
}
call_delay = delay.duration.fold(node.arg_exprs)
except KeyError as e:
if getattr(node, "remote_fn", False):
note = diagnostic.Diagnostic("note",
"function called here", {},
node.loc)
self.abort("due to arguments passed remotely", node.loc, note)
else:
assert False
else:
call_delay = iodelay.Const(0)
self.current_delay += call_delay
node.iodelay = call_delay

File diff suppressed because it is too large Load Diff

View File

@ -1,51 +0,0 @@
"""
:class:`LocalDemoter` is a constant propagation transform:
it replaces reads of any local variable with only one write
in a function without closures with the value that was written.
:class:`LocalAccessValidator` must be run before this transform
to ensure that the transformation it performs is sound.
"""
from collections import defaultdict
from .. import ir
class LocalDemoter:
def process(self, functions):
for func in functions:
self.process_function(func)
def process_function(self, func):
env_safe = {}
env_gets = defaultdict(lambda: set())
env_sets = defaultdict(lambda: set())
for insn in func.instructions():
if isinstance(insn, (ir.GetLocal, ir.SetLocal)):
if "$" in insn.var_name:
continue
env = insn.environment()
if env not in env_safe:
for use in env.uses:
if not isinstance(use, (ir.GetLocal, ir.SetLocal)):
env_safe[env] = False
break
else:
env_safe[env] = True
if not env_safe[env]:
continue
if isinstance(insn, ir.SetLocal):
env_sets[(env, insn.var_name)].add(insn)
else:
env_gets[(env, insn.var_name)].add(insn)
for (env, var_name) in env_sets:
if len(env_sets[(env, var_name)]) == 1:
set_insn = next(iter(env_sets[(env, var_name)]))
for get_insn in env_gets[(env, var_name)]:
get_insn.replace_all_uses_with(set_insn.value())
get_insn.erase()

View File

@ -1,89 +0,0 @@
"""
:class:`TypedtreePrinter` prints a human-readable representation of typedtrees.
"""
from pythonparser import algorithm, ast
from .. import types, asttyped
class TypedtreePrinter(algorithm.Visitor):
def __init__(self):
self.str = None
self.level = None
self.last_nl = None
self.type_printer = None
def print(self, node):
try:
self.str = ""
self.level = 0
self.last_nl = 0
self.type_printer = types.TypePrinter()
self.visit(node)
self._nl()
return self.str
finally:
self.str = None
self.level = None
self.last_nl = 0
self.type_printer = None
def _nl(self):
# self.str += "·"
if len(self.str) != self.last_nl:
self.str += "\n" + (" " * self.level)
self.last_nl = len(self.str)
def _indent(self):
self.level += 1
self._nl()
def _dedent(self):
self._nl()
self.level -= 1
self.str = self.str[:-2]
self.last_nl -= 2
def visit(self, obj):
if isinstance(obj, ast.AST):
attrs = set(obj._fields) - {'ctx'}
if isinstance(obj, asttyped.commontyped):
attrs.update(set(obj._types))
for attr in set(attrs):
if not getattr(obj, attr):
attrs.remove(attr) # omit falsey stuff
self.str += obj.__class__.__name__ + "("
if len(attrs) > 1:
self._indent()
for attr in attrs:
if len(attrs) > 1:
self._nl()
self.str += attr + "="
self.visit(getattr(obj, attr))
if len(attrs) > 1:
self._nl()
if len(attrs) > 1:
self._dedent()
self.str += ")"
elif isinstance(obj, types.Type):
self.str += self.type_printer.name(obj, max_depth=0)
elif isinstance(obj, list):
self.str += "["
if len(obj) > 1:
self._indent()
for elem in obj:
if len(obj) > 1:
self._nl()
self.visit(elem)
if len(obj) > 1:
self._nl()
if len(obj) > 1:
self._dedent()
self.str += "]"
else:
self.str += repr(obj)

View File

@ -1,890 +0,0 @@
"""
The :mod:`types` module contains the classes describing the types
in :mod:`asttyped`.
"""
import builtins
import string
from collections import OrderedDict
from . import iodelay
class UnificationError(Exception):
def __init__(self, typea, typeb):
self.typea, self.typeb = typea, typeb
def genalnum():
ident = ["a"]
while True:
yield "".join(ident)
pos = len(ident) - 1
while pos >= 0:
cur_n = string.ascii_lowercase.index(ident[pos])
if cur_n < 25:
ident[pos] = string.ascii_lowercase[cur_n + 1]
break
else:
ident[pos] = "a"
pos -= 1
if pos < 0:
ident = ["a"] + ident
def _freeze(dict_):
return tuple((key, dict_[key]) for key in dict_)
def _map_find(elts):
if isinstance(elts, list):
return [x.find() for x in elts]
elif isinstance(elts, dict):
return {k: elts[k].find() for k in elts}
else:
assert False
class Type(object):
def __str__(self):
return TypePrinter().name(self)
class TVar(Type):
"""
A type variable.
In effect, the classic union-find data structure is intrusively
folded into this class.
"""
def __init__(self):
self.parent = self
self.rank = 0
def find(self):
parent = self.parent
if parent is self:
return self
else:
# The recursive find() invocation is turned into a loop
# because paths resulting from unification of large arrays
# can easily cause a stack overflow.
root = self
while parent.__class__ == TVar and root is not parent:
_, parent = root, root.parent = parent, parent.parent
return root.parent
def unify(self, other):
if other is self:
return
x = other.find()
y = self.find()
if x is y:
return
if y.__class__ == TVar:
if x.__class__ == TVar:
if x.rank < y.rank:
x, y = y, x
y.parent = x
if x.rank == y.rank:
x.rank += 1
else:
y.parent = x
else:
y.unify(x)
def fold(self, accum, fn):
if self.parent is self:
return fn(accum, self)
else:
return self.find().fold(accum, fn)
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
if self.parent is self:
return "<artiq.compiler.types.TVar %d>" % id(self)
else:
return repr(self.find())
# __eq__ and __hash__ are not overridden and default to
# comparison by identity. Use .find() explicitly before
# any lookups or comparisons.
class TMono(Type):
"""
A monomorphic type, possibly parametric.
:class:`TMono` is supposed to be subclassed by builtin types,
unlike all other :class:`Type` descendants. Similarly,
instances of :class:`TMono` should never be allocated directly,
as that will break the type-sniffing code in :mod:`builtins`.
"""
attributes = OrderedDict()
def __init__(self, name, params={}):
assert isinstance(params, (dict, OrderedDict))
self.name, self.params = name, OrderedDict(sorted(params.items()))
def find(self):
return self
def unify(self, other):
if other is self:
return
if isinstance(other, TMono) and self.name == other.name:
assert self.params.keys() == other.params.keys()
for param in self.params:
self.params[param].unify(other.params[param])
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)
def fold(self, accum, fn):
for param in self.params:
accum = self.params[param].fold(accum, fn)
return fn(accum, self)
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TMono(%s, %s)" % (repr(self.name), repr(self.params))
def __getitem__(self, param):
return self.params[param]
def __eq__(self, other):
return isinstance(other, TMono) and \
self.name == other.name and \
_map_find(self.params) == _map_find(other.params)
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash((self.name, _freeze(self.params)))
class TTuple(Type):
"""
A tuple type.
:ivar elts: (list of :class:`Type`) elements
"""
attributes = OrderedDict()
def __init__(self, elts=[]):
self.elts = elts
def find(self):
return self
def unify(self, other):
if other is self:
return
if isinstance(other, TTuple) and len(self.elts) == len(other.elts):
for selfelt, otherelt in zip(self.elts, other.elts):
selfelt.unify(otherelt)
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)
def fold(self, accum, fn):
for elt in self.elts:
accum = elt.fold(accum, fn)
return fn(accum, self)
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TTuple(%s)" % repr(self.elts)
def __eq__(self, other):
return isinstance(other, TTuple) and \
_map_find(self.elts) == _map_find(other.elts)
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash(tuple(self.elts))
class _TPointer(TMono):
def __init__(self, elt=None):
if elt is None:
elt = TMono("int", {"width": 8}) # i8*
super().__init__("pointer", params={"elt": elt})
class TFunction(Type):
"""
A function type.
:ivar args: (:class:`collections.OrderedDict` of string to :class:`Type`)
mandatory arguments
:ivar optargs: (:class:`collections.OrderedDict` of string to :class:`Type`)
optional arguments
:ivar ret: (:class:`Type`)
return type
:ivar delay: (:class:`Type`)
RTIO delay
"""
attributes = OrderedDict([
('__closure__', _TPointer()),
('__code__', _TPointer()),
])
def __init__(self, args, optargs, ret):
assert isinstance(args, OrderedDict)
assert isinstance(optargs, OrderedDict)
assert isinstance(ret, Type)
self.args, self.optargs, self.ret = args, optargs, ret
self.delay = TVar()
def arity(self):
return len(self.args) + len(self.optargs)
def arg_names(self):
return list(self.args.keys()) + list(self.optargs.keys())
def find(self):
return self
def unify(self, other):
if other is self:
return
if isinstance(other, TFunction) and \
self.args.keys() == other.args.keys() and \
self.optargs.keys() == other.optargs.keys():
for selfarg, otherarg in zip(list(self.args.values()) + list(self.optargs.values()),
list(other.args.values()) + list(other.optargs.values())):
selfarg.unify(otherarg)
self.ret.unify(other.ret)
self.delay.unify(other.delay)
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)
def fold(self, accum, fn):
for arg in self.args:
accum = self.args[arg].fold(accum, fn)
for optarg in self.optargs:
accum = self.optargs[optarg].fold(accum, fn)
accum = self.ret.fold(accum, fn)
return fn(accum, self)
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TFunction({}, {}, {})".format(
repr(self.args), repr(self.optargs), repr(self.ret))
def __eq__(self, other):
return isinstance(other, TFunction) and \
_map_find(self.args) == _map_find(other.args) and \
_map_find(self.optargs) == _map_find(other.optargs)
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash((_freeze(self.args), _freeze(self.optargs), self.ret))
class TExternalFunction(TFunction):
"""
A type of an externally-provided function.
This can be any function following the C ABI, such as provided by the
C/Rust runtime, or a compiler backend intrinsic. The mangled name to link
against is encoded as part of the type.
:ivar name: (str) external symbol name.
This will be the symbol linked against (following any extra C name
mangling rules).
:ivar flags: (set of str) function flags.
Flag ``nounwind`` means the function never raises an exception.
Flag ``nowrite`` means the function never accesses any memory
that the ARTIQ Python code can observe.
:ivar broadcast_across_arrays: (bool)
If True, the function is transparently applied element-wise when called
with TArray arguments.
"""
attributes = OrderedDict()
def __init__(self, args, ret, name, flags=set(), broadcast_across_arrays=False):
assert isinstance(flags, set)
for flag in flags:
assert flag in {'nounwind', 'nowrite'}
super().__init__(args, OrderedDict(), ret)
self.name = name
self.delay = TFixedDelay(iodelay.Const(0))
self.flags = flags
self.broadcast_across_arrays = broadcast_across_arrays
def unify(self, other):
if other is self:
return
if isinstance(other, TExternalFunction) and \
self.name == other.name:
super().unify(other)
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)
class TRPC(Type):
"""
A type of a remote call.
:ivar ret: (:class:`Type`)
return type
:ivar service: (int) RPC service number
:ivar is_async: (bool) whether the RPC blocks until return
"""
attributes = OrderedDict()
def __init__(self, ret, service, is_async=False):
assert isinstance(ret, Type)
self.ret, self.service, self.is_async = ret, service, is_async
def find(self):
return self
def unify(self, other):
if other is self:
return
if isinstance(other, TRPC) and \
self.service == other.service and \
self.is_async == other.is_async:
self.ret.unify(other.ret)
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)
def fold(self, accum, fn):
accum = self.ret.fold(accum, fn)
return fn(accum, self)
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TRPC({})".format(repr(self.ret))
def __eq__(self, other):
return isinstance(other, TRPC) and \
self.service == other.service and \
self.is_async == other.is_async
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash(self.service)
class TSubkernel(TFunction):
"""
A kernel to be run on a satellite.
:ivar args: (:class:`collections.OrderedDict` of string to :class:`Type`)
function arguments
:ivar ret: (:class:`Type`)
return type
:ivar sid: (int) subkernel ID number
:ivar destination: (int) satellite destination number
"""
attributes = OrderedDict()
def __init__(self, args, optargs, ret, sid, destination):
assert isinstance(ret, Type)
super().__init__(args, optargs, ret)
self.sid, self.destination = sid, destination
self.delay = TFixedDelay(iodelay.Const(0))
def unify(self, other):
if other is self:
return
if isinstance(other, TSubkernel) and \
self.sid == other.sid and \
self.destination == other.destination:
self.ret.unify(other.ret)
elif isinstance(other, TVar):
other.unify(self)
else:
raise UnificationError(self, other)
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TSubkernel({})".format(repr(self.ret))
def __eq__(self, other):
return isinstance(other, TSubkernel) and \
self.sid == other.sid
def __hash__(self):
return hash(self.sid)
class TBuiltin(Type):
"""
An instance of builtin type. Every instance of a builtin
type is treated specially according to its name.
"""
def __init__(self, name):
assert isinstance(name, str)
self.name = name
self.attributes = OrderedDict()
def find(self):
return self
def unify(self, other):
if other is self:
return
if self != other:
raise UnificationError(self, other)
def fold(self, accum, fn):
return fn(accum, self)
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.{}({})".format(type(self).__name__, repr(self.name))
def __eq__(self, other):
return isinstance(other, TBuiltin) and \
self.name == other.name
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash(self.name)
class TBuiltinFunction(TBuiltin):
"""
A type of a builtin function.
Builtin functions are treated specially throughout all stages of the
compilation process according to their name (e.g. calls may not actually
lower to a function call). See :class:`TExternalFunction` for externally
defined functions that are otherwise regular.
"""
class TConstructor(TBuiltin):
"""
A type of a constructor of a class, e.g. ``list``.
Note that this is not the same as the type of an instance of
the class, which is ``TMono("list", ...)`` (or a descendant).
:ivar instance: (:class:`Type`)
the type of the instance created by this constructor
"""
def __init__(self, instance):
assert isinstance(instance, TMono)
super().__init__(instance.name)
self.instance = instance
class TExceptionConstructor(TConstructor):
"""
A type of a constructor of an exception, e.g. ``Exception``.
Note that this is not the same as the type of an instance of
the class, which is ``TMono("Exception", ...)``.
"""
class TInstance(TMono):
"""
A type of an instance of a user-defined class.
:ivar constructor: (:class:`TConstructor`)
the type of the constructor with which this instance
was created
"""
def __init__(self, name, attributes):
assert isinstance(attributes, OrderedDict)
super().__init__(name)
self.attributes = attributes
self.constant_attributes = set()
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TInstance({}, {})".format(
repr(self.name), repr(self.attributes))
class TModule(TMono):
"""
A type of a module.
"""
def __init__(self, name, attributes):
assert isinstance(attributes, OrderedDict)
super().__init__(name)
self.attributes = attributes
self.constant_attributes = set()
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TModule({}, {})".format(
repr(self.name), repr(self.attributes))
class TMethod(TMono):
"""
A type of a method.
"""
def __init__(self, self_type, function_type):
super().__init__("method", {"self": self_type, "fn": function_type})
self.attributes = OrderedDict([
("__func__", function_type),
("__self__", self_type),
])
class TValue(Type):
"""
A type-level value (such as the integer denoting width of
a generic integer type.
"""
def __init__(self, value):
self.value = value
def find(self):
return self
def unify(self, other):
if other is self:
return
if isinstance(other, TVar):
other.unify(self)
elif self != other:
raise UnificationError(self, other)
def fold(self, accum, fn):
return fn(accum, self)
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TValue(%s)" % repr(self.value)
def __eq__(self, other):
return isinstance(other, TValue) and \
self.value == other.value
def __ne__(self, other):
return not (self == other)
def __hash__(self):
return hash(self.value)
class TDelay(Type):
"""
The type-level representation of IO delay.
"""
def __init__(self, duration, cause):
# Avoid pulling in too many dependencies with `artiq.language`.
from pythonparser import diagnostic
assert duration is None or isinstance(duration, iodelay.Expr)
assert cause is None or isinstance(cause, diagnostic.Diagnostic)
assert (not (duration and cause)) and (duration or cause)
self.duration, self.cause = duration, cause
def is_fixed(self):
return self.duration is not None
def is_indeterminate(self):
return self.cause is not None
def find(self):
return self
def unify(self, other):
other = other.find()
if isinstance(other, TVar):
other.unify(self)
elif self.is_fixed() and other.is_fixed() and \
self.duration.fold() == other.duration.fold():
pass
elif self is not other:
raise UnificationError(self, other)
def fold(self, accum, fn):
# delay types do not participate in folding
pass
def __eq__(self, other):
return isinstance(other, TDelay) and \
(self.duration == other.duration and \
self.cause == other.cause)
def __ne__(self, other):
return not (self == other)
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
if self.duration is None:
return "<{}.TIndeterminateDelay>".format(__name__)
elif self.cause is None:
return "{}.TFixedDelay({})".format(__name__, self.duration)
else:
assert False
def TIndeterminateDelay(cause):
return TDelay(None, cause)
def TFixedDelay(duration):
return TDelay(duration, None)
def is_var(typ):
return isinstance(typ.find(), TVar)
def is_mono(typ, name=None, **params):
typ = typ.find()
if not isinstance(typ, TMono):
return False
if name is not None and typ.name != name:
return False
for param in params:
if param not in typ.params:
return False
if typ.params[param].find() != params[param].find():
return False
return True
def is_polymorphic(typ):
return typ.fold(False, lambda accum, typ: accum or is_var(typ))
def is_tuple(typ, elts=None):
typ = typ.find()
if elts:
return isinstance(typ, TTuple) and \
elts == typ.elts
else:
return isinstance(typ, TTuple)
def _is_pointer(typ):
return isinstance(typ.find(), _TPointer)
def is_function(typ):
return isinstance(typ.find(), TFunction)
def is_rpc(typ):
return isinstance(typ.find(), TRPC)
def is_subkernel(typ):
return isinstance(typ.find(), TSubkernel)
def is_external_function(typ, name=None):
typ = typ.find()
if name is None:
return isinstance(typ, TExternalFunction)
else:
return isinstance(typ, TExternalFunction) and \
typ.name == name
def is_builtin(typ, name=None):
typ = typ.find()
if name is None:
return isinstance(typ, TBuiltin)
else:
return isinstance(typ, TBuiltin) and \
typ.name == name
def is_builtin_function(typ, name=None):
typ = typ.find()
if name is None:
return isinstance(typ, TBuiltinFunction)
else:
return isinstance(typ, TBuiltinFunction) and \
typ.name == name
def is_broadcast_across_arrays(typ):
# For now, broadcasting is only exposed to predefined external functions, and
# statically selected. Might be extended to user-defined functions if the design
# pans out.
typ = typ.find()
if not isinstance(typ, TExternalFunction):
return False
return typ.broadcast_across_arrays
def is_constructor(typ, name=None):
typ = typ.find()
if name is not None:
return isinstance(typ, TConstructor) and \
typ.name == name
else:
return isinstance(typ, TConstructor)
def is_exn_constructor(typ, name=None):
typ = typ.find()
if name is not None:
return isinstance(typ, TExceptionConstructor) and \
typ.name == name
else:
return isinstance(typ, TExceptionConstructor)
def is_instance(typ, name=None):
typ = typ.find()
if name is not None:
return isinstance(typ, TInstance) and \
typ.name == name
else:
return isinstance(typ, TInstance)
def is_module(typ, name=None):
typ = typ.find()
if name is not None:
return isinstance(typ, TModule) and \
typ.name == name
else:
return isinstance(typ, TModule)
def is_method(typ):
return isinstance(typ.find(), TMethod)
def get_method_self(typ):
if is_method(typ):
return typ.find().params["self"].find()
def get_method_function(typ):
if is_method(typ):
return typ.find().params["fn"].find()
def is_value(typ):
return isinstance(typ.find(), TValue)
def get_value(typ):
typ = typ.find()
if isinstance(typ, TVar):
return None
elif isinstance(typ, TValue):
return typ.value
else:
assert False
def is_delay(typ):
return isinstance(typ.find(), TDelay)
def is_fixed_delay(typ):
return is_delay(typ) and typ.find().is_fixed()
def is_indeterminate_delay(typ):
return is_delay(typ) and typ.find().is_indeterminate()
class TypePrinter(object):
"""
A class that prints types using Python-like syntax and gives
type variables sequential alphabetic names.
"""
custom_printers = {}
def __init__(self):
self.gen = genalnum()
self.map = {}
self.recurse_guard = set()
def name(self, typ, depth=0, max_depth=1):
typ = typ.find()
if isinstance(typ, TVar):
if typ not in self.map:
self.map[typ] = "'%s" % next(self.gen)
return self.map[typ]
elif isinstance(typ, TInstance):
if typ in self.recurse_guard or depth >= max_depth:
return "<instance {}>".format(typ.name)
elif len(typ.attributes) > 0:
self.recurse_guard.add(typ)
attrs = ",\n\t\t".join(["{}: {}".format(attr, self.name(typ.attributes[attr],
depth + 1))
for attr in typ.attributes])
return "<instance {} {{\n\t\t{}\n\t}}>".format(typ.name, attrs)
else:
self.recurse_guard.add(typ)
return "<instance {} {{}}>".format(typ.name)
elif isinstance(typ, TMono):
if typ.name in self.custom_printers:
return self.custom_printers[typ.name](typ, self, depth + 1, max_depth)
elif typ.params == {}:
return typ.name
else:
return "%s(%s)" % (typ.name, ", ".join(
["%s=%s" % (k, self.name(typ.params[k], depth + 1)) for k in typ.params]))
elif isinstance(typ, _TPointer):
return "{}*".format(self.name(typ["elt"], depth + 1))
elif isinstance(typ, TTuple):
if len(typ.elts) == 1:
return "(%s,)" % self.name(typ.elts[0], depth + 1)
else:
return "(%s)" % ", ".join([self.name(typ, depth + 1) for typ in typ.elts])
elif isinstance(typ, (TFunction, TExternalFunction)):
args = []
args += [ "%s:%s" % (arg, self.name(typ.args[arg], depth + 1))
for arg in typ.args]
args += ["?%s:%s" % (arg, self.name(typ.optargs[arg], depth + 1))
for arg in typ.optargs]
signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret, depth + 1))
delay = typ.delay.find()
if isinstance(delay, TVar):
signature += " delay({})".format(self.name(delay, depth + 1))
elif not (delay.is_fixed() and iodelay.is_zero(delay.duration)):
signature += " " + self.name(delay, depth + 1)
if isinstance(typ, TExternalFunction):
return "[ffi {}]{}".format(repr(typ.name), signature)
elif isinstance(typ, TFunction):
return signature
elif isinstance(typ, TRPC):
return "[rpc{} #{}](...)->{}".format(typ.service,
" async" if typ.is_async else "",
self.name(typ.ret, depth + 1))
elif isinstance(typ, TSubkernel):
return "<subkernel{} dest#{}>->{}".format(typ.sid,
typ.destination,
self.name(typ.ret, depth + 1))
elif isinstance(typ, TBuiltinFunction):
return "<function {}>".format(typ.name)
elif isinstance(typ, (TConstructor, TExceptionConstructor)):
if typ in self.recurse_guard or depth >= max_depth:
return "<constructor {}>".format(typ.name)
elif len(typ.attributes) > 0:
self.recurse_guard.add(typ)
attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr],
depth + 1))
for attr in typ.attributes])
return "<constructor {} {{{}}}>".format(typ.name, attrs)
else:
self.recurse_guard.add(typ)
return "<constructor {} {{}}>".format(typ.name)
elif isinstance(typ, TBuiltin):
return "<builtin {}>".format(typ.name)
elif isinstance(typ, TValue):
return repr(typ.value)
elif isinstance(typ, TDelay):
if typ.is_fixed():
return "delay({} mu)".format(typ.duration)
elif typ.is_indeterminate():
return "delay(?)"
else:
assert False
else:
assert False

View File

@ -1,4 +0,0 @@
from .monomorphism import MonomorphismValidator
from .escape import EscapeValidator
from .local_access import LocalAccessValidator
from .constness import ConstnessValidator

View File

@ -1,58 +0,0 @@
"""
:class:`ConstnessValidator` checks that no attribute marked
as constant is ever set.
"""
from pythonparser import algorithm, diagnostic
from .. import types, builtins
class ConstnessValidator(algorithm.Visitor):
def __init__(self, engine):
self.engine = engine
self.in_assign = False
def visit_Assign(self, node):
self.visit(node.value)
self.in_assign = True
self.visit(node.targets)
self.in_assign = False
def visit_AugAssign(self, node):
self.visit(node.value)
self.in_assign = True
self.visit(node.target)
self.in_assign = False
def visit_SubscriptT(self, node):
old_in_assign, self.in_assign = self.in_assign, False
self.visit(node.value)
self.visit(node.slice)
self.in_assign = old_in_assign
if self.in_assign and builtins.is_bytes(node.value.type):
diag = diagnostic.Diagnostic("error",
"type {typ} is not mutable",
{"typ": "bytes"},
node.loc)
self.engine.process(diag)
def visit_AttributeT(self, node):
old_in_assign, self.in_assign = self.in_assign, False
self.visit(node.value)
self.in_assign = old_in_assign
if self.in_assign:
typ = node.value.type.find()
if types.is_instance(typ) and node.attr in typ.constant_attributes:
diag = diagnostic.Diagnostic("error",
"cannot assign to constant attribute '{attr}' of class '{class}'",
{"attr": node.attr, "class": typ.name},
node.loc)
self.engine.process(diag)
return
if builtins.is_array(typ):
diag = diagnostic.Diagnostic("error",
"array attributes cannot be assigned to",
{}, node.loc)
self.engine.process(diag)
return

View File

@ -1,372 +0,0 @@
"""
:class:`EscapeValidator` verifies that no mutable data escapes
the region of its allocation.
"""
import functools
from pythonparser import algorithm, diagnostic
from .. import asttyped, types, builtins
def has_region(typ):
return typ.fold(False, lambda accum, typ: accum or builtins.is_allocated(typ))
class Global:
def __repr__(self):
return "Global()"
class Argument:
def __init__(self, loc):
self.loc = loc
def __repr__(self):
return "Argument()"
class Region:
"""
A last-in-first-out allocation region. Tied to lexical scoping
and is internally represented simply by a source range.
:ivar range: (:class:`pythonparser.source.Range` or None)
"""
def __init__(self, source_range=None):
self.range = source_range
def present(self):
return bool(self.range)
def includes(self, other):
assert self.range
assert self.range.source_buffer == other.range.source_buffer
return self.range.begin_pos <= other.range.begin_pos and \
self.range.end_pos >= other.range.end_pos
def intersects(self, other):
assert self.range
assert self.range.source_buffer == other.range.source_buffer
return (self.range.begin_pos <= other.range.begin_pos <= self.range.end_pos and \
other.range.end_pos > self.range.end_pos) or \
(other.range.begin_pos <= self.range.begin_pos <= other.range.end_pos and \
self.range.end_pos > other.range.end_pos)
def outlives(lhs, rhs):
if not isinstance(lhs, Region): # lhs lives nonlexically
return True
elif not isinstance(rhs, Region): # rhs lives nonlexically, lhs does not
return False
else:
assert not lhs.intersects(rhs)
return lhs.includes(rhs)
def __repr__(self):
return "Region({})".format(repr(self.range))
class RegionOf(algorithm.Visitor):
"""
Visit an expression and return the region that must be alive for the
expression to execute.
For expressions involving multiple regions, the shortest-lived one is
returned.
"""
def __init__(self, env_stack, youngest_region):
self.env_stack, self.youngest_region = env_stack, youngest_region
# Liveness determined by assignments
def visit_NameT(self, node):
# First, look at stack regions
for region in reversed(self.env_stack[1:]):
if node.id in region:
return region[node.id]
# Then, look at the global region of this module
if node.id in self.env_stack[0]:
return Global()
assert False
# Value lives as long as the current scope, if it's mutable,
# or else forever
def visit_sometimes_allocating(self, node):
if has_region(node.type):
return self.youngest_region
else:
return Global()
visit_BinOpT = visit_sometimes_allocating
def visit_CallT(self, node):
if types.is_external_function(node.func.type, "cache_get"):
# The cache is borrow checked dynamically
return Global()
if (types.is_builtin_function(node.func.type, "array")
or types.is_builtin_function(node.func.type, "make_array")
or types.is_builtin_function(node.func.type, "numpy.transpose")):
# While lifetime tracking across function calls in general is currently
# broken (see below), these special builtins that allocate an array on
# the stack of the caller _always_ allocate regardless of the parameters,
# and we can thus handle them without running into the precision issue
# mentioned in commit ae999db.
return self.visit_allocating(node)
# FIXME: Return statement missing here, but see m-labs/artiq#1497 and
# commit ae999db.
self.visit_sometimes_allocating(node)
# Value lives as long as the object/container, if it's mutable,
# or else forever
def visit_accessor(self, node):
if has_region(node.type):
return self.visit(node.value)
else:
return Global()
visit_AttributeT = visit_accessor
visit_SubscriptT = visit_accessor
# Value lives as long as the shortest living operand
def visit_selecting(self, nodes):
regions = [self.visit(node) for node in nodes]
regions = list(filter(lambda x: x, regions))
if any(regions):
regions.sort(key=functools.cmp_to_key(Region.outlives), reverse=True)
return regions[0]
else:
return Global()
def visit_BoolOpT(self, node):
return self.visit_selecting(node.values)
def visit_IfExpT(self, node):
return self.visit_selecting([node.body, node.orelse])
def visit_TupleT(self, node):
return self.visit_selecting(node.elts)
# Value lives as long as the current scope
def visit_allocating(self, node):
return self.youngest_region
visit_DictT = visit_allocating
visit_DictCompT = visit_allocating
visit_GeneratorExpT = visit_allocating
visit_LambdaT = visit_allocating
visit_ListT = visit_allocating
visit_ListCompT = visit_allocating
visit_SetT = visit_allocating
visit_SetCompT = visit_allocating
# Value lives forever
def visit_immutable(self, node):
assert not has_region(node.type)
return Global()
visit_NameConstantT = visit_immutable
visit_NumT = visit_immutable
visit_EllipsisT = visit_immutable
visit_UnaryOpT = visit_sometimes_allocating # possibly array op
visit_CompareT = visit_immutable
# Value lives forever
def visit_global(self, node):
return Global()
visit_StrT = visit_global
visit_QuoteT = visit_global
# Not implemented
def visit_unimplemented(self, node):
assert False
visit_StarredT = visit_unimplemented
visit_YieldT = visit_unimplemented
visit_YieldFromT = visit_unimplemented
class AssignedNamesOf(algorithm.Visitor):
"""
Visit an expression and return the list of names that appear
on the lhs of assignment, directly or through an accessor.
"""
def visit_name(self, node):
return [node]
visit_NameT = visit_name
visit_QuoteT = visit_name
def visit_accessor(self, node):
return self.visit(node.value)
visit_AttributeT = visit_accessor
visit_SubscriptT = visit_accessor
def visit_sequence(self, node):
return functools.reduce(list.__add__, map(self.visit, node.elts))
visit_TupleT = visit_sequence
visit_ListT = visit_sequence
def visit_StarredT(self, node):
assert False
class EscapeValidator(algorithm.Visitor):
def __init__(self, engine):
self.engine = engine
self.youngest_region = Global()
self.env_stack = []
self.youngest_env = None
def _region_of(self, expr):
return RegionOf(self.env_stack, self.youngest_region).visit(expr)
def _names_of(self, expr):
return AssignedNamesOf().visit(expr)
def _diagnostics_for(self, region, loc, descr="the value of the expression"):
if isinstance(region, Region):
return [
diagnostic.Diagnostic("note",
"{descr} is alive from this point...", {"descr": descr},
region.range.begin()),
diagnostic.Diagnostic("note",
"... to this point", {},
region.range.end())
]
elif isinstance(region, Global):
return [
diagnostic.Diagnostic("note",
"{descr} is alive forever", {"descr": descr},
loc)
]
elif isinstance(region, Argument):
return [
diagnostic.Diagnostic("note",
"{descr} is still alive after this function returns", {"descr": descr},
loc),
diagnostic.Diagnostic("note",
"{descr} is introduced here as a formal argument", {"descr": descr},
region.loc)
]
else:
assert False
def visit_in_region(self, node, region, typing_env, args=[]):
try:
old_youngest_region = self.youngest_region
self.youngest_region = region
old_youngest_env = self.youngest_env
self.youngest_env = {}
for name in typing_env:
if has_region(typing_env[name]):
if name in args:
self.youngest_env[name] = args[name]
else:
self.youngest_env[name] = Region(None) # not yet known
else:
self.youngest_env[name] = Global()
self.env_stack.append(self.youngest_env)
self.generic_visit(node)
finally:
self.env_stack.pop()
self.youngest_env = old_youngest_env
self.youngest_region = old_youngest_region
def visit_ModuleT(self, node):
self.visit_in_region(node, None, node.typing_env)
def visit_FunctionDefT(self, node):
self.youngest_env[node.name] = self.youngest_region
self.visit_in_region(node, Region(node.loc), node.typing_env,
args={ arg.arg: Argument(arg.loc) for arg in node.args.args })
visit_QuotedFunctionDefT = visit_FunctionDefT
def visit_ClassDefT(self, node):
self.youngest_env[node.name] = self.youngest_region
self.visit_in_region(node, Region(node.loc), node.constructor_type.attributes)
# Only three ways for a pointer to escape:
# * Assigning or op-assigning it (we ensure an outlives relationship)
# * Returning it (we only allow returning values that live forever)
# * Raising it (we forbid allocating exceptions that refer to mutable data)¹
#
# Literals doesn't count: a constructed object is always
# outlived by all its constituents.
# Closures don't count: see above.
# Calling functions doesn't count: arguments never outlive
# the function body.
#
# ¹Strings are currently never allocated with a limited lifetime,
# and exceptions can only refer to strings, so we don't actually check
# this property. But we will need to, if string operations are ever added.
def visit_assignment(self, target, value):
value_region = self._region_of(value)
# If we assign to an attribute of a quoted value, there will be no names
# in the assignment lhs.
target_names = self._names_of(target) or []
# Adopt the value region for any variables declared on the lhs.
for name in target_names:
region = self._region_of(name)
if isinstance(region, Region) and not region.present():
# Find the name's environment to overwrite the region.
for env in self.env_stack[::-1]:
if name.id in env:
env[name.id] = value_region
break
# The assigned value should outlive the assignee
target_regions = [self._region_of(name) for name in target_names]
for target_region in target_regions:
if not Region.outlives(value_region, target_region):
diag = diagnostic.Diagnostic("error",
"the assigned value does not outlive the assignment target", {},
value.loc, [target.loc],
notes=self._diagnostics_for(target_region, target.loc,
"the assignment target") +
self._diagnostics_for(value_region, value.loc,
"the assigned value"))
self.engine.process(diag)
def visit_Assign(self, node):
for target in node.targets:
self.visit_assignment(target, node.value)
def visit_AugAssign(self, node):
if builtins.is_list(node.target.type):
note = diagnostic.Diagnostic("note",
"try using `{lhs} = {lhs} {op} {rhs}` instead",
{"lhs": node.target.loc.source(),
"rhs": node.value.loc.source(),
"op": node.op.loc.source()[:-1]},
node.loc)
diag = diagnostic.Diagnostic("error",
"lists cannot be mutated in-place", {},
node.op.loc, [node.target.loc],
notes=[note])
self.engine.process(diag)
self.visit_assignment(node.target, node.value)
def visit_Return(self, node):
region = self._region_of(node.value)
if isinstance(region, Region):
note = diagnostic.Diagnostic("note",
"this expression has type {type}",
{"type": types.TypePrinter().name(node.value.type)},
node.value.loc)
diag = diagnostic.Diagnostic("error",
"cannot return an allocated value that does not live forever", {},
node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note])
self.engine.process(diag)

View File

@ -1,187 +0,0 @@
"""
:class:`LocalAccessValidator` verifies that local variables
are not accessed before being used.
"""
from functools import reduce
from pythonparser import diagnostic
from .. import ir, analyses
def is_special_variable(name):
return "$" in name
class LocalAccessValidator:
def __init__(self, engine):
self.engine = engine
def process(self, functions):
for func in functions:
self.process_function(func)
def process_function(self, func):
# Find all environments and closures allocated in this func.
environments, closures = [], []
for insn in func.instructions():
if isinstance(insn, ir.Alloc) and ir.is_environment(insn.type):
environments.append(insn)
elif isinstance(insn, ir.Closure):
closures.append(insn)
# Compute initial state of interesting environments.
# Environments consisting only of internal variables (containing a ".")
# are ignored.
initial_state = {}
for env in environments:
env_state = {var: False for var in env.type.params if "." not in var}
if any(env_state):
initial_state[env] = env_state
# Traverse the acyclic graph made of basic blocks and forward edges only,
# while updating the environment state.
domtree = analyses.DominatorTree(func)
state = {}
def traverse(block):
# Have we computed the state of this block already?
if block in state:
return state[block]
# No! Which forward edges lead to this block?
# If we dominate a predecessor, it's a back edge instead.
forward_edge_preds = [pred for pred in block.predecessors()
if block not in domtree.dominators(pred)]
# Figure out what the state is before the leader
# instruction of this block.
pred_states = [traverse(pred) for pred in forward_edge_preds]
block_state = {}
if len(pred_states) > 1:
for env in initial_state:
# The variable has to be initialized in all predecessors
# in order to be initialized in this block.
def merge_state(a, b):
return {var: a[var] and b[var] for var in a}
block_state[env] = reduce(merge_state,
[state[env] for state in pred_states])
elif len(pred_states) == 1:
# The state is the same as at the terminator of predecessor.
# We'll mutate it, so copy.
pred_state = pred_states[0]
for env in initial_state:
env_state = pred_state[env]
block_state[env] = {var: env_state[var] for var in env_state}
else:
# This is the entry block.
for env in initial_state:
env_state = initial_state[env]
block_state[env] = {var: env_state[var] for var in env_state}
# Update the state based on block contents, while validating
# that no access to uninitialized variables will be done.
for insn in block.instructions:
def pred_at_fault(env, var_name):
# Find out where the uninitialized state comes from.
for pred, pred_state in zip(forward_edge_preds, pred_states):
if not pred_state[env][var_name]:
return pred
# It's the entry block and it was never initialized.
return None
set_local_in_this_frame = False
if (isinstance(insn, (ir.SetLocal, ir.GetLocal)) and
not is_special_variable(insn.var_name)):
env, var_name = insn.environment(), insn.var_name
# Make sure that the variable is defined in the scope of this function.
if env in block_state and var_name in block_state[env]:
if isinstance(insn, ir.SetLocal):
# We've just initialized it.
block_state[env][var_name] = True
set_local_in_this_frame = True
else: # isinstance(insn, ir.GetLocal)
if not block_state[env][var_name]:
# Oops, accessing it uninitialized.
self._uninitialized_access(insn, var_name,
pred_at_fault(env, var_name))
closures_to_check = []
if (isinstance(insn, (ir.SetLocal, ir.SetAttr, ir.SetElem)) and
not set_local_in_this_frame):
# Closures may escape via these mechanisms and be invoked elsewhere.
if isinstance(insn.value(), ir.Closure):
closures_to_check.append(insn.value())
if isinstance(insn, (ir.Call, ir.Invoke)):
# We can't always trace the flow of closures from point of
# definition to point of call; however, we know that, by transitiveness
# of this analysis, only closures defined in this function can contain
# uninitialized variables.
#
# Thus, enumerate the closures, and check all of them during any operation
# that may eventually result in the closure being called.
closures_to_check = closures
for closure in closures_to_check:
env = closure.environment()
# Make sure this environment has any interesting variables.
if env in block_state:
for var_name in block_state[env]:
if not block_state[env][var_name] and not is_special_variable(var_name):
# A closure would capture this variable while it is not always
# initialized. Note that this check is transitive.
self._uninitialized_access(closure, var_name,
pred_at_fault(env, var_name))
# Save the state.
state[block] = block_state
return block_state
for block in func.basic_blocks:
traverse(block)
def _uninitialized_access(self, insn, var_name, pred_at_fault):
if pred_at_fault is not None:
visited = set()
possible_preds = [pred_at_fault]
uninitialized_loc = None
while uninitialized_loc is None:
possible_pred = possible_preds.pop(0)
visited.add(possible_pred)
for pred_insn in reversed(possible_pred.instructions):
if pred_insn.loc is not None:
uninitialized_loc = pred_insn.loc.begin()
break
for block in possible_pred.predecessors():
if block not in visited:
possible_preds.append(block)
assert uninitialized_loc is not None
note = diagnostic.Diagnostic("note",
"variable is not initialized when control flows from this point", {},
uninitialized_loc)
else:
note = None
if note is not None:
notes = [note]
else:
notes = []
if isinstance(insn, ir.Closure):
diag = diagnostic.Diagnostic("error",
"variable '{name}' can be captured in a closure uninitialized here",
{"name": var_name},
insn.loc, notes=notes)
else:
diag = diagnostic.Diagnostic("error",
"variable '{name}' is not always initialized here",
{"name": var_name},
insn.loc, notes=notes)
self.engine.process(diag)

View File

@ -1,41 +0,0 @@
"""
:class:`MonomorphismValidator` verifies that all type variables have been
elided, which is necessary for code generation.
"""
from pythonparser import algorithm, diagnostic
from .. import asttyped, types, builtins
class MonomorphismValidator(algorithm.Visitor):
def __init__(self, engine):
self.engine = engine
def visit_FunctionDefT(self, node):
super().generic_visit(node)
return_type = node.signature_type.find().ret
if types.is_polymorphic(return_type):
note = diagnostic.Diagnostic("note",
"the function has return type {type}",
{"type": types.TypePrinter().name(return_type)},
node.name_loc)
diag = diagnostic.Diagnostic("error",
"the return type of this function cannot be fully inferred", {},
node.name_loc, notes=[note])
self.engine.process(diag)
visit_QuotedFunctionDefT = visit_FunctionDefT
def generic_visit(self, node):
super().generic_visit(node)
if isinstance(node, asttyped.commontyped):
if types.is_polymorphic(node.type):
note = diagnostic.Diagnostic("note",
"the expression has type {type}",
{"type": types.TypePrinter().name(node.type)},
node.loc)
diag = diagnostic.Diagnostic("error",
"the type of this expression cannot be fully inferred", {},
node.loc, notes=[note])
self.engine.process(diag)

View File

@ -8,17 +8,19 @@ time is an error.
# Designed from the data sheets and somewhat after the linux kernel # Designed from the data sheets and somewhat after the linux kernel
# iio driver. # iio driver.
from numpy import int32 from numpy import int32, int64
from artiq.language.core import (kernel, portable, delay_mu, delay, now_mu, from artiq.language.core import *
at_mu)
from artiq.language.units import ns, us from artiq.language.units import ns, us
from artiq.coredevice import spi2 as spi from artiq.coredevice.core import Core
from artiq.coredevice.ttl import TTLOut
from artiq.coredevice.spi2 import *
SPI_AD53XX_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | SPI_AD53XX_CONFIG = (0*SPI_OFFLINE | 1*SPI_END |
0*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE | 0*SPI_INPUT | 0*SPI_CS_POLARITY |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) 0*SPI_CLK_POLARITY | 1*SPI_CLK_PHASE |
0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
AD53XX_CMD_DATA = 3 << 22 AD53XX_CMD_DATA = 3 << 22
AD53XX_CMD_OFFSET = 2 << 22 AD53XX_CMD_OFFSET = 2 << 22
@ -52,7 +54,7 @@ AD53XX_READ_AB3 = 0x109 << 7
@portable @portable
def ad53xx_cmd_write_ch(channel, value, op): def ad53xx_cmd_write_ch(channel: int32, value: int32, op: int32) -> int32:
"""Returns the word that must be written to the DAC to set a DAC """Returns the word that must be written to the DAC to set a DAC
channel register to a given value. channel register to a given value.
@ -67,7 +69,7 @@ def ad53xx_cmd_write_ch(channel, value, op):
@portable @portable
def ad53xx_cmd_read_ch(channel, op): def ad53xx_cmd_read_ch(channel: int32, op: int32) -> int32:
"""Returns the word that must be written to the DAC to read a given """Returns the word that must be written to the DAC to read a given
DAC channel register. DAC channel register.
@ -82,7 +84,7 @@ def ad53xx_cmd_read_ch(channel, op):
# maintain function definition for backward compatibility # maintain function definition for backward compatibility
@portable @portable
def voltage_to_mu(voltage, offset_dacs=0x2000, vref=5.): def voltage_to_mu(voltage: float, offset_dacs: int32 = 0x2000, vref: float = 5.) -> int32:
"""Returns the 16-bit DAC register value required to produce a given output """Returns the 16-bit DAC register value required to produce a given output
voltage, assuming offset and gain errors have been trimmed out. voltage, assuming offset and gain errors have been trimmed out.
@ -100,22 +102,24 @@ def voltage_to_mu(voltage, offset_dacs=0x2000, vref=5.):
:param vref: DAC reference voltage (default: 5.) :param vref: DAC reference voltage (default: 5.)
:return: The 16-bit DAC register value :return: The 16-bit DAC register value
""" """
code = int(round((1 << 16) * (voltage / (4. * vref)) + offset_dacs * 0x4)) code = round(float(1 << 16) * (voltage / (4. * vref))) + offset_dacs * 0x4
if code < 0x0 or code > 0xffff: if code < 0x0 or code > 0xffff:
raise ValueError("Invalid DAC voltage!") raise ValueError("Invalid DAC voltage!")
return code return code
@nac3
class _DummyTTL: class _DummyTTL:
@portable @kernel
def on(self): def on(self):
pass pass
@portable @kernel
def off(self): def off(self):
pass pass
@nac3
class AD53xx: class AD53xx:
"""Analog devices AD53[67][0123] family of multi-channel Digital to Analog """Analog devices AD53[67][0123] family of multi-channel Digital to Analog
Converters. Converters.
@ -137,8 +141,15 @@ class AD53xx:
experiments. (default: 8192) experiments. (default: 8192)
:param core_device: Core device name (default: "core") :param core_device: Core device name (default: "core")
""" """
kernel_invariants = {"bus", "ldac", "clr", "chip_select", "div_write", core: KernelInvariant[Core]
"div_read", "vref", "core"} bus: KernelInvariant[SPIMaster]
ldac: KernelInvariant[TTLOut]
clr: KernelInvariant[TTLOut]
chip_select: KernelInvariant[int32]
div_write: KernelInvariant[int32]
div_read: KernelInvariant[int32]
vref: KernelInvariant[float]
offset_dacs: Kernel[int32]
def __init__(self, dmgr, spi_device, ldac_device=None, clr_device=None, def __init__(self, dmgr, spi_device, ldac_device=None, clr_device=None,
chip_select=1, div_write=4, div_read=16, vref=5., chip_select=1, div_write=4, div_read=16, vref=5.,
@ -161,7 +172,7 @@ class AD53xx:
self.core = dmgr.get(core) self.core = dmgr.get(core)
@kernel @kernel
def init(self, blind=False): def init(self, blind: bool = False):
"""Configures the SPI bus, drives LDAC and CLR high, programmes """Configures the SPI bus, drives LDAC and CLR high, programmes
the offset DACs, and enables overtemperature shutdown. the offset DACs, and enables overtemperature shutdown.
@ -177,22 +188,22 @@ class AD53xx:
self.chip_select) self.chip_select)
self.write_offset_dacs_mu(self.offset_dacs) self.write_offset_dacs_mu(self.offset_dacs)
if not blind: if not blind:
ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL) ctrl = self.read_reg(0, AD53XX_READ_CONTROL)
if ctrl == 0xffff: if ctrl == 0xffff:
raise ValueError("DAC not found") raise ValueError("DAC not found")
if ctrl & 0b10000: if (ctrl & 0b10000) != 0:
raise ValueError("DAC over temperature") raise ValueError("DAC over temperature")
delay(25*us) self.core.delay(25.*us)
self.bus.write( # enable power and overtemperature shutdown self.bus.write( # enable power and overtemperature shutdown
(AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_CONTROL | 0b0010) << 8) (AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_CONTROL | 0b0010) << 8)
if not blind: if not blind:
ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL) ctrl = self.read_reg(0, AD53XX_READ_CONTROL)
if (ctrl & 0b10111) != 0b00010: if (ctrl & 0b10111) != 0b00010:
raise ValueError("DAC CONTROL readback mismatch") raise ValueError("DAC CONTROL readback mismatch")
delay(15*us) self.core.delay(15.*us)
@kernel @kernel
def read_reg(self, channel=0, op=AD53XX_READ_X1A): def read_reg(self, channel: int32 = 0, op: int32 = AD53XX_READ_X1A) -> int32:
"""Read a DAC register. """Read a DAC register.
This method advances the timeline by the duration of two SPI transfers This method advances the timeline by the duration of two SPI transfers
@ -205,17 +216,16 @@ class AD53xx:
:return: The 16 bit register value :return: The 16 bit register value
""" """
self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8) self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8)
self.bus.set_config_mu(SPI_AD53XX_CONFIG | spi.SPI_INPUT, 24, self.bus.set_config_mu(SPI_AD53XX_CONFIG | SPI_INPUT, 24,
self.div_read, self.chip_select) self.div_read, self.chip_select)
delay(270*ns) # t_21 min sync high in readback self.core.delay(270.*ns) # t_21 min sync high in readback
self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_NOP) << 8) self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_NOP) << 8)
self.bus.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write, self.bus.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write,
self.chip_select) self.chip_select)
# FIXME: the int32 should not be needed to resolve unification return self.bus.read() & 0xffff
return self.bus.read() & int32(0xffff)
@kernel @kernel
def write_offset_dacs_mu(self, value): def write_offset_dacs_mu(self, value: int32):
"""Program the OFS0 and OFS1 offset DAC registers. """Program the OFS0 and OFS1 offset DAC registers.
Writes to the offset DACs take effect immediately without requiring Writes to the offset DACs take effect immediately without requiring
@ -230,10 +240,10 @@ class AD53xx:
self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_OFS1 | value) << 8) self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_OFS1 | value) << 8)
@kernel @kernel
def write_gain_mu(self, channel, gain=0xffff): def write_gain_mu(self, channel: int32, gain: int32 = 0xffff):
"""Program the gain register for a DAC channel. """Program the gain register for a DAC channel.
The DAC output is not updated until LDAC is pulsed (see :meth:`load`). The DAC output is not updated until LDAC is pulsed (see :meth load:).
This method advances the timeline by the duration of one SPI transfer. This method advances the timeline by the duration of one SPI transfer.
:param gain: 16-bit gain register value (default: 0xffff) :param gain: 16-bit gain register value (default: 0xffff)
@ -242,10 +252,10 @@ class AD53xx:
ad53xx_cmd_write_ch(channel, gain, AD53XX_CMD_GAIN) << 8) ad53xx_cmd_write_ch(channel, gain, AD53XX_CMD_GAIN) << 8)
@kernel @kernel
def write_offset_mu(self, channel, offset=0x8000): def write_offset_mu(self, channel: int32, offset: int32 = 0x8000):
"""Program the offset register for a DAC channel. """Program the offset register for a DAC channel.
The DAC output is not updated until LDAC is pulsed (see :meth:`load`). The DAC output is not updated until LDAC is pulsed (see :meth load:).
This method advances the timeline by the duration of one SPI transfer. This method advances the timeline by the duration of one SPI transfer.
:param offset: 16-bit offset register value (default: 0x8000) :param offset: 16-bit offset register value (default: 0x8000)
@ -254,11 +264,11 @@ class AD53xx:
ad53xx_cmd_write_ch(channel, offset, AD53XX_CMD_OFFSET) << 8) ad53xx_cmd_write_ch(channel, offset, AD53XX_CMD_OFFSET) << 8)
@kernel @kernel
def write_offset(self, channel, voltage): def write_offset(self, channel: int32, voltage: float):
"""Program the DAC offset voltage for a channel. """Program the DAC offset voltage for a channel.
An offset of +V can be used to trim out a DAC offset error of -V. An offset of +V can be used to trim out a DAC offset error of -V.
The DAC output is not updated until LDAC is pulsed (see :meth:`load`). The DAC output is not updated until LDAC is pulsed (see :meth load:).
This method advances the timeline by the duration of one SPI transfer. This method advances the timeline by the duration of one SPI transfer.
:param voltage: the offset voltage :param voltage: the offset voltage
@ -267,20 +277,20 @@ class AD53xx:
self.vref)) self.vref))
@kernel @kernel
def write_dac_mu(self, channel, value): def write_dac_mu(self, channel: int32, value: int32):
"""Program the DAC input register for a channel. """Program the DAC input register for a channel.
The DAC output is not updated until LDAC is pulsed (see :meth:`load`). The DAC output is not updated until LDAC is pulsed (see :meth load:).
This method advances the timeline by the duration of one SPI transfer. This method advances the timeline by the duration of one SPI transfer.
""" """
self.bus.write( self.bus.write(
ad53xx_cmd_write_ch(channel, value, AD53XX_CMD_DATA) << 8) ad53xx_cmd_write_ch(channel, value, AD53XX_CMD_DATA) << 8)
@kernel @kernel
def write_dac(self, channel, voltage): def write_dac(self, channel: int32, voltage: float):
"""Program the DAC output voltage for a channel. """Program the DAC output voltage for a channel.
The DAC output is not updated until LDAC is pulsed (see :meth:`load`). The DAC output is not updated until LDAC is pulsed (see :meth load:).
This method advances the timeline by the duration of one SPI transfer. This method advances the timeline by the duration of one SPI transfer.
""" """
self.write_dac_mu(channel, voltage_to_mu(voltage, self.offset_dacs, self.write_dac_mu(channel, voltage_to_mu(voltage, self.offset_dacs,
@ -299,11 +309,11 @@ class AD53xx:
This method advances the timeline by two RTIO clock periods. This method advances the timeline by two RTIO clock periods.
""" """
self.ldac.off() self.ldac.off()
delay_mu(2*self.bus.ref_period_mu) # t13 = 10ns ldac pulse width low delay_mu(int64(2)*self.bus.ref_period_mu) # t13 = 10ns ldac pulse width low
self.ldac.on() self.ldac.on()
@kernel @kernel
def set_dac_mu(self, values, channels=list(range(40))): def set_dac_mu(self, values: list[int32], channels: Option[list[int32]] = none):
"""Program multiple DAC channels and pulse LDAC to update the DAC """Program multiple DAC channels and pulse LDAC to update the DAC
outputs. outputs.
@ -313,7 +323,7 @@ class AD53xx:
If no LDAC device was defined, the LDAC pulse is skipped. If no LDAC device was defined, the LDAC pulse is skipped.
See :meth:`load`. See :meth load:.
:param values: list of DAC values to program :param values: list of DAC values to program
:param channels: list of DAC channels to program. If not specified, :param channels: list of DAC channels to program. If not specified,
@ -322,17 +332,18 @@ class AD53xx:
t0 = now_mu() t0 = now_mu()
# t10: max busy period after writing to DAC registers # t10: max busy period after writing to DAC registers
t_10 = self.core.seconds_to_mu(1500*ns) t_10 = self.core.seconds_to_mu(1500.*ns)
# compensate all delays that will be applied # compensate all delays that will be applied
delay_mu(-t_10-len(values)*self.bus.xfer_duration_mu) delay_mu(-t_10-int64(len(values))*self.bus.xfer_duration_mu)
channels_list = channels.unwrap() if channels.is_some() else [i for i in range(40)]
for i in range(len(values)): for i in range(len(values)):
self.write_dac_mu(channels[i], values[i]) self.write_dac_mu(channels_list[i], values[i])
delay_mu(t_10) delay_mu(t_10)
self.load() self.load()
at_mu(t0) at_mu(t0)
@kernel @kernel
def set_dac(self, voltages, channels=list(range(40))): def set_dac(self, voltages: list[float], channels: Option[list[int32]] = none):
"""Program multiple DAC channels and pulse LDAC to update the DAC """Program multiple DAC channels and pulse LDAC to update the DAC
outputs. outputs.
@ -351,11 +362,11 @@ class AD53xx:
self.set_dac_mu(values, channels) self.set_dac_mu(values, channels)
@kernel @kernel
def calibrate(self, channel, vzs, vfs): def calibrate(self, channel: int32, vzs: float, vfs: float):
""" Two-point calibration of a DAC channel. """Two-point calibration of a DAC channel.
Programs the offset and gain register to trim out DAC errors. Does not Programs the offset and gain register to trim out DAC errors. Does not
take effect until LDAC is pulsed (see :meth:`load`). take effect until LDAC is pulsed (see :meth load:).
Calibration consists of measuring the DAC output voltage for a channel Calibration consists of measuring the DAC output voltage for a channel
with the DAC set to zero-scale (0x0000) and full-scale (0xffff). with the DAC set to zero-scale (0x0000) and full-scale (0xffff).
@ -379,7 +390,7 @@ class AD53xx:
self.write_gain_mu(channel, 0xffff-gain_err) self.write_gain_mu(channel, 0xffff-gain_err)
@portable @portable
def voltage_to_mu(self, voltage): def voltage_to_mu(self, voltage: float) -> int32:
"""Returns the 16-bit DAC register value required to produce a given """Returns the 16-bit DAC register value required to produce a given
output voltage, assuming offset and gain errors have been trimmed out. output voltage, assuming offset and gain errors have been trimmed out.

View File

@ -1,17 +1,13 @@
from numpy import int32, int64 from numpy import int32, int64
from artiq.language.core import ( from artiq.language.core import *
kernel, delay, portable, delay_mu, now_mu, at_mu)
from artiq.language.units import us, ms from artiq.language.units import us, ms
from artiq.language.types import TBool, TInt32, TInt64, TFloat, TList, TTuple
from artiq.coredevice import spi2 as spi from artiq.coredevice.spi2 import *
from artiq.coredevice import urukul from artiq.coredevice.urukul import *
from artiq.coredevice.urukul import DEFAULT_PROFILE from artiq.coredevice.ttl import TTLOut
from artiq.coredevice.kasli_i2c import KasliEEPROM # NAC3TODO
# Work around ARTIQ-Python import machinery
urukul_sta_pll_lock = urukul.urukul_sta_pll_lock
urukul_sta_smp_err = urukul.urukul_sta_smp_err
__all__ = [ __all__ = [
"AD9910", "AD9910",
@ -64,8 +60,12 @@ RAM_MODE_CONT_RAMPUP = 4
# Default profile for RAM mode # Default profile for RAM mode
_DEFAULT_PROFILE_RAM = 0 _DEFAULT_PROFILE_RAM = 0
@nac3
class SyncDataUser: class SyncDataUser:
core: KernelInvariant[Core]
sync_delay_seed: Kernel[int32]
io_update_delay: Kernel[int32]
def __init__(self, core, sync_delay_seed, io_update_delay): def __init__(self, core, sync_delay_seed, io_update_delay):
self.core = core self.core = core
self.sync_delay_seed = sync_delay_seed self.sync_delay_seed = sync_delay_seed
@ -76,7 +76,14 @@ class SyncDataUser:
pass pass
@nac3
class SyncDataEeprom: class SyncDataEeprom:
core: KernelInvariant[Core]
eeprom_device: KernelInvariant[KasliEEPROM] # NAC3TODO support generic EEPROM driver
eeprom_offset: KernelInvariant[int32]
sync_delay_seed: Kernel[int32]
io_update_delay: Kernel[int32]
def __init__(self, dmgr, core, eeprom_str): def __init__(self, dmgr, core, eeprom_str):
self.core = core self.core = core
@ -102,6 +109,7 @@ class SyncDataEeprom:
self.io_update_delay = int32(io_update_delay) self.io_update_delay = int32(io_update_delay)
@nac3
class AD9910: class AD9910:
""" """
AD9910 DDS channel on Urukul. AD9910 DDS channel on Urukul.
@ -119,7 +127,7 @@ class AD9910:
f_ref/clk_div*pll_n where f_ref is the reference frequency and f_ref/clk_div*pll_n where f_ref is the reference frequency and
clk_div is the reference clock divider (both set in the parent clk_div is the reference clock divider (both set in the parent
Urukul CPLD instance). Urukul CPLD instance).
:param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1). :param pll_en: PLL enable bit, set to False to bypass PLL (default: True).
Note that when bypassing the PLL the red front panel LED may remain on. Note that when bypassing the PLL the red front panel LED may remain on.
:param pll_cp: DDS PLL charge pump setting. :param pll_cp: DDS PLL charge pump setting.
:param pll_vco: DDS PLL VCO range selection. :param pll_vco: DDS PLL VCO range selection.
@ -138,9 +146,24 @@ class AD9910:
to the same string value. to the same string value.
""" """
core: KernelInvariant[Core]
cpld: KernelInvariant[CPLD]
bus: KernelInvariant[SPIMaster]
chip_select: KernelInvariant[int32]
pll_en: KernelInvariant[bool]
pll_n: KernelInvariant[int32]
pll_vco: KernelInvariant[int32]
pll_cp: KernelInvariant[int32]
ftw_per_hz: KernelInvariant[float]
sysclk_per_mu: KernelInvariant[int32]
sysclk: KernelInvariant[float]
sw: KernelInvariant[Option[TTLOut]]
sync_data: KernelInvariant[SyncDataUser]
phase_mode: Kernel[int32]
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None, def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1, pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1,
io_update_delay=0, pll_en=1): io_update_delay=0, pll_en=True):
self.kernel_invariants = {"cpld", "core", "bus", "chip_select", self.kernel_invariants = {"cpld", "core", "bus", "chip_select",
"pll_en", "pll_n", "pll_vco", "pll_cp", "pll_en", "pll_n", "pll_vco", "pll_cp",
"ftw_per_hz", "sysclk_per_mu", "sysclk", "ftw_per_hz", "sysclk_per_mu", "sysclk",
@ -151,8 +174,9 @@ class AD9910:
assert 3 <= chip_select <= 7 assert 3 <= chip_select <= 7
self.chip_select = chip_select self.chip_select = chip_select
if sw_device: if sw_device:
self.sw = dmgr.get(sw_device) self.sw = Some(dmgr.get(sw_device))
self.kernel_invariants.add("sw") else:
self.sw = none
clk = self.cpld.refclk / [4, 1, 2, 4][self.cpld.clk_div] clk = self.cpld.refclk / [4, 1, 2, 4][self.cpld.clk_div]
self.pll_en = pll_en self.pll_en = pll_en
self.pll_n = pll_n self.pll_n = pll_n
@ -174,6 +198,7 @@ class AD9910:
self.sysclk_per_mu = int(round(sysclk * self.core.ref_period)) self.sysclk_per_mu = int(round(sysclk * self.core.ref_period))
self.sysclk = sysclk self.sysclk = sysclk
# NAC3TODO
if isinstance(sync_delay_seed, str) or isinstance(io_update_delay, if isinstance(sync_delay_seed, str) or isinstance(io_update_delay,
str): str):
if sync_delay_seed != io_update_delay: if sync_delay_seed != io_update_delay:
@ -187,7 +212,7 @@ class AD9910:
self.phase_mode = PHASE_MODE_CONTINUOUS self.phase_mode = PHASE_MODE_CONTINUOUS
@kernel @kernel
def set_phase_mode(self, phase_mode: TInt32): def set_phase_mode(self, phase_mode: int32):
r"""Set the default phase mode. r"""Set the default phase mode.
for future calls to :meth:`set` and for future calls to :meth:`set` and
@ -232,103 +257,103 @@ class AD9910:
self.phase_mode = phase_mode self.phase_mode = phase_mode
@kernel @kernel
def write16(self, addr: TInt32, data: TInt32): def write16(self, addr: int32, data: int32):
"""Write to 16 bit register. """Write to 16 bit register.
:param addr: Register address :param addr: Register address
:param data: Data to be written :param data: Data to be written
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 24, self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write((addr << 24) | ((data & 0xffff) << 8)) self.bus.write((addr << 24) | ((data & 0xffff) << 8))
@kernel @kernel
def write32(self, addr: TInt32, data: TInt32): def write32(self, addr: int32, data: int32):
"""Write to 32 bit register. """Write to 32 bit register.
:param addr: Register address :param addr: Register address
:param data: Data to be written :param data: Data to be written
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, self.bus.set_config_mu(SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write(addr << 24) self.bus.write(addr << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write(data) self.bus.write(data)
@kernel @kernel
def read16(self, addr: TInt32) -> TInt32: def read16(self, addr: int32) -> int32:
"""Read from 16 bit register. """Read from 16 bit register.
:param addr: Register address :param addr: Register address
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, self.bus.set_config_mu(SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write((addr | 0x80) << 24) self.bus.write((addr | 0x80) << 24)
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, SPI_CONFIG | SPI_END | SPI_INPUT,
16, urukul.SPIT_DDS_RD, self.chip_select) 16, SPIT_DDS_RD, self.chip_select)
self.bus.write(0) self.bus.write(0)
return self.bus.read() return self.bus.read()
@kernel @kernel
def read32(self, addr: TInt32) -> TInt32: def read32(self, addr: int32) -> int32:
"""Read from 32 bit register. """Read from 32 bit register.
:param addr: Register address :param addr: Register address
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, self.bus.set_config_mu(SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write((addr | 0x80) << 24) self.bus.write((addr | 0x80) << 24)
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, SPI_CONFIG | SPI_END | SPI_INPUT,
32, urukul.SPIT_DDS_RD, self.chip_select) 32, SPIT_DDS_RD, self.chip_select)
self.bus.write(0) self.bus.write(0)
return self.bus.read() return self.bus.read()
@kernel @kernel
def read64(self, addr: TInt32) -> TInt64: def read64(self, addr: int32) -> int64:
"""Read from 64 bit register. """Read from 64 bit register.
:param addr: Register address :param addr: Register address
:return: 64 bit integer register value :return: 64 bit integer register value
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG, 8, SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write((addr | 0x80) << 24) self.bus.write((addr | 0x80) << 24)
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_INPUT, 32, SPI_CONFIG | SPI_INPUT, 32,
urukul.SPIT_DDS_RD, self.chip_select) SPIT_DDS_RD, self.chip_select)
self.bus.write(0) self.bus.write(0)
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 32, SPI_CONFIG | SPI_END | SPI_INPUT, 32,
urukul.SPIT_DDS_RD, self.chip_select) SPIT_DDS_RD, self.chip_select)
self.bus.write(0) self.bus.write(0)
hi = self.bus.read() hi = self.bus.read()
lo = self.bus.read() lo = self.bus.read()
return (int64(hi) << 32) | lo return (int64(hi) << 32) | int64(lo)
@kernel @kernel
def write64(self, addr: TInt32, data_high: TInt32, data_low: TInt32): def write64(self, addr: int32, data_high: int32, data_low: int32):
"""Write to 64 bit register. """Write to 64 bit register.
:param addr: Register address :param addr: Register address
:param data_high: High (MSB) 32 bits of the data :param data_high: High (MSB) 32 bits of the data
:param data_low: Low (LSB) 32 data bits :param data_low: Low (LSB) 32 data bits
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, self.bus.set_config_mu(SPI_CONFIG, 8,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write(addr << 24) self.bus.write(addr << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG, 32, self.bus.set_config_mu(SPI_CONFIG, 32,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write(data_high) self.bus.write(data_high)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write(data_low) self.bus.write(data_low)
@kernel @kernel
def write_ram(self, data: TList(TInt32)): def write_ram(self, data: list[int32]):
"""Write data to RAM. """Write data to RAM.
The profile to write to and the step, start, and end address The profile to write to and the step, start, and end address
@ -337,19 +362,19 @@ class AD9910:
:param data: Data to be written to RAM. :param data: Data to be written to RAM.
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_DDS_WR,
self.chip_select) self.chip_select)
self.bus.write(_AD9910_REG_RAM << 24) self.bus.write(_AD9910_REG_RAM << 24)
self.bus.set_config_mu(urukul.SPI_CONFIG, 32, self.bus.set_config_mu(SPI_CONFIG, 32,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
for i in range(len(data) - 1): for i in range(len(data) - 1):
self.bus.write(data[i]) self.bus.write(data[i])
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write(data[len(data) - 1]) self.bus.write(data[len(data) - 1])
@kernel @kernel
def read_ram(self, data: TList(TInt32)): def read_ram(self, data: list[int32]):
"""Read data from RAM. """Read data from RAM.
The profile to read from and the step, start, and end address The profile to read from and the step, start, and end address
@ -358,38 +383,38 @@ class AD9910:
:param data: List to be filled with data read from RAM. :param data: List to be filled with data read from RAM.
""" """
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_DDS_WR,
self.chip_select) self.chip_select)
self.bus.write((_AD9910_REG_RAM | 0x80) << 24) self.bus.write((_AD9910_REG_RAM | 0x80) << 24)
n = len(data) - 1 n = len(data) - 1
if n > 0: if n > 0:
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_INPUT, 32, self.bus.set_config_mu(SPI_CONFIG | SPI_INPUT, 32,
urukul.SPIT_DDS_RD, self.chip_select) SPIT_DDS_RD, self.chip_select)
preload = min(n, 8) preload = min(n, 8)
for i in range(n): for i in range(n):
self.bus.write(0) self.bus.write(0)
if i >= preload: if i >= preload:
data[i - preload] = self.bus.read() data[i - preload] = self.bus.read()
self.bus.set_config_mu( self.bus.set_config_mu(
urukul.SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 32, SPI_CONFIG | SPI_INPUT | SPI_END, 32,
urukul.SPIT_DDS_RD, self.chip_select) SPIT_DDS_RD, self.chip_select)
self.bus.write(0) self.bus.write(0)
for i in range(preload + 1): for i in range(preload + 1):
data[(n - preload) + i] = self.bus.read() data[(n - preload) + i] = self.bus.read()
@kernel @kernel
def set_cfr1(self, def set_cfr1(self,
power_down: TInt32 = 0b0000, power_down: int32 = 0b0000,
phase_autoclear: TInt32 = 0, phase_autoclear: int32 = 0,
drg_load_lrr: TInt32 = 0, drg_load_lrr: int32 = 0,
drg_autoclear: TInt32 = 0, drg_autoclear: int32 = 0,
phase_clear: TInt32 = 0, phase_clear: int32 = 0,
internal_profile: TInt32 = 0, internal_profile: int32 = 0,
ram_destination: TInt32 = 0, ram_destination: int32 = 0,
ram_enable: TInt32 = 0, ram_enable: int32 = 0,
manual_osk_external: TInt32 = 0, manual_osk_external: int32 = 0,
osk_enable: TInt32 = 0, osk_enable: int32 = 0,
select_auto_osk: TInt32 = 0): select_auto_osk: int32 = 0):
"""Set CFR1. See the AD9910 datasheet for parameter meanings. """Set CFR1. See the AD9910 datasheet for parameter meanings.
This method does not pulse IO_UPDATE. This method does not pulse IO_UPDATE.
@ -424,11 +449,11 @@ class AD9910:
@kernel @kernel
def set_cfr2(self, def set_cfr2(self,
asf_profile_enable: TInt32 = 1, asf_profile_enable: int32 = 1,
drg_enable: TInt32 = 0, drg_enable: int32 = 0,
effective_ftw: TInt32 = 1, effective_ftw: int32 = 1,
sync_validation_disable: TInt32 = 0, sync_validation_disable: int32 = 0,
matched_latency_enable: TInt32 = 0): matched_latency_enable: int32 = 0):
"""Set CFR2. See the AD9910 datasheet for parameter meanings. """Set CFR2. See the AD9910 datasheet for parameter meanings.
This method does not pulse IO_UPDATE. This method does not pulse IO_UPDATE.
@ -452,7 +477,7 @@ class AD9910:
(sync_validation_disable << 5)) (sync_validation_disable << 5))
@kernel @kernel
def init(self, blind: TBool = False): def init(self, blind: bool = False):
"""Initialize and configure the DDS. """Initialize and configure the DDS.
Sets up SPI mode, confirms chip presence, powers down unused blocks, Sets up SPI mode, confirms chip presence, powers down unused blocks,
@ -465,66 +490,66 @@ class AD9910:
if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div: if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div:
raise ValueError("parent cpld does not drive SYNC") raise ValueError("parent cpld does not drive SYNC")
if self.sync_data.sync_delay_seed >= 0: if self.sync_data.sync_delay_seed >= 0:
if self.sysclk_per_mu != self.sysclk * self.core.ref_period: if float(self.sysclk_per_mu) != self.sysclk * self.core.ref_period:
raise ValueError("incorrect clock ratio for synchronization") raise ValueError("incorrect clock ratio for synchronization")
delay(50 * ms) # slack self.core.delay(50. * ms) # slack
# Set SPI mode # Set SPI mode
self.set_cfr1() self.set_cfr1()
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1. * us)
delay(1 * ms) self.core.delay(1. * ms)
if not blind: if not blind:
# Use the AUX DAC setting to identify and confirm presence # Use the AUX DAC setting to identify and confirm presence
aux_dac = self.read32(_AD9910_REG_AUX_DAC) aux_dac = self.read32(_AD9910_REG_AUX_DAC)
if aux_dac & 0xff != 0x7f: if aux_dac & 0xff != 0x7f:
raise ValueError("Urukul AD9910 AUX_DAC mismatch") raise ValueError("Urukul AD9910 AUX_DAC mismatch")
delay(50 * us) # slack self.core.delay(50. * us) # slack
# Configure PLL settings and bring up PLL # Configure PLL settings and bring up PLL
# enable amplitude scale from profiles # enable amplitude scale from profiles
# read effective FTW # read effective FTW
# sync timing validation disable (enabled later) # sync timing validation disable (enabled later)
self.set_cfr2(sync_validation_disable=1) self.set_cfr2(sync_validation_disable=1)
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1. * us)
cfr3 = (0x0807c000 | (self.pll_vco << 24) | cfr3 = (0x0807c000 | (self.pll_vco << 24) |
(self.pll_cp << 19) | (self.pll_en << 8) | (self.pll_cp << 19) | (int32(self.pll_en) << 8) |
(self.pll_n << 1)) (int32(self.pll_n) << 1))
self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1. * us)
if self.pll_en: if self.pll_en:
self.write32(_AD9910_REG_CFR3, cfr3) self.write32(_AD9910_REG_CFR3, cfr3)
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1. * us)
if blind: if blind:
delay(100 * ms) self.core.delay(100. * ms)
else: else:
# Wait for PLL lock, up to 100 ms # Wait for PLL lock, up to 100 ms
for i in range(100): for i in range(100):
sta = self.cpld.sta_read() sta = self.cpld.sta_read()
lock = urukul_sta_pll_lock(sta) lock = urukul_sta_pll_lock(sta)
delay(1 * ms) self.core.delay(1. * ms)
if lock & (1 << self.chip_select - 4): if lock & (1 << self.chip_select - 4) != 0:
break break
if i >= 100 - 1: if i >= 100 - 1:
raise ValueError("PLL lock timeout") raise ValueError("PLL lock timeout")
delay(10 * us) # slack self.core.delay(10. * us) # slack
if self.sync_data.sync_delay_seed >= 0 and not blind: if self.sync_data.sync_delay_seed >= 0 and not blind:
self.tune_sync_delay(self.sync_data.sync_delay_seed) self.tune_sync_delay(self.sync_data.sync_delay_seed)
delay(1 * ms) self.core.delay(1. * ms)
@kernel @kernel
def power_down(self, bits: TInt32 = 0b1111): def power_down(self, bits: int32 = 0b1111):
"""Power down DDS. """Power down DDS.
:param bits: Power down bits, see datasheet :param bits: Power down bits, see datasheet
""" """
self.set_cfr1(power_down=bits) self.set_cfr1(power_down=bits)
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1. * us)
@kernel @kernel
def set_mu(self, ftw: TInt32 = 0, pow_: TInt32 = 0, asf: TInt32 = 0x3fff, def set_mu(self, ftw: int32 = 0, pow_: int32 = 0, asf: int32 = 0x3fff,
phase_mode: TInt32 = _PHASE_MODE_DEFAULT, phase_mode: int32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1), ref_time_mu: int64 = int64(-1),
profile: TInt32 = DEFAULT_PROFILE, profile: int32 = DEFAULT_PROFILE,
ram_destination: TInt32 = -1) -> TInt32: ram_destination: int32 = -1) -> int32:
"""Set DDS data in machine units. """Set DDS data in machine units.
This uses machine units (FTW, POW, ASF). The frequency tuning word This uses machine units (FTW, POW, ASF). The frequency tuning word
@ -559,15 +584,15 @@ class AD9910:
phase_mode = self.phase_mode phase_mode = self.phase_mode
# Align to coarse RTIO which aligns SYNC_CLK. I.e. clear fine TSC # Align to coarse RTIO which aligns SYNC_CLK. I.e. clear fine TSC
# This will not cause a collision or sequence error. # This will not cause a collision or sequence error.
at_mu(now_mu() & ~7) at_mu(now_mu() & ~int64(7))
if phase_mode != PHASE_MODE_CONTINUOUS: if phase_mode != PHASE_MODE_CONTINUOUS:
# Auto-clear phase accumulator on IO_UPDATE. # Auto-clear phase accumulator on IO_UPDATE.
# This is active already for the next IO_UPDATE # This is active already for the next IO_UPDATE
self.set_cfr1(phase_autoclear=1) self.set_cfr1(phase_autoclear=1)
if phase_mode == PHASE_MODE_TRACKING and ref_time_mu < 0: if phase_mode == PHASE_MODE_TRACKING and ref_time_mu < int64(0):
# set default fiducial time stamp # set default fiducial time stamp
ref_time_mu = 0 ref_time_mu = int64(0)
if ref_time_mu >= 0: if ref_time_mu >= int64(0):
# 32 LSB are sufficient. # 32 LSB are sufficient.
# Also no need to use IO_UPDATE time as this # Also no need to use IO_UPDATE time as this
# is equivalent to an output pipeline latency. # is equivalent to an output pipeline latency.
@ -585,16 +610,16 @@ class AD9910:
if not ram_destination == RAM_DEST_POW: if not ram_destination == RAM_DEST_POW:
self.set_pow(pow_) self.set_pow(pow_)
delay_mu(int64(self.sync_data.io_update_delay)) delay_mu(int64(self.sync_data.io_update_delay))
self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYN_CCLK self.cpld.io_update.pulse_mu(int64(8)) # assumes 8 mu > t_SYN_CCLK
at_mu(now_mu() & ~7) # clear fine TSC again at_mu(now_mu() & ~int64(7)) # clear fine TSC again
if phase_mode != PHASE_MODE_CONTINUOUS: if phase_mode != PHASE_MODE_CONTINUOUS:
self.set_cfr1() self.set_cfr1()
# future IO_UPDATE will activate # future IO_UPDATE will activate
return pow_ return pow_
@kernel @kernel
def get_mu(self, profile: TInt32 = DEFAULT_PROFILE def get_mu(self, profile: int32 = DEFAULT_PROFILE
) -> TTuple([TInt32, TInt32, TInt32]): ) -> tuple[int32, int32, int32]:
"""Get the frequency tuning word, phase offset word, """Get the frequency tuning word, phase offset word,
and amplitude scale factor. and amplitude scale factor.
@ -608,15 +633,15 @@ class AD9910:
data = int64(self.read64(_AD9910_REG_PROFILE0 + profile)) data = int64(self.read64(_AD9910_REG_PROFILE0 + profile))
# Extract and return fields # Extract and return fields
ftw = int32(data) ftw = int32(data)
pow_ = int32((data >> 32) & 0xffff) pow_ = int32(data >> 32) & 0xffff
asf = int32((data >> 48) & 0x3fff) asf = int32(data >> 48) & 0x3fff
return ftw, pow_, asf return ftw, pow_, asf
@kernel @kernel
def set_profile_ram(self, start: TInt32, end: TInt32, step: TInt32 = 1, def set_profile_ram(self, start: int32, end: int32, step: int32 = 1,
profile: TInt32 = _DEFAULT_PROFILE_RAM, profile: int32 = _DEFAULT_PROFILE_RAM,
nodwell_high: TInt32 = 0, zero_crossing: TInt32 = 0, nodwell_high: int32 = 0, zero_crossing: int32 = 0,
mode: TInt32 = 1): mode: int32 = 1):
"""Set the RAM profile settings. """Set the RAM profile settings.
:param start: Profile start address in RAM. :param start: Profile start address in RAM.
@ -640,7 +665,7 @@ class AD9910:
self.write64(_AD9910_REG_PROFILE0 + profile, hi, lo) self.write64(_AD9910_REG_PROFILE0 + profile, hi, lo)
@kernel @kernel
def set_ftw(self, ftw: TInt32): def set_ftw(self, ftw: int32):
"""Set the value stored to the AD9910's frequency tuning word (FTW) """Set the value stored to the AD9910's frequency tuning word (FTW)
register. register.
@ -649,7 +674,7 @@ class AD9910:
self.write32(_AD9910_REG_FTW, ftw) self.write32(_AD9910_REG_FTW, ftw)
@kernel @kernel
def set_asf(self, asf: TInt32): def set_asf(self, asf: int32):
"""Set the value stored to the AD9910's amplitude scale factor (ASF) """Set the value stored to the AD9910's amplitude scale factor (ASF)
register. register.
@ -658,7 +683,7 @@ class AD9910:
self.write32(_AD9910_REG_ASF, asf << 2) self.write32(_AD9910_REG_ASF, asf << 2)
@kernel @kernel
def set_pow(self, pow_: TInt32): def set_pow(self, pow_: int32):
"""Set the value stored to the AD9910's phase offset word (POW) """Set the value stored to the AD9910's phase offset word (POW)
register. register.
@ -667,7 +692,7 @@ class AD9910:
self.write16(_AD9910_REG_POW, pow_) self.write16(_AD9910_REG_POW, pow_)
@kernel @kernel
def get_ftw(self) -> TInt32: def get_ftw(self) -> int32:
"""Get the value stored to the AD9910's frequency tuning word (FTW) """Get the value stored to the AD9910's frequency tuning word (FTW)
register. register.
@ -676,7 +701,7 @@ class AD9910:
return self.read32(_AD9910_REG_FTW) return self.read32(_AD9910_REG_FTW)
@kernel @kernel
def get_asf(self) -> TInt32: def get_asf(self) -> int32:
"""Get the value stored to the AD9910's amplitude scale factor (ASF) """Get the value stored to the AD9910's amplitude scale factor (ASF)
register. register.
@ -685,7 +710,7 @@ class AD9910:
return self.read32(_AD9910_REG_ASF) >> 2 return self.read32(_AD9910_REG_ASF) >> 2
@kernel @kernel
def get_pow(self) -> TInt32: def get_pow(self) -> int32:
"""Get the value stored to the AD9910's phase offset word (POW) """Get the value stored to the AD9910's phase offset word (POW)
register. register.
@ -693,49 +718,49 @@ class AD9910:
""" """
return self.read16(_AD9910_REG_POW) return self.read16(_AD9910_REG_POW)
@portable(flags={"fast-math"}) @portable
def frequency_to_ftw(self, frequency: TFloat) -> TInt32: def frequency_to_ftw(self, frequency: float) -> int32:
"""Return the 32-bit frequency tuning word corresponding to the given """Return the 32-bit frequency tuning word corresponding to the given
frequency. frequency.
""" """
return int32(round(self.ftw_per_hz * frequency)) return int32(round(self.ftw_per_hz * frequency))
@portable(flags={"fast-math"}) @portable
def ftw_to_frequency(self, ftw: TInt32) -> TFloat: def ftw_to_frequency(self, ftw: int32) -> float:
"""Return the frequency corresponding to the given frequency tuning """Return the frequency corresponding to the given frequency tuning
word. word.
""" """
return ftw / self.ftw_per_hz return float(ftw) / self.ftw_per_hz
@portable(flags={"fast-math"}) @portable
def turns_to_pow(self, turns: TFloat) -> TInt32: def turns_to_pow(self, turns: float) -> int32:
"""Return the 16-bit phase offset word corresponding to the given phase """Return the 16-bit phase offset word corresponding to the given phase
in turns.""" in turns."""
return int32(round(turns * 0x10000)) & int32(0xffff) return round(turns * float(0x10000)) & 0xffff
@portable(flags={"fast-math"}) @portable
def pow_to_turns(self, pow_: TInt32) -> TFloat: def pow_to_turns(self, pow_: int32) -> float:
"""Return the phase in turns corresponding to a given phase offset """Return the phase in turns corresponding to a given phase offset
word.""" word."""
return pow_ / 0x10000 return pow_ / 0x10000
@portable(flags={"fast-math"}) @portable
def amplitude_to_asf(self, amplitude: TFloat) -> TInt32: def amplitude_to_asf(self, amplitude: float) -> int32:
"""Return 14-bit amplitude scale factor corresponding to given """Return 14-bit amplitude scale factor corresponding to given
fractional amplitude.""" fractional amplitude."""
code = int32(round(amplitude * 0x3fff)) code = round(amplitude * float(0x3fff))
if code < 0 or code > 0x3fff: if code < 0 or code > 0x3fff:
raise ValueError("Invalid AD9910 fractional amplitude!") raise ValueError("Invalid AD9910 fractional amplitude!")
return code return code
@portable(flags={"fast-math"}) @portable
def asf_to_amplitude(self, asf: TInt32) -> TFloat: def asf_to_amplitude(self, asf: int32) -> float:
"""Return amplitude as a fraction of full scale corresponding to given """Return amplitude as a fraction of full scale corresponding to given
amplitude scale factor.""" amplitude scale factor."""
return asf / float(0x3fff) return float(asf) / float(0x3fff)
@portable(flags={"fast-math"}) @portable
def frequency_to_ram(self, frequency: TList(TFloat), ram: TList(TInt32)): def frequency_to_ram(self, frequency: list[float], ram: list[int32]):
"""Convert frequency values to RAM profile data. """Convert frequency values to RAM profile data.
To be used with :const:`RAM_DEST_FTW`. To be used with :const:`RAM_DEST_FTW`.
@ -747,8 +772,8 @@ class AD9910:
for i in range(len(ram)): for i in range(len(ram)):
ram[i] = self.frequency_to_ftw(frequency[i]) ram[i] = self.frequency_to_ftw(frequency[i])
@portable(flags={"fast-math"}) @portable
def turns_to_ram(self, turns: TList(TFloat), ram: TList(TInt32)): def turns_to_ram(self, turns: list[float], ram: list[int32]):
"""Convert phase values to RAM profile data. """Convert phase values to RAM profile data.
To be used with :const:`RAM_DEST_POW`. To be used with :const:`RAM_DEST_POW`.
@ -760,8 +785,8 @@ class AD9910:
for i in range(len(ram)): for i in range(len(ram)):
ram[i] = self.turns_to_pow(turns[i]) << 16 ram[i] = self.turns_to_pow(turns[i]) << 16
@portable(flags={"fast-math"}) @portable
def amplitude_to_ram(self, amplitude: TList(TFloat), ram: TList(TInt32)): def amplitude_to_ram(self, amplitude: list[float], ram: list[int32]):
"""Convert amplitude values to RAM profile data. """Convert amplitude values to RAM profile data.
To be used with :const:`RAM_DEST_ASF`. To be used with :const:`RAM_DEST_ASF`.
@ -773,9 +798,9 @@ class AD9910:
for i in range(len(ram)): for i in range(len(ram)):
ram[i] = self.amplitude_to_asf(amplitude[i]) << 18 ram[i] = self.amplitude_to_asf(amplitude[i]) << 18
@portable(flags={"fast-math"}) @portable
def turns_amplitude_to_ram(self, turns: TList(TFloat), def turns_amplitude_to_ram(self, turns: list[float],
amplitude: TList(TFloat), ram: TList(TInt32)): amplitude: list[float], ram: list[int32]):
"""Convert phase and amplitude values to RAM profile data. """Convert phase and amplitude values to RAM profile data.
To be used with :const:`RAM_DEST_POWASF`. To be used with :const:`RAM_DEST_POWASF`.
@ -790,7 +815,7 @@ class AD9910:
self.amplitude_to_asf(amplitude[i]) << 2) self.amplitude_to_asf(amplitude[i]) << 2)
@kernel @kernel
def set_frequency(self, frequency: TFloat): def set_frequency(self, frequency: float):
"""Set the value stored to the AD9910's frequency tuning word (FTW) """Set the value stored to the AD9910's frequency tuning word (FTW)
register. register.
@ -799,7 +824,7 @@ class AD9910:
self.set_ftw(self.frequency_to_ftw(frequency)) self.set_ftw(self.frequency_to_ftw(frequency))
@kernel @kernel
def set_amplitude(self, amplitude: TFloat): def set_amplitude(self, amplitude: float):
"""Set the value stored to the AD9910's amplitude scale factor (ASF) """Set the value stored to the AD9910's amplitude scale factor (ASF)
register. register.
@ -808,7 +833,7 @@ class AD9910:
self.set_asf(self.amplitude_to_asf(amplitude)) self.set_asf(self.amplitude_to_asf(amplitude))
@kernel @kernel
def set_phase(self, turns: TFloat): def set_phase(self, turns: float):
"""Set the value stored to the AD9910's phase offset word (POW) """Set the value stored to the AD9910's phase offset word (POW)
register. register.
@ -817,7 +842,7 @@ class AD9910:
self.set_pow(self.turns_to_pow(turns)) self.set_pow(self.turns_to_pow(turns))
@kernel @kernel
def get_frequency(self) -> TFloat: def get_frequency(self) -> float:
"""Get the value stored to the AD9910's frequency tuning word (FTW) """Get the value stored to the AD9910's frequency tuning word (FTW)
register. register.
@ -826,7 +851,7 @@ class AD9910:
return self.ftw_to_frequency(self.get_ftw()) return self.ftw_to_frequency(self.get_ftw())
@kernel @kernel
def get_amplitude(self) -> TFloat: def get_amplitude(self) -> float:
"""Get the value stored to the AD9910's amplitude scale factor (ASF) """Get the value stored to the AD9910's amplitude scale factor (ASF)
register. register.
@ -835,7 +860,7 @@ class AD9910:
return self.asf_to_amplitude(self.get_asf()) return self.asf_to_amplitude(self.get_asf())
@kernel @kernel
def get_phase(self) -> TFloat: def get_phase(self) -> float:
"""Get the value stored to the AD9910's phase offset word (POW) """Get the value stored to the AD9910's phase offset word (POW)
register. register.
@ -844,10 +869,10 @@ class AD9910:
return self.pow_to_turns(self.get_pow()) return self.pow_to_turns(self.get_pow())
@kernel @kernel
def set(self, frequency: TFloat = 0.0, phase: TFloat = 0.0, def set(self, frequency: float = 0.0, phase: float = 0.0,
amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT, amplitude: float = 1.0, phase_mode: int32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1), profile: TInt32 = DEFAULT_PROFILE, ref_time_mu: int64 = int64(-1), profile: int32 = DEFAULT_PROFILE,
ram_destination: TInt32 = -1) -> TFloat: ram_destination: int32 = -1) -> float:
"""Set DDS data in SI units. """Set DDS data in SI units.
.. seealso:: :meth:`set_mu` .. seealso:: :meth:`set_mu`
@ -867,8 +892,8 @@ class AD9910:
profile, ram_destination)) profile, ram_destination))
@kernel @kernel
def get(self, profile: TInt32 = DEFAULT_PROFILE def get(self, profile: int32 = DEFAULT_PROFILE
) -> TTuple([TFloat, TFloat, TFloat]): ) -> tuple[float, float, float]:
"""Get the frequency, phase, and amplitude. """Get the frequency, phase, and amplitude.
.. seealso:: :meth:`get_mu` .. seealso:: :meth:`get_mu`
@ -884,7 +909,7 @@ class AD9910:
self.asf_to_amplitude(asf)) self.asf_to_amplitude(asf))
@kernel @kernel
def set_att_mu(self, att: TInt32): def set_att_mu(self, att: int32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
@ -896,7 +921,7 @@ class AD9910:
self.cpld.set_att_mu(self.chip_select - 4, att) self.cpld.set_att_mu(self.chip_select - 4, att)
@kernel @kernel
def set_att(self, att: TFloat): def set_att(self, att: float):
"""Set digital step attenuator in SI units. """Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
@ -908,7 +933,7 @@ class AD9910:
self.cpld.set_att(self.chip_select - 4, att) self.cpld.set_att(self.chip_select - 4, att)
@kernel @kernel
def get_att_mu(self) -> TInt32: def get_att_mu(self) -> int32:
"""Get digital step attenuator value in machine units. """Get digital step attenuator value in machine units.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu` .. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu`
@ -918,7 +943,7 @@ class AD9910:
return self.cpld.get_channel_att_mu(self.chip_select - 4) return self.cpld.get_channel_att_mu(self.chip_select - 4)
@kernel @kernel
def get_att(self) -> TFloat: def get_att(self) -> float:
"""Get digital step attenuator value in SI units. """Get digital step attenuator value in SI units.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att` .. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att`
@ -928,7 +953,7 @@ class AD9910:
return self.cpld.get_channel_att(self.chip_select - 4) return self.cpld.get_channel_att(self.chip_select - 4)
@kernel @kernel
def cfg_sw(self, state: TBool): def cfg_sw(self, state: bool):
"""Set CPLD CFG RF switch state. The RF switch is controlled by the """Set CPLD CFG RF switch state. The RF switch is controlled by the
logical or of the CPLD configuration shift register logical or of the CPLD configuration shift register
RF switch bit and the SW TTL line (if used). RF switch bit and the SW TTL line (if used).
@ -939,9 +964,9 @@ class AD9910:
@kernel @kernel
def set_sync(self, def set_sync(self,
in_delay: TInt32, in_delay: int32,
window: TInt32, window: int32,
en_sync_gen: TInt32 = 0): en_sync_gen: int32 = 0):
"""Set the relevant parameters in the multi device synchronization """Set the relevant parameters in the multi device synchronization
register. See the AD9910 datasheet for details. The SYNC clock register. See the AD9910 datasheet for details. The SYNC clock
generator preset value is set to zero, and the SYNC_OUT generator is generator preset value is set to zero, and the SYNC_OUT generator is
@ -974,14 +999,14 @@ class AD9910:
Also modifies CFR2. Also modifies CFR2.
""" """
self.set_cfr2(sync_validation_disable=1) # clear SMP_ERR self.set_cfr2(sync_validation_disable=1) # clear SMP_ERR
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1. * us)
delay(10 * us) # slack self.core.delay(10. * us) # slack
self.set_cfr2(sync_validation_disable=0) # enable SMP_ERR self.set_cfr2(sync_validation_disable=0) # enable SMP_ERR
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1. * us)
@kernel @kernel
def tune_sync_delay(self, def tune_sync_delay(self,
search_seed: TInt32 = 15) -> TTuple([TInt32, TInt32]): search_seed: int32 = 15) -> tuple[int32, int32]:
"""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
@ -1008,7 +1033,7 @@ class AD9910:
next_seed = -1 next_seed = -1
for in_delay in range(search_span - 2 * window): for in_delay in range(search_span - 2 * window):
# alternate search direction around search_seed # alternate search direction around search_seed
if in_delay & 1: if in_delay & 1 != 0:
in_delay = -in_delay in_delay = -in_delay
in_delay = search_seed + (in_delay >> 1) in_delay = search_seed + (in_delay >> 1)
if in_delay < 0 or in_delay > 31: if in_delay < 0 or in_delay > 31:
@ -1016,9 +1041,9 @@ class AD9910:
self.set_sync(in_delay, window) self.set_sync(in_delay, window)
self.clear_smp_err() self.clear_smp_err()
# integrate SMP_ERR statistics for a few hundred cycles # integrate SMP_ERR statistics for a few hundred cycles
delay(100 * us) self.core.delay(100. * us)
err = urukul_sta_smp_err(self.cpld.sta_read()) err = urukul_sta_smp_err(self.cpld.sta_read())
delay(100 * us) # slack self.core.delay(100. * us) # slack
if not (err >> (self.chip_select - 4)) & 1: if not (err >> (self.chip_select - 4)) & 1:
next_seed = in_delay next_seed = in_delay
break break
@ -1030,15 +1055,15 @@ class AD9910:
window = max(min_window, window - 1 - margin) window = max(min_window, window - 1 - margin)
self.set_sync(search_seed, window) self.set_sync(search_seed, window)
self.clear_smp_err() self.clear_smp_err()
delay(100 * us) # slack self.core.delay(100. * us) # slack
return search_seed, window return search_seed, window
else: else:
break break
raise ValueError("no valid window/delay") raise ValueError("no valid window/delay")
@kernel @kernel
def measure_io_update_alignment(self, delay_start: TInt64, def measure_io_update_alignment(self, delay_start: int64,
delay_stop: TInt64) -> TInt32: delay_stop: int64) -> int32:
"""Use the digital ramp generator to locate the alignment between """Use the digital ramp generator to locate the alignment between
IO_UPDATE and SYNC_CLK. IO_UPDATE and SYNC_CLK.
@ -1062,25 +1087,25 @@ class AD9910:
# dFTW = 1, (work around negative slope) # dFTW = 1, (work around negative slope)
self.write64(_AD9910_REG_RAMP_STEP, -1, 0) self.write64(_AD9910_REG_RAMP_STEP, -1, 0)
# delay io_update after RTIO edge # delay io_update after RTIO edge
t = now_mu() + 8 & ~7 t = now_mu() + int64(8) & ~int64(7)
at_mu(t + delay_start) at_mu(t + delay_start)
# assumes a maximum t_SYNC_CLK period # assumes a maximum t_SYNC_CLK period
self.cpld.io_update.pulse_mu(16 - delay_start) # realign self.cpld.io_update.pulse_mu(int64(16) - delay_start) # realign
# disable DRG autoclear and LRR on io_update # disable DRG autoclear and LRR on io_update
self.set_cfr1() self.set_cfr1()
# stop DRG # stop DRG
self.write64(_AD9910_REG_RAMP_STEP, 0, 0) self.write64(_AD9910_REG_RAMP_STEP, 0, 0)
at_mu(t + 0x1000 + delay_stop) at_mu(t + int64(0x1000) + delay_stop)
self.cpld.io_update.pulse_mu(16 - delay_stop) # realign self.cpld.io_update.pulse_mu(int64(16) - delay_stop) # realign
ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW
delay(100 * us) # slack self.core.delay(100. * us) # slack
# disable DRG # disable DRG
self.set_cfr2(drg_enable=0) self.set_cfr2(drg_enable=0)
self.cpld.io_update.pulse_mu(8) self.cpld.io_update.pulse_mu(int64(8))
return ftw & 1 return ftw & 1
@kernel @kernel
def tune_io_update_delay(self) -> TInt32: def tune_io_update_delay(self) -> int32:
"""Find a stable IO_UPDATE delay alignment. """Find a stable IO_UPDATE delay alignment.
Scan through increasing IO_UPDATE delays until a delay is found that Scan through increasing IO_UPDATE delays until a delay is found that
@ -1102,14 +1127,14 @@ class AD9910:
t = 0 t = 0
# check whether the sync edge is strictly between i, i+2 # check whether the sync edge is strictly between i, i+2
for j in range(repeat): for j in range(repeat):
t += self.measure_io_update_alignment(i, i + 2) t += self.measure_io_update_alignment(int64(i), int64(i + 2))
if t != 0: # no certain edge if t != 0: # no certain edge
continue continue
# check left/right half: i,i+1 and i+1,i+2 # check left/right half: i,i+1 and i+1,i+2
t1 = [0, 0] t1 = [0, 0]
for j in range(repeat): for j in range(repeat):
t1[0] += self.measure_io_update_alignment(i, i + 1) t1[0] += self.measure_io_update_alignment(int64(i), int64(i + 1))
t1[1] += self.measure_io_update_alignment(i + 1, i + 2) t1[1] += self.measure_io_update_alignment(int64(i + 1), int64(i + 2))
if ((t1[0] == 0 and t1[1] == 0) or if ((t1[0] == 0 and t1[1] == 0) or
(t1[0] == repeat and t1[1] == repeat)): (t1[0] == repeat and t1[1] == repeat)):
# edge is not close to i + 1, can't interpret result # edge is not close to i + 1, can't interpret result

View File

@ -1,14 +1,16 @@
from numpy import int32, int64 from numpy import int32, int64
from artiq.language.types import TInt32, TInt64, TFloat, TTuple, TBool from artiq.language.core import *
from artiq.language.core import kernel, delay, portable
from artiq.language.units import ms, us, ns from artiq.language.units import ms, us, ns
from artiq.coredevice.ad9912_reg import * from artiq.coredevice.ad9912_reg import *
from artiq.coredevice import spi2 as spi from artiq.coredevice.core import Core
from artiq.coredevice import urukul from artiq.coredevice.spi2 import *
from artiq.coredevice.urukul import *
from artiq.coredevice.ttl import TTLOut
@nac3
class AD9912: class AD9912:
""" """
AD9912 DDS channel on Urukul AD9912 DDS channel on Urukul
@ -25,22 +27,30 @@ class AD9912:
f_ref/clk_div*pll_n where f_ref is the reference frequency and clk_div f_ref/clk_div*pll_n where f_ref is the reference frequency and clk_div
is the reference clock divider (both set in the parent Urukul CPLD is the reference clock divider (both set in the parent Urukul CPLD
instance). instance).
:param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1). :param pll_en: PLL enable bit, set to False to bypass PLL (default: True).
Note that when bypassing the PLL the red front panel LED may remain on. Note that when bypassing the PLL the red front panel LED may remain on.
""" """
core: KernelInvariant[Core]
cpld: KernelInvariant[CPLD]
bus: KernelInvariant[SPIMaster]
chip_select: KernelInvariant[int32]
pll_n: KernelInvariant[int32]
pll_en: KernelInvariant[bool]
ftw_per_hz: KernelInvariant[float]
sw: KernelInvariant[Option[TTLOut]]
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None, def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
pll_n=10, pll_en=1): pll_n=10, pll_en=True):
self.kernel_invariants = {"cpld", "core", "bus", "chip_select",
"pll_n", "pll_en", "ftw_per_hz"}
self.cpld = dmgr.get(cpld_device) self.cpld = dmgr.get(cpld_device)
self.core = self.cpld.core self.core = self.cpld.core
self.bus = self.cpld.bus self.bus = self.cpld.bus
assert 4 <= chip_select <= 7 assert 4 <= chip_select <= 7
self.chip_select = chip_select self.chip_select = chip_select
if sw_device: if sw_device:
self.sw = dmgr.get(sw_device) self.sw = Some(dmgr.get(sw_device))
self.kernel_invariants.add("sw") else:
self.sw = none
self.pll_en = pll_en self.pll_en = pll_en
self.pll_n = pll_n self.pll_n = pll_n
if pll_en: if pll_en:
@ -48,10 +58,10 @@ class AD9912:
else: else:
sysclk = self.cpld.refclk sysclk = self.cpld.refclk
assert sysclk <= 1e9 assert sysclk <= 1e9
self.ftw_per_hz = 1 / sysclk * (int64(1) << 48) self.ftw_per_hz = 1 / sysclk * (1 << 48)
@kernel @kernel
def write(self, addr: TInt32, data: TInt32, length: TInt32): def write(self, addr: int32, data: int32, length: int32):
"""Variable length write to a register. """Variable length write to a register.
Up to 4 bytes. Up to 4 bytes.
@ -61,15 +71,15 @@ class AD9912:
""" """
assert length > 0 assert length > 0
assert length <= 4 assert length <= 4
self.bus.set_config_mu(urukul.SPI_CONFIG, 16, self.bus.set_config_mu(SPI_CONFIG, 16,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write((addr | ((length - 1) << 13)) << 16) self.bus.write((addr | ((length - 1) << 13)) << 16)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, length * 8, self.bus.set_config_mu(SPI_CONFIG | SPI_END, length * 8,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write(data << (32 - length * 8)) self.bus.write(data << (32 - length * 8))
@kernel @kernel
def read(self, addr: TInt32, length: TInt32) -> TInt32: def read(self, addr: int32, length: int32) -> int32:
"""Variable length read from a register. """Variable length read from a register.
Up to 4 bytes. Up to 4 bytes.
@ -79,12 +89,12 @@ class AD9912:
""" """
assert length > 0 assert length > 0
assert length <= 4 assert length <= 4
self.bus.set_config_mu(urukul.SPI_CONFIG, 16, self.bus.set_config_mu(SPI_CONFIG, 16,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write((addr | ((length - 1) << 13) | 0x8000) << 16) self.bus.write((addr | ((length - 1) << 13) | 0x8000) << 16)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END self.bus.set_config_mu(SPI_CONFIG | SPI_END
| spi.SPI_INPUT, length * 8, | SPI_INPUT, length * 8,
urukul.SPIT_DDS_RD, self.chip_select) SPIT_DDS_RD, self.chip_select)
self.bus.write(0) self.bus.write(0)
data = self.bus.read() data = self.bus.read()
if length < 4: if length < 4:
@ -100,27 +110,27 @@ class AD9912:
IO_UPDATE signal multiple times. IO_UPDATE signal multiple times.
""" """
# SPI mode # SPI mode
self.write(AD9912_SER_CONF, 0x99, length=1) self.write(AD9912_SER_CONF, 0x99, 1)
self.cpld.io_update.pulse(2 * us) self.cpld.io_update.pulse(2. * us)
# Verify chip ID and presence # Verify chip ID and presence
prodid = self.read(AD9912_PRODIDH, length=2) prodid = self.read(AD9912_PRODIDH, 2)
if (prodid != 0x1982) and (prodid != 0x1902): if (prodid != 0x1982) and (prodid != 0x1902):
raise ValueError("Urukul AD9912 product id mismatch") raise ValueError("Urukul AD9912 product id mismatch")
delay(50 * us) self.core.delay(50. * us)
# HSTL power down, CMOS power down # HSTL power down, CMOS power down
pwrcntrl1 = 0x80 | ((~self.pll_en & 1) << 4) pwrcntrl1 = 0x80 | (int32(not self.pll_en) << 4)
self.write(AD9912_PWRCNTRL1, pwrcntrl1, length=1) self.write(AD9912_PWRCNTRL1, pwrcntrl1, 1)
self.cpld.io_update.pulse(2 * us) self.cpld.io_update.pulse(2. * us)
if self.pll_en: if self.pll_en:
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1) self.write(AD9912_N_DIV, self.pll_n // 2 - 2, 1)
self.cpld.io_update.pulse(2 * us) self.cpld.io_update.pulse(2. * us)
# I_cp = 375 µA, VCO high range # I_cp = 375 µA, VCO high range
self.write(AD9912_PLLCFG, 0b00000101, length=1) self.write(AD9912_PLLCFG, 0b00000101, 1)
self.cpld.io_update.pulse(2 * us) self.cpld.io_update.pulse(2. * us)
delay(1 * ms) self.core.delay(1. * ms)
@kernel @kernel
def set_att_mu(self, att: TInt32): def set_att_mu(self, att: int32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
@ -132,7 +142,7 @@ class AD9912:
self.cpld.set_att_mu(self.chip_select - 4, att) self.cpld.set_att_mu(self.chip_select - 4, att)
@kernel @kernel
def set_att(self, att: TFloat): def set_att(self, att: float):
"""Set digital step attenuator in SI units. """Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
@ -144,7 +154,7 @@ class AD9912:
self.cpld.set_att(self.chip_select - 4, att) self.cpld.set_att(self.chip_select - 4, att)
@kernel @kernel
def get_att_mu(self) -> TInt32: def get_att_mu(self) -> int32:
"""Get digital step attenuator value in machine units. """Get digital step attenuator value in machine units.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu` .. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu`
@ -154,7 +164,7 @@ class AD9912:
return self.cpld.get_channel_att_mu(self.chip_select - 4) return self.cpld.get_channel_att_mu(self.chip_select - 4)
@kernel @kernel
def get_att(self) -> TFloat: def get_att(self) -> float:
"""Get digital step attenuator value in SI units. """Get digital step attenuator value in SI units.
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att` .. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att`
@ -164,7 +174,7 @@ class AD9912:
return self.cpld.get_channel_att(self.chip_select - 4) return self.cpld.get_channel_att(self.chip_select - 4)
@kernel @kernel
def set_mu(self, ftw: TInt64, pow_: TInt32 = 0): def set_mu(self, ftw: int64, pow_: int32 = 0):
"""Set profile 0 data in machine units. """Set profile 0 data in machine units.
After the SPI transfer, the shared IO update pin is pulsed to After the SPI transfer, the shared IO update pin is pulsed to
@ -174,19 +184,19 @@ class AD9912:
:param pow_: Phase tuning word: 16 bit unsigned. :param pow_: Phase tuning word: 16 bit unsigned.
""" """
# streaming transfer of FTW and POW # streaming transfer of FTW and POW
self.bus.set_config_mu(urukul.SPI_CONFIG, 16, self.bus.set_config_mu(SPI_CONFIG, 16,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write((AD9912_POW1 << 16) | (3 << 29)) self.bus.write((AD9912_POW1 << 16) | (3 << 29))
self.bus.set_config_mu(urukul.SPI_CONFIG, 32, self.bus.set_config_mu(SPI_CONFIG, 32,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write((pow_ << 16) | (int32(ftw >> 32) & 0xffff)) self.bus.write((pow_ << 16) | (int32(ftw >> 32) & 0xffff))
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
urukul.SPIT_DDS_WR, self.chip_select) SPIT_DDS_WR, self.chip_select)
self.bus.write(int32(ftw)) self.bus.write(int32(ftw))
self.cpld.io_update.pulse(10 * ns) self.cpld.io_update.pulse(10. * ns)
@kernel @kernel
def get_mu(self) -> TTuple([TInt64, TInt32]): def get_mu(self) -> tuple[int64, int32]:
"""Get the frequency tuning word and phase offset word. """Get the frequency tuning word and phase offset word.
.. seealso:: :meth:`get` .. seealso:: :meth:`get`
@ -203,30 +213,30 @@ class AD9912:
pow_ = (high >> 16) & 0x3fff pow_ = (high >> 16) & 0x3fff
return ftw, pow_ return ftw, pow_
@portable(flags={"fast-math"}) @portable
def frequency_to_ftw(self, frequency: TFloat) -> TInt64: def frequency_to_ftw(self, frequency: float) -> int64:
"""Returns the 48-bit frequency tuning word corresponding to the given """Returns the 48-bit frequency tuning word corresponding to the given
frequency. frequency.
""" """
return int64(round(self.ftw_per_hz * frequency)) & ( return round64(self.ftw_per_hz * frequency) & (
(int64(1) << 48) - 1) (int64(1) << 48) - int64(1))
@portable(flags={"fast-math"}) @portable
def ftw_to_frequency(self, ftw: TInt64) -> TFloat: def ftw_to_frequency(self, ftw: int64) -> float:
"""Returns the frequency corresponding to the given """Returns the frequency corresponding to the given
frequency tuning word. frequency tuning word.
""" """
return ftw / self.ftw_per_hz return float(ftw) / self.ftw_per_hz
@portable(flags={"fast-math"}) @portable
def turns_to_pow(self, phase: TFloat) -> TInt32: def turns_to_pow(self, phase: float) -> int32:
"""Returns the 16-bit phase offset word corresponding to the given """Returns the 16-bit phase offset word corresponding to the given
phase. phase.
""" """
return int32(round((1 << 14) * phase)) & 0xffff return int32(round(float(1 << 14) * phase)) & 0xffff
@portable(flags={"fast-math"}) @portable
def pow_to_turns(self, pow_: TInt32) -> TFloat: def pow_to_turns(self, pow_: int32) -> float:
"""Return the phase in turns corresponding to a given phase offset """Return the phase in turns corresponding to a given phase offset
word. word.
@ -236,7 +246,7 @@ class AD9912:
return pow_ / (1 << 14) return pow_ / (1 << 14)
@kernel @kernel
def set(self, frequency: TFloat, phase: TFloat = 0.0): def set(self, frequency: float, phase: float = 0.0):
"""Set profile 0 data in SI units. """Set profile 0 data in SI units.
.. seealso:: :meth:`set_mu` .. seealso:: :meth:`set_mu`
@ -248,7 +258,7 @@ class AD9912:
self.turns_to_pow(phase)) self.turns_to_pow(phase))
@kernel @kernel
def get(self) -> TTuple([TFloat, TFloat]): def get(self) -> tuple[float, float]:
"""Get the frequency and phase. """Get the frequency and phase.
.. seealso:: :meth:`get_mu` .. seealso:: :meth:`get_mu`
@ -262,7 +272,7 @@ class AD9912:
return self.ftw_to_frequency(ftw), self.pow_to_turns(pow_) return self.ftw_to_frequency(ftw), self.pow_to_turns(pow_)
@kernel @kernel
def cfg_sw(self, state: TBool): def cfg_sw(self, state: bool):
"""Set CPLD CFG RF switch state. The RF switch is controlled by the """Set CPLD CFG RF switch state. The RF switch is controlled by the
logical or of the CPLD configuration shift register logical or of the CPLD configuration shift register
RF switch bit and the SW TTL line (if used). RF switch bit and the SW TTL line (if used).

View File

@ -1,78 +1,78 @@
# auto-generated, do not edit # auto-generated, do not edit
from numpy import int32
from artiq.language.core import portable from artiq.language.core import portable
from artiq.language.types import TInt32
AD9912_SER_CONF = 0x000 AD9912_SER_CONF = 0x000
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_SDOACTIVE_SET(x: TInt32) -> TInt32: def AD9912_SDOACTIVE_SET(x: int32) -> int32:
return (x & 0x1) << 0 return (x & 0x1) << 0
@portable @portable
def AD9912_SDOACTIVE_GET(x: TInt32) -> TInt32: def AD9912_SDOACTIVE_GET(x: int32) -> int32:
return (x >> 0) & 0x1 return (x >> 0) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_LSBFIRST_SET(x: TInt32) -> TInt32: def AD9912_LSBFIRST_SET(x: int32) -> int32:
return (x & 0x1) << 1 return (x & 0x1) << 1
@portable @portable
def AD9912_LSBFIRST_GET(x: TInt32) -> TInt32: def AD9912_LSBFIRST_GET(x: int32) -> int32:
return (x >> 1) & 0x1 return (x >> 1) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_SOFTRESET_SET(x: TInt32) -> TInt32: def AD9912_SOFTRESET_SET(x: int32) -> int32:
return (x & 0x1) << 2 return (x & 0x1) << 2
@portable @portable
def AD9912_SOFTRESET_GET(x: TInt32) -> TInt32: def AD9912_SOFTRESET_GET(x: int32) -> int32:
return (x >> 2) & 0x1 return (x >> 2) & 0x1
# default: 0x01, access: R/W # default: 0x01, access: R/W
@portable @portable
def AD9912_LONGINSN_SET(x: TInt32) -> TInt32: def AD9912_LONGINSN_SET(x: int32) -> int32:
return (x & 0x1) << 3 return (x & 0x1) << 3
@portable @portable
def AD9912_LONGINSN_GET(x: TInt32) -> TInt32: def AD9912_LONGINSN_GET(x: int32) -> int32:
return (x >> 3) & 0x1 return (x >> 3) & 0x1
# default: 0x01, access: R/W # default: 0x01, access: R/W
@portable @portable
def AD9912_LONGINSN_M_SET(x: TInt32) -> TInt32: def AD9912_LONGINSN_M_SET(x: int32) -> int32:
return (x & 0x1) << 4 return (x & 0x1) << 4
@portable @portable
def AD9912_LONGINSN_M_GET(x: TInt32) -> TInt32: def AD9912_LONGINSN_M_GET(x: int32) -> int32:
return (x >> 4) & 0x1 return (x >> 4) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_SOFTRESET_M_SET(x: TInt32) -> TInt32: def AD9912_SOFTRESET_M_SET(x: int32) -> int32:
return (x & 0x1) << 5 return (x & 0x1) << 5
@portable @portable
def AD9912_SOFTRESET_M_GET(x: TInt32) -> TInt32: def AD9912_SOFTRESET_M_GET(x: int32) -> int32:
return (x >> 5) & 0x1 return (x >> 5) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_LSBFIRST_M_SET(x: TInt32) -> TInt32: def AD9912_LSBFIRST_M_SET(x: int32) -> int32:
return (x & 0x1) << 6 return (x & 0x1) << 6
@portable @portable
def AD9912_LSBFIRST_M_GET(x: TInt32) -> TInt32: def AD9912_LSBFIRST_M_GET(x: int32) -> int32:
return (x >> 6) & 0x1 return (x >> 6) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_SDOACTIVE_M_SET(x: TInt32) -> TInt32: def AD9912_SDOACTIVE_M_SET(x: int32) -> int32:
return (x & 0x1) << 7 return (x & 0x1) << 7
@portable @portable
def AD9912_SDOACTIVE_M_GET(x: TInt32) -> TInt32: def AD9912_SDOACTIVE_M_GET(x: int32) -> int32:
return (x >> 7) & 0x1 return (x >> 7) & 0x1
@ -83,118 +83,118 @@ AD9912_PRODIDH = 0x003
AD9912_SER_OPT1 = 0x004 AD9912_SER_OPT1 = 0x004
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_READ_BUF_SET(x: TInt32) -> TInt32: def AD9912_READ_BUF_SET(x: int32) -> int32:
return (x & 0x1) << 0 return (x & 0x1) << 0
@portable @portable
def AD9912_READ_BUF_GET(x: TInt32) -> TInt32: def AD9912_READ_BUF_GET(x: int32) -> int32:
return (x >> 0) & 0x1 return (x >> 0) & 0x1
AD9912_SER_OPT2 = 0x005 AD9912_SER_OPT2 = 0x005
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_RED_UPDATE_SET(x: TInt32) -> TInt32: def AD9912_RED_UPDATE_SET(x: int32) -> int32:
return (x & 0x1) << 0 return (x & 0x1) << 0
@portable @portable
def AD9912_RED_UPDATE_GET(x: TInt32) -> TInt32: def AD9912_RED_UPDATE_GET(x: int32) -> int32:
return (x >> 0) & 0x1 return (x >> 0) & 0x1
AD9912_PWRCNTRL1 = 0x010 AD9912_PWRCNTRL1 = 0x010
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_PD_DIGITAL_SET(x: TInt32) -> TInt32: def AD9912_PD_DIGITAL_SET(x: int32) -> int32:
return (x & 0x1) << 0 return (x & 0x1) << 0
@portable @portable
def AD9912_PD_DIGITAL_GET(x: TInt32) -> TInt32: def AD9912_PD_DIGITAL_GET(x: int32) -> int32:
return (x >> 0) & 0x1 return (x >> 0) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_PD_FULL_SET(x: TInt32) -> TInt32: def AD9912_PD_FULL_SET(x: int32) -> int32:
return (x & 0x1) << 1 return (x & 0x1) << 1
@portable @portable
def AD9912_PD_FULL_GET(x: TInt32) -> TInt32: def AD9912_PD_FULL_GET(x: int32) -> int32:
return (x >> 1) & 0x1 return (x >> 1) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_PD_SYSCLK_SET(x: TInt32) -> TInt32: def AD9912_PD_SYSCLK_SET(x: int32) -> int32:
return (x & 0x1) << 4 return (x & 0x1) << 4
@portable @portable
def AD9912_PD_SYSCLK_GET(x: TInt32) -> TInt32: def AD9912_PD_SYSCLK_GET(x: int32) -> int32:
return (x >> 4) & 0x1 return (x >> 4) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_EN_DOUBLER_SET(x: TInt32) -> TInt32: def AD9912_EN_DOUBLER_SET(x: int32) -> int32:
return (x & 0x1) << 5 return (x & 0x1) << 5
@portable @portable
def AD9912_EN_DOUBLER_GET(x: TInt32) -> TInt32: def AD9912_EN_DOUBLER_GET(x: int32) -> int32:
return (x >> 5) & 0x1 return (x >> 5) & 0x1
# default: 0x01, access: R/W # default: 0x01, access: R/W
@portable @portable
def AD9912_EN_CMOS_SET(x: TInt32) -> TInt32: def AD9912_EN_CMOS_SET(x: int32) -> int32:
return (x & 0x1) << 6 return (x & 0x1) << 6
@portable @portable
def AD9912_EN_CMOS_GET(x: TInt32) -> TInt32: def AD9912_EN_CMOS_GET(x: int32) -> int32:
return (x >> 6) & 0x1 return (x >> 6) & 0x1
# default: 0x01, access: R/W # default: 0x01, access: R/W
@portable @portable
def AD9912_PD_HSTL_SET(x: TInt32) -> TInt32: def AD9912_PD_HSTL_SET(x: int32) -> int32:
return (x & 0x1) << 7 return (x & 0x1) << 7
@portable @portable
def AD9912_PD_HSTL_GET(x: TInt32) -> TInt32: def AD9912_PD_HSTL_GET(x: int32) -> int32:
return (x >> 7) & 0x1 return (x >> 7) & 0x1
AD9912_PWRCNTRL2 = 0x012 AD9912_PWRCNTRL2 = 0x012
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_DDS_RESET_SET(x: TInt32) -> TInt32: def AD9912_DDS_RESET_SET(x: int32) -> int32:
return (x & 0x1) << 0 return (x & 0x1) << 0
@portable @portable
def AD9912_DDS_RESET_GET(x: TInt32) -> TInt32: def AD9912_DDS_RESET_GET(x: int32) -> int32:
return (x >> 0) & 0x1 return (x >> 0) & 0x1
AD9912_PWRCNTRL3 = 0x013 AD9912_PWRCNTRL3 = 0x013
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_S_DIV_RESET_SET(x: TInt32) -> TInt32: def AD9912_S_DIV_RESET_SET(x: int32) -> int32:
return (x & 0x1) << 1 return (x & 0x1) << 1
@portable @portable
def AD9912_S_DIV_RESET_GET(x: TInt32) -> TInt32: def AD9912_S_DIV_RESET_GET(x: int32) -> int32:
return (x >> 1) & 0x1 return (x >> 1) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_S_DIV2_RESET_SET(x: TInt32) -> TInt32: def AD9912_S_DIV2_RESET_SET(x: int32) -> int32:
return (x & 0x1) << 3 return (x & 0x1) << 3
@portable @portable
def AD9912_S_DIV2_RESET_GET(x: TInt32) -> TInt32: def AD9912_S_DIV2_RESET_GET(x: int32) -> int32:
return (x >> 3) & 0x1 return (x >> 3) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_PD_FUND_SET(x: TInt32) -> TInt32: def AD9912_PD_FUND_SET(x: int32) -> int32:
return (x & 0x1) << 7 return (x & 0x1) << 7
@portable @portable
def AD9912_PD_FUND_GET(x: TInt32) -> TInt32: def AD9912_PD_FUND_GET(x: int32) -> int32:
return (x >> 7) & 0x1 return (x >> 7) & 0x1
@ -203,38 +203,38 @@ AD9912_N_DIV = 0x020
AD9912_PLLCFG = 0x022 AD9912_PLLCFG = 0x022
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_PLL_ICP_SET(x: TInt32) -> TInt32: def AD9912_PLL_ICP_SET(x: int32) -> int32:
return (x & 0x3) << 0 return (x & 0x3) << 0
@portable @portable
def AD9912_PLL_ICP_GET(x: TInt32) -> TInt32: def AD9912_PLL_ICP_GET(x: int32) -> int32:
return (x >> 0) & 0x3 return (x >> 0) & 0x3
# default: 0x01, access: R/W # default: 0x01, access: R/W
@portable @portable
def AD9912_VCO_RANGE_SET(x: TInt32) -> TInt32: def AD9912_VCO_RANGE_SET(x: int32) -> int32:
return (x & 0x1) << 2 return (x & 0x1) << 2
@portable @portable
def AD9912_VCO_RANGE_GET(x: TInt32) -> TInt32: def AD9912_VCO_RANGE_GET(x: int32) -> int32:
return (x >> 2) & 0x1 return (x >> 2) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_PLL_REF2X_SET(x: TInt32) -> TInt32: def AD9912_PLL_REF2X_SET(x: int32) -> int32:
return (x & 0x1) << 3 return (x & 0x1) << 3
@portable @portable
def AD9912_PLL_REF2X_GET(x: TInt32) -> TInt32: def AD9912_PLL_REF2X_GET(x: int32) -> int32:
return (x >> 3) & 0x1 return (x >> 3) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_VCO_AUTO_RANGE_SET(x: TInt32) -> TInt32: def AD9912_VCO_AUTO_RANGE_SET(x: int32) -> int32:
return (x & 0x1) << 7 return (x & 0x1) << 7
@portable @portable
def AD9912_VCO_AUTO_RANGE_GET(x: TInt32) -> TInt32: def AD9912_VCO_AUTO_RANGE_GET(x: int32) -> int32:
return (x >> 7) & 0x1 return (x >> 7) & 0x1
@ -245,20 +245,20 @@ AD9912_S_DIVH = 0x105
AD9912_S_DIV_CFG = 0x106 AD9912_S_DIV_CFG = 0x106
# default: 0x01, access: R/W # default: 0x01, access: R/W
@portable @portable
def AD9912_S_DIV2_SET(x: TInt32) -> TInt32: def AD9912_S_DIV2_SET(x: int32) -> int32:
return (x & 0x1) << 0 return (x & 0x1) << 0
@portable @portable
def AD9912_S_DIV2_GET(x: TInt32) -> TInt32: def AD9912_S_DIV2_GET(x: int32) -> int32:
return (x >> 0) & 0x1 return (x >> 0) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_S_DIV_FALL_SET(x: TInt32) -> TInt32: def AD9912_S_DIV_FALL_SET(x: int32) -> int32:
return (x & 0x1) << 7 return (x & 0x1) << 7
@portable @portable
def AD9912_S_DIV_FALL_GET(x: TInt32) -> TInt32: def AD9912_S_DIV_FALL_GET(x: int32) -> int32:
return (x >> 7) & 0x1 return (x >> 7) & 0x1
@ -281,31 +281,31 @@ AD9912_POW1 = 0x1ad
AD9912_HSTL = 0x200 AD9912_HSTL = 0x200
# default: 0x01, access: R/W # default: 0x01, access: R/W
@portable @portable
def AD9912_HSTL_CFG_SET(x: TInt32) -> TInt32: def AD9912_HSTL_CFG_SET(x: int32) -> int32:
return (x & 0x3) << 0 return (x & 0x3) << 0
@portable @portable
def AD9912_HSTL_CFG_GET(x: TInt32) -> TInt32: def AD9912_HSTL_CFG_GET(x: int32) -> int32:
return (x >> 0) & 0x3 return (x >> 0) & 0x3
# default: 0x01, access: R/W # default: 0x01, access: R/W
@portable @portable
def AD9912_HSTL_OPOL_SET(x: TInt32) -> TInt32: def AD9912_HSTL_OPOL_SET(x: int32) -> int32:
return (x & 0x1) << 4 return (x & 0x1) << 4
@portable @portable
def AD9912_HSTL_OPOL_GET(x: TInt32) -> TInt32: def AD9912_HSTL_OPOL_GET(x: int32) -> int32:
return (x >> 4) & 0x1 return (x >> 4) & 0x1
AD9912_CMOS = 0x201 AD9912_CMOS = 0x201
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_CMOS_MUX_SET(x: TInt32) -> TInt32: def AD9912_CMOS_MUX_SET(x: int32) -> int32:
return (x & 0x1) << 0 return (x & 0x1) << 0
@portable @portable
def AD9912_CMOS_MUX_GET(x: TInt32) -> TInt32: def AD9912_CMOS_MUX_GET(x: int32) -> int32:
return (x >> 0) & 0x1 return (x >> 0) & 0x1
@ -316,29 +316,29 @@ AD9912_FSC1 = 0x40c
AD9912_HSR_A_CFG = 0x500 AD9912_HSR_A_CFG = 0x500
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_HSR_A_HARMONIC_SET(x: TInt32) -> TInt32: def AD9912_HSR_A_HARMONIC_SET(x: int32) -> int32:
return (x & 0xf) << 0 return (x & 0xf) << 0
@portable @portable
def AD9912_HSR_A_HARMONIC_GET(x: TInt32) -> TInt32: def AD9912_HSR_A_HARMONIC_GET(x: int32) -> int32:
return (x >> 0) & 0xf return (x >> 0) & 0xf
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_HSR_A_MAG2X_SET(x: TInt32) -> TInt32: def AD9912_HSR_A_MAG2X_SET(x: int32) -> int32:
return (x & 0x1) << 6 return (x & 0x1) << 6
@portable @portable
def AD9912_HSR_A_MAG2X_GET(x: TInt32) -> TInt32: def AD9912_HSR_A_MAG2X_GET(x: int32) -> int32:
return (x >> 6) & 0x1 return (x >> 6) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_HSR_A_EN_SET(x: TInt32) -> TInt32: def AD9912_HSR_A_EN_SET(x: int32) -> int32:
return (x & 0x1) << 7 return (x & 0x1) << 7
@portable @portable
def AD9912_HSR_A_EN_GET(x: TInt32) -> TInt32: def AD9912_HSR_A_EN_GET(x: int32) -> int32:
return (x >> 7) & 0x1 return (x >> 7) & 0x1
@ -351,29 +351,29 @@ AD9912_HSR_A_POW1 = 0x504
AD9912_HSR_B_CFG = 0x505 AD9912_HSR_B_CFG = 0x505
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_HSR_B_HARMONIC_SET(x: TInt32) -> TInt32: def AD9912_HSR_B_HARMONIC_SET(x: int32) -> int32:
return (x & 0xf) << 0 return (x & 0xf) << 0
@portable @portable
def AD9912_HSR_B_HARMONIC_GET(x: TInt32) -> TInt32: def AD9912_HSR_B_HARMONIC_GET(x: int32) -> int32:
return (x >> 0) & 0xf return (x >> 0) & 0xf
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_HSR_B_MAG2X_SET(x: TInt32) -> TInt32: def AD9912_HSR_B_MAG2X_SET(x: int32) -> int32:
return (x & 0x1) << 6 return (x & 0x1) << 6
@portable @portable
def AD9912_HSR_B_MAG2X_GET(x: TInt32) -> TInt32: def AD9912_HSR_B_MAG2X_GET(x: int32) -> int32:
return (x >> 6) & 0x1 return (x >> 6) & 0x1
# default: 0x00, access: R/W # default: 0x00, access: R/W
@portable @portable
def AD9912_HSR_B_EN_SET(x: TInt32) -> TInt32: def AD9912_HSR_B_EN_SET(x: int32) -> int32:
return (x & 0x1) << 7 return (x & 0x1) << 7
@portable @portable
def AD9912_HSR_B_EN_GET(x: TInt32) -> TInt32: def AD9912_HSR_B_EN_GET(x: int32) -> int32:
return (x >> 7) & 0x1 return (x >> 7) & 0x1

View File

@ -2,13 +2,12 @@
Driver for the AD9914 DDS (with parallel bus) on RTIO. Driver for the AD9914 DDS (with parallel bus) on RTIO.
""" """
from numpy import int32, int64
from artiq.language.core import * from artiq.language.core import *
from artiq.language.types import *
from artiq.language.units import * from artiq.language.units import *
from artiq.coredevice.rtio import rtio_output from artiq.coredevice.rtio import rtio_output
from artiq.coredevice.core import Core
from numpy import int32, int64
__all__ = [ __all__ = [
@ -43,6 +42,7 @@ AD9914_FUD = 0x80
AD9914_GPIO = 0x81 AD9914_GPIO = 0x81
@nac3
class AD9914: class AD9914:
"""Driver for one AD9914 DDS channel. """Driver for one AD9914 DDS channel.
@ -57,10 +57,21 @@ class AD9914:
:param channel: channel number (on the bus) of the DDS device to control. :param channel: channel number (on the bus) of the DDS device to control.
""" """
kernel_invariants = {"core", "sysclk", "bus_channel", "channel", core: KernelInvariant[Core]
"rtio_period_mu", "sysclk_per_mu", "write_duration_mu", sysclk: KernelInvariant[float]
"dac_cal_duration_mu", "init_duration_mu", "init_sync_duration_mu", bus_channel: KernelInvariant[int32]
"set_duration_mu", "set_x_duration_mu", "exit_x_duration_mu"} channel: KernelInvariant[int32]
phase_mode: Kernel[int32]
rtio_period_mu: KernelInvariant[int64]
sysclk_per_mu: KernelInvariant[int64]
write_duration_mu: KernelInvariant[int64]
dac_cal_duration_mu: KernelInvariant[int64]
init_duration_mu: KernelInvariant[int64]
init_sync_duration_mu: KernelInvariant[int64]
set_duration_mu: KernelInvariant[int64]
set_x_duration_mu: KernelInvariant[int64]
exit_x_duration_mu: KernelInvariant[int64]
def __init__(self, dmgr, sysclk, bus_channel, channel, core_device="core"): def __init__(self, dmgr, sysclk, bus_channel, channel, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -70,7 +81,7 @@ class AD9914:
self.phase_mode = PHASE_MODE_CONTINUOUS self.phase_mode = PHASE_MODE_CONTINUOUS
self.rtio_period_mu = int64(8) self.rtio_period_mu = int64(8)
self.sysclk_per_mu = int32(self.sysclk * self.core.ref_period) self.sysclk_per_mu = int64(self.sysclk * self.core.ref_period)
self.write_duration_mu = 5 * self.rtio_period_mu self.write_duration_mu = 5 * self.rtio_period_mu
self.dac_cal_duration_mu = 147000 * self.rtio_period_mu self.dac_cal_duration_mu = 147000 * self.rtio_period_mu
@ -88,7 +99,7 @@ class AD9914:
return [] return []
@kernel @kernel
def write(self, addr, data): def write(self, addr: int32, data: int32):
rtio_output((self.bus_channel << 8) | addr, data) rtio_output((self.bus_channel << 8) | addr, data)
delay_mu(self.write_duration_mu) delay_mu(self.write_duration_mu)
@ -120,7 +131,7 @@ class AD9914:
self.write(AD9914_FUD, 0) self.write(AD9914_FUD, 0)
@kernel @kernel
def init_sync(self, sync_delay): def init_sync(self, sync_delay: int32):
"""Resets and initializes the DDS channel as well as configures """Resets and initializes the DDS channel as well as configures
the AD9914 DDS for synchronisation. The synchronisation procedure the AD9914 DDS for synchronisation. The synchronisation procedure
follows the steps outlined in the AN-1254 application note. follows the steps outlined in the AN-1254 application note.
@ -164,7 +175,7 @@ class AD9914:
self.write(AD9914_FUD, 0) self.write(AD9914_FUD, 0)
@kernel @kernel
def set_phase_mode(self, phase_mode): def set_phase_mode(self, phase_mode: int32):
"""Sets the phase mode of the DDS channel. Supported phase modes are: """Sets the phase mode of the DDS channel. Supported phase modes are:
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged when * :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged when
@ -188,8 +199,8 @@ class AD9914:
self.phase_mode = phase_mode self.phase_mode = phase_mode
@kernel @kernel
def set_mu(self, ftw, pow=0, phase_mode=_PHASE_MODE_DEFAULT, def set_mu(self, ftw: int32, pow: int32 = 0, phase_mode: int32 = _PHASE_MODE_DEFAULT,
asf=0x0fff, ref_time_mu=-1): asf: int32 = 0x0fff, ref_time_mu: int64 = int64(-1)) -> int32:
"""Sets the DDS channel to the specified frequency and phase. """Sets the DDS channel to the specified frequency and phase.
This uses machine units (FTW and POW). The frequency tuning word width This uses machine units (FTW and POW). The frequency tuning word width
@ -212,7 +223,7 @@ class AD9914:
""" """
if phase_mode == _PHASE_MODE_DEFAULT: if phase_mode == _PHASE_MODE_DEFAULT:
phase_mode = self.phase_mode phase_mode = self.phase_mode
if ref_time_mu < 0: if ref_time_mu < int64(0):
ref_time_mu = now_mu() ref_time_mu = now_mu()
delay_mu(-self.set_duration_mu) delay_mu(-self.set_duration_mu)
@ -231,60 +242,60 @@ class AD9914:
# Clear phase accumulator on FUD # Clear phase accumulator on FUD
# Enable autoclear phase accumulator and enables OSK. # Enable autoclear phase accumulator and enables OSK.
self.write(AD9914_REG_CFR1L, 0x2108) self.write(AD9914_REG_CFR1L, 0x2108)
fud_time = now_mu() + 2 * self.write_duration_mu fud_time = now_mu() + int64(2) * self.write_duration_mu
pow -= int32((ref_time_mu - fud_time) * self.sysclk_per_mu * ftw >> (32 - 16)) pow -= int32((ref_time_mu - fud_time) * self.sysclk_per_mu * int64(ftw) >> int64(32 - 16))
if phase_mode == PHASE_MODE_TRACKING: if phase_mode == PHASE_MODE_TRACKING:
pow += int32(ref_time_mu * self.sysclk_per_mu * ftw >> (32 - 16)) pow += int32(ref_time_mu * self.sysclk_per_mu * int64(ftw) >> int64(32 - 16))
self.write(AD9914_REG_POW, pow) self.write(AD9914_REG_POW, pow)
self.write(AD9914_REG_ASF, asf) self.write(AD9914_REG_ASF, asf)
self.write(AD9914_FUD, 0) self.write(AD9914_FUD, 0)
return pow return pow
@portable(flags={"fast-math"}) @portable
def frequency_to_ftw(self, frequency): def frequency_to_ftw(self, frequency: float) -> int32:
"""Returns the 32-bit frequency tuning word corresponding to the given """Returns the 32-bit frequency tuning word corresponding to the given
frequency. frequency.
""" """
return int32(round(float(int64(2)**32*frequency/self.sysclk))) return round(float(int64(2)**int64(32))*frequency/self.sysclk)
@portable(flags={"fast-math"}) @portable
def ftw_to_frequency(self, ftw): def ftw_to_frequency(self, ftw: int32) -> float:
"""Returns the frequency corresponding to the given frequency tuning """Returns the frequency corresponding to the given frequency tuning
word. word.
""" """
return ftw*self.sysclk/int64(2)**32 return float(ftw)*self.sysclk/float(int64(2)**int64(32))
@portable(flags={"fast-math"}) @portable
def turns_to_pow(self, turns): def turns_to_pow(self, turns: float) -> int32:
"""Returns the 16-bit phase offset word corresponding to the given """Returns the 16-bit phase offset word corresponding to the given
phase in turns.""" phase in turns."""
return round(float(turns*2**16)) & 0xffff return round(float(turns*float(2**16))) & 0xffff
@portable(flags={"fast-math"}) @portable
def pow_to_turns(self, pow): def pow_to_turns(self, pow: int32) -> float:
"""Returns the phase in turns corresponding to the given phase offset """Returns the phase in turns corresponding to the given phase offset
word.""" word."""
return pow/2**16 return float(pow)/float(2**16)
@portable(flags={"fast-math"}) @portable
def amplitude_to_asf(self, amplitude): def amplitude_to_asf(self, amplitude: float) -> int32:
"""Returns 12-bit amplitude scale factor corresponding to given """Returns 12-bit amplitude scale factor corresponding to given
amplitude.""" amplitude."""
code = round(float(amplitude * 0x0fff)) code = round(float(amplitude * float(0x0fff)))
if code < 0 or code > 0xfff: if code < 0 or code > 0xfff:
raise ValueError("Invalid AD9914 amplitude!") raise ValueError("Invalid AD9914 amplitude!")
return code return code
@portable(flags={"fast-math"}) @portable
def asf_to_amplitude(self, asf): def asf_to_amplitude(self, asf: int32) -> float:
"""Returns the amplitude corresponding to the given amplitude scale """Returns the amplitude corresponding to the given amplitude scale
factor.""" factor."""
return asf/0x0fff return asf/0x0fff
@kernel @kernel
def set(self, frequency, phase=0.0, phase_mode=_PHASE_MODE_DEFAULT, def set(self, frequency: float, phase: float = 0.0, phase_mode: int32 = _PHASE_MODE_DEFAULT,
amplitude=1.0): amplitude: float = 1.0) -> float:
"""Like :meth:`set_mu`, but uses Hz and turns.""" """Like :meth:`set_mu`, but uses Hz and turns."""
return self.pow_to_turns( return self.pow_to_turns(
self.set_mu(self.frequency_to_ftw(frequency), self.set_mu(self.frequency_to_ftw(frequency),
@ -293,7 +304,7 @@ class AD9914:
# Extended-resolution functions # Extended-resolution functions
@kernel @kernel
def set_x_mu(self, xftw, amplitude=0x0fff): def set_x_mu(self, xftw: int64, amplitude: int32 = 0x0fff):
"""Set the DDS frequency and amplitude with an extended-resolution """Set the DDS frequency and amplitude with an extended-resolution
(63-bit) frequency tuning word. (63-bit) frequency tuning word.
@ -307,10 +318,10 @@ class AD9914:
self.write(AD9914_GPIO, (1 << self.channel) << 1) self.write(AD9914_GPIO, (1 << self.channel) << 1)
self.write(AD9914_REG_DRGAL, xftw & 0xffff) self.write(AD9914_REG_DRGAL, int32(xftw) & 0xffff)
self.write(AD9914_REG_DRGAH, (xftw >> 16) & 0x7fff) self.write(AD9914_REG_DRGAH, int32(xftw >> int64(16)) & 0x7fff)
self.write(AD9914_REG_DRGFL, (xftw >> 31) & 0xffff) self.write(AD9914_REG_DRGFL, int32(xftw >> int64(31)) & 0xffff)
self.write(AD9914_REG_DRGFH, (xftw >> 47) & 0xffff) self.write(AD9914_REG_DRGFH, int32(xftw >> int64(47)) & 0xffff)
self.write(AD9914_REG_ASF, amplitude) self.write(AD9914_REG_ASF, amplitude)
self.write(AD9914_FUD, 0) self.write(AD9914_FUD, 0)
@ -323,23 +334,23 @@ class AD9914:
self.write(AD9914_REG_DRGAL, 0) self.write(AD9914_REG_DRGAL, 0)
self.write(AD9914_REG_DRGAH, 0) self.write(AD9914_REG_DRGAH, 0)
@portable(flags={"fast-math"}) @portable
def frequency_to_xftw(self, frequency): def frequency_to_xftw(self, frequency: float) -> int64:
"""Returns the 63-bit frequency tuning word corresponding to the given """Returns the 63-bit frequency tuning word corresponding to the given
frequency (extended resolution mode). frequency (extended resolution mode).
""" """
return int64(round(2.0*float(int64(2)**62)*frequency/self.sysclk)) & ( return round64(2.0*float(int64(2)**int64(62))*frequency/self.sysclk) & (
(int64(1) << 63) - 1) (int64(1) << int64(63)) - int64(1))
@portable(flags={"fast-math"}) @portable
def xftw_to_frequency(self, xftw): def xftw_to_frequency(self, xftw: int64) -> float:
"""Returns the frequency corresponding to the given frequency tuning """Returns the frequency corresponding to the given frequency tuning
word (extended resolution mode). word (extended resolution mode).
""" """
return xftw*self.sysclk/(2.0*float(int64(2)**62)) return float(xftw)*self.sysclk/(2.0*float(int64(2)**int64(62)))
@kernel @kernel
def set_x(self, frequency, amplitude=1.0): def set_x(self, frequency: float, amplitude: float = 1.0):
"""Like :meth:`set_x_mu`, but uses Hz and turns. """Like :meth:`set_x_mu`, but uses Hz and turns.
Note that the precision of ``float`` is less than the precision Note that the precision of ``float`` is less than the precision

View File

@ -8,25 +8,27 @@ on Mirny-style prefixed SPI buses.
# https://www.analog.com/media/en/technical-documentation/data-sheets/ADF5355.pdf # https://www.analog.com/media/en/technical-documentation/data-sheets/ADF5355.pdf
# https://www.analog.com/media/en/technical-documentation/user-guides/EV-ADF5355SD1Z-UG-1087.pdf # https://www.analog.com/media/en/technical-documentation/user-guides/EV-ADF5355SD1Z-UG-1087.pdf
from numpy import int32, int64
from math import floor, ceil
from artiq.language.core import kernel, portable, delay from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable, round64
from artiq.language.units import us, GHz, MHz from artiq.language.units import us, GHz, MHz
from artiq.language.types import TInt32, TInt64 from artiq.coredevice.core import Core
from artiq.coredevice import spi2 as spi from artiq.coredevice.mirny import Mirny
from artiq.coredevice.ttl import TTLOut
from artiq.coredevice.spi2 import *
from artiq.coredevice.adf5356_reg import * from artiq.coredevice.adf5356_reg import *
from numpy import int32, int64, floor, ceil
SPI_CONFIG = ( SPI_CONFIG = (
0 * spi.SPI_OFFLINE 0 * SPI_OFFLINE
| 0 * spi.SPI_END | 0 * SPI_END
| 0 * spi.SPI_INPUT | 0 * SPI_INPUT
| 1 * spi.SPI_CS_POLARITY | 1 * SPI_CS_POLARITY
| 0 * spi.SPI_CLK_POLARITY | 0 * SPI_CLK_POLARITY
| 0 * spi.SPI_CLK_PHASE | 0 * SPI_CLK_PHASE
| 0 * spi.SPI_LSB_FIRST | 0 * SPI_LSB_FIRST
| 0 * spi.SPI_HALF_DUPLEX | 0 * SPI_HALF_DUPLEX
) )
@ -38,6 +40,7 @@ ADF5356_MAX_MODULUS2 = int32(1 << 28) # FIXME: ADF5356 has 28 bits MOD2
ADF5356_MAX_R_CNT = int32(1023) ADF5356_MAX_R_CNT = int32(1023)
@nac3
class ADF5356: class ADF5356:
"""Analog Devices AD[45]35[56] family of GHz PLLs. """Analog Devices AD[45]35[56] family of GHz PLLs.
@ -49,7 +52,14 @@ class ADF5356:
:param core_device: Core device name (default: "core") :param core_device: Core device name (default: "core")
""" """
kernel_invariants = {"cpld", "sw", "channel", "core", "sysclk"} core: KernelInvariant[Core]
cpld: KernelInvariant[Mirny]
channel: KernelInvariant[int32]
sw: KernelInvariant[TTLOut]
sysclk: KernelInvariant[float]
regs: Kernel[list[int32]]
ref_doubler: Kernel[bool]
ref_divider: Kernel[bool]
def __init__( def __init__(
self, self,
@ -78,7 +88,7 @@ class ADF5356:
return [] return []
@kernel @kernel
def init(self, blind=False): def init(self, blind: bool = False):
""" """
Initialize and configure the PLL. Initialize and configure the PLL.
@ -88,18 +98,18 @@ class ADF5356:
# MUXOUT = VDD # MUXOUT = VDD
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 1) self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 1)
self.sync() self.sync()
delay(1000 * us) self.core.delay(1000. * us)
if not self.read_muxout(): if not self.read_muxout():
raise ValueError("MUXOUT not high") raise ValueError("MUXOUT not high")
delay(800 * us) self.core.delay(800. * us)
# MUXOUT = DGND # MUXOUT = DGND
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 2) self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 2)
self.sync() self.sync()
delay(1000 * us) self.core.delay(1000. * us)
if self.read_muxout(): if self.read_muxout():
raise ValueError("MUXOUT not low") raise ValueError("MUXOUT not low")
delay(800 * us) self.core.delay(800. * us)
# MUXOUT = digital lock-detect # MUXOUT = digital lock-detect
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 6) self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 6)
@ -107,7 +117,7 @@ class ADF5356:
self.sync() self.sync()
@kernel @kernel
def set_att(self, att): def set_att(self, att: float):
"""Set digital step attenuator in SI units. """Set digital step attenuator in SI units.
This method will write the attenuator settings of the channel. This method will write the attenuator settings of the channel.
@ -119,7 +129,7 @@ class ADF5356:
self.cpld.set_att(self.channel, att) self.cpld.set_att(self.channel, att)
@kernel @kernel
def set_att_mu(self, att): def set_att_mu(self, att: int32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
:param att: Attenuation setting, 8 bit digital. :param att: Attenuation setting, 8 bit digital.
@ -127,11 +137,11 @@ class ADF5356:
self.cpld.set_att_mu(self.channel, att) self.cpld.set_att_mu(self.channel, att)
@kernel @kernel
def write(self, data): def write(self, data: int32):
self.cpld.write_ext(self.channel | 4, 32, data) self.cpld.write_ext(self.channel | 4, 32, data)
@kernel @kernel
def read_muxout(self): def read_muxout(self) -> bool:
""" """
Read the state of the MUXOUT line. Read the state of the MUXOUT line.
@ -140,7 +150,7 @@ class ADF5356:
return bool(self.cpld.read_reg(0) & (1 << (self.channel + 8))) return bool(self.cpld.read_reg(0) & (1 << (self.channel + 8)))
@kernel @kernel
def set_output_power_mu(self, n): def set_output_power_mu(self, n: int32):
""" """
Set the power level at output A of the PLL chip in machine units. Set the power level at output A of the PLL chip in machine units.
@ -148,13 +158,13 @@ class ADF5356:
:param n: output power setting, 0, 1, 2, or 3 (see ADF5356 datasheet, fig. 44). :param n: output power setting, 0, 1, 2, or 3 (see ADF5356 datasheet, fig. 44).
""" """
if n not in [0, 1, 2, 3]: if not 0 <= n <= 3:
raise ValueError("invalid power setting") raise ValueError("invalid power setting")
self.regs[6] = ADF5356_REG6_RF_OUTPUT_A_POWER_UPDATE(self.regs[6], n) self.regs[6] = ADF5356_REG6_RF_OUTPUT_A_POWER_UPDATE(self.regs[6], n)
self.sync() self.sync()
@portable @portable
def output_power_mu(self): def output_power_mu(self) -> int32:
""" """
Return the power level at output A of the PLL chip in machine units. Return the power level at output A of the PLL chip in machine units.
""" """
@ -177,13 +187,13 @@ class ADF5356:
self.sync() self.sync()
@kernel @kernel
def set_frequency(self, f): def set_frequency(self, f: float):
""" """
Output given frequency on output A. Output given frequency on output A.
:param f: 53.125 MHz <= f <= 6800 MHz :param f: 53.125 MHz <= f <= 6800 MHz
""" """
freq = int64(round(f)) freq = round64(f)
if freq > ADF5356_MAX_VCO_FREQ: if freq > ADF5356_MAX_VCO_FREQ:
raise ValueError("Requested too high frequency") raise ValueError("Requested too high frequency")
@ -209,7 +219,7 @@ class ADF5356:
n_min, n_max = 75, 65535 n_min, n_max = 75, 65535
# adjust reference divider to be able to match n_min constraint # adjust reference divider to be able to match n_min constraint
while n_min * f_pfd > freq: while int64(n_min) * f_pfd > freq:
r = ADF5356_REG4_R_COUNTER_GET(self.regs[4]) r = ADF5356_REG4_R_COUNTER_GET(self.regs[4])
self.regs[4] = ADF5356_REG4_R_COUNTER_UPDATE(self.regs[4], r + 1) self.regs[4] = ADF5356_REG4_R_COUNTER_UPDATE(self.regs[4], r + 1)
f_pfd = self.f_pfd() f_pfd = self.f_pfd()
@ -237,10 +247,10 @@ class ADF5356:
self.regs[6] = ADF5356_REG6_RF_DIVIDER_SELECT_UPDATE(self.regs[6], rf_div_sel) self.regs[6] = ADF5356_REG6_RF_DIVIDER_SELECT_UPDATE(self.regs[6], rf_div_sel)
self.regs[6] = ADF5356_REG6_CP_BLEED_CURRENT_UPDATE( self.regs[6] = ADF5356_REG6_CP_BLEED_CURRENT_UPDATE(
self.regs[6], int32(floor(24 * f_pfd / (61.44 * MHz))) self.regs[6], floor(24. * float(f_pfd) / (61.44 * MHz))
) )
self.regs[9] = ADF5356_REG9_VCO_BAND_DIVISION_UPDATE( self.regs[9] = ADF5356_REG9_VCO_BAND_DIVISION_UPDATE(
self.regs[9], int32(ceil(f_pfd / 160e3)) self.regs[9], ceil(float(f_pfd) / 160e3)
) )
# commit # commit
@ -252,12 +262,12 @@ class ADF5356:
Write all registers to the device. Attempts to lock the PLL. Write all registers to the device. Attempts to lock the PLL.
""" """
f_pfd = self.f_pfd() f_pfd = self.f_pfd()
delay(200 * us) # Slack self.core.delay(200. * us) # Slack
if f_pfd <= 75.0 * MHz: if f_pfd <= round64(75.0 * MHz):
for i in range(13, 0, -1): for i in range(13, 0, -1):
self.write(self.regs[i]) self.write(self.regs[i])
delay(200 * us) self.core.delay(200. * us)
self.write(self.regs[0] | ADF5356_REG0_AUTOCAL(1)) self.write(self.regs[0] | ADF5356_REG0_AUTOCAL(1))
else: else:
# AUTOCAL AT HALF PFD FREQUENCY # AUTOCAL AT HALF PFD FREQUENCY
@ -266,7 +276,7 @@ class ADF5356:
n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb) = calculate_pll( n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb) = calculate_pll(
self.f_vco(), f_pfd >> 1 self.f_vco(), f_pfd >> 1
) )
delay(200 * us) # Slack self.core.delay(200. * us) # Slack
self.write( self.write(
13 13
@ -289,7 +299,7 @@ class ADF5356:
) )
self.write(1 | ADF5356_REG1_MAIN_FRAC_VALUE(frac1)) self.write(1 | ADF5356_REG1_MAIN_FRAC_VALUE(frac1))
delay(200 * us) self.core.delay(200. * us)
self.write(ADF5356_REG0_INT_VALUE(n) | ADF5356_REG0_AUTOCAL(1)) self.write(ADF5356_REG0_INT_VALUE(n) | ADF5356_REG0_AUTOCAL(1))
# RELOCK AT WANTED PFD FREQUENCY # RELOCK AT WANTED PFD FREQUENCY
@ -301,7 +311,7 @@ class ADF5356:
self.write(self.regs[0] & ~ADF5356_REG0_AUTOCAL(1)) self.write(self.regs[0] & ~ADF5356_REG0_AUTOCAL(1))
@portable @portable
def f_pfd(self) -> TInt64: def f_pfd(self) -> int64:
""" """
Return the PFD frequency for the cached set of registers. Return the PFD frequency for the cached set of registers.
""" """
@ -311,35 +321,35 @@ class ADF5356:
return self._compute_pfd_frequency(r, d, t) return self._compute_pfd_frequency(r, d, t)
@portable @portable
def f_vco(self) -> TInt64: def f_vco(self) -> int64:
""" """
Return the VCO frequency for the cached set of registers. Return the VCO frequency for the cached set of registers.
""" """
return int64( return round64(
self.f_pfd() float(self.f_pfd())
* ( * (
self.pll_n() float(self.pll_n())
+ (self.pll_frac1() + self.pll_frac2() / self.pll_mod2()) + (float(self.pll_frac1() + self.pll_frac2()) / float(self.pll_mod2()))
/ ADF5356_MODULUS1 / float(ADF5356_MODULUS1)
) )
) )
@portable @portable
def pll_n(self) -> TInt32: def pll_n(self) -> int32:
""" """
Return the PLL integer value (INT) for the cached set of registers. Return the PLL integer value (INT) for the cached set of registers.
""" """
return ADF5356_REG0_INT_VALUE_GET(self.regs[0]) return ADF5356_REG0_INT_VALUE_GET(self.regs[0])
@portable @portable
def pll_frac1(self) -> TInt32: def pll_frac1(self) -> int32:
""" """
Return the main fractional value (FRAC1) for the cached set of registers. Return the main fractional value (FRAC1) for the cached set of registers.
""" """
return ADF5356_REG1_MAIN_FRAC_VALUE_GET(self.regs[1]) return ADF5356_REG1_MAIN_FRAC_VALUE_GET(self.regs[1])
@portable @portable
def pll_frac2(self) -> TInt32: def pll_frac2(self) -> int32:
""" """
Return the auxiliary fractional value (FRAC2) for the cached set of registers. Return the auxiliary fractional value (FRAC2) for the cached set of registers.
""" """
@ -348,7 +358,7 @@ class ADF5356:
) | ADF5356_REG2_AUX_FRAC_LSB_VALUE_GET(self.regs[2]) ) | ADF5356_REG2_AUX_FRAC_LSB_VALUE_GET(self.regs[2])
@portable @portable
def pll_mod2(self) -> TInt32: def pll_mod2(self) -> int32:
""" """
Return the auxiliary modulus value (MOD2) for the cached set of registers. Return the auxiliary modulus value (MOD2) for the cached set of registers.
""" """
@ -357,14 +367,14 @@ class ADF5356:
) | ADF5356_REG2_AUX_MOD_LSB_VALUE_GET(self.regs[2]) ) | ADF5356_REG2_AUX_MOD_LSB_VALUE_GET(self.regs[2])
@portable @portable
def ref_counter(self) -> TInt32: def ref_counter(self) -> int32:
""" """
Return the reference counter value (R) for the cached set of registers. Return the reference counter value (R) for the cached set of registers.
""" """
return ADF5356_REG4_R_COUNTER_GET(self.regs[4]) return ADF5356_REG4_R_COUNTER_GET(self.regs[4])
@portable @portable
def output_divider(self) -> TInt32: def output_divider(self) -> int32:
""" """
Return the value of the output A divider. Return the value of the output A divider.
""" """
@ -414,7 +424,7 @@ class ADF5356:
# single-ended reference mode is recommended # single-ended reference mode is recommended
# for references up to 250 MHz, even if the signal is differential # for references up to 250 MHz, even if the signal is differential
if self.sysclk <= 250 * MHz: if self.sysclk <= 250.*MHz:
self.regs[4] |= ADF5356_REG4_REF_MODE(0) self.regs[4] |= ADF5356_REG4_REF_MODE(0)
else: else:
self.regs[4] |= ADF5356_REG4_REF_MODE(1) self.regs[4] |= ADF5356_REG4_REF_MODE(1)
@ -456,7 +466,7 @@ class ADF5356:
# charge pump bleed current # charge pump bleed current
self.regs[6] |= ADF5356_REG6_CP_BLEED_CURRENT( self.regs[6] |= ADF5356_REG6_CP_BLEED_CURRENT(
int32(floor(24 * self.f_pfd() / (61.44 * MHz))) floor(24. * float(self.f_pfd()) / (61.44 * MHz))
) )
# direct feedback from VCO to N counter # direct feedback from VCO to N counter
@ -500,7 +510,7 @@ class ADF5356:
) )
self.regs[9] |= ADF5356_REG9_VCO_BAND_DIVISION( self.regs[9] |= ADF5356_REG9_VCO_BAND_DIVISION(
int32(ceil(self.f_pfd() / 160e3)) ceil(float(self.f_pfd()) / 160e3)
) )
# REG10 # REG10
@ -529,39 +539,39 @@ class ADF5356:
self.regs[12] = int32(0x15FC) self.regs[12] = int32(0x15FC)
@portable @portable
def _compute_pfd_frequency(self, r, d, t) -> TInt64: def _compute_pfd_frequency(self, r: int32, d: int32, t: int32) -> int64:
""" """
Calculate the PFD frequency from the given reference path parameters Calculate the PFD frequency from the given reference path parameters
""" """
return int64(self.sysclk * ((1 + d) / (r * (1 + t)))) return round64(self.sysclk * (float(1 + d) / float(r * (1 + t))))
@portable @portable
def _compute_reference_counter(self) -> TInt32: def _compute_reference_counter(self) -> int32:
""" """
Determine the reference counter R that maximizes the PFD frequency Determine the reference counter R that maximizes the PFD frequency
""" """
d = ADF5356_REG4_R_DOUBLER_GET(self.regs[4]) d = ADF5356_REG4_R_DOUBLER_GET(self.regs[4])
t = ADF5356_REG4_R_DIVIDER_GET(self.regs[4]) t = ADF5356_REG4_R_DIVIDER_GET(self.regs[4])
r = 1 r = 1
while self._compute_pfd_frequency(r, d, t) > ADF5356_MAX_FREQ_PFD: while self._compute_pfd_frequency(r, d, t) > int64(ADF5356_MAX_FREQ_PFD):
r += 1 r += 1
return int32(r) return r
@portable @portable
def gcd(a, b): def gcd(a: int64, b: int64) -> int64:
while b: while b != int64(0):
a, b = b, a % b a, b = b, a % b
return a return a
@portable @portable
def split_msb_lsb_28b(v): def split_msb_lsb_28b(v: int32) -> tuple[int32, int32]:
return int32((v >> 14) & 0x3FFF), int32(v & 0x3FFF) return (v >> 14) & 0x3FFF, v & 0x3FFF
@portable @portable
def calculate_pll(f_vco: TInt64, f_pfd: TInt64): def calculate_pll(f_vco: int64, f_pfd: int64) -> tuple[int32, int32, tuple[int32, int32], tuple[int32, int32]]:
""" """
Calculate fractional-N PLL parameters such that Calculate fractional-N PLL parameters such that
@ -574,20 +584,18 @@ def calculate_pll(f_vco: TInt64, f_pfd: TInt64):
:param f_pfd: PFD frequency :param f_pfd: PFD frequency
:return: ``(n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb))`` :return: ``(n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb))``
""" """
f_pfd = int64(f_pfd)
f_vco = int64(f_vco)
# integral part # integral part
n, r = int32(f_vco // f_pfd), f_vco % f_pfd n, r = int32(f_vco // f_pfd), f_vco % f_pfd
# main fractional part # main fractional part
r *= ADF5356_MODULUS1 r *= int64(ADF5356_MODULUS1)
frac1, frac2 = int32(r // f_pfd), r % f_pfd frac1, frac2 = int32(r // f_pfd), r % f_pfd
# auxiliary fractional part # auxiliary fractional part
mod2 = f_pfd mod2 = f_pfd
while mod2 > ADF5356_MAX_MODULUS2: while mod2 > int64(ADF5356_MAX_MODULUS2):
mod2 >>= 1 mod2 >>= 1
frac2 >>= 1 frac2 >>= 1
@ -595,4 +603,4 @@ def calculate_pll(f_vco: TInt64, f_pfd: TInt64):
mod2 //= gcd_div mod2 //= gcd_div
frac2 //= gcd_div frac2 //= gcd_div
return n, frac1, split_msb_lsb_28b(frac2), split_msb_lsb_28b(mod2) return n, frac1, split_msb_lsb_28b(int32(frac2)), split_msb_lsb_28b(int32(mod2))

View File

@ -1,642 +1,643 @@
# auto-generated, do not edit # auto-generated, do not edit
from artiq.language.core import portable
from artiq.language.types import TInt32
from numpy import int32 from numpy import int32
from artiq.language.core import portable
@portable @portable
def ADF5356_REG0_AUTOCAL_GET(reg: TInt32) -> TInt32: def ADF5356_REG0_AUTOCAL_GET(reg: int32) -> int32:
return int32((reg >> 21) & 0x1) return int32((reg >> 21) & 0x1)
@portable @portable
def ADF5356_REG0_AUTOCAL(x: TInt32) -> TInt32: def ADF5356_REG0_AUTOCAL(x: int32) -> int32:
return int32((x & 0x1) << 21) return int32((x & 0x1) << 21)
@portable @portable
def ADF5356_REG0_AUTOCAL_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG0_AUTOCAL_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 21)) | ((x & 0x1) << 21)) return int32((reg & ~(0x1 << 21)) | ((x & 0x1) << 21))
@portable @portable
def ADF5356_REG0_INT_VALUE_GET(reg: TInt32) -> TInt32: def ADF5356_REG0_INT_VALUE_GET(reg: int32) -> int32:
return int32((reg >> 4) & 0xffff) return int32((reg >> 4) & 0xffff)
@portable @portable
def ADF5356_REG0_INT_VALUE(x: TInt32) -> TInt32: def ADF5356_REG0_INT_VALUE(x: int32) -> int32:
return int32((x & 0xffff) << 4) return int32((x & 0xffff) << 4)
@portable @portable
def ADF5356_REG0_INT_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG0_INT_VALUE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0xffff << 4)) | ((x & 0xffff) << 4)) return int32((reg & ~(0xffff << 4)) | ((x & 0xffff) << 4))
@portable @portable
def ADF5356_REG0_PRESCALER_GET(reg: TInt32) -> TInt32: def ADF5356_REG0_PRESCALER_GET(reg: int32) -> int32:
return int32((reg >> 20) & 0x1) return int32((reg >> 20) & 0x1)
@portable @portable
def ADF5356_REG0_PRESCALER(x: TInt32) -> TInt32: def ADF5356_REG0_PRESCALER(x: int32) -> int32:
return int32((x & 0x1) << 20) return int32((x & 0x1) << 20)
@portable @portable
def ADF5356_REG0_PRESCALER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG0_PRESCALER_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 20)) | ((x & 0x1) << 20)) return int32((reg & ~(0x1 << 20)) | ((x & 0x1) << 20))
@portable @portable
def ADF5356_REG1_MAIN_FRAC_VALUE_GET(reg: TInt32) -> TInt32: def ADF5356_REG1_MAIN_FRAC_VALUE_GET(reg: int32) -> int32:
return int32((reg >> 4) & 0xffffff) return int32((reg >> 4) & 0xffffff)
@portable @portable
def ADF5356_REG1_MAIN_FRAC_VALUE(x: TInt32) -> TInt32: def ADF5356_REG1_MAIN_FRAC_VALUE(x: int32) -> int32:
return int32((x & 0xffffff) << 4) return int32((x & 0xffffff) << 4)
@portable @portable
def ADF5356_REG1_MAIN_FRAC_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG1_MAIN_FRAC_VALUE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0xffffff << 4)) | ((x & 0xffffff) << 4)) return int32((reg & ~(0xffffff << 4)) | ((x & 0xffffff) << 4))
@portable @portable
def ADF5356_REG2_AUX_FRAC_LSB_VALUE_GET(reg: TInt32) -> TInt32: def ADF5356_REG2_AUX_FRAC_LSB_VALUE_GET(reg: int32) -> int32:
return int32((reg >> 18) & 0x3fff) return int32((reg >> 18) & 0x3fff)
@portable @portable
def ADF5356_REG2_AUX_FRAC_LSB_VALUE(x: TInt32) -> TInt32: def ADF5356_REG2_AUX_FRAC_LSB_VALUE(x: int32) -> int32:
return int32((x & 0x3fff) << 18) return int32((x & 0x3fff) << 18)
@portable @portable
def ADF5356_REG2_AUX_FRAC_LSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG2_AUX_FRAC_LSB_VALUE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x3fff << 18)) | ((x & 0x3fff) << 18)) return int32((reg & ~(0x3fff << 18)) | ((x & 0x3fff) << 18))
@portable @portable
def ADF5356_REG2_AUX_MOD_LSB_VALUE_GET(reg: TInt32) -> TInt32: def ADF5356_REG2_AUX_MOD_LSB_VALUE_GET(reg: int32) -> int32:
return int32((reg >> 4) & 0x3fff) return int32((reg >> 4) & 0x3fff)
@portable @portable
def ADF5356_REG2_AUX_MOD_LSB_VALUE(x: TInt32) -> TInt32: def ADF5356_REG2_AUX_MOD_LSB_VALUE(x: int32) -> int32:
return int32((x & 0x3fff) << 4) return int32((x & 0x3fff) << 4)
@portable @portable
def ADF5356_REG2_AUX_MOD_LSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG2_AUX_MOD_LSB_VALUE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x3fff << 4)) | ((x & 0x3fff) << 4)) return int32((reg & ~(0x3fff << 4)) | ((x & 0x3fff) << 4))
@portable @portable
def ADF5356_REG3_PHASE_ADJUST_GET(reg: TInt32) -> TInt32: def ADF5356_REG3_PHASE_ADJUST_GET(reg: int32) -> int32:
return int32((reg >> 28) & 0x1) return int32((reg >> 28) & 0x1)
@portable @portable
def ADF5356_REG3_PHASE_ADJUST(x: TInt32) -> TInt32: def ADF5356_REG3_PHASE_ADJUST(x: int32) -> int32:
return int32((x & 0x1) << 28) return int32((x & 0x1) << 28)
@portable @portable
def ADF5356_REG3_PHASE_ADJUST_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG3_PHASE_ADJUST_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 28)) | ((x & 0x1) << 28)) return int32((reg & ~(0x1 << 28)) | ((x & 0x1) << 28))
@portable @portable
def ADF5356_REG3_PHASE_RESYNC_GET(reg: TInt32) -> TInt32: def ADF5356_REG3_PHASE_RESYNC_GET(reg: int32) -> int32:
return int32((reg >> 29) & 0x1) return int32((reg >> 29) & 0x1)
@portable @portable
def ADF5356_REG3_PHASE_RESYNC(x: TInt32) -> TInt32: def ADF5356_REG3_PHASE_RESYNC(x: int32) -> int32:
return int32((x & 0x1) << 29) return int32((x & 0x1) << 29)
@portable @portable
def ADF5356_REG3_PHASE_RESYNC_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG3_PHASE_RESYNC_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 29)) | ((x & 0x1) << 29)) return int32((reg & ~(0x1 << 29)) | ((x & 0x1) << 29))
@portable @portable
def ADF5356_REG3_PHASE_VALUE_GET(reg: TInt32) -> TInt32: def ADF5356_REG3_PHASE_VALUE_GET(reg: int32) -> int32:
return int32((reg >> 4) & 0xffffff) return int32((reg >> 4) & 0xffffff)
@portable @portable
def ADF5356_REG3_PHASE_VALUE(x: TInt32) -> TInt32: def ADF5356_REG3_PHASE_VALUE(x: int32) -> int32:
return int32((x & 0xffffff) << 4) return int32((x & 0xffffff) << 4)
@portable @portable
def ADF5356_REG3_PHASE_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG3_PHASE_VALUE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0xffffff << 4)) | ((x & 0xffffff) << 4)) return int32((reg & ~(0xffffff << 4)) | ((x & 0xffffff) << 4))
@portable @portable
def ADF5356_REG3_SD_LOAD_RESET_GET(reg: TInt32) -> TInt32: def ADF5356_REG3_SD_LOAD_RESET_GET(reg: int32) -> int32:
return int32((reg >> 30) & 0x1) return int32((reg >> 30) & 0x1)
@portable @portable
def ADF5356_REG3_SD_LOAD_RESET(x: TInt32) -> TInt32: def ADF5356_REG3_SD_LOAD_RESET(x: int32) -> int32:
return int32((x & 0x1) << 30) return int32((x & 0x1) << 30)
@portable @portable
def ADF5356_REG3_SD_LOAD_RESET_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG3_SD_LOAD_RESET_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 30)) | ((x & 0x1) << 30)) return int32((reg & ~(0x1 << 30)) | ((x & 0x1) << 30))
@portable @portable
def ADF5356_REG4_COUNTER_RESET_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_COUNTER_RESET_GET(reg: int32) -> int32:
return int32((reg >> 4) & 0x1) return int32((reg >> 4) & 0x1)
@portable @portable
def ADF5356_REG4_COUNTER_RESET(x: TInt32) -> TInt32: def ADF5356_REG4_COUNTER_RESET(x: int32) -> int32:
return int32((x & 0x1) << 4) return int32((x & 0x1) << 4)
@portable @portable
def ADF5356_REG4_COUNTER_RESET_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_COUNTER_RESET_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4)) return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4))
@portable @portable
def ADF5356_REG4_CP_THREE_STATE_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_CP_THREE_STATE_GET(reg: int32) -> int32:
return int32((reg >> 5) & 0x1) return int32((reg >> 5) & 0x1)
@portable @portable
def ADF5356_REG4_CP_THREE_STATE(x: TInt32) -> TInt32: def ADF5356_REG4_CP_THREE_STATE(x: int32) -> int32:
return int32((x & 0x1) << 5) return int32((x & 0x1) << 5)
@portable @portable
def ADF5356_REG4_CP_THREE_STATE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_CP_THREE_STATE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 5)) | ((x & 0x1) << 5)) return int32((reg & ~(0x1 << 5)) | ((x & 0x1) << 5))
@portable @portable
def ADF5356_REG4_CURRENT_SETTING_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_CURRENT_SETTING_GET(reg: int32) -> int32:
return int32((reg >> 10) & 0xf) return int32((reg >> 10) & 0xf)
@portable @portable
def ADF5356_REG4_CURRENT_SETTING(x: TInt32) -> TInt32: def ADF5356_REG4_CURRENT_SETTING(x: int32) -> int32:
return int32((x & 0xf) << 10) return int32((x & 0xf) << 10)
@portable @portable
def ADF5356_REG4_CURRENT_SETTING_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_CURRENT_SETTING_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0xf << 10)) | ((x & 0xf) << 10)) return int32((reg & ~(0xf << 10)) | ((x & 0xf) << 10))
@portable @portable
def ADF5356_REG4_DOUBLE_BUFF_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_DOUBLE_BUFF_GET(reg: int32) -> int32:
return int32((reg >> 14) & 0x1) return int32((reg >> 14) & 0x1)
@portable @portable
def ADF5356_REG4_DOUBLE_BUFF(x: TInt32) -> TInt32: def ADF5356_REG4_DOUBLE_BUFF(x: int32) -> int32:
return int32((x & 0x1) << 14) return int32((x & 0x1) << 14)
@portable @portable
def ADF5356_REG4_DOUBLE_BUFF_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_DOUBLE_BUFF_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 14)) | ((x & 0x1) << 14)) return int32((reg & ~(0x1 << 14)) | ((x & 0x1) << 14))
@portable @portable
def ADF5356_REG4_MUX_LOGIC_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_MUX_LOGIC_GET(reg: int32) -> int32:
return int32((reg >> 8) & 0x1) return int32((reg >> 8) & 0x1)
@portable @portable
def ADF5356_REG4_MUX_LOGIC(x: TInt32) -> TInt32: def ADF5356_REG4_MUX_LOGIC(x: int32) -> int32:
return int32((x & 0x1) << 8) return int32((x & 0x1) << 8)
@portable @portable
def ADF5356_REG4_MUX_LOGIC_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_MUX_LOGIC_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 8)) | ((x & 0x1) << 8)) return int32((reg & ~(0x1 << 8)) | ((x & 0x1) << 8))
@portable @portable
def ADF5356_REG4_MUXOUT_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_MUXOUT_GET(reg: int32) -> int32:
return int32((reg >> 27) & 0x7) return int32((reg >> 27) & 0x7)
@portable @portable
def ADF5356_REG4_MUXOUT(x: TInt32) -> TInt32: def ADF5356_REG4_MUXOUT(x: int32) -> int32:
return int32((x & 0x7) << 27) return int32((x & 0x7) << 27)
@portable @portable
def ADF5356_REG4_MUXOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_MUXOUT_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x7 << 27)) | ((x & 0x7) << 27)) return int32((reg & ~(0x7 << 27)) | ((x & 0x7) << 27))
@portable @portable
def ADF5356_REG4_PD_POLARITY_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_PD_POLARITY_GET(reg: int32) -> int32:
return int32((reg >> 7) & 0x1) return int32((reg >> 7) & 0x1)
@portable @portable
def ADF5356_REG4_PD_POLARITY(x: TInt32) -> TInt32: def ADF5356_REG4_PD_POLARITY(x: int32) -> int32:
return int32((x & 0x1) << 7) return int32((x & 0x1) << 7)
@portable @portable
def ADF5356_REG4_PD_POLARITY_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_PD_POLARITY_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 7)) | ((x & 0x1) << 7)) return int32((reg & ~(0x1 << 7)) | ((x & 0x1) << 7))
@portable @portable
def ADF5356_REG4_POWER_DOWN_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_POWER_DOWN_GET(reg: int32) -> int32:
return int32((reg >> 6) & 0x1) return int32((reg >> 6) & 0x1)
@portable @portable
def ADF5356_REG4_POWER_DOWN(x: TInt32) -> TInt32: def ADF5356_REG4_POWER_DOWN(x: int32) -> int32:
return int32((x & 0x1) << 6) return int32((x & 0x1) << 6)
@portable @portable
def ADF5356_REG4_POWER_DOWN_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_POWER_DOWN_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 6)) | ((x & 0x1) << 6)) return int32((reg & ~(0x1 << 6)) | ((x & 0x1) << 6))
@portable @portable
def ADF5356_REG4_R_COUNTER_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_R_COUNTER_GET(reg: int32) -> int32:
return int32((reg >> 15) & 0x3ff) return int32((reg >> 15) & 0x3ff)
@portable @portable
def ADF5356_REG4_R_COUNTER(x: TInt32) -> TInt32: def ADF5356_REG4_R_COUNTER(x: int32) -> int32:
return int32((x & 0x3ff) << 15) return int32((x & 0x3ff) << 15)
@portable @portable
def ADF5356_REG4_R_COUNTER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_R_COUNTER_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x3ff << 15)) | ((x & 0x3ff) << 15)) return int32((reg & ~(0x3ff << 15)) | ((x & 0x3ff) << 15))
@portable @portable
def ADF5356_REG4_R_DIVIDER_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_R_DIVIDER_GET(reg: int32) -> int32:
return int32((reg >> 25) & 0x1) return int32((reg >> 25) & 0x1)
@portable @portable
def ADF5356_REG4_R_DIVIDER(x: TInt32) -> TInt32: def ADF5356_REG4_R_DIVIDER(x: int32) -> int32:
return int32((x & 0x1) << 25) return int32((x & 0x1) << 25)
@portable @portable
def ADF5356_REG4_R_DIVIDER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_R_DIVIDER_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 25)) | ((x & 0x1) << 25)) return int32((reg & ~(0x1 << 25)) | ((x & 0x1) << 25))
@portable @portable
def ADF5356_REG4_R_DOUBLER_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_R_DOUBLER_GET(reg: int32) -> int32:
return int32((reg >> 26) & 0x1) return int32((reg >> 26) & 0x1)
@portable @portable
def ADF5356_REG4_R_DOUBLER(x: TInt32) -> TInt32: def ADF5356_REG4_R_DOUBLER(x: int32) -> int32:
return int32((x & 0x1) << 26) return int32((x & 0x1) << 26)
@portable @portable
def ADF5356_REG4_R_DOUBLER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_R_DOUBLER_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 26)) | ((x & 0x1) << 26)) return int32((reg & ~(0x1 << 26)) | ((x & 0x1) << 26))
@portable @portable
def ADF5356_REG4_REF_MODE_GET(reg: TInt32) -> TInt32: def ADF5356_REG4_REF_MODE_GET(reg: int32) -> int32:
return int32((reg >> 9) & 0x1) return int32((reg >> 9) & 0x1)
@portable @portable
def ADF5356_REG4_REF_MODE(x: TInt32) -> TInt32: def ADF5356_REG4_REF_MODE(x: int32) -> int32:
return int32((x & 0x1) << 9) return int32((x & 0x1) << 9)
@portable @portable
def ADF5356_REG4_REF_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG4_REF_MODE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 9)) | ((x & 0x1) << 9)) return int32((reg & ~(0x1 << 9)) | ((x & 0x1) << 9))
@portable @portable
def ADF5356_REG6_BLEED_POLARITY_GET(reg: TInt32) -> TInt32: def ADF5356_REG6_BLEED_POLARITY_GET(reg: int32) -> int32:
return int32((reg >> 31) & 0x1) return int32((reg >> 31) & 0x1)
@portable @portable
def ADF5356_REG6_BLEED_POLARITY(x: TInt32) -> TInt32: def ADF5356_REG6_BLEED_POLARITY(x: int32) -> int32:
return int32((x & 0x1) << 31) return int32((x & 0x1) << 31)
@portable @portable
def ADF5356_REG6_BLEED_POLARITY_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG6_BLEED_POLARITY_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 31)) | ((x & 0x1) << 31)) return int32((reg & ~(0x1 << 31)) | ((x & 0x1) << 31))
@portable @portable
def ADF5356_REG6_CP_BLEED_CURRENT_GET(reg: TInt32) -> TInt32: def ADF5356_REG6_CP_BLEED_CURRENT_GET(reg: int32) -> int32:
return int32((reg >> 13) & 0xff) return int32((reg >> 13) & 0xff)
@portable @portable
def ADF5356_REG6_CP_BLEED_CURRENT(x: TInt32) -> TInt32: def ADF5356_REG6_CP_BLEED_CURRENT(x: int32) -> int32:
return int32((x & 0xff) << 13) return int32((x & 0xff) << 13)
@portable @portable
def ADF5356_REG6_CP_BLEED_CURRENT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG6_CP_BLEED_CURRENT_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0xff << 13)) | ((x & 0xff) << 13)) return int32((reg & ~(0xff << 13)) | ((x & 0xff) << 13))
@portable @portable
def ADF5356_REG6_FB_SELECT_GET(reg: TInt32) -> TInt32: def ADF5356_REG6_FB_SELECT_GET(reg: int32) -> int32:
return int32((reg >> 24) & 0x1) return int32((reg >> 24) & 0x1)
@portable @portable
def ADF5356_REG6_FB_SELECT(x: TInt32) -> TInt32: def ADF5356_REG6_FB_SELECT(x: int32) -> int32:
return int32((x & 0x1) << 24) return int32((x & 0x1) << 24)
@portable @portable
def ADF5356_REG6_FB_SELECT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG6_FB_SELECT_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 24)) | ((x & 0x1) << 24)) return int32((reg & ~(0x1 << 24)) | ((x & 0x1) << 24))
@portable @portable
def ADF5356_REG6_GATE_BLEED_GET(reg: TInt32) -> TInt32: def ADF5356_REG6_GATE_BLEED_GET(reg: int32) -> int32:
return int32((reg >> 30) & 0x1) return int32((reg >> 30) & 0x1)
@portable @portable
def ADF5356_REG6_GATE_BLEED(x: TInt32) -> TInt32: def ADF5356_REG6_GATE_BLEED(x: int32) -> int32:
return int32((x & 0x1) << 30) return int32((x & 0x1) << 30)
@portable @portable
def ADF5356_REG6_GATE_BLEED_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG6_GATE_BLEED_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 30)) | ((x & 0x1) << 30)) return int32((reg & ~(0x1 << 30)) | ((x & 0x1) << 30))
@portable @portable
def ADF5356_REG6_MUTE_TILL_LD_GET(reg: TInt32) -> TInt32: def ADF5356_REG6_MUTE_TILL_LD_GET(reg: int32) -> int32:
return int32((reg >> 11) & 0x1) return int32((reg >> 11) & 0x1)
@portable @portable
def ADF5356_REG6_MUTE_TILL_LD(x: TInt32) -> TInt32: def ADF5356_REG6_MUTE_TILL_LD(x: int32) -> int32:
return int32((x & 0x1) << 11) return int32((x & 0x1) << 11)
@portable @portable
def ADF5356_REG6_MUTE_TILL_LD_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG6_MUTE_TILL_LD_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 11)) | ((x & 0x1) << 11)) return int32((reg & ~(0x1 << 11)) | ((x & 0x1) << 11))
@portable @portable
def ADF5356_REG6_NEGATIVE_BLEED_GET(reg: TInt32) -> TInt32: def ADF5356_REG6_NEGATIVE_BLEED_GET(reg: int32) -> int32:
return int32((reg >> 29) & 0x1) return int32((reg >> 29) & 0x1)
@portable @portable
def ADF5356_REG6_NEGATIVE_BLEED(x: TInt32) -> TInt32: def ADF5356_REG6_NEGATIVE_BLEED(x: int32) -> int32:
return int32((x & 0x1) << 29) return int32((x & 0x1) << 29)
@portable @portable
def ADF5356_REG6_NEGATIVE_BLEED_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG6_NEGATIVE_BLEED_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 29)) | ((x & 0x1) << 29)) return int32((reg & ~(0x1 << 29)) | ((x & 0x1) << 29))
@portable @portable
def ADF5356_REG6_RF_DIVIDER_SELECT_GET(reg: TInt32) -> TInt32: def ADF5356_REG6_RF_DIVIDER_SELECT_GET(reg: int32) -> int32:
return int32((reg >> 21) & 0x7) return int32((reg >> 21) & 0x7)
@portable @portable
def ADF5356_REG6_RF_DIVIDER_SELECT(x: TInt32) -> TInt32: def ADF5356_REG6_RF_DIVIDER_SELECT(x: int32) -> int32:
return int32((x & 0x7) << 21) return int32((x & 0x7) << 21)
@portable @portable
def ADF5356_REG6_RF_DIVIDER_SELECT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG6_RF_DIVIDER_SELECT_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x7 << 21)) | ((x & 0x7) << 21)) return int32((reg & ~(0x7 << 21)) | ((x & 0x7) << 21))
@portable @portable
def ADF5356_REG6_RF_OUTPUT_A_ENABLE_GET(reg: TInt32) -> TInt32: def ADF5356_REG6_RF_OUTPUT_A_ENABLE_GET(reg: int32) -> int32:
return int32((reg >> 6) & 0x1) return int32((reg >> 6) & 0x1)
@portable @portable
def ADF5356_REG6_RF_OUTPUT_A_ENABLE(x: TInt32) -> TInt32: def ADF5356_REG6_RF_OUTPUT_A_ENABLE(x: int32) -> int32:
return int32((x & 0x1) << 6) return int32((x & 0x1) << 6)
@portable @portable
def ADF5356_REG6_RF_OUTPUT_A_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG6_RF_OUTPUT_A_ENABLE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 6)) | ((x & 0x1) << 6)) return int32((reg & ~(0x1 << 6)) | ((x & 0x1) << 6))
@portable @portable
def ADF5356_REG6_RF_OUTPUT_A_POWER_GET(reg: TInt32) -> TInt32: def ADF5356_REG6_RF_OUTPUT_A_POWER_GET(reg: int32) -> int32:
return int32((reg >> 4) & 0x3) return int32((reg >> 4) & 0x3)
@portable @portable
def ADF5356_REG6_RF_OUTPUT_A_POWER(x: TInt32) -> TInt32: def ADF5356_REG6_RF_OUTPUT_A_POWER(x: int32) -> int32:
return int32((x & 0x3) << 4) return int32((x & 0x3) << 4)
@portable @portable
def ADF5356_REG6_RF_OUTPUT_A_POWER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG6_RF_OUTPUT_A_POWER_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x3 << 4)) | ((x & 0x3) << 4)) return int32((reg & ~(0x3 << 4)) | ((x & 0x3) << 4))
@portable @portable
def ADF5356_REG6_RF_OUTPUT_B_ENABLE_GET(reg: TInt32) -> TInt32: def ADF5356_REG6_RF_OUTPUT_B_ENABLE_GET(reg: int32) -> int32:
return int32((reg >> 10) & 0x1) return int32((reg >> 10) & 0x1)
@portable @portable
def ADF5356_REG6_RF_OUTPUT_B_ENABLE(x: TInt32) -> TInt32: def ADF5356_REG6_RF_OUTPUT_B_ENABLE(x: int32) -> int32:
return int32((x & 0x1) << 10) return int32((x & 0x1) << 10)
@portable @portable
def ADF5356_REG6_RF_OUTPUT_B_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG6_RF_OUTPUT_B_ENABLE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 10)) | ((x & 0x1) << 10)) return int32((reg & ~(0x1 << 10)) | ((x & 0x1) << 10))
@portable @portable
def ADF5356_REG7_FRAC_N_LD_PRECISION_GET(reg: TInt32) -> TInt32: def ADF5356_REG7_FRAC_N_LD_PRECISION_GET(reg: int32) -> int32:
return int32((reg >> 5) & 0x3) return int32((reg >> 5) & 0x3)
@portable @portable
def ADF5356_REG7_FRAC_N_LD_PRECISION(x: TInt32) -> TInt32: def ADF5356_REG7_FRAC_N_LD_PRECISION(x: int32) -> int32:
return int32((x & 0x3) << 5) return int32((x & 0x3) << 5)
@portable @portable
def ADF5356_REG7_FRAC_N_LD_PRECISION_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG7_FRAC_N_LD_PRECISION_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x3 << 5)) | ((x & 0x3) << 5)) return int32((reg & ~(0x3 << 5)) | ((x & 0x3) << 5))
@portable @portable
def ADF5356_REG7_LD_CYCLE_COUNT_GET(reg: TInt32) -> TInt32: def ADF5356_REG7_LD_CYCLE_COUNT_GET(reg: int32) -> int32:
return int32((reg >> 8) & 0x3) return int32((reg >> 8) & 0x3)
@portable @portable
def ADF5356_REG7_LD_CYCLE_COUNT(x: TInt32) -> TInt32: def ADF5356_REG7_LD_CYCLE_COUNT(x: int32) -> int32:
return int32((x & 0x3) << 8) return int32((x & 0x3) << 8)
@portable @portable
def ADF5356_REG7_LD_CYCLE_COUNT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG7_LD_CYCLE_COUNT_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x3 << 8)) | ((x & 0x3) << 8)) return int32((reg & ~(0x3 << 8)) | ((x & 0x3) << 8))
@portable @portable
def ADF5356_REG7_LD_MODE_GET(reg: TInt32) -> TInt32: def ADF5356_REG7_LD_MODE_GET(reg: int32) -> int32:
return int32((reg >> 4) & 0x1) return int32((reg >> 4) & 0x1)
@portable @portable
def ADF5356_REG7_LD_MODE(x: TInt32) -> TInt32: def ADF5356_REG7_LD_MODE(x: int32) -> int32:
return int32((x & 0x1) << 4) return int32((x & 0x1) << 4)
@portable @portable
def ADF5356_REG7_LD_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG7_LD_MODE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4)) return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4))
@portable @portable
def ADF5356_REG7_LE_SEL_SYNC_EDGE_GET(reg: TInt32) -> TInt32: def ADF5356_REG7_LE_SEL_SYNC_EDGE_GET(reg: int32) -> int32:
return int32((reg >> 27) & 0x1) return int32((reg >> 27) & 0x1)
@portable @portable
def ADF5356_REG7_LE_SEL_SYNC_EDGE(x: TInt32) -> TInt32: def ADF5356_REG7_LE_SEL_SYNC_EDGE(x: int32) -> int32:
return int32((x & 0x1) << 27) return int32((x & 0x1) << 27)
@portable @portable
def ADF5356_REG7_LE_SEL_SYNC_EDGE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG7_LE_SEL_SYNC_EDGE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 27)) | ((x & 0x1) << 27)) return int32((reg & ~(0x1 << 27)) | ((x & 0x1) << 27))
@portable @portable
def ADF5356_REG7_LE_SYNC_GET(reg: TInt32) -> TInt32: def ADF5356_REG7_LE_SYNC_GET(reg: int32) -> int32:
return int32((reg >> 25) & 0x1) return int32((reg >> 25) & 0x1)
@portable @portable
def ADF5356_REG7_LE_SYNC(x: TInt32) -> TInt32: def ADF5356_REG7_LE_SYNC(x: int32) -> int32:
return int32((x & 0x1) << 25) return int32((x & 0x1) << 25)
@portable @portable
def ADF5356_REG7_LE_SYNC_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG7_LE_SYNC_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 25)) | ((x & 0x1) << 25)) return int32((reg & ~(0x1 << 25)) | ((x & 0x1) << 25))
@portable @portable
def ADF5356_REG7_LOL_MODE_GET(reg: TInt32) -> TInt32: def ADF5356_REG7_LOL_MODE_GET(reg: int32) -> int32:
return int32((reg >> 7) & 0x1) return int32((reg >> 7) & 0x1)
@portable @portable
def ADF5356_REG7_LOL_MODE(x: TInt32) -> TInt32: def ADF5356_REG7_LOL_MODE(x: int32) -> int32:
return int32((x & 0x1) << 7) return int32((x & 0x1) << 7)
@portable @portable
def ADF5356_REG7_LOL_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG7_LOL_MODE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 7)) | ((x & 0x1) << 7)) return int32((reg & ~(0x1 << 7)) | ((x & 0x1) << 7))
@portable @portable
def ADF5356_REG9_AUTOCAL_TIMEOUT_GET(reg: TInt32) -> TInt32: def ADF5356_REG9_AUTOCAL_TIMEOUT_GET(reg: int32) -> int32:
return int32((reg >> 9) & 0x1f) return int32((reg >> 9) & 0x1f)
@portable @portable
def ADF5356_REG9_AUTOCAL_TIMEOUT(x: TInt32) -> TInt32: def ADF5356_REG9_AUTOCAL_TIMEOUT(x: int32) -> int32:
return int32((x & 0x1f) << 9) return int32((x & 0x1f) << 9)
@portable @portable
def ADF5356_REG9_AUTOCAL_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG9_AUTOCAL_TIMEOUT_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1f << 9)) | ((x & 0x1f) << 9)) return int32((reg & ~(0x1f << 9)) | ((x & 0x1f) << 9))
@portable @portable
def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_GET(reg: TInt32) -> TInt32: def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_GET(reg: int32) -> int32:
return int32((reg >> 4) & 0x1f) return int32((reg >> 4) & 0x1f)
@portable @portable
def ADF5356_REG9_SYNTH_LOCK_TIMEOUT(x: TInt32) -> TInt32: def ADF5356_REG9_SYNTH_LOCK_TIMEOUT(x: int32) -> int32:
return int32((x & 0x1f) << 4) return int32((x & 0x1f) << 4)
@portable @portable
def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1f << 4)) | ((x & 0x1f) << 4)) return int32((reg & ~(0x1f << 4)) | ((x & 0x1f) << 4))
@portable @portable
def ADF5356_REG9_TIMEOUT_GET(reg: TInt32) -> TInt32: def ADF5356_REG9_TIMEOUT_GET(reg: int32) -> int32:
return int32((reg >> 14) & 0x3ff) return int32((reg >> 14) & 0x3ff)
@portable @portable
def ADF5356_REG9_TIMEOUT(x: TInt32) -> TInt32: def ADF5356_REG9_TIMEOUT(x: int32) -> int32:
return int32((x & 0x3ff) << 14) return int32((x & 0x3ff) << 14)
@portable @portable
def ADF5356_REG9_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG9_TIMEOUT_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x3ff << 14)) | ((x & 0x3ff) << 14)) return int32((reg & ~(0x3ff << 14)) | ((x & 0x3ff) << 14))
@portable @portable
def ADF5356_REG9_VCO_BAND_DIVISION_GET(reg: TInt32) -> TInt32: def ADF5356_REG9_VCO_BAND_DIVISION_GET(reg: int32) -> int32:
return int32((reg >> 24) & 0xff) return int32((reg >> 24) & 0xff)
@portable @portable
def ADF5356_REG9_VCO_BAND_DIVISION(x: TInt32) -> TInt32: def ADF5356_REG9_VCO_BAND_DIVISION(x: int32) -> int32:
return int32((x & 0xff) << 24) return int32((x & 0xff) << 24)
@portable @portable
def ADF5356_REG9_VCO_BAND_DIVISION_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG9_VCO_BAND_DIVISION_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0xff << 24)) | ((x & 0xff) << 24)) return int32((reg & ~(0xff << 24)) | ((x & 0xff) << 24))
@portable @portable
def ADF5356_REG10_ADC_CLK_DIV_GET(reg: TInt32) -> TInt32: def ADF5356_REG10_ADC_CLK_DIV_GET(reg: int32) -> int32:
return int32((reg >> 6) & 0xff) return int32((reg >> 6) & 0xff)
@portable @portable
def ADF5356_REG10_ADC_CLK_DIV(x: TInt32) -> TInt32: def ADF5356_REG10_ADC_CLK_DIV(x: int32) -> int32:
return int32((x & 0xff) << 6) return int32((x & 0xff) << 6)
@portable @portable
def ADF5356_REG10_ADC_CLK_DIV_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG10_ADC_CLK_DIV_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0xff << 6)) | ((x & 0xff) << 6)) return int32((reg & ~(0xff << 6)) | ((x & 0xff) << 6))
@portable @portable
def ADF5356_REG10_ADC_CONV_GET(reg: TInt32) -> TInt32: def ADF5356_REG10_ADC_CONV_GET(reg: int32) -> int32:
return int32((reg >> 5) & 0x1) return int32((reg >> 5) & 0x1)
@portable @portable
def ADF5356_REG10_ADC_CONV(x: TInt32) -> TInt32: def ADF5356_REG10_ADC_CONV(x: int32) -> int32:
return int32((x & 0x1) << 5) return int32((x & 0x1) << 5)
@portable @portable
def ADF5356_REG10_ADC_CONV_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG10_ADC_CONV_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 5)) | ((x & 0x1) << 5)) return int32((reg & ~(0x1 << 5)) | ((x & 0x1) << 5))
@portable @portable
def ADF5356_REG10_ADC_ENABLE_GET(reg: TInt32) -> TInt32: def ADF5356_REG10_ADC_ENABLE_GET(reg: int32) -> int32:
return int32((reg >> 4) & 0x1) return int32((reg >> 4) & 0x1)
@portable @portable
def ADF5356_REG10_ADC_ENABLE(x: TInt32) -> TInt32: def ADF5356_REG10_ADC_ENABLE(x: int32) -> int32:
return int32((x & 0x1) << 4) return int32((x & 0x1) << 4)
@portable @portable
def ADF5356_REG10_ADC_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG10_ADC_ENABLE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4)) return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4))
@portable @portable
def ADF5356_REG11_VCO_BAND_HOLD_GET(reg: TInt32) -> TInt32: def ADF5356_REG11_VCO_BAND_HOLD_GET(reg: int32) -> int32:
return int32((reg >> 24) & 0x1) return int32((reg >> 24) & 0x1)
@portable @portable
def ADF5356_REG11_VCO_BAND_HOLD(x: TInt32) -> TInt32: def ADF5356_REG11_VCO_BAND_HOLD(x: int32) -> int32:
return int32((x & 0x1) << 24) return int32((x & 0x1) << 24)
@portable @portable
def ADF5356_REG11_VCO_BAND_HOLD_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG11_VCO_BAND_HOLD_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x1 << 24)) | ((x & 0x1) << 24)) return int32((reg & ~(0x1 << 24)) | ((x & 0x1) << 24))
@portable @portable
def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_GET(reg: TInt32) -> TInt32: def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_GET(reg: int32) -> int32:
return int32((reg >> 12) & 0xfffff) return int32((reg >> 12) & 0xfffff)
@portable @portable
def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE(x: TInt32) -> TInt32: def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE(x: int32) -> int32:
return int32((x & 0xfffff) << 12) return int32((x & 0xfffff) << 12)
@portable @portable
def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0xfffff << 12)) | ((x & 0xfffff) << 12)) return int32((reg & ~(0xfffff << 12)) | ((x & 0xfffff) << 12))
@portable @portable
def ADF5356_REG13_AUX_FRAC_MSB_VALUE_GET(reg: TInt32) -> TInt32: def ADF5356_REG13_AUX_FRAC_MSB_VALUE_GET(reg: int32) -> int32:
return int32((reg >> 18) & 0x3fff) return int32((reg >> 18) & 0x3fff)
@portable @portable
def ADF5356_REG13_AUX_FRAC_MSB_VALUE(x: TInt32) -> TInt32: def ADF5356_REG13_AUX_FRAC_MSB_VALUE(x: int32) -> int32:
return int32((x & 0x3fff) << 18) return int32((x & 0x3fff) << 18)
@portable @portable
def ADF5356_REG13_AUX_FRAC_MSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG13_AUX_FRAC_MSB_VALUE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x3fff << 18)) | ((x & 0x3fff) << 18)) return int32((reg & ~(0x3fff << 18)) | ((x & 0x3fff) << 18))
@portable @portable
def ADF5356_REG13_AUX_MOD_MSB_VALUE_GET(reg: TInt32) -> TInt32: def ADF5356_REG13_AUX_MOD_MSB_VALUE_GET(reg: int32) -> int32:
return int32((reg >> 4) & 0x3fff) return int32((reg >> 4) & 0x3fff)
@portable @portable
def ADF5356_REG13_AUX_MOD_MSB_VALUE(x: TInt32) -> TInt32: def ADF5356_REG13_AUX_MOD_MSB_VALUE(x: int32) -> int32:
return int32((x & 0x3fff) << 4) return int32((x & 0x3fff) << 4)
@portable @portable
def ADF5356_REG13_AUX_MOD_MSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: def ADF5356_REG13_AUX_MOD_MSB_VALUE_UPDATE(reg: int32, x: int32) -> int32:
return int32((reg & ~(0x3fff << 4)) | ((x & 0x3fff) << 4)) return int32((reg & ~(0x3fff << 4)) | ((x & 0x3fff) << 4))
ADF5356_NUM_REGS = 14 ADF5356_NUM_REGS = 14

View File

@ -1,7 +1,12 @@
from artiq.language.core import kernel, portable
from numpy import int32 from numpy import int32
from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable
from artiq.language.units import us
from artiq.coredevice.core import Core
from artiq.coredevice.mirny import Mirny
from artiq.coredevice.spi2 import *
# almazny-specific data # almazny-specific data
ALMAZNY_LEGACY_REG_BASE = 0x0C ALMAZNY_LEGACY_REG_BASE = 0x0C
@ -14,6 +19,7 @@ ALMAZNY_LEGACY_OE_SHIFT = 12
ALMAZNY_LEGACY_SPIT_WR = 32 ALMAZNY_LEGACY_SPIT_WR = 32
@nac3
class AlmaznyLegacy: class AlmaznyLegacy:
""" """
Almazny (High frequency mezzanine board for Mirny) Almazny (High frequency mezzanine board for Mirny)
@ -24,8 +30,15 @@ class AlmaznyLegacy:
:param host_mirny: Mirny device Almazny is connected to :param host_mirny: Mirny device Almazny is connected to
""" """
core: KernelInvariant[Core]
mirny_cpld: KernelInvariant[Mirny]
att_mu: Kernel[list[int32]]
channel_sw: Kernel[list[int32]]
output_enable: Kernel[bool]
def __init__(self, dmgr, host_mirny): def __init__(self, dmgr, host_mirny):
self.mirny_cpld = dmgr.get(host_mirny) self.mirny_cpld = dmgr.get(host_mirny)
self.core = self.mirny_cpld.core
self.att_mu = [0x3f] * 4 self.att_mu = [0x3f] * 4
self.channel_sw = [0] * 4 self.channel_sw = [0] * 4
self.output_enable = False self.output_enable = False
@ -35,7 +48,7 @@ class AlmaznyLegacy:
self.output_toggle(self.output_enable) self.output_toggle(self.output_enable)
@kernel @kernel
def att_to_mu(self, att): def att_to_mu(self, att: float) -> int32:
""" """
Convert an attenuator setting in dB to machine units. Convert an attenuator setting in dB to machine units.
@ -48,17 +61,17 @@ class AlmaznyLegacy:
return mu return mu
@kernel @kernel
def mu_to_att(self, att_mu): def mu_to_att(self, att_mu: int32) -> float:
""" """
Convert a digital attenuator setting to dB. Convert a digital attenuator setting to dB.
:param att_mu: attenuator setting in machine units :param att_mu: attenuator setting in machine units
:return: attenuator setting in dB :return: attenuator setting in dB
""" """
return att_mu / 2 return float(att_mu) / 2.
@kernel @kernel
def set_att(self, channel, att, rf_switch=True): def set_att(self, channel: int32, att: float, rf_switch: bool = True):
""" """
Sets attenuators on chosen shift register (channel). Sets attenuators on chosen shift register (channel).
@ -69,7 +82,7 @@ class AlmaznyLegacy:
self.set_att_mu(channel, self.att_to_mu(att), rf_switch) self.set_att_mu(channel, self.att_to_mu(att), rf_switch)
@kernel @kernel
def set_att_mu(self, channel, att_mu, rf_switch=True): def set_att_mu(self, channel: int32, att_mu: int32, rf_switch: bool = True):
""" """
Sets attenuators on chosen shift register (channel). Sets attenuators on chosen shift register (channel).
@ -82,7 +95,7 @@ class AlmaznyLegacy:
self._update_register(channel) self._update_register(channel)
@kernel @kernel
def output_toggle(self, oe): def output_toggle(self, oe: bool):
""" """
Toggles output on all shift registers on or off. Toggles output on all shift registers on or off.
@ -91,13 +104,13 @@ class AlmaznyLegacy:
self.output_enable = oe self.output_enable = oe
cfg_reg = self.mirny_cpld.read_reg(1) cfg_reg = self.mirny_cpld.read_reg(1)
en = 1 if self.output_enable else 0 en = 1 if self.output_enable else 0
delay(100 * us) self.core.delay(100. * us)
new_reg = (en << ALMAZNY_LEGACY_OE_SHIFT) | (cfg_reg & 0x3FF) new_reg = (en << ALMAZNY_LEGACY_OE_SHIFT) | (cfg_reg & 0x3FF)
self.mirny_cpld.write_reg(1, new_reg) self.mirny_cpld.write_reg(1, new_reg)
delay(100 * us) self.core.delay(100. * us)
@kernel @kernel
def _flip_mu_bits(self, mu): def _flip_mu_bits(self, mu: int32) -> int32:
# in this form MSB is actually 0.5dB attenuator # in this form MSB is actually 0.5dB attenuator
# unnatural for users, so we flip the six bits # unnatural for users, so we flip the six bits
return (((mu & 0x01) << 5) return (((mu & 0x01) << 5)
@ -108,14 +121,14 @@ class AlmaznyLegacy:
| ((mu & 0x20) >> 5)) | ((mu & 0x20) >> 5))
@kernel @kernel
def _update_register(self, ch): def _update_register(self, ch: int32):
self.mirny_cpld.write_ext( self.mirny_cpld.write_ext(
ALMAZNY_LEGACY_REG_BASE + ch, ALMAZNY_LEGACY_REG_BASE + ch,
8, 8,
self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6), self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6),
ALMAZNY_LEGACY_SPIT_WR ALMAZNY_LEGACY_SPIT_WR
) )
delay(100 * us) self.core.delay(100. * us)
@kernel @kernel
def _update_all_registers(self): def _update_all_registers(self):
@ -123,6 +136,7 @@ class AlmaznyLegacy:
self._update_register(i) self._update_register(i)
@nac3
class AlmaznyChannel: class AlmaznyChannel:
""" """
One Almazny channel One Almazny channel
@ -136,12 +150,17 @@ class AlmaznyChannel:
:param channel: channel index (0-3) :param channel: channel index (0-3)
""" """
core: KernelInvariant[Core]
mirny_cpld: KernelInvariant[Mirny]
channel: KernelInvariant[int32]
def __init__(self, dmgr, host_mirny, channel): def __init__(self, dmgr, host_mirny, channel):
self.channel = channel
self.mirny_cpld = dmgr.get(host_mirny) self.mirny_cpld = dmgr.get(host_mirny)
self.core = self.mirny_cpld.core
self.channel = channel
@portable @portable
def to_mu(self, att, enable, led): def to_mu(self, att: float, enable: bool, led: bool) -> int32:
""" """
Convert an attenuation in dB, RF switch state and LED state to machine Convert an attenuation in dB, RF switch state and LED state to machine
units. units.
@ -151,7 +170,7 @@ class AlmaznyChannel:
:param led: LED state (bool) :param led: LED state (bool)
:return: channel setting in machine units :return: channel setting in machine units
""" """
mu = int32(round(att * 2.)) mu = round(att * 2.)
if mu >= 64 or mu < 0: if mu >= 64 or mu < 0:
raise ValueError("Attenuation out of range") raise ValueError("Attenuation out of range")
# unfortunate hardware design: bit reverse # unfortunate hardware design: bit reverse
@ -164,7 +183,7 @@ class AlmaznyChannel:
return mu return mu
@kernel @kernel
def set_mu(self, mu): def set_mu(self, mu: int32):
""" """
Set channel state (machine units). Set channel state (machine units).
@ -174,7 +193,7 @@ class AlmaznyChannel:
addr=0xc + self.channel, length=8, data=mu, ext_div=32) addr=0xc + self.channel, length=8, data=mu, ext_div=32)
@kernel @kernel
def set(self, att, enable, led=False): def set(self, att: float, enable: bool, led: bool = False):
""" """
Set attenuation, RF switch, and LED state (SI units). Set attenuation, RF switch, and LED state (SI units).

View File

@ -1,23 +1,29 @@
from artiq.language.core import * from numpy import int32
from artiq.language.types import *
from artiq.language.core import nac3, extern, kernel, KernelInvariant
from artiq.coredevice.core import Core
@syscall(flags={"nounwind"}) @extern
def cache_get(key: TStr) -> TList(TInt32): def cache_get(key: str) -> list[int32]:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall @extern
def cache_put(key: TStr, value: TList(TInt32)) -> TNone: def cache_put(key: str, value: list[int32]):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@nac3
class CoreCache: class CoreCache:
"""Core device cache access""" """Core device cache access"""
core: KernelInvariant[Core]
def __init__(self, dmgr, core_device="core"): def __init__(self, dmgr, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@kernel @kernel
def get(self, key): def get(self, key: str) -> list[int32]:
"""Extract a value from the core device cache. """Extract a value from the core device cache.
After a value is extracted, it cannot be replaced with another value using After a value is extracted, it cannot be replaced with another value using
:meth:`put` until all kernel functions finish executing; attempting :meth:`put` until all kernel functions finish executing; attempting
@ -34,7 +40,7 @@ class CoreCache:
return cache_get(key) return cache_get(key)
@kernel @kernel
def put(self, key, value): def put(self, key: str, value: list[int32]):
"""Put a value into the core device cache. The value will persist until reboot. """Put a value into the core device cache. The value will persist until reboot.
To remove a value from the cache, call :meth:`put` with an empty list. To remove a value from the cache, call :meth:`put` with an empty list.

View File

@ -3,12 +3,17 @@ import logging
import traceback import traceback
import numpy import numpy
import socket import socket
import re
import linecache
import os
from enum import Enum from enum import Enum
from fractions import Fraction from fractions import Fraction
from collections import namedtuple from collections import namedtuple
from artiq.coredevice import exceptions from artiq.coredevice import exceptions
from artiq import __version__ as software_version from artiq import __version__ as software_version
from artiq import __artiq_dir__ as artiq_dir
from sipyco.keepalive import create_connection from sipyco.keepalive import create_connection
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,8 +28,6 @@ class Request(Enum):
RPCReply = 7 RPCReply = 7
RPCException = 8 RPCException = 8
SubkernelUpload = 9
class Reply(Enum): class Reply(Enum):
SystemInfo = 2 SystemInfo = 2
@ -165,13 +168,116 @@ class CommKernelDummy:
def run(self): def run(self):
pass pass
def serve(self, embedding_map, symbolizer, demangler): def serve(self, embedding_map, symbolizer):
pass pass
def check_system_info(self): def check_system_info(self):
pass pass
class SourceLoader:
def __init__(self, runtime_root):
self.runtime_root = runtime_root
def get_source(self, filename):
with open(os.path.join(self.runtime_root, filename)) as f:
return f.read()
source_loader = SourceLoader(os.path.join(artiq_dir, "soc", "runtime"))
class CoreException:
"""Information about an exception raised or passed through the core device."""
def __init__(self, exceptions, exception_info, traceback, stack_pointers):
self.exceptions = exceptions
self.exception_info = exception_info
self.traceback = list(traceback)
self.stack_pointers = stack_pointers
first_exception = exceptions[0]
name = first_exception[0]
if ':' in name:
exn_id, self.name = name.split(':', 2)
self.id = int(exn_id)
else:
self.id, self.name = 0, name
self.message = first_exception[1]
self.params = first_exception[2]
def append_backtrace(self, record, inlined=False):
filename, line, column, function, address = record
stub_globals = {"__name__": filename, "__loader__": source_loader}
source_line = linecache.getline(filename, line, stub_globals)
indentation = re.search(r"^\s*", source_line).end()
if address is None:
formatted_address = ""
elif inlined:
formatted_address = " (inlined)"
else:
formatted_address = " (RA=+0x{:x})".format(address)
filename = filename.replace(artiq_dir, "<artiq>")
lines = []
if column == -1:
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
lines.append(" File \"{file}\", line {line}, in {function}{address}".
format(file=filename, line=line, function=function,
address=formatted_address))
else:
lines.append(" {}^".format(" " * (column - indentation)))
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
lines.append(" File \"{file}\", line {line}, column {column},"
" in {function}{address}".
format(file=filename, line=line, column=column + 1,
function=function, address=formatted_address))
return lines
def single_traceback(self, exception_index):
# note that we insert in reversed order
lines = []
last_sp = 0
start_backtrace_index = self.exception_info[exception_index][1]
zipped = list(zip(self.traceback[start_backtrace_index:],
self.stack_pointers[start_backtrace_index:]))
exception = self.exceptions[exception_index]
name = exception[0]
message = exception[1]
params = exception[2]
if ':' in name:
exn_id, name = name.split(':', 2)
exn_id = int(exn_id)
else:
exn_id = 0
lines.append("{}({}): {}".format(name, exn_id, message.format(*params)))
zipped.append(((exception[3], exception[4], exception[5], exception[6],
None, []), None))
for ((filename, line, column, function, address, inlined), sp) in zipped:
# backtrace of nested exceptions may be discontinuous
# but the stack pointer must increase monotonically
if sp is not None and sp <= last_sp:
continue
last_sp = sp
for record in reversed(inlined):
lines += self.append_backtrace(record, True)
lines += self.append_backtrace((filename, line, column, function,
address))
lines.append("Traceback (most recent call first):")
return "\n".join(reversed(lines))
def __str__(self):
tracebacks = [self.single_traceback(i) for i in range(len(self.exceptions))]
traceback_str = ('\n\nDuring handling of the above exception, ' +
'another exception occurred:\n\n').join(tracebacks)
return 'Core Device Traceback:\n' +\
traceback_str +\
'\n\nEnd of Core Device Traceback\n'
def incompatible_versions(v1, v2): def incompatible_versions(v1, v2):
if v1.endswith(".beta") or v2.endswith(".beta"): if v1.endswith(".beta") or v2.endswith(".beta"):
# Beta branches may introduce breaking changes. Check version strictly. # Beta branches may introduce breaking changes. Check version strictly.
@ -210,7 +316,6 @@ class CommKernel:
self.unpack_float64 = struct.Struct(self.endian + "d").unpack self.unpack_float64 = struct.Struct(self.endian + "d").unpack
self.pack_header = struct.Struct(self.endian + "lB").pack self.pack_header = struct.Struct(self.endian + "lB").pack
self.pack_int8 = struct.Struct(self.endian + "B").pack
self.pack_int32 = struct.Struct(self.endian + "l").pack self.pack_int32 = struct.Struct(self.endian + "l").pack
self.pack_int64 = struct.Struct(self.endian + "q").pack self.pack_int64 = struct.Struct(self.endian + "q").pack
self.pack_float64 = struct.Struct(self.endian + "d").pack self.pack_float64 = struct.Struct(self.endian + "d").pack
@ -325,7 +430,7 @@ class CommKernel:
self._write(chunk) self._write(chunk)
def _write_int8(self, value): def _write_int8(self, value):
self._write(self.pack_int8(value)) self._write(value)
def _write_int32(self, value): def _write_int32(self, value):
self._write(self.pack_int32(value)) self._write(self.pack_int32(value))
@ -385,19 +490,6 @@ class CommKernel:
else: else:
self._read_expect(Reply.LoadCompleted) self._read_expect(Reply.LoadCompleted)
def upload_subkernel(self, kernel_library, id, destination):
self._write_header(Request.SubkernelUpload)
self._write_int32(id)
self._write_int8(destination)
self._write_bytes(kernel_library)
self._flush()
self._read_header()
if self._read_type == Reply.LoadFailed:
raise LoadError(self._read_string())
else:
self._read_expect(Reply.LoadCompleted)
def run(self): def run(self):
self._write_empty(Request.RunKernel) self._write_empty(Request.RunKernel)
self._flush() self._flush()
@ -579,9 +671,19 @@ class CommKernel:
return_tags = self._read_bytes() return_tags = self._read_bytes()
if service_id == 0: if service_id == 0:
def service(obj, attr, value): return setattr(obj, attr, value) def service(*values):
counter = 0
for obj in embedding_map.attributes_writeback:
old_val = obj["obj"]
if "fields" in obj:
for name in obj["fields"]:
setattr(old_val, name, values[counter])
counter += 1
else: else:
service = embedding_map.retrieve_object(service_id) old_val[:] = values[counter]
counter += 1
else:
service = embedding_map.retrieve_function(service_id)
logger.debug("rpc service: [%d]%r%s %r %r -> %s", service_id, service, logger.debug("rpc service: [%d]%r%s %r %r -> %s", service_id, service,
(" (async)" if is_async else ""), args, kwargs, return_tags) (" (async)" if is_async else ""), args, kwargs, return_tags)
@ -650,7 +752,7 @@ class CommKernel:
result, result, service) result, result, service)
self._flush() self._flush()
def _serve_exception(self, embedding_map, symbolizer, demangler): def _serve_exception(self, embedding_map, symbolizer):
exception_count = self._read_int32() exception_count = self._read_int32()
nested_exceptions = [] nested_exceptions = []
@ -674,10 +776,6 @@ class CommKernel:
nested_exceptions.append([name, message, params, nested_exceptions.append([name, message, params,
filename, line, column, function]) filename, line, column, function])
demangled_names = demangler([ex[6] for ex in nested_exceptions])
for i in range(exception_count):
nested_exceptions[i][6] = demangled_names[i]
exception_info = [] exception_info = []
for _ in range(exception_count): for _ in range(exception_count):
sp = self._read_int32() sp = self._read_int32()
@ -694,7 +792,7 @@ class CommKernel:
self._process_async_error() self._process_async_error()
traceback = list(symbolizer(backtrace)) traceback = list(symbolizer(backtrace))
core_exn = exceptions.CoreException(nested_exceptions, exception_info, core_exn = CoreException(nested_exceptions, exception_info,
traceback, stack_pointers) traceback, stack_pointers)
if core_exn.id == 0: if core_exn.id == 0:
@ -723,13 +821,13 @@ class CommKernel:
logger.warning(f"{(', '.join(errors[:-1]) + ' and ') if len(errors) > 1 else ''}{errors[-1]} " logger.warning(f"{(', '.join(errors[:-1]) + ' and ') if len(errors) > 1 else ''}{errors[-1]} "
f"reported during kernel execution") f"reported during kernel execution")
def serve(self, embedding_map, symbolizer, demangler): def serve(self, embedding_map, symbolizer):
while True: while True:
self._read_header() self._read_header()
if self._read_type == Reply.RPCRequest: if self._read_type == Reply.RPCRequest:
self._serve_rpc(embedding_map) self._serve_rpc(embedding_map)
elif self._read_type == Reply.KernelException: elif self._read_type == Reply.KernelException:
self._serve_exception(embedding_map, symbolizer, demangler) self._serve_exception(embedding_map, symbolizer)
elif self._read_type == Reply.ClockFailure: elif self._read_type == Reply.ClockFailure:
raise exceptions.ClockFailure raise exceptions.ClockFailure
else: else:

View File

@ -1,70 +1,39 @@
import os, sys import os, sys, tempfile, subprocess, io
import numpy from numpy import int32, int64
from inspect import getfullargspec
from functools import wraps
from pythonparser import diagnostic import nac3artiq
from artiq import __artiq_dir__ as artiq_dir
from artiq.language.core import * from artiq.language.core import *
from artiq.language.types import * from artiq.language.core import _ConstGenericMarker
from artiq.language import core as core_language
from artiq.language.units import * from artiq.language.units import *
from artiq.language.embedding_map import EmbeddingMap
from artiq.compiler.module import Module
from artiq.compiler.embedding import Stitcher
from artiq.compiler.targets import RV32IMATarget, RV32GTarget, CortexA9Target
from artiq.coredevice.comm_kernel import CommKernel, CommKernelDummy from artiq.coredevice.comm_kernel import CommKernel, CommKernelDummy
# Import for side effects (creating the exception classes).
from artiq.coredevice import exceptions
def _render_diagnostic(diagnostic, colored): @extern
def shorten_path(path): def rtio_init():
return path.replace(artiq_dir, "<artiq>")
lines = [shorten_path(path) for path in diagnostic.render(colored=colored)]
return "\n".join(lines)
colors_supported = os.name == "posix"
class _DiagnosticEngine(diagnostic.Engine):
def render_diagnostic(self, diagnostic):
sys.stderr.write(_render_diagnostic(diagnostic, colored=colors_supported) + "\n")
class CompileError(Exception):
def __init__(self, diagnostic):
self.diagnostic = diagnostic
def __str__(self):
# Prepend a newline so that the message shows up on after
# exception class name printed by Python.
return "\n" + _render_diagnostic(self.diagnostic, colored=colors_supported)
@syscall
def rtio_init() -> TNone:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) @extern
def rtio_get_destination_status(linkno: TInt32) -> TBool: def rtio_get_destination_status(destination: int32) -> bool:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) @extern
def rtio_get_counter() -> TInt64: def rtio_get_counter() -> int64:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
def get_target_cls(target): artiq_builtins = {
if target == "rv32g": "none": none,
return RV32GTarget "virtual": virtual,
elif target == "rv32ima": "_ConstGenericMarker": _ConstGenericMarker,
return RV32IMATarget "Option": Option,
elif target == "cortexa9": }
return CortexA9Target
else:
raise ValueError("Unsupported target")
@nac3
class Core: class Core:
"""Core device driver. """Core device driver.
@ -78,183 +47,87 @@ class Core:
:param ref_multiplier: ratio between the RTIO fine timestamp frequency :param ref_multiplier: ratio between the RTIO fine timestamp frequency
and the RTIO coarse timestamp frequency (e.g. SERDES multiplication and the RTIO coarse timestamp frequency (e.g. SERDES multiplication
factor). factor).
:param analyzer_proxy: name of the core device analyzer proxy to trigger
(optional).
:param analyze_at_run_end: automatically trigger the core device analyzer
proxy after the Experiment's run stage finishes.
""" """
ref_period: KernelInvariant[float]
ref_multiplier: KernelInvariant[int32]
coarse_ref_period: KernelInvariant[float]
kernel_invariants = { def __init__(self, dmgr, host, ref_period, ref_multiplier=8, target="rv32g"):
"core", "ref_period", "coarse_ref_period", "ref_multiplier",
}
def __init__(self, dmgr,
host, ref_period,
analyzer_proxy=None, analyze_at_run_end=False,
ref_multiplier=8,
target="rv32g", satellite_cpu_targets={}):
self.ref_period = ref_period self.ref_period = ref_period
self.ref_multiplier = ref_multiplier self.ref_multiplier = ref_multiplier
self.satellite_cpu_targets = satellite_cpu_targets
self.target_cls = get_target_cls(target)
self.coarse_ref_period = ref_period*ref_multiplier self.coarse_ref_period = ref_period*ref_multiplier
if host is None: if host is None:
self.comm = CommKernelDummy() self.comm = CommKernelDummy()
else: else:
self.comm = CommKernel(host) self.comm = CommKernel(host)
self.analyzer_proxy_name = analyzer_proxy
self.analyze_at_run_end = analyze_at_run_end
self.first_run = True self.first_run = True
self.dmgr = dmgr self.dmgr = dmgr
self.core = self self.core = self
self.comm.core = self self.comm.core = self
self.analyzer_proxy = None self.target = target
self.analyzed = False
def notify_run_end(self): self.compiler = nac3artiq.NAC3(target, artiq_builtins)
if self.analyze_at_run_end:
self.trigger_analyzer_proxy()
def close(self): def close(self):
self.comm.close() self.comm.close()
def compile(self, function, args, kwargs, set_result=None, def compile(self, method, args, kwargs, embedding_map, file_output=None, target=None):
attribute_writeback=True, print_as_rpc=True, if target is not None:
target=None, destination=0, subkernel_arg_types=[]): # NAC3TODO: subkernels
try: raise NotImplementedError
engine = _DiagnosticEngine(all_errors_are_fatal=True)
stitcher = Stitcher(engine=engine, core=self, dmgr=self.dmgr, if not self.analyzed:
print_as_rpc=print_as_rpc, self.compiler.analyze(core_language._registered_functions, core_language._registered_classes)
destination=destination, subkernel_arg_types=subkernel_arg_types) self.analyzed = True
stitcher.stitch_call(function, args, kwargs, set_result)
stitcher.finalize()
module = Module(stitcher, if hasattr(method, "__self__"):
ref_period=self.ref_period, obj = method.__self__
attribute_writeback=attribute_writeback) name = method.__name__
target = target if target is not None else self.target_cls() else:
obj = method
name = ""
library = target.compile_and_link([module]) if file_output is None:
stripped_library = target.strip(library) return self.compiler.compile_method_to_mem(obj, name, args, embedding_map)
else:
self.compiler.compile_method_to_file(obj, name, args, file_output, embedding_map)
return stitcher.embedding_map, stripped_library, \ def run(self, function, args, kwargs):
lambda addresses: target.symbolize(library, addresses), \ embedding_map = EmbeddingMap()
lambda symbols: target.demangle(symbols), \ kernel_library = self.compile(function, args, kwargs, embedding_map)
module.subkernel_arg_types
except diagnostic.Error as error:
raise CompileError(error.diagnostic) from error
def _run_compiled(self, kernel_library, embedding_map, symbolizer, demangler):
if self.first_run: if self.first_run:
self.comm.check_system_info() self.comm.check_system_info()
self.first_run = False self.first_run = False
symbolizer = lambda addresses: symbolize(kernel_library, addresses)
self.comm.load(kernel_library) self.comm.load(kernel_library)
self.comm.run() self.comm.run()
self.comm.serve(embedding_map, symbolizer, demangler) self.comm.serve(embedding_map, symbolizer)
def run(self, function, args, kwargs):
result = None
@rpc(flags={"async"})
def set_result(new_result):
nonlocal result
result = new_result
embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
self.compile(function, args, kwargs, set_result)
self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types)
self._run_compiled(kernel_library, embedding_map, symbolizer, demangler)
return result
def compile_subkernel(self, sid, subkernel_fn, embedding_map, args, subkernel_arg_types):
# pass self to subkernels (if applicable)
# assuming the first argument is self
subkernel_args = getfullargspec(subkernel_fn.artiq_embedded.function)
self_arg = []
if len(subkernel_args[0]) > 0:
if subkernel_args[0][0] == 'self':
self_arg = args[:1]
destination = subkernel_fn.artiq_embedded.destination
destination_tgt = self.satellite_cpu_targets[destination]
target = get_target_cls(destination_tgt)(subkernel_id=sid)
object_map, kernel_library, _, _, _ = \
self.compile(subkernel_fn, self_arg, {}, attribute_writeback=False,
print_as_rpc=False, target=target, destination=destination,
subkernel_arg_types=subkernel_arg_types.get(sid, []))
if object_map.has_rpc_or_subkernel():
raise ValueError("Subkernel must not use RPC or subkernels in other destinations")
return destination, kernel_library
def compile_and_upload_subkernels(self, embedding_map, args, subkernel_arg_types):
for sid, subkernel_fn in embedding_map.subkernels().items():
destination, kernel_library = \
self.compile_subkernel(sid, subkernel_fn, embedding_map,
args, subkernel_arg_types)
self.comm.upload_subkernel(kernel_library, sid, destination)
def precompile(self, function, *args, **kwargs):
"""Precompile a kernel and return a callable that executes it on the core device
at a later time.
Arguments to the kernel are set at compilation time and passed to this function,
as additional positional and keyword arguments.
The returned callable accepts no arguments.
Precompiled kernels may use RPCs and subkernels.
Object attributes at the beginning of a precompiled kernel execution have the
values they had at precompilation time. If up-to-date values are required,
use RPC to read them.
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
via the CPLD register.
The return value of the callable is the return value of the kernel, if any.
The callable may be called several times.
"""
if not hasattr(function, "artiq_embedded"):
raise ValueError("Argument is not a kernel")
result = None
@rpc(flags={"async"})
def set_result(new_result):
nonlocal result
result = new_result
embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
self.compile(function, args, kwargs, set_result, attribute_writeback=False)
self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types)
@wraps(function)
def run_precompiled():
nonlocal result
self._run_compiled(kernel_library, embedding_map, symbolizer, demangler)
return result
return run_precompiled
@portable @portable
def seconds_to_mu(self, seconds): def seconds_to_mu(self, seconds: float) -> int64:
"""Convert seconds to the corresponding number of machine units """Convert seconds to the corresponding number of machine units
(RTIO cycles). (RTIO cycles).
:param seconds: time (in seconds) to convert. :param seconds: time (in seconds) to convert.
""" """
return numpy.int64(seconds//self.ref_period) return int64(seconds//self.ref_period)
@portable @portable
def mu_to_seconds(self, mu): def mu_to_seconds(self, mu: int64) -> float:
"""Convert machine units (RTIO cycles) to seconds. """Convert machine units (RTIO cycles) to seconds.
:param mu: cycle count to convert. :param mu: cycle count to convert.
""" """
return mu*self.ref_period return float(mu)*self.ref_period
@kernel @kernel
def get_rtio_counter_mu(self): def delay(self, dt: float):
delay_mu(self.seconds_to_mu(dt))
@kernel
def get_rtio_counter_mu(self) -> int64:
"""Retrieve the current value of the hardware RTIO timeline counter. """Retrieve the current value of the hardware RTIO timeline counter.
As the timing of kernel code executed on the CPU is inherently As the timing of kernel code executed on the CPU is inherently
@ -267,7 +140,7 @@ class Core:
return rtio_get_counter() return rtio_get_counter()
@kernel @kernel
def wait_until_mu(self, cursor_mu): def wait_until_mu(self, cursor_mu: int64):
"""Block execution until the hardware RTIO counter reaches the given """Block execution until the hardware RTIO counter reaches the given
value (see :meth:`get_rtio_counter_mu`). value (see :meth:`get_rtio_counter_mu`).
@ -278,7 +151,7 @@ class Core:
pass pass
@kernel @kernel
def get_rtio_destination_status(self, destination): def get_rtio_destination_status(self, destination: int32) -> bool:
"""Returns whether the specified RTIO destination is up. """Returns whether the specified RTIO destination is up.
This is particularly useful in startup kernels to delay This is particularly useful in startup kernels to delay
startup until certain DRTIO destinations are up.""" startup until certain DRTIO destinations are up."""
@ -290,7 +163,7 @@ class Core:
at the current value of the hardware RTIO counter plus a margin of at the current value of the hardware RTIO counter plus a margin of
125000 machine units.""" 125000 machine units."""
rtio_init() rtio_init()
at_mu(rtio_get_counter() + 125000) at_mu(rtio_get_counter() + int64(125000))
@kernel @kernel
def break_realtime(self): def break_realtime(self):
@ -299,26 +172,99 @@ class Core:
If the time cursor is already after that position, this function If the time cursor is already after that position, this function
does nothing.""" does nothing."""
min_now = rtio_get_counter() + 125000 min_now = rtio_get_counter() + int64(125000)
if now_mu() < min_now: if now_mu() < min_now:
at_mu(min_now) at_mu(min_now)
def trigger_analyzer_proxy(self):
"""Causes the core analyzer proxy to retrieve a dump from the device,
and distribute it to all connected clients (typically dashboards).
Returns only after the dump has been retrieved from the device. class RunTool:
def __init__(self, pattern, **tempdata):
self._pattern = pattern
self._tempdata = tempdata
self._tempnames = {}
self._tempfiles = {}
Raises IOError if no analyzer proxy has been configured, or if the def __enter__(self):
analyzer proxy fails. In the latter case, more details would be for key, data in self._tempdata.items():
available in the proxy log. if data is None:
""" fd, filename = tempfile.mkstemp()
if self.analyzer_proxy is None: os.close(fd)
if self.analyzer_proxy_name is not None: self._tempnames[key] = filename
self.analyzer_proxy = self.dmgr.get(self.analyzer_proxy_name)
if self.analyzer_proxy is None:
raise IOError("No analyzer proxy configured")
else: else:
success = self.analyzer_proxy.trigger() with tempfile.NamedTemporaryFile(delete=False) as f:
if not success: f.write(data)
raise IOError("Analyzer proxy reported failure") self._tempnames[key] = f.name
cmdline = []
for argument in self._pattern:
cmdline.append(argument.format(**self._tempnames))
# https://bugs.python.org/issue17023
windows = os.name == "nt"
process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, shell=windows)
stdout, stderr = process.communicate()
if process.returncode != 0:
raise Exception("{} invocation failed: {}".
format(cmdline[0], stderr))
self._tempfiles["__stdout__"] = io.StringIO(stdout)
for key in self._tempdata:
if self._tempdata[key] is None:
self._tempfiles[key] = open(self._tempnames[key], "rb")
return self._tempfiles
def __exit__(self, exc_typ, exc_value, exc_trace):
for file in self._tempfiles.values():
file.close()
for filename in self._tempnames.values():
os.unlink(filename)
def symbolize(library, addresses):
if addresses == []:
return []
# We got a list of return addresses, i.e. addresses of instructions
# just after the call. Offset them back to get an address somewhere
# inside the call instruction (or its delay slot), since that's what
# the backtrace entry should point at.
last_inlined = None
offset_addresses = [hex(addr - 1) for addr in addresses]
with RunTool(["llvm-addr2line", "--addresses", "--functions", "--inlines",
"--demangle", "--exe={library}"] + offset_addresses,
library=library) \
as results:
lines = iter(results["__stdout__"].read().rstrip().split("\n"))
backtrace = []
while True:
try:
address_or_function = next(lines)
except StopIteration:
break
if address_or_function[:2] == "0x":
address = int(address_or_function[2:], 16) + 1 # remove offset
function = next(lines)
inlined = False
else:
address = backtrace[-1][4] # inlined
function = address_or_function
inlined = True
location = next(lines)
filename, line = location.rsplit(":", 1)
if filename == "??" or filename == "<synthesized>":
continue
if line == "?":
line = -1
else:
line = int(line)
# can't get column out of addr2line D:
if inlined:
last_inlined.append((filename, line, -1, function, address))
else:
last_inlined = []
backtrace.append((filename, line, -1, function, address,
last_inlined))
return backtrace

View File

@ -142,7 +142,7 @@
"properties": { "properties": {
"type": { "type": {
"type": "string", "type": "string",
"enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"] "enum": ["dio", "dio_spi", "urukul", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"]
}, },
"board": { "board": {
"type": "string" "type": "string"
@ -315,10 +315,8 @@
"type": "integer" "type": "integer"
}, },
"pll_en": { "pll_en": {
"type": "integer", "type": "boolean",
"minimum": 0, "default": true
"maximum": 1,
"default": 1
}, },
"pll_vco": { "pll_vco": {
"type": "integer" "type": "integer"
@ -331,50 +329,6 @@
}, },
"required": ["ports"] "required": ["ports"]
} }
}, {
"title": "Novogorny",
"if": {
"properties": {
"type": {
"const": "novogorny"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 1
}
},
"required": ["ports"]
}
}, {
"title": "Sampler",
"if": {
"properties": {
"type": {
"const": "sampler"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 2
}
},
"required": ["ports"]
}
}, { }, {
"title": "SUServo", "title": "SUServo",
"if": { "if": {
@ -429,10 +383,8 @@
"default": 32 "default": 32
}, },
"pll_en": { "pll_en": {
"type": "integer", "type": "boolean",
"minimum": 0, "default": true
"maximum": 1,
"default": 1
}, },
"pll_vco": { "pll_vco": {
"type": "integer" "type": "integer"
@ -630,7 +582,8 @@
"maxItems": 2 "maxItems": 2
}, },
"drtio_destination": { "drtio_destination": {
"type": "integer" "type": "integer",
"default": 4
} }
}, },
"required": ["ports"] "required": ["ports"]

View File

@ -5,34 +5,36 @@ the core device's SDRAM, and playing them back at higher speeds than the CPU
alone could achieve. alone could achieve.
""" """
from artiq.language.core import syscall, kernel from numpy import int32, int64
from artiq.language.types import TInt32, TInt64, TStr, TNone, TTuple, TBool
from artiq.language.core import nac3, extern, kernel, Kernel, KernelInvariant
from artiq.coredevice.exceptions import DMAError from artiq.coredevice.exceptions import DMAError
from artiq.coredevice.core import Core
from numpy import int64
@syscall
def dma_record_start(name: TStr) -> TNone: @extern
def dma_record_start(name: str):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall @extern
def dma_record_stop(duration: TInt64, enable_ddma: TBool) -> TNone: def dma_record_stop(duration: int64, enable_ddma: bool):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall @extern
def dma_erase(name: TStr) -> TNone: def dma_erase(name: str):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall @extern
def dma_retrieve(name: TStr) -> TTuple([TInt64, TInt32, TBool]): def dma_retrieve(name: str) -> tuple[int64, int32, bool]:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall @extern
def dma_playback(timestamp: TInt64, ptr: TInt32, enable_ddma: TBool) -> TNone: def dma_playback(timestamp: int64, ptr: int32, enable_ddma: bool):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@nac3
class DMARecordContextManager: class DMARecordContextManager:
"""Context manager returned by :meth:`CoreDMA.record()`. """Context manager returned by :meth:`CoreDMA.record()`.
@ -44,6 +46,9 @@ class DMARecordContextManager:
are stored in a newly created trace, and ``now`` is restored to the value are stored in a newly created trace, and ``now`` is restored to the value
it had before the context manager was entered. it had before the context manager was entered.
""" """
name: Kernel[str]
saved_now_mu: Kernel[int64]
def __init__(self): def __init__(self):
self.name = "" self.name = ""
self.saved_now_mu = int64(0) self.saved_now_mu = int64(0)
@ -53,21 +58,24 @@ class DMARecordContextManager:
def __enter__(self): def __enter__(self):
dma_record_start(self.name) # this may raise, so do it before altering now dma_record_start(self.name) # this may raise, so do it before altering now
self.saved_now_mu = now_mu() self.saved_now_mu = now_mu()
at_mu(0) at_mu(int64(0))
@kernel @kernel
def __exit__(self, type, value, traceback): def __exit__(self):
dma_record_stop(now_mu(), self.enable_ddma) # see above dma_record_stop(now_mu(), self.enable_ddma) # see above
at_mu(self.saved_now_mu) at_mu(self.saved_now_mu)
@nac3
class CoreDMA: class CoreDMA:
"""Core device Direct Memory Access (DMA) driver. """Core device Direct Memory Access (DMA) driver.
Gives access to the DMA functionality of the core device. Gives access to the DMA functionality of the core device.
""" """
kernel_invariants = {"core", "recorder"} core: KernelInvariant[Core]
recorder: KernelInvariant[DMARecordContextManager]
epoch: Kernel[int32]
def __init__(self, dmgr, core_device="core"): def __init__(self, dmgr, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -75,7 +83,7 @@ class CoreDMA:
self.epoch = 0 self.epoch = 0
@kernel @kernel
def record(self, name, enable_ddma=False): def record(self, name: str, enable_ddma: bool = False) -> DMARecordContextManager:
"""Returns a context manager that will record a DMA trace called ``name``. """Returns a context manager that will record a DMA trace called ``name``.
Any previously recorded trace with the same name is overwritten. Any previously recorded trace with the same name is overwritten.
The trace will persist across kernel switches. The trace will persist across kernel switches.
@ -92,13 +100,13 @@ class CoreDMA:
return self.recorder return self.recorder
@kernel @kernel
def erase(self, name): def erase(self, name: str):
"""Removes the DMA trace with the given name from storage.""" """Removes the DMA trace with the given name from storage."""
self.epoch += 1 self.epoch += 1
dma_erase(name) dma_erase(name)
@kernel @kernel
def playback(self, name): def playback(self, name: str):
"""Replays a previously recorded DMA trace. This function blocks until """Replays a previously recorded DMA trace. This function blocks until
the entire trace is submitted to the RTIO FIFOs.""" the entire trace is submitted to the RTIO FIFOs."""
(advance_mu, ptr, uses_ddma) = dma_retrieve(name) (advance_mu, ptr, uses_ddma) = dma_retrieve(name)
@ -106,14 +114,14 @@ class CoreDMA:
delay_mu(advance_mu) delay_mu(advance_mu)
@kernel @kernel
def get_handle(self, name): def get_handle(self, name: str) -> tuple[int32, int64, int32]:
"""Returns a handle to a previously recorded DMA trace. The returned handle """Returns a handle to a previously recorded DMA trace. The returned handle
is only valid until the next call to :meth:`record` or :meth:`erase`.""" is only valid until the next call to :meth:`record` or :meth:`erase`."""
(advance_mu, ptr, uses_ddma) = dma_retrieve(name) (advance_mu, ptr, uses_ddma) = dma_retrieve(name)
return (self.epoch, advance_mu, ptr, uses_ddma) return (self.epoch, advance_mu, ptr, uses_ddma)
@kernel @kernel
def playback_handle(self, handle): def playback_handle(self, handle: tuple[int32, int64, int32]):
"""Replays a handle obtained with :meth:`get_handle`. Using this function """Replays a handle obtained with :meth:`get_handle`. Using this function
is much faster than :meth:`playback` for replaying a set of traces repeatedly, is much faster than :meth:`playback` for replaying a set of traces repeatedly,
but incurs the overhead of managing the handles onto the programmer.""" but incurs the overhead of managing the handles onto the programmer."""

View File

@ -51,11 +51,13 @@ See :mod:`artiq.gateware.rtio.phy.edge_counter` and
:meth:`artiq.gateware.eem.DIO.add_std` for the gateware components. :meth:`artiq.gateware.eem.DIO.add_std` for the gateware components.
""" """
from numpy import int32, int64
from artiq.coredevice.core import Core
from artiq.language.core import * from artiq.language.core import *
from artiq.language.types import *
from artiq.coredevice.rtio import (rtio_output, rtio_input_data, from artiq.coredevice.rtio import (rtio_output, rtio_input_data,
rtio_input_timestamped_data) rtio_input_timestamped_data)
from numpy import int32, int64
CONFIG_COUNT_RISING = 0b0001 CONFIG_COUNT_RISING = 0b0001
CONFIG_COUNT_FALLING = 0b0010 CONFIG_COUNT_FALLING = 0b0010
@ -63,12 +65,14 @@ CONFIG_SEND_COUNT_EVENT = 0b0100
CONFIG_RESET_TO_ZERO = 0b1000 CONFIG_RESET_TO_ZERO = 0b1000
@nac3
class CounterOverflow(Exception): class CounterOverflow(Exception):
"""Raised when an edge counter value is read which indicates that the """Raised when an edge counter value is read which indicates that the
counter might have overflowed.""" counter might have overflowed."""
pass pass
@nac3
class EdgeCounter: class EdgeCounter:
"""RTIO TTL edge counter driver driver. """RTIO TTL edge counter driver driver.
@ -84,7 +88,9 @@ class EdgeCounter:
the gateware needs to be rebuilt. the gateware needs to be rebuilt.
""" """
kernel_invariants = {"core", "channel", "counter_max"} core: KernelInvariant[Core]
channel: KernelInvariant[int32]
counter_max: KernelInvariant[int32]
def __init__(self, dmgr, channel, gateware_width=31, core_device="core"): def __init__(self, dmgr, channel, gateware_width=31, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -96,7 +102,7 @@ class EdgeCounter:
return [(channel, None)] return [(channel, None)]
@kernel @kernel
def gate_rising(self, duration): def gate_rising(self, duration: float) -> int64:
"""Count rising edges for the given duration and request the total at """Count rising edges for the given duration and request the total at
the end. the end.
@ -110,7 +116,7 @@ class EdgeCounter:
return self.gate_rising_mu(self.core.seconds_to_mu(duration)) return self.gate_rising_mu(self.core.seconds_to_mu(duration))
@kernel @kernel
def gate_falling(self, duration): def gate_falling(self, duration: float) -> int64:
"""Count falling edges for the given duration and request the total at """Count falling edges for the given duration and request the total at
the end. the end.
@ -124,7 +130,7 @@ class EdgeCounter:
return self.gate_falling_mu(self.core.seconds_to_mu(duration)) return self.gate_falling_mu(self.core.seconds_to_mu(duration))
@kernel @kernel
def gate_both(self, duration): def gate_both(self, duration: float) -> int64:
"""Count both rising and falling edges for the given duration, and """Count both rising and falling edges for the given duration, and
request the total at the end. request the total at the end.
@ -138,25 +144,25 @@ class EdgeCounter:
return self.gate_both_mu(self.core.seconds_to_mu(duration)) return self.gate_both_mu(self.core.seconds_to_mu(duration))
@kernel @kernel
def gate_rising_mu(self, duration_mu): def gate_rising_mu(self, duration_mu: int64) -> int64:
"""See :meth:`gate_rising`.""" """See :meth:`gate_rising`."""
return self._gate_mu( return self._gate_mu(
duration_mu, count_rising=True, count_falling=False) duration_mu, count_rising=True, count_falling=False)
@kernel @kernel
def gate_falling_mu(self, duration_mu): def gate_falling_mu(self, duration_mu: int64) -> int64:
"""See :meth:`gate_falling`.""" """See :meth:`gate_falling`."""
return self._gate_mu( return self._gate_mu(
duration_mu, count_rising=False, count_falling=True) duration_mu, count_rising=False, count_falling=True)
@kernel @kernel
def gate_both_mu(self, duration_mu): def gate_both_mu(self, duration_mu: int64) -> int64:
"""See :meth:`gate_both_mu`.""" """See :meth:`gate_both_mu`."""
return self._gate_mu( return self._gate_mu(
duration_mu, count_rising=True, count_falling=True) duration_mu, count_rising=True, count_falling=True)
@kernel @kernel
def _gate_mu(self, duration_mu, count_rising, count_falling): def _gate_mu(self, duration_mu: int64, count_rising: bool, count_falling: bool) -> int64:
self.set_config( self.set_config(
count_rising=count_rising, count_rising=count_rising,
count_falling=count_falling, count_falling=count_falling,
@ -171,8 +177,8 @@ class EdgeCounter:
return now_mu() return now_mu()
@kernel @kernel
def set_config(self, count_rising: TBool, count_falling: TBool, def set_config(self, count_rising: bool, count_falling: bool,
send_count_event: TBool, reset_to_zero: TBool): send_count_event: bool, reset_to_zero: bool):
"""Emit an RTIO event at the current timeline position to set the """Emit an RTIO event at the current timeline position to set the
gateware configuration. gateware configuration.
@ -197,7 +203,7 @@ class EdgeCounter:
rtio_output(self.channel << 8, config) rtio_output(self.channel << 8, config)
@kernel @kernel
def fetch_count(self) -> TInt32: def fetch_count(self) -> int32:
"""Wait for and return count total from previously requested input """Wait for and return count total from previously requested input
event. event.
@ -216,7 +222,7 @@ class EdgeCounter:
@kernel @kernel
def fetch_timestamped_count( def fetch_timestamped_count(
self, timeout_mu=int64(-1)) -> TTuple([TInt64, TInt32]): self, timeout_mu: int64 = int64(-1)) -> tuple[int64, int32]:
"""Wait for and return the timestamp and count total of a previously """Wait for and return the timestamp and count total of a previously
requested input event. requested input event.

View File

@ -1,121 +1,8 @@
import builtins from artiq.language.core import nac3, UnwrapNoneError
import linecache from builtins import ZeroDivisionError, ValueError, IndexError, RuntimeError, AssertionError
import re
import os
from artiq import __artiq_dir__ as artiq_dir
from artiq.coredevice.runtime import source_loader
ZeroDivisionError = builtins.ZeroDivisionError
ValueError = builtins.ValueError
IndexError = builtins.IndexError
RuntimeError = builtins.RuntimeError
AssertionError = builtins.AssertionError
class CoreException:
"""Information about an exception raised or passed through the core device."""
def __init__(self, exceptions, exception_info, traceback, stack_pointers):
self.exceptions = exceptions
self.exception_info = exception_info
self.traceback = list(traceback)
self.stack_pointers = stack_pointers
first_exception = exceptions[0]
name = first_exception[0]
if ':' in name:
exn_id, self.name = name.split(':', 2)
self.id = int(exn_id)
else:
self.id, self.name = 0, name
self.message = first_exception[1]
self.params = first_exception[2]
def append_backtrace(self, record, inlined=False):
filename, line, column, function, address = record
stub_globals = {"__name__": filename, "__loader__": source_loader}
source_line = linecache.getline(filename, line, stub_globals)
indentation = re.search(r"^\s*", source_line).end()
if address is None:
formatted_address = ""
elif inlined:
formatted_address = " (inlined)"
else:
formatted_address = " (RA=+0x{:x})".format(address)
filename = filename.replace(artiq_dir, "<artiq>")
lines = []
if column == -1:
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
lines.append(" File \"{file}\", line {line}, in {function}{address}".
format(file=filename, line=line, function=function,
address=formatted_address))
else:
lines.append(" {}^".format(" " * (column - indentation)))
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
lines.append(" File \"{file}\", line {line}, column {column},"
" in {function}{address}".
format(file=filename, line=line, column=column + 1,
function=function, address=formatted_address))
return lines
def single_traceback(self, exception_index):
# note that we insert in reversed order
lines = []
last_sp = 0
start_backtrace_index = self.exception_info[exception_index][1]
zipped = list(zip(self.traceback[start_backtrace_index:],
self.stack_pointers[start_backtrace_index:]))
exception = self.exceptions[exception_index]
name = exception[0]
message = exception[1]
params = exception[2]
if ':' in name:
exn_id, name = name.split(':', 2)
exn_id = int(exn_id)
else:
exn_id = 0
lines.append("{}({}): {}".format(name, exn_id, message.format(*params)))
zipped.append(((exception[3], exception[4], exception[5], exception[6],
None, []), None))
for ((filename, line, column, function, address, inlined), sp) in zipped:
# backtrace of nested exceptions may be discontinuous
# but the stack pointer must increase monotonically
if sp is not None and sp <= last_sp:
continue
last_sp = sp
for record in reversed(inlined):
lines += self.append_backtrace(record, True)
lines += self.append_backtrace((filename, line, column, function,
address))
lines.append("Traceback (most recent call first):")
return "\n".join(reversed(lines))
def __str__(self):
tracebacks = [self.single_traceback(i) for i in range(len(self.exceptions))]
traceback_str = ('\n\nDuring handling of the above exception, ' +
'another exception occurred:\n\n').join(tracebacks)
return 'Core Device Traceback:\n' +\
traceback_str +\
'\n\nEnd of Core Device Traceback\n'
class InternalError(Exception):
"""Raised when the runtime encounters an internal error condition."""
artiq_builtin = True
class CacheError(Exception):
"""Raised when putting a value into a cache row would violate memory safety."""
artiq_builtin = True
@nac3
class RTIOUnderflow(Exception): class RTIOUnderflow(Exception):
"""Raised when the CPU or DMA core fails to submit a RTIO event early """Raised when the CPU or DMA core fails to submit a RTIO event early
enough (with respect to the event's timestamp). enough (with respect to the event's timestamp).
@ -125,6 +12,7 @@ class RTIOUnderflow(Exception):
artiq_builtin = True artiq_builtin = True
@nac3
class RTIOOverflow(Exception): class RTIOOverflow(Exception):
"""Raised when at least one event could not be registered into the RTIO """Raised when at least one event could not be registered into the RTIO
input FIFO because it was full (CPU not reading fast enough). input FIFO because it was full (CPU not reading fast enough).
@ -136,6 +24,7 @@ class RTIOOverflow(Exception):
artiq_builtin = True artiq_builtin = True
@nac3
class RTIODestinationUnreachable(Exception): class RTIODestinationUnreachable(Exception):
"""Raised with a RTIO operation could not be completed due to a DRTIO link """Raised with a RTIO operation could not be completed due to a DRTIO link
being down. being down.
@ -143,27 +32,32 @@ class RTIODestinationUnreachable(Exception):
artiq_builtin = True artiq_builtin = True
@nac3
class CacheError(Exception):
"""Raised when putting a value into a cache row would violate memory safety."""
artiq_builtin = True
@nac3
class DMAError(Exception): class DMAError(Exception):
"""Raised when performing an invalid DMA operation.""" """Raised when performing an invalid DMA operation."""
artiq_builtin = True artiq_builtin = True
class SubkernelError(Exception): @nac3
"""Raised when an operation regarding a subkernel is invalid class ClockFailure(Exception):
or cannot be completed. """Raised when RTIO PLL has lost lock."""
"""
artiq_builtin = True artiq_builtin = True
class ClockFailure(Exception): @nac3
"""Raised when RTIO PLL has lost lock."""
class I2CError(Exception): class I2CError(Exception):
"""Raised when a I2C transaction fails.""" """Raised when a I2C transaction fails."""
pass artiq_builtin = True
@nac3
class SPIError(Exception): class SPIError(Exception):
"""Raised when a SPI transaction fails.""" """Raised when a SPI transaction fails."""
pass artiq_builtin = True

View File

@ -3,13 +3,14 @@ streaming DAC.
""" """
from numpy import int32, int64 from numpy import int32, int64
from artiq.language.core import kernel, portable, delay, delay_mu from artiq.language.core import nac3, kernel, portable, KernelInvariant
from artiq.coredevice.rtio import (rtio_output, rtio_output_wide, from artiq.coredevice.rtio import (rtio_output, rtio_output_wide,
rtio_input_data) rtio_input_data)
from artiq.language.units import ns from artiq.language.units import ns
from artiq.language.types import TInt32, TList from artiq.coredevice.core import Core
@nac3
class Fastino: class Fastino:
"""Fastino 32-channel, 16-bit, 2.5 MS/s per channel streaming DAC """Fastino 32-channel, 16-bit, 2.5 MS/s per channel streaming DAC
@ -41,7 +42,11 @@ class Fastino:
:param log2_width: Width of DAC channel group (logarithm base 2). :param log2_width: Width of DAC channel group (logarithm base 2).
Value must match the corresponding value in the RTIO PHY (gateware). Value must match the corresponding value in the RTIO PHY (gateware).
""" """
kernel_invariants = {"core", "channel", "width", "t_frame"}
core: KernelInvariant[Core]
channel: KernelInvariant[int32]
width: KernelInvariant[int32]
t_frame: KernelInvariant[int64]
def __init__(self, dmgr, channel, core_device="core", log2_width=0): def __init__(self, dmgr, channel, core_device="core", log2_width=0):
self.channel = channel << 8 self.channel = channel << 8
@ -73,15 +78,15 @@ class Fastino:
Note: On Fastino gateware before v0.2 this may lead to 0 voltage being emitted Note: On Fastino gateware before v0.2 this may lead to 0 voltage being emitted
transiently. transiently.
""" """
self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=1) self.set_cfg(reset=False, afe_power_down=False, dac_clr=False, clr_err=True)
delay_mu(self.t_frame) delay_mu(self.t_frame)
self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=0) self.set_cfg(reset=False, afe_power_down=False, dac_clr=False, clr_err=False)
delay_mu(self.t_frame) delay_mu(self.t_frame)
self.set_continuous(0) self.set_continuous(0)
delay_mu(self.t_frame) delay_mu(self.t_frame)
self.stage_cic(1) self.stage_cic(1)
delay_mu(self.t_frame) delay_mu(self.t_frame)
self.apply_cic(0xffffffff) self.apply_cic(int32(int64(0xffffffff)))
delay_mu(self.t_frame) delay_mu(self.t_frame)
self.set_leds(0) self.set_leds(0)
delay_mu(self.t_frame) delay_mu(self.t_frame)
@ -89,7 +94,7 @@ class Fastino:
delay_mu(self.t_frame) delay_mu(self.t_frame)
@kernel @kernel
def write(self, addr, data): def write(self, addr: int32, data: int32):
"""Write data to a Fastino register. """Write data to a Fastino register.
:param addr: Address to write to. :param addr: Address to write to.
@ -98,7 +103,7 @@ class Fastino:
rtio_output(self.channel | addr, data) rtio_output(self.channel | addr, data)
@kernel @kernel
def read(self, addr): def read(self, addr: int32):
"""Read from Fastino register. """Read from Fastino register.
TODO: untested TODO: untested
@ -106,12 +111,12 @@ class Fastino:
:param addr: Address to read from. :param addr: Address to read from.
:return: The data read. :return: The data read.
""" """
raise NotImplementedError raise NotImplementedError()
# rtio_output(self.channel | addr | 0x80) # rtio_output(self.channel | addr | 0x80)
# return rtio_input_data(self.channel >> 8) # return rtio_input_data(self.channel >> 8)
@kernel @kernel
def set_dac_mu(self, dac, data): def set_dac_mu(self, dac: int32, data: int32):
"""Write DAC data in machine units. """Write DAC data in machine units.
:param dac: DAC channel to write to (0-31). :param dac: DAC channel to write to (0-31).
@ -121,7 +126,7 @@ class Fastino:
self.write(dac, data) self.write(dac, data)
@kernel @kernel
def set_group_mu(self, dac: TInt32, data: TList(TInt32)): def set_group_mu(self, dac: int32, data: list[int32]):
"""Write a group of DAC channels in machine units. """Write a group of DAC channels in machine units.
:param dac: First channel in DAC channel group (0-31). The `log2_width` :param dac: First channel in DAC channel group (0-31). The `log2_width`
@ -131,24 +136,24 @@ class Fastino:
If the list length is less than group size, the remaining If the list length is less than group size, the remaining
DAC channels within the group are cleared to 0 (machine units). DAC channels within the group are cleared to 0 (machine units).
""" """
if dac & (self.width - 1): if dac & (self.width - 1) != 0:
raise ValueError("Group index LSBs must be zero") raise ValueError("Group index LSBs must be zero")
rtio_output_wide(self.channel | dac, data) rtio_output_wide(self.channel | dac, data)
@portable @portable
def voltage_to_mu(self, voltage): def voltage_to_mu(self, voltage: float) -> int32:
"""Convert SI Volts to DAC machine units. """Convert SI Volts to DAC machine units.
:param voltage: Voltage in SI Volts. :param voltage: Voltage in SI Volts.
:return: DAC data word in machine units, 16 bit integer. :return: DAC data word in machine units, 16 bit integer.
""" """
data = int32(round((0x8000/10.)*voltage)) + int32(0x8000) data = int32(round((float(0x8000)/10.)*voltage)) + int32(0x8000)
if data < 0 or data > 0xffff: if data < 0 or data > 0xffff:
raise ValueError("DAC voltage out of bounds") raise ValueError("DAC voltage out of bounds")
return data return data
@portable @portable
def voltage_group_to_mu(self, voltage, data): def voltage_group_to_mu(self, voltage: list[float], data: list[int32]):
"""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.
@ -157,12 +162,12 @@ class Fastino:
""" """
for i in range(len(voltage)): for i in range(len(voltage)):
v = self.voltage_to_mu(voltage[i]) v = self.voltage_to_mu(voltage[i])
if i & 1: if i & 1 != 0:
v = data[i // 2] | (v << 16) v = data[i // 2] | (v << 16)
data[i // 2] = int32(v) data[i // 2] = int32(v)
@kernel @kernel
def set_dac(self, dac, voltage): def set_dac(self, dac: int32, voltage: float):
"""Set DAC data to given voltage. """Set DAC data to given voltage.
:param dac: DAC channel (0-31). :param dac: DAC channel (0-31).
@ -171,18 +176,18 @@ class Fastino:
self.write(dac, self.voltage_to_mu(voltage)) self.write(dac, self.voltage_to_mu(voltage))
@kernel @kernel
def set_group(self, dac, voltage): def set_group(self, dac: int32, voltage: list[float]):
"""Set DAC group data to given voltage. """Set DAC group data to given voltage.
:param dac: DAC channel (0-31). :param dac: DAC channel (0-31).
:param voltage: Desired output voltage. :param voltage: Desired output voltage.
""" """
data = [int32(0)] * (len(voltage) // 2) data = [int32(0) for _ in range(len(voltage) // 2)]
self.voltage_group_to_mu(voltage, data) self.voltage_group_to_mu(voltage, data)
self.set_group_mu(dac, data) self.set_group_mu(dac, data)
@kernel @kernel
def update(self, update): def update(self, update: int32):
"""Schedule channels for update. """Schedule channels for update.
:param update: Bit mask of channels to update (32 bit). :param update: Bit mask of channels to update (32 bit).
@ -190,7 +195,7 @@ class Fastino:
self.write(0x20, update) self.write(0x20, update)
@kernel @kernel
def set_hold(self, hold): def set_hold(self, hold: int32):
"""Set channels to manual update. """Set channels to manual update.
:param hold: Bit mask of channels to hold (32 bit). :param hold: Bit mask of channels to hold (32 bit).
@ -198,7 +203,7 @@ class Fastino:
self.write(0x21, hold) self.write(0x21, hold)
@kernel @kernel
def set_cfg(self, reset=0, afe_power_down=0, dac_clr=0, clr_err=0): def set_cfg(self, reset: bool = False, afe_power_down: bool = False, dac_clr: bool = False, clr_err: bool = False):
"""Set configuration bits. """Set configuration bits.
:param reset: Reset SPI PLL and SPI clock domain. :param reset: Reset SPI PLL and SPI clock domain.
@ -209,11 +214,11 @@ class Fastino:
This clears the sticky red error LED. Must be cleared to enable This clears the sticky red error LED. Must be cleared to enable
error counting. error counting.
""" """
self.write(0x22, (reset << 0) | (afe_power_down << 1) | self.write(0x22, (int32(reset) << 0) | (int32(afe_power_down) << 1) |
(dac_clr << 2) | (clr_err << 3)) (int32(dac_clr) << 2) | (int32(clr_err) << 3))
@kernel @kernel
def set_leds(self, leds): def set_leds(self, leds: int32):
"""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
@ -222,14 +227,14 @@ class Fastino:
self.write(0x23, leds) self.write(0x23, leds)
@kernel @kernel
def set_continuous(self, channel_mask): def set_continuous(self, channel_mask: int32):
"""Enable continuous DAC updates on channels regardless of new data """Enable continuous DAC updates on channels regardless of new data
being submitted. being submitted.
""" """
self.write(0x25, channel_mask) self.write(0x25, channel_mask)
@kernel @kernel
def stage_cic_mu(self, rate_mantissa, rate_exponent, gain_exponent): def stage_cic_mu(self, rate_mantissa: int32, rate_exponent: int32, gain_exponent: int32):
"""Stage machine unit CIC interpolator configuration. """Stage machine unit CIC interpolator configuration.
""" """
if rate_mantissa < 0 or rate_mantissa >= 1 << 6: if rate_mantissa < 0 or rate_mantissa >= 1 << 6:
@ -242,7 +247,7 @@ class Fastino:
self.write(0x26, config) self.write(0x26, config)
@kernel @kernel
def stage_cic(self, rate) -> TInt32: def stage_cic(self, rate: int32) -> int32:
"""Compute and stage interpolator configuration. """Compute and stage interpolator configuration.
This method approximates the desired interpolation rate using a 10 bit This method approximates the desired interpolation rate using a 10 bit
@ -278,7 +283,7 @@ class Fastino:
return rate_mantissa << rate_exponent return rate_mantissa << rate_exponent
@kernel @kernel
def apply_cic(self, channel_mask): def apply_cic(self, channel_mask: int32):
"""Apply the staged interpolator configuration on the specified channels. """Apply the staged interpolator configuration on the specified channels.
Each Fastino channel starting with gateware v0.2 includes a fourth order Each Fastino channel starting with gateware v0.2 includes a fourth order

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

@ -1,19 +1,23 @@
from numpy import int32, int64 from numpy import int32, int64
from artiq.language.core import * from artiq.language.core import *
from artiq.language.types import *
from artiq.coredevice.rtio import rtio_output, rtio_input_data from artiq.coredevice.rtio import rtio_output, rtio_input_data
from artiq.coredevice.core import Core
@nac3
class OutOfSyncException(Exception): class OutOfSyncException(Exception):
"""Raised when an incorrect number of ROI engine outputs has been """Raised when an incorrect number of ROI engine outputs has been
retrieved from the RTIO input FIFO.""" retrieved from the RTIO input FIFO."""
pass pass
@nac3
class Grabber: class Grabber:
"""Driver for the Grabber camera interface.""" """Driver for the Grabber camera interface."""
kernel_invariants = {"core", "channel_base", "sentinel"} core: KernelInvariant[Core]
channel_base: KernelInvariant[int32]
sentinel: KernelInvariant[int32]
def __init__(self, dmgr, channel_base, res_width=12, count_shift=0, def __init__(self, dmgr, channel_base, res_width=12, count_shift=0,
core_device="core"): core_device="core"):
@ -30,7 +34,7 @@ class Grabber:
return [(channel_base, "ROI coordinates"), (channel_base + 1, "ROI mask")] return [(channel_base, "ROI coordinates"), (channel_base + 1, "ROI mask")]
@kernel @kernel
def setup_roi(self, n, x0, y0, x1, y1): def setup_roi(self, n: int32, x0: int32, y0: int32, x1: int32, y1: int32):
""" """
Defines the coordinates of a ROI. Defines the coordinates of a ROI.
@ -54,7 +58,7 @@ class Grabber:
delay_mu(c) delay_mu(c)
@kernel @kernel
def gate_roi(self, mask): def gate_roi(self, mask: int32):
""" """
Defines which ROI engines produce input events. Defines which ROI engines produce input events.
@ -74,15 +78,15 @@ class Grabber:
rtio_output((self.channel_base + 1) << 8, mask) rtio_output((self.channel_base + 1) << 8, mask)
@kernel @kernel
def gate_roi_pulse(self, mask, dt): def gate_roi_pulse(self, mask: int32, dt: float):
"""Sets a temporary mask for the specified duration (in seconds), before """Sets a temporary mask for the specified duration (in seconds), before
disabling all ROI engines.""" disabling all ROI engines."""
self.gate_roi(mask) self.gate_roi(mask)
delay(dt) self.core.delay(dt)
self.gate_roi(0) self.gate_roi(0)
@kernel @kernel
def input_mu(self, data): def input_mu(self, data: list[int32]):
""" """
Retrieves the accumulated values for one frame from the ROI engines. Retrieves the accumulated values for one frame from the ROI engines.
Blocks until values are available. Blocks until values are available.
@ -100,10 +104,10 @@ class Grabber:
sentinel = rtio_input_data(channel) sentinel = rtio_input_data(channel)
if sentinel != self.sentinel: if sentinel != self.sentinel:
raise OutOfSyncException raise OutOfSyncException()
for i in range(len(data)): for i in range(len(data)):
roi_output = rtio_input_data(channel) roi_output = rtio_input_data(channel)
if roi_output == self.sentinel: if roi_output == self.sentinel:
raise OutOfSyncException raise OutOfSyncException()
data[i] = roi_output data[i] = roi_output

View File

@ -2,44 +2,45 @@
Non-realtime drivers for I2C chips on the core device. Non-realtime drivers for I2C chips on the core device.
""" """
from numpy import int32
from artiq.language.core import syscall, kernel from artiq.language.core import nac3, extern, kernel, KernelInvariant
from artiq.language.types import TBool, TInt32, TNone
from artiq.coredevice.exceptions import I2CError from artiq.coredevice.exceptions import I2CError
from artiq.coredevice.core import Core
@syscall(flags={"nounwind", "nowrite"}) @extern
def i2c_start(busno: TInt32) -> TNone: def i2c_start(busno: int32):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) @extern
def i2c_restart(busno: TInt32) -> TNone: def i2c_restart(busno: int32):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) @extern
def i2c_stop(busno: TInt32) -> TNone: def i2c_stop(busno: int32):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) @extern
def i2c_write(busno: TInt32, b: TInt32) -> TBool: def i2c_write(busno: int32, b: int32) -> bool:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) @extern
def i2c_read(busno: TInt32, ack: TBool) -> TInt32: def i2c_read(busno: int32, ack: bool) -> int32:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) @extern
def i2c_switch_select(busno: TInt32, address: TInt32, mask: TInt32) -> TNone: def i2c_switch_select(busno: int32, address: int32, mask: int32):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@kernel @kernel
def i2c_poll(busno, busaddr): def i2c_poll(busno: int32, busaddr: int32) -> bool:
"""Poll I2C device at address. """Poll I2C device at address.
:param busno: I2C bus number :param busno: I2C bus number
@ -53,7 +54,7 @@ def i2c_poll(busno, busaddr):
@kernel @kernel
def i2c_write_byte(busno, busaddr, data, ack=True): def i2c_write_byte(busno: int32, busaddr: int32, data: int32, ack: bool = True):
"""Write one byte to a device. """Write one byte to a device.
:param busno: I2C bus number :param busno: I2C bus number
@ -72,7 +73,7 @@ def i2c_write_byte(busno, busaddr, data, ack=True):
@kernel @kernel
def i2c_read_byte(busno, busaddr): def i2c_read_byte(busno: int32, busaddr: int32) -> int32:
"""Read one byte from a device. """Read one byte from a device.
:param busno: I2C bus number :param busno: I2C bus number
@ -91,7 +92,7 @@ def i2c_read_byte(busno, busaddr):
@kernel @kernel
def i2c_write_many(busno, busaddr, addr, data, ack_last=True): def i2c_write_many(busno: int32, busaddr: int32, addr: int32, data: list[int32], ack_last: bool = True):
"""Transfer multiple bytes to a device. """Transfer multiple bytes to a device.
:param busno: I2c bus number :param busno: I2c bus number
@ -117,7 +118,7 @@ def i2c_write_many(busno, busaddr, addr, data, ack_last=True):
@kernel @kernel
def i2c_read_many(busno, busaddr, addr, data): def i2c_read_many(busno: int32, busaddr: int32, addr: int32, data: list[int32]):
"""Transfer multiple bytes from a device. """Transfer multiple bytes from a device.
:param busno: I2c bus number :param busno: I2c bus number
@ -142,6 +143,7 @@ def i2c_read_many(busno, busaddr, addr, data):
i2c_stop(busno) i2c_stop(busno)
@nac3
class I2CSwitch: class I2CSwitch:
"""Driver for the I2C bus switch. """Driver for the I2C bus switch.
@ -153,13 +155,18 @@ class I2CSwitch:
On the KC705, this chip is used for selecting the I2C buses on the two FMC On the KC705, this chip is used for selecting the I2C buses on the two FMC
connectors. HPC=1, LPC=2. connectors. HPC=1, LPC=2.
""" """
core: KernelInvariant[Core]
busno: KernelInvariant[int32]
address: KernelInvariant[int32]
def __init__(self, dmgr, busno=0, address=0xe8, core_device="core"): def __init__(self, dmgr, busno=0, address=0xe8, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
self.busno = busno self.busno = busno
self.address = address self.address = address
@kernel @kernel
def set(self, channel): def set(self, channel: int32):
"""Enable one channel. """Enable one channel.
:param channel: channel number (0-7) :param channel: channel number (0-7)
@ -173,6 +180,7 @@ class I2CSwitch:
i2c_switch_select(self.busno, self.address >> 1, 0) i2c_switch_select(self.busno, self.address >> 1, 0)
@kernel
class TCA6424A: class TCA6424A:
"""Driver for the TCA6424A I2C I/O expander. """Driver for the TCA6424A I2C I/O expander.
@ -181,18 +189,23 @@ class TCA6424A:
On the NIST QC2 hardware, this chip is used for switching the directions On the NIST QC2 hardware, this chip is used for switching the directions
of TTL buffers.""" of TTL buffers."""
core: KernelInvariant[Core]
busno: KernelInvariant[int32]
address: KernelInvariant[int32]
def __init__(self, dmgr, busno=0, address=0x44, core_device="core"): def __init__(self, dmgr, busno=0, address=0x44, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
self.busno = busno self.busno = busno
self.address = address self.address = address
@kernel @kernel
def _write24(self, addr, value): def _write24(self, addr: int32, value: int32):
i2c_write_many(self.busno, self.address, addr, i2c_write_many(self.busno, self.address, addr,
[value >> 16, value >> 8, value]) [value >> 16, value >> 8, value])
@kernel @kernel
def set(self, outputs): def set(self, outputs: int32):
"""Drive all pins of the chip to the levels given by the """Drive all pins of the chip to the levels given by the
specified 24-bit word. specified 24-bit word.
@ -209,19 +222,26 @@ class TCA6424A:
self._write24(0x8c, 0) # set all directions to output self._write24(0x8c, 0) # set all directions to output
self._write24(0x84, outputs_le) # set levels self._write24(0x84, outputs_le) # set levels
@nac3
class PCF8574A: class PCF8574A:
"""Driver for the PCF8574 I2C remote 8-bit I/O expander. """Driver for the PCF8574 I2C remote 8-bit I/O expander.
I2C transactions not real-time, and are performed by the CPU without I2C transactions not real-time, and are performed by the CPU without
involving RTIO. involving RTIO.
""" """
core: KernelInvariant[Core]
busno: KernelInvariant[int32]
address: KernelInvariant[int32]
def __init__(self, dmgr, busno=0, address=0x7c, core_device="core"): def __init__(self, dmgr, busno=0, address=0x7c, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
self.busno = busno self.busno = busno
self.address = address self.address = address
@kernel @kernel
def set(self, data): def set(self, data: int32):
"""Drive data on the quasi-bidirectional pins. """Drive data on the quasi-bidirectional pins.
:param data: Pin data. High bits are weakly driven high :param data: Pin data. High bits are weakly driven high
@ -237,7 +257,7 @@ class PCF8574A:
i2c_stop(self.busno) i2c_stop(self.busno)
@kernel @kernel
def get(self): def get(self) -> int32:
"""Retrieve quasi-bidirectional pin input data. """Retrieve quasi-bidirectional pin input data.
:return: Pin data :return: Pin data

View File

@ -1,7 +1,8 @@
from numpy import int32 from numpy import int32
from artiq.experiment import * from artiq.experiment import *
from artiq.coredevice.i2c import i2c_write_many, i2c_read_many, i2c_poll from artiq.coredevice.core import Core
from artiq.coredevice.i2c import I2CSwitch, i2c_write_many, i2c_read_many, i2c_poll
port_mapping = { port_mapping = {
@ -24,7 +25,15 @@ port_mapping = {
} }
@nac3
class KasliEEPROM: class KasliEEPROM:
core: KernelInvariant[Core]
sw0: KernelInvariant[I2CSwitch]
sw1: KernelInvariant[I2CSwitch]
busno: KernelInvariant[int32]
port: KernelInvariant[int32]
address: KernelInvariant[int32]
def __init__(self, dmgr, port, busno=0, def __init__(self, dmgr, port, busno=0,
core_device="core", sw0_device="i2c_switch0", sw1_device="i2c_switch1"): core_device="core", sw0_device="i2c_switch0", sw1_device="i2c_switch1"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -50,10 +59,10 @@ class KasliEEPROM:
self.sw1.unset() self.sw1.unset()
@kernel @kernel
def write_i32(self, addr, value): def write_i32(self, addr: int32, value: int32):
self.select() self.select()
try: try:
data = [0]*4 data = [0 for _ in range(4)]
for i in range(4): for i in range(4):
data[i] = (value >> 24) & 0xff data[i] = (value >> 24) & 0xff
value <<= 8 value <<= 8
@ -63,12 +72,12 @@ class KasliEEPROM:
self.deselect() self.deselect()
@kernel @kernel
def read_i32(self, addr): def read_i32(self, addr: int32) -> int32:
self.select() self.select()
try:
data = [0]*4
i2c_read_many(self.busno, self.address, addr, data)
value = int32(0) value = int32(0)
try:
data = [0 for _ in range(4)]
i2c_read_many(self.busno, self.address, addr, data)
for i in range(4): for i in range(4):
value <<= 8 value <<= 8
value |= data[i] value |= data[i]

View File

@ -1,23 +1,24 @@
"""RTIO driver for Mirny (4 channel GHz PLLs) """RTIO driver for Mirny (4 channel GHz PLLs)
""" """
from artiq.language.core import kernel, delay, portable from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable
from artiq.language.units import us from artiq.language.units import us
from numpy import int32 from numpy import int32
from artiq.coredevice import spi2 as spi from artiq.coredevice.core import Core
from artiq.coredevice.spi2 import *
SPI_CONFIG = ( SPI_CONFIG = (
0 * spi.SPI_OFFLINE 0 * SPI_OFFLINE
| 0 * spi.SPI_END | 0 * SPI_END
| 0 * spi.SPI_INPUT | 0 * SPI_INPUT
| 1 * spi.SPI_CS_POLARITY | 1 * SPI_CS_POLARITY
| 0 * spi.SPI_CLK_POLARITY | 0 * SPI_CLK_POLARITY
| 0 * spi.SPI_CLK_PHASE | 0 * SPI_CLK_PHASE
| 0 * spi.SPI_LSB_FIRST | 0 * SPI_LSB_FIRST
| 0 * spi.SPI_HALF_DUPLEX | 0 * SPI_HALF_DUPLEX
) )
# SPI clock write and read dividers # SPI clock write and read dividers
@ -32,6 +33,7 @@ WE = 1 << 24
PROTO_REV_MATCH = 0x0 PROTO_REV_MATCH = 0x0
@nac3
class Mirny: class Mirny:
""" """
Mirny PLL-based RF generator. Mirny PLL-based RF generator.
@ -46,8 +48,12 @@ class Mirny:
The effect depends on the hardware revision. The effect depends on the hardware revision.
:param core_device: Core device name (default: "core") :param core_device: Core device name (default: "core")
""" """
core: KernelInvariant[Core]
kernel_invariants = {"bus", "core", "refclk", "clk_sel_hw_rev"} bus: KernelInvariant[SPIMaster]
refclk: KernelInvariant[float]
clk_sel_hw_rev: Kernel[list[int32]]
hw_rev: Kernel[int32]
clk_sel: Kernel[int32]
def __init__(self, dmgr, spi_device, refclk=100e6, clk_sel="XO", core_device="core"): def __init__(self, dmgr, spi_device, refclk=100e6, clk_sel="XO", core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -81,22 +87,22 @@ class Mirny:
# TODO: support clk_div on v1.0 boards # TODO: support clk_div on v1.0 boards
@kernel @kernel
def read_reg(self, addr): def read_reg(self, addr: int32) -> int32:
"""Read a register""" """Read a register"""
self.bus.set_config_mu( self.bus.set_config_mu(
SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 24, SPIT_RD, SPI_CS SPI_CONFIG | SPI_INPUT | SPI_END, 24, SPIT_RD, SPI_CS
) )
self.bus.write((addr << 25)) self.bus.write((addr << 25))
return self.bus.read() & int32(0xFFFF) return self.bus.read() & 0xFFFF
@kernel @kernel
def write_reg(self, addr, data): def write_reg(self, addr: int32, data: int32):
"""Write a register""" """Write a register"""
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, SPIT_WR, SPI_CS) self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24, SPIT_WR, SPI_CS)
self.bus.write((addr << 25) | WE | ((data & 0xFFFF) << 8)) self.bus.write((addr << 25) | WE | ((data & 0xFFFF) << 8))
@kernel @kernel
def init(self, blind=False): def init(self, blind: bool = False):
""" """
Initialize and detect Mirny. Initialize and detect Mirny.
@ -111,7 +117,7 @@ class Mirny:
if not blind: if not blind:
if (reg0 >> 2) & 0x3 != PROTO_REV_MATCH: if (reg0 >> 2) & 0x3 != PROTO_REV_MATCH:
raise ValueError("Mirny PROTO_REV mismatch") raise ValueError("Mirny PROTO_REV mismatch")
delay(100 * us) # slack self.core.delay(100. * us) # slack
# select clock source # select clock source
self.clk_sel = self.clk_sel_hw_rev[self.hw_rev] self.clk_sel = self.clk_sel_hw_rev[self.hw_rev]
@ -120,31 +126,31 @@ class Mirny:
raise ValueError("Hardware revision not supported") raise ValueError("Hardware revision not supported")
self.write_reg(1, (self.clk_sel << 4)) self.write_reg(1, (self.clk_sel << 4))
delay(1000 * us) self.core.delay(1000. * us)
@portable(flags={"fast-math"}) @portable
def att_to_mu(self, att): def att_to_mu(self, att: float) -> int32:
"""Convert an attenuation setting in dB to machine units. """Convert an attenuation setting in dB to machine units.
:param att: Attenuation setting in dB. :param att: Attenuation setting in dB.
:return: Digital attenuation setting. :return: Digital attenuation setting.
""" """
code = int32(255) - int32(round(att * 8)) code = int32(255) - int32(round(att * 8.))
if code < 0 or code > 255: if code < 0 or code > 255:
raise ValueError("Invalid Mirny attenuation!") raise ValueError("Invalid Mirny attenuation!")
return code return code
@kernel @kernel
def set_att_mu(self, channel, att): def set_att_mu(self, channel: int32, att: int32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
:param att: Attenuation setting, 8 bit digital. :param att: Attenuation setting, 8 bit digital.
""" """
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 16, SPIT_WR, SPI_CS) self.bus.set_config_mu(SPI_CONFIG | SPI_END, 16, SPIT_WR, SPI_CS)
self.bus.write(((channel | 8) << 25) | (att << 16)) self.bus.write(((channel | 8) << 25) | (att << 16))
@kernel @kernel
def set_att(self, channel, att): def set_att(self, channel: int32, att: float):
"""Set digital step attenuator in SI units. """Set digital step attenuator in SI units.
This method will write the attenuator settings of the selected channel. This method will write the attenuator settings of the selected channel.
@ -159,11 +165,11 @@ class Mirny:
self.set_att_mu(channel, self.att_to_mu(att)) self.set_att_mu(channel, self.att_to_mu(att))
@kernel @kernel
def write_ext(self, addr, length, data, ext_div=SPIT_WR): def write_ext(self, addr: int32, length: int32, data: int32, ext_div: int32 = SPIT_WR):
"""Perform SPI write to a prefixed address""" """Perform SPI write to a prefixed address"""
self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_WR, SPI_CS) self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_WR, SPI_CS)
self.bus.write(addr << 25) self.bus.write(addr << 25)
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, length, ext_div, SPI_CS) self.bus.set_config_mu(SPI_CONFIG | SPI_END, length, ext_div, SPI_CS)
if length < 32: if length < 32:
data <<= 32 - length data <<= 32 - length
self.bus.write(data) self.bus.write(data)

View File

@ -1,174 +0,0 @@
from artiq.language.core import kernel, delay, portable
from artiq.language.units import ns
from artiq.coredevice import spi2 as spi
SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
SPI_CS_ADC = 1
SPI_CS_SR = 2
@portable
def adc_ctrl(channel=1, softspan=0b111, valid=1):
"""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 data & 0x7
@portable
def adc_channel(data):
"""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 (data >> 8) & 0xffff
@portable
def adc_value(data, v_ref=5.):
"""Convert a ADC result packet to SI units (Volt)"""
softspan = adc_softspan(data)
data = adc_data(data)
g = 625
if softspan & 4:
g *= 2
if softspan & 2:
h = 1 << 15
else:
h = 1 << 16
data = -(data & h) + (data & ~h)
if softspan & 1:
h *= 500
else:
h *= 512
v_per_lsb = v_ref*g/h
return data*v_per_lsb
class Novogorny:
"""Novogorny ADC.
Controls the LTC2335-16 8 channel ADC with SPI interface and
the switchable gain instrumentation amplifiers using a shift
register.
:param spi_device: SPI bus device name
:param cnv_device: CNV RTIO TTLOut channel name
:param div: SPI clock divider (default: 8)
:param gains: Initial value for PGIA gains shift register
(default: 0x0000). Knowledge of this state is not transferred
between experiments.
:param core_device: Core device name
"""
kernel_invariants = {"bus", "core", "cnv", "div", "v_ref"}
def __init__(self, dmgr, spi_device, cnv_device, div=8, gains=0x0000,
core_device="core"):
self.bus = dmgr.get(spi_device)
self.core = dmgr.get(core_device)
self.cnv = dmgr.get(cnv_device)
self.div = div
self.gains = gains
self.v_ref = 5. # 5 Volt reference
@kernel
def set_gain_mu(self, channel, gain):
"""Set instrumentation amplifier gain of a channel.
The four gain settings (0, 1, 2, 3) corresponds to gains of
(1, 10, 100, 1000) respectively.
:param channel: Channel index
:param gain: Gain setting
"""
gains = self.gains
gains &= ~(0b11 << (channel*2))
gains |= gain << (channel*2)
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END,
16, self.div, SPI_CS_SR)
self.bus.write(gains << 16)
self.gains = gains
@kernel
def configure(self, data):
"""Set up the ADC sequencer.
:param data: List of 8 bit control words to write into the sequencer
table.
"""
if len(data) > 1:
self.bus.set_config_mu(SPI_CONFIG,
8, self.div, SPI_CS_ADC)
for i in range(len(data) - 1):
self.bus.write(data[i] << 24)
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END,
8, self.div, SPI_CS_ADC)
self.bus.write(data[len(data) - 1] << 24)
@kernel
def sample_mu(self, next_ctrl=0):
"""Acquire a sample:
Perform a conversion and transfer the sample.
:param next_ctrl: ADC control word for the next sample
:return: The ADC result packet (machine units)
"""
self.cnv.pulse(40*ns) # t_CNVH
delay(560*ns) # t_CONV max
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END,
24, self.div, SPI_CS_ADC)
self.bus.write(next_ctrl << 24)
return self.bus.read()
@kernel
def sample(self, next_ctrl=0):
"""Acquire a sample
.. seealso:: :meth:`sample_mu`
:param next_ctrl: ADC control word for the next sample
:return: The ADC result packet (Volt)
"""
return adc_value(self.sample_mu(), self.v_ref)
@kernel
def burst_mu(self, data, dt_mu, ctrl=0):
"""Acquire a burst of samples.
If the burst is too long and the sample rate too high, there will be
RTIO input overflows.
High sample rates lead to gain errors since the impedance between the
instrumentation amplifier and the ADC is high.
:param data: List of data values to write result packets into.
In machine units.
:param dt: Sample interval in machine units.
:param ctrl: ADC control word to write during each result packet
transfer.
"""
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END,
24, self.div, SPI_CS_ADC)
for i in range(len(data)):
t0 = now_mu()
self.cnv.pulse(40*ns) # t_CNVH
delay(560*ns) # t_CONV max
self.bus.write(ctrl << 24)
at_mu(t0 + dt_mu)
for i in range(len(data)):
data[i] = self.bus.read()

File diff suppressed because it is too large Load Diff

View File

@ -1,30 +1,31 @@
from artiq.language.core import syscall from numpy import int32, int64
from artiq.language.types import TInt32, TInt64, TList, TNone, TTuple
from artiq.language.core import extern
@syscall(flags={"nowrite"}) @extern
def rtio_output(target: TInt32, data: TInt32) -> TNone: def rtio_output(target: int32, data: int32):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nowrite"}) @extern
def rtio_output_wide(target: TInt32, data: TList(TInt32)) -> TNone: def rtio_output_wide(target: int32, data: list[int32]):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nowrite"}) @extern
def rtio_input_timestamp(timeout_mu: TInt64, channel: TInt32) -> TInt64: def rtio_input_timestamp(timeout_mu: int64, channel: int32) -> int64:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nowrite"}) @extern
def rtio_input_data(channel: TInt32) -> TInt32: def rtio_input_data(channel: int32) -> int32:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nowrite"}) @extern
def rtio_input_timestamped_data(timeout_mu: TInt64, def rtio_input_timestamped_data(timeout_mu: int64,
channel: TInt32) -> TTuple([TInt64, TInt32]): channel: int32) -> tuple[int64, int32]:
"""Wait for an input event up to timeout_mu on the given channel, and """Wait for an input event up to timeout_mu on the given channel, and
return a tuple of timestamp and attached data, or (-1, 0) if the timeout is return a tuple of timestamp and attached data, or (-1, 0) if the timeout is
reached.""" reached."""

View File

@ -1,14 +0,0 @@
import os
from artiq import __artiq_dir__ as artiq_dir
class SourceLoader:
def __init__(self, runtime_root):
self.runtime_root = runtime_root
def get_source(self, filename):
with open(os.path.join(self.runtime_root, filename)) as f:
return f.read()
source_loader = SourceLoader(os.path.join(artiq_dir, "soc", "runtime"))

View File

@ -1,13 +1,17 @@
from artiq.language.core import kernel, delay, portable from numpy import int32
from artiq.language.core import nac3, kernel, portable, Kernel, KernelInvariant
from artiq.language.units import ns from artiq.language.units import ns
from artiq.coredevice import spi2 as spi from artiq.coredevice.core import Core
from artiq.coredevice.spi2 import *
from artiq.coredevice.ttl import TTLOut
SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END | SPI_CONFIG = (0*SPI_OFFLINE | 0*SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | 0*SPI_INPUT | 0*SPI_CS_POLARITY |
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | 0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
SPI_CS_ADC = 0 # no CS, SPI_END does not matter, framing is done with CNV SPI_CS_ADC = 0 # no CS, SPI_END does not matter, framing is done with CNV
@ -15,7 +19,7 @@ SPI_CS_PGIA = 1 # separate SPI bus, CS used as RCLK
@portable @portable
def adc_mu_to_volt(data, gain=0, corrected_fs=True): def adc_mu_to_volt(data: int32, gain: int32 = 0, corrected_fs: bool = True) -> float:
"""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
@ -24,19 +28,21 @@ def adc_mu_to_volt(data, gain=0, corrected_fs=True):
Should be True for Samplers' revisions after v2.1. False for v2.1 and earlier. Should be True for Samplers' revisions after v2.1. False for v2.1 and earlier.
:return: Voltage in Volts :return: Voltage in Volts
""" """
volt_per_lsb = 0.
if gain == 0: if gain == 0:
volt_per_lsb = 20.48 / (1 << 16) if corrected_fs else 20. / (1 << 16) volt_per_lsb = 20.48 / float(1 << 16) if corrected_fs else 20. / float(1 << 16)
elif gain == 1: elif gain == 1:
volt_per_lsb = 2.048 / (1 << 16) if corrected_fs else 2. / (1 << 16) volt_per_lsb = 2.048 / float(1 << 16) if corrected_fs else 2. / float(1 << 16)
elif gain == 2: elif gain == 2:
volt_per_lsb = .2048 / (1 << 16) if corrected_fs else .2 / (1 << 16) volt_per_lsb = .2048 / float(1 << 16) if corrected_fs else .2 / float(1 << 16)
elif gain == 3: elif gain == 3:
volt_per_lsb = 0.02048 / (1 << 16) if corrected_fs else .02 / (1 << 16) volt_per_lsb = 0.02048 / float(1 << 16) if corrected_fs else .02 / float(1 << 16)
else: else:
raise ValueError("invalid gain") raise ValueError("invalid gain")
return data * volt_per_lsb return float(data)* volt_per_lsb
@nac3
class Sampler: class Sampler:
"""Sampler ADC. """Sampler ADC.
@ -53,7 +59,13 @@ class Sampler:
:param hw_rev: Sampler's hardware revision string (default 'v2.2') :param hw_rev: Sampler's hardware revision string (default 'v2.2')
:param core_device: Core device name :param core_device: Core device name
""" """
kernel_invariants = {"bus_adc", "bus_pgia", "core", "cnv", "div", "corrected_fs"} core: KernelInvariant[Core]
bus_adc: KernelInvariant[SPIMaster]
bus_pgia: KernelInvariant[SPIMaster]
cnv: KernelInvariant[TTLOut]
div: KernelInvariant[int32]
gains: Kernel[int32]
corrected_fs: KernelInvariant[bool]
def __init__(self, dmgr, spi_adc_device, spi_pgia_device, cnv_device, def __init__(self, dmgr, spi_adc_device, spi_pgia_device, cnv_device,
div=8, gains=0x0000, hw_rev="v2.2", core_device="core"): div=8, gains=0x0000, hw_rev="v2.2", core_device="core"):
@ -77,13 +89,13 @@ class Sampler:
Sets up SPI channels. Sets up SPI channels.
""" """
self.bus_adc.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, self.bus_adc.set_config_mu(SPI_CONFIG | SPI_INPUT | SPI_END,
32, self.div, SPI_CS_ADC) 32, self.div, SPI_CS_ADC)
self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END, self.bus_pgia.set_config_mu(SPI_CONFIG | SPI_END,
16, self.div, SPI_CS_PGIA) 16, self.div, SPI_CS_PGIA)
@kernel @kernel
def set_gain_mu(self, channel, gain): def set_gain_mu(self, channel: int32, gain: int32):
"""Set instrumentation amplifier gain of a channel. """Set instrumentation amplifier gain of a channel.
The four gain settings (0, 1, 2, 3) corresponds to gains of The four gain settings (0, 1, 2, 3) corresponds to gains of
@ -99,21 +111,21 @@ class Sampler:
self.gains = gains self.gains = gains
@kernel @kernel
def get_gains_mu(self): def get_gains_mu(self) -> int32:
"""Read the PGIA gain settings of all channels. """Read the PGIA gain settings of all channels.
:return: The PGIA gain settings in machine units. :return: The PGIA gain settings in machine units.
""" """
self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, self.bus_pgia.set_config_mu(SPI_CONFIG | SPI_END | SPI_INPUT,
16, self.div, SPI_CS_PGIA) 16, self.div, SPI_CS_PGIA)
self.bus_pgia.write(self.gains << 16) self.bus_pgia.write(self.gains << 16)
self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END, self.bus_pgia.set_config_mu(SPI_CONFIG | SPI_END,
16, self.div, SPI_CS_PGIA) 16, self.div, SPI_CS_PGIA)
self.gains = self.bus_pgia.read() & 0xffff self.gains = self.bus_pgia.read() & 0xffff
return self.gains return self.gains
@kernel @kernel
def sample_mu(self, data): def sample_mu(self, data: list[int32]):
"""Acquire a set of samples. """Acquire a set of samples.
Perform a conversion and transfer the samples. Perform a conversion and transfer the samples.
@ -127,8 +139,8 @@ class Sampler:
The `data` list will always be filled with the last item The `data` list will always be filled with the last item
holding to the sample from channel 7. holding to the sample from channel 7.
""" """
self.cnv.pulse(30*ns) # t_CNVH self.cnv.pulse(30.*ns) # t_CNVH
delay(450*ns) # t_CONV self.core.delay(450.*ns) # t_CONV
mask = 1 << 15 mask = 1 << 15
for i in range(len(data)//2): for i in range(len(data)//2):
self.bus_adc.write(0) self.bus_adc.write(0)
@ -139,7 +151,7 @@ class Sampler:
data[i - 1] = -(val & mask) + (val & ~mask) data[i - 1] = -(val & mask) + (val & ~mask)
@kernel @kernel
def sample(self, data): def sample(self, data: list[float]):
"""Acquire a set of samples. """Acquire a set of samples.
.. seealso:: :meth:`sample_mu` .. seealso:: :meth:`sample_mu`
@ -147,7 +159,7 @@ class Sampler:
:param data: List of floating point data samples to fill. :param data: List of floating point data samples to fill.
""" """
n = len(data) n = len(data)
adc_data = [0]*n adc_data = [0 for _ in range(n)]
self.sample_mu(adc_data) self.sample_mu(adc_data)
for i in range(n): for i in range(n):
channel = i + 8 - len(data) channel = i + 8 - len(data)

View File

@ -1,18 +1,21 @@
from artiq.language.core import * from numpy import int32, int64
from artiq.language.types import *
from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable, Option, none
from artiq.coredevice.rtio import rtio_output, rtio_input_data from artiq.coredevice.rtio import rtio_output, rtio_input_data
from artiq.coredevice import spi2 as spi from artiq.coredevice.core import Core
from artiq.coredevice.spi2 import *
from artiq.language.units import us from artiq.language.units import us
@portable @portable
def shuttler_volt_to_mu(volt): def shuttler_volt_to_mu(volt: float) -> int32:
"""Return the equivalent DAC code. Valid input range is from -10 to """Return the equivalent DAC code. Valid input range is from -10 to
10 - LSB. 10 - LSB.
""" """
return round((1 << 16) * (volt / 20.0)) & 0xffff return round(float(1 << 16) * (volt / 20.0)) & 0xffff
@nac3
class Config: class Config:
"""Shuttler configuration registers interface. """Shuttler configuration registers interface.
@ -28,10 +31,13 @@ class Config:
:param channel: RTIO channel number of this interface. :param channel: RTIO channel number of this interface.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariants = { core: KernelInvariant[Core]
"core", "channel", "target_base", "target_read", channel: KernelInvariant[int32]
"target_gain", "target_offset", "target_clr" target_base: KernelInvariant[int32]
} target_read: KernelInvariant[int32]
target_gain: KernelInvariant[int32]
target_offset: KernelInvariant[int32]
target_clr: KernelInvariant[int32]
def __init__(self, dmgr, channel, core_device="core"): def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -43,7 +49,7 @@ class Config:
self.target_clr = 1 * (1 << 5) self.target_clr = 1 * (1 << 5)
@kernel @kernel
def set_clr(self, clr): def set_clr(self, clr: int32):
"""Set/Unset waveform phase clear bits. """Set/Unset waveform phase clear bits.
Each bit corresponds to a Shuttler waveform generator core. Setting a Each bit corresponds to a Shuttler waveform generator core. Setting a
@ -57,7 +63,7 @@ class Config:
rtio_output(self.target_base | self.target_clr, clr) rtio_output(self.target_base | self.target_clr, clr)
@kernel @kernel
def set_gain(self, channel, gain): def set_gain(self, channel: int32, gain: int32):
"""Set the 16-bits pre-DAC gain register of a Shuttler Core channel. """Set the 16-bits pre-DAC gain register of a Shuttler Core channel.
The `gain` parameter represents the decimal portion of the gain The `gain` parameter represents the decimal portion of the gain
@ -70,7 +76,7 @@ class Config:
rtio_output(self.target_base | self.target_gain | channel, gain) rtio_output(self.target_base | self.target_gain | channel, gain)
@kernel @kernel
def get_gain(self, channel): def get_gain(self, channel: int32) -> int32:
"""Return the pre-DAC gain value of a Shuttler Core channel. """Return the pre-DAC gain value of a Shuttler Core channel.
:param channel: The Shuttler Core channel. :param channel: The Shuttler Core channel.
@ -81,7 +87,7 @@ class Config:
return rtio_input_data(self.channel) return rtio_input_data(self.channel)
@kernel @kernel
def set_offset(self, channel, offset): def set_offset(self, channel: int32, offset: int32):
"""Set the 16-bits pre-DAC offset register of a Shuttler Core channel. """Set the 16-bits pre-DAC offset register of a Shuttler Core channel.
.. seealso:: .. seealso::
@ -93,7 +99,7 @@ class Config:
rtio_output(self.target_base | self.target_offset | channel, offset) rtio_output(self.target_base | self.target_offset | channel, offset)
@kernel @kernel
def get_offset(self, channel): def get_offset(self, channel: int32) -> int32:
"""Return the pre-DAC offset value of a Shuttler Core channel. """Return the pre-DAC offset value of a Shuttler Core channel.
:param channel: The Shuttler Core channel. :param channel: The Shuttler Core channel.
@ -104,6 +110,7 @@ class Config:
return rtio_input_data(self.channel) return rtio_input_data(self.channel)
@nac3
class DCBias: class DCBias:
"""Shuttler Core cubic DC-bias spline. """Shuttler Core cubic DC-bias spline.
@ -125,7 +132,9 @@ class DCBias:
:param channel: RTIO channel number of this DC-bias spline interface. :param channel: RTIO channel number of this DC-bias spline interface.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariants = {"core", "channel", "target_o"} core: KernelInvariant[Core]
channel: KernelInvariant[int32]
target_o: KernelInvariant[int32]
def __init__(self, dmgr, channel, core_device="core"): def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -133,7 +142,7 @@ class DCBias:
self.target_o = channel << 8 self.target_o = channel << 8
@kernel @kernel
def set_waveform(self, a0: TInt32, a1: TInt32, a2: TInt64, a3: TInt64): def set_waveform(self, a0: int32, a1: int32, a2: int64, a3: int64):
"""Set the DC-bias spline waveform. """Set the DC-bias spline waveform.
Given `a(t)` as defined in :class:`DCBias`, the coefficients should be Given `a(t)` as defined in :class:`DCBias`, the coefficients should be
@ -166,12 +175,12 @@ class DCBias:
a0, a0,
a1, a1,
a1 >> 16, a1 >> 16,
a2 & 0xFFFF, int32(a2 & int64(0xFFFF)),
(a2 >> 16) & 0xFFFF, int32((a2 >> 16) & int64(0xFFFF)),
(a2 >> 32) & 0xFFFF, int32((a2 >> 32) & int64(0xFFFF)),
a3 & 0xFFFF, int32(a3 & int64(0xFFFF)),
(a3 >> 16) & 0xFFFF, int32((a3 >> 16) & int64(0xFFFF)),
(a3 >> 32) & 0xFFFF, int32((a3 >> 32) & int64(0xFFFF)),
] ]
for i in range(len(coef_words)): for i in range(len(coef_words)):
@ -179,6 +188,7 @@ class DCBias:
delay_mu(int64(self.core.ref_multiplier)) delay_mu(int64(self.core.ref_multiplier))
@nac3
class DDS: class DDS:
"""Shuttler Core DDS spline. """Shuttler Core DDS spline.
@ -204,7 +214,9 @@ class DDS:
:param channel: RTIO channel number of this DC-bias spline interface. :param channel: RTIO channel number of this DC-bias spline interface.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariants = {"core", "channel", "target_o"} core: KernelInvariant[Core]
channel: KernelInvariant[int32]
target_o: KernelInvariant[int32]
def __init__(self, dmgr, channel, core_device="core"): def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -212,8 +224,8 @@ class DDS:
self.target_o = channel << 8 self.target_o = channel << 8
@kernel @kernel
def set_waveform(self, b0: TInt32, b1: TInt32, b2: TInt64, b3: TInt64, def set_waveform(self, b0: int32, b1: int32, b2: int64, b3: int64,
c0: TInt32, c1: TInt32, c2: TInt32): c0: int32, c1: int32, c2: int32):
"""Set the DDS spline waveform. """Set the DDS spline waveform.
Given `b(t)` and `c(t)` as defined in :class:`DDS`, the coefficients Given `b(t)` and `c(t)` as defined in :class:`DDS`, the coefficients
@ -256,12 +268,12 @@ class DDS:
b0, b0,
b1, b1,
b1 >> 16, b1 >> 16,
b2 & 0xFFFF, int32(b2 & int64(0xFFFF)),
(b2 >> 16) & 0xFFFF, int32((b2 >> 16) & int64(0xFFFF)),
(b2 >> 32) & 0xFFFF, int32((b2 >> 32) & int64(0xFFFF)),
b3 & 0xFFFF, int32(b3 & int64(0xFFFF)),
(b3 >> 16) & 0xFFFF, int32((b3 >> 16) & int64(0xFFFF)),
(b3 >> 32) & 0xFFFF, int32((b3 >> 32) & int64(0xFFFF)),
c0, c0,
c1, c1,
c1 >> 16, c1 >> 16,
@ -274,13 +286,16 @@ class DDS:
delay_mu(int64(self.core.ref_multiplier)) delay_mu(int64(self.core.ref_multiplier))
@nac3
class Trigger: class Trigger:
"""Shuttler Core spline coefficients update trigger. """Shuttler Core spline coefficients update trigger.
:param channel: RTIO channel number of the trigger interface. :param channel: RTIO channel number of the trigger interface.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariants = {"core", "channel", "target_o"} core: KernelInvariant[Core]
channel: KernelInvariant[int32]
target_o: KernelInvariant[int32]
def __init__(self, dmgr, channel, core_device="core"): def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -288,7 +303,7 @@ class Trigger:
self.target_o = channel << 8 self.target_o = channel << 8
@kernel @kernel
def trigger(self, trig_out): def trigger(self, trig_out: int32):
"""Triggers coefficient update of (a) Shuttler Core channel(s). """Triggers coefficient update of (a) Shuttler Core channel(s).
Each bit corresponds to a Shuttler waveform generator core. Setting Each bit corresponds to a Shuttler waveform generator core. Setting
@ -302,15 +317,15 @@ class Trigger:
rtio_output(self.target_o, trig_out) rtio_output(self.target_o, trig_out)
RELAY_SPI_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END | RELAY_SPI_CONFIG = (0*SPI_OFFLINE | 1*SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | 0*SPI_INPUT | 0*SPI_CS_POLARITY |
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | 0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
ADC_SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END | ADC_SPI_CONFIG = (0*SPI_OFFLINE | 0*SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | 0*SPI_INPUT | 0*SPI_CS_POLARITY |
1*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE | 1*SPI_CLK_POLARITY | 1*SPI_CLK_PHASE |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
# SPI clock write and read dividers # SPI clock write and read dividers
# CS should assert at least 9.5 ns after clk pulse # CS should assert at least 9.5 ns after clk pulse
@ -333,6 +348,7 @@ _AD4115_REG_CH0 = 0x10
_AD4115_REG_SETUPCON0 = 0x20 _AD4115_REG_SETUPCON0 = 0x20
@nac3
class Relay: class Relay:
"""Shuttler AFE relay switches. """Shuttler AFE relay switches.
@ -347,7 +363,8 @@ class Relay:
:param spi_device: SPI bus device name. :param spi_device: SPI bus device name.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariant = {"core", "bus"} core: KernelInvariant[Core]
bus: KernelInvariant[SPIMaster]
def __init__(self, dmgr, spi_device, core_device="core"): def __init__(self, dmgr, spi_device, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -364,7 +381,7 @@ class Relay:
RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED) RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED)
@kernel @kernel
def enable(self, en: TInt32): def enable(self, en: int32):
"""Enable/Disable relay switches of corresponding channels. """Enable/Disable relay switches of corresponding channels.
Each bit corresponds to the relay switch of a channel. Asserting a bit Each bit corresponds to the relay switch of a channel. Asserting a bit
@ -377,20 +394,22 @@ class Relay:
self.bus.write(en << 16) self.bus.write(en << 16)
@nac3
class ADC: class ADC:
"""Shuttler AFE ADC (AD4115) driver. """Shuttler AFE ADC (AD4115) driver.
:param spi_device: SPI bus device name. :param spi_device: SPI bus device name.
:param core_device: Core device name. :param core_device: Core device name.
""" """
kernel_invariant = {"core", "bus"} core: KernelInvariant[Core]
bus: KernelInvariant[SPIMaster]
def __init__(self, dmgr, spi_device, core_device="core"): def __init__(self, dmgr, spi_device, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
self.bus = dmgr.get(spi_device) self.bus = dmgr.get(spi_device)
@kernel @kernel
def read_id(self) -> TInt32: def read_id(self) -> int32:
"""Read the product ID of the ADC. """Read the product ID of the ADC.
The expected return value is 0x38DX, the 4 LSbs are don't cares. The expected return value is 0x38DX, the 4 LSbs are don't cares.
@ -412,86 +431,86 @@ class ADC:
after the transfer appears to interrupt the start-up sequence. after the transfer appears to interrupt the start-up sequence.
""" """
self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC) self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC)
self.bus.write(0xffffffff) self.bus.write(-1)
self.bus.write(0xffffffff) self.bus.write(-1)
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC) ADC_SPI_CONFIG | SPI_END, 32, SPIT_ADC_WR, CS_ADC)
self.bus.write(0xffffffff) self.bus.write(-1)
@kernel @kernel
def read8(self, addr: TInt32) -> TInt32: def read8(self, addr: int32) -> int32:
"""Read from 8 bit register. """Read from 8 bit register.
:param addr: Register address. :param addr: Register address.
:return: Read-back register content. :return: Read-back register content.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, ADC_SPI_CONFIG | SPI_END | SPI_INPUT,
16, SPIT_ADC_RD, CS_ADC) 16, SPIT_ADC_RD, CS_ADC)
self.bus.write((addr | 0x40) << 24) self.bus.write((addr | 0x40) << 24)
return self.bus.read() & 0xff return self.bus.read() & 0xff
@kernel @kernel
def read16(self, addr: TInt32) -> TInt32: def read16(self, addr: int32) -> int32:
"""Read from 16 bit register. """Read from 16 bit register.
:param addr: Register address. :param addr: Register address.
:return: Read-back register content. :return: Read-back register content.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, ADC_SPI_CONFIG | SPI_END | SPI_INPUT,
24, SPIT_ADC_RD, CS_ADC) 24, SPIT_ADC_RD, CS_ADC)
self.bus.write((addr | 0x40) << 24) self.bus.write((addr | 0x40) << 24)
return self.bus.read() & 0xffff return self.bus.read() & 0xffff
@kernel @kernel
def read24(self, addr: TInt32) -> TInt32: def read24(self, addr: int32) -> int32:
"""Read from 24 bit register. """Read from 24 bit register.
:param addr: Register address. :param addr: Register address.
:return: Read-back register content. :return: Read-back register content.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, ADC_SPI_CONFIG | SPI_END | SPI_INPUT,
32, SPIT_ADC_RD, CS_ADC) 32, SPIT_ADC_RD, CS_ADC)
self.bus.write((addr | 0x40) << 24) self.bus.write((addr | 0x40) << 24)
return self.bus.read() & 0xffffff return self.bus.read() & 0xffffff
@kernel @kernel
def write8(self, addr: TInt32, data: TInt32): def write8(self, addr: int32, data: int32):
"""Write to 8 bit register. """Write to 8 bit register.
:param addr: Register address. :param addr: Register address.
:param data: Data to be written. :param data: Data to be written.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 16, SPIT_ADC_WR, CS_ADC) ADC_SPI_CONFIG | SPI_END, 16, SPIT_ADC_WR, CS_ADC)
self.bus.write(addr << 24 | (data & 0xff) << 16) self.bus.write(addr << 24 | (data & 0xff) << 16)
@kernel @kernel
def write16(self, addr: TInt32, data: TInt32): def write16(self, addr: int32, data: int32):
"""Write to 16 bit register. """Write to 16 bit register.
:param addr: Register address. :param addr: Register address.
:param data: Data to be written. :param data: Data to be written.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 24, SPIT_ADC_WR, CS_ADC) ADC_SPI_CONFIG | SPI_END, 24, SPIT_ADC_WR, CS_ADC)
self.bus.write(addr << 24 | (data & 0xffff) << 8) self.bus.write(addr << 24 | (data & 0xffff) << 8)
@kernel @kernel
def write24(self, addr: TInt32, data: TInt32): def write24(self, addr: int32, data: int32):
"""Write to 24 bit register. """Write to 24 bit register.
:param addr: Register address. :param addr: Register address.
:param data: Data to be written. :param data: Data to be written.
""" """
self.bus.set_config_mu( self.bus.set_config_mu(
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC) ADC_SPI_CONFIG | SPI_END, 32, SPIT_ADC_WR, CS_ADC)
self.bus.write(addr << 24 | (data & 0xffffff)) self.bus.write(addr << 24 | (data & 0xffffff))
@kernel @kernel
def read_ch(self, channel: TInt32) -> TFloat: def read_ch(self, channel: int32) -> float:
"""Sample a Shuttler channel on the AFE. """Sample a Shuttler channel on the AFE.
It performs a single conversion using profile 0 and setup 0, on the It performs a single conversion using profile 0 and setup 0, on the
@ -505,9 +524,9 @@ class ADC:
self.write16(_AD4115_REG_SETUPCON0, 0x1300) self.write16(_AD4115_REG_SETUPCON0, 0x1300)
self.single_conversion() self.single_conversion()
delay(100*us) self.core.delay(100.*us)
adc_code = self.read24(_AD4115_REG_DATA) adc_code = self.read24(_AD4115_REG_DATA)
return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1 return ((float(adc_code) / float(1 << 23)) - 1.) * 2.5 / 0.1
@kernel @kernel
def single_conversion(self): def single_conversion(self):
@ -558,10 +577,10 @@ class ADC:
self.reset() self.reset()
# Although the datasheet claims 500 us reset wait time, only waiting # Although the datasheet claims 500 us reset wait time, only waiting
# for ~500 us can result in DOUT pin stuck in high # for ~500 us can result in DOUT pin stuck in high
delay(2500*us) self.core.delay(2500.*us)
@kernel @kernel
def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]): def calibrate(self, volts: list[DCBias], trigger: Trigger, config: Config, samples: Option[list[float]] = none):
"""Calibrate the Shuttler waveform generator using the ADC on the AFE. """Calibrate the Shuttler waveform generator using the ADC on the AFE.
It finds the average slope rate and average offset by samples, and It finds the average slope rate and average offset by samples, and
@ -586,33 +605,35 @@ class ADC:
:param samples: A list of sample voltages for calibration. There must :param samples: A list of sample voltages for calibration. There must
be at least 2 samples to perform slope rate calculation. be at least 2 samples to perform slope rate calculation.
""" """
assert len(volts) == 16 samples_l = samples.unwrap() if samples.is_some() else [-5.0, 0.0, 5.0]
assert len(samples) > 1
measurements = [0.0] * len(samples) assert len(volts) == 16
assert len(samples_l) > 1
measurements = [0.0 for _ in range(len(samples_l))]
for ch in range(16): for ch in range(16):
# Find the average slope rate and offset # Find the average slope rate and offset
for i in range(len(samples)): for i in range(len(samples_l)):
self.core.break_realtime() self.core.break_realtime()
volts[ch].set_waveform( volts[ch].set_waveform(
shuttler_volt_to_mu(samples[i]), 0, 0, 0) shuttler_volt_to_mu(samples_l[i]), 0, int64(0), int64(0))
trigger.trigger(1 << ch) trigger.trigger(1 << ch)
measurements[i] = self.read_ch(ch) measurements[i] = self.read_ch(ch)
# Find the average output slope # Find the average output slope
slope_sum = 0.0 slope_sum = 0.0
for i in range(len(samples) - 1): for i in range(len(samples_l) - 1):
slope_sum += (measurements[i+1] - measurements[i])/(samples[i+1] - samples[i]) slope_sum += (measurements[i+1] - measurements[i])/(samples_l[i+1] - samples_l[i])
slope_avg = slope_sum / (len(samples) - 1) slope_avg = slope_sum / float(len(samples_l) - 1)
gain_code = int32(1 / slope_avg * (2 ** 16)) & 0xffff gain_code = int32(1. / slope_avg * float(2 ** 16)) & 0xffff
# Scale the measurements by 1/slope, find average offset # Scale the measurements by 1/slope, find average offset
offset_sum = 0.0 offset_sum = 0.0
for i in range(len(samples)): for i in range(len(samples_l)):
offset_sum += (measurements[i] / slope_avg) - samples[i] offset_sum += (measurements[i] / slope_avg) - samples_l[i]
offset_avg = offset_sum / len(samples) offset_avg = offset_sum / float(len(samples_l))
offset_code = shuttler_volt_to_mu(-offset_avg) offset_code = shuttler_volt_to_mu(-offset_avg)

View File

@ -7,8 +7,10 @@ Output event replacement is not supported and issuing commands at the same
time is an error. time is an error.
""" """
from artiq.language.core import syscall, kernel, portable, delay_mu from numpy import int32, int64
from artiq.language.types import TInt32, TNone
from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable, extern
from artiq.coredevice.core import Core
from artiq.coredevice.rtio import rtio_output, rtio_input_data from artiq.coredevice.rtio import rtio_output, rtio_input_data
@ -33,6 +35,7 @@ SPI_LSB_FIRST = 0x40
SPI_HALF_DUPLEX = 0x80 SPI_HALF_DUPLEX = 0x80
@nac3
class SPIMaster: class SPIMaster:
"""Core device Serial Peripheral Interface (SPI) bus master. """Core device Serial Peripheral Interface (SPI) bus master.
@ -62,12 +65,14 @@ class SPIMaster:
:meth:`update_xfer_duration_mu` :meth:`update_xfer_duration_mu`
:param core_device: Core device name :param core_device: Core device name
""" """
kernel_invariants = {"core", "ref_period_mu", "channel"} core: KernelInvariant[Core]
ref_period_mu: KernelInvariant[int64]
channel: KernelInvariant[int32]
xfer_duration_mu: Kernel[int64]
def __init__(self, dmgr, channel, div=0, length=0, core_device="core"): def __init__(self, dmgr, channel, div=0, length=0, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
self.ref_period_mu = self.core.seconds_to_mu( self.ref_period_mu = self.core.seconds_to_mu(self.core.coarse_ref_period)
self.core.coarse_ref_period)
assert self.ref_period_mu == self.core.ref_multiplier assert self.ref_period_mu == self.core.ref_multiplier
self.channel = channel self.channel = channel
self.update_xfer_duration_mu(div, length) self.update_xfer_duration_mu(div, length)
@ -77,12 +82,12 @@ class SPIMaster:
return [(channel, None)] return [(channel, None)]
@portable @portable
def frequency_to_div(self, f): def frequency_to_div(self, f: float) -> int32:
"""Convert a SPI clock frequency to the closest SPI clock divider.""" """Convert a SPI clock frequency to the closest SPI clock divider."""
return int(round(1/(f*self.core.mu_to_seconds(self.ref_period_mu)))) return round(1./(f*self.core.mu_to_seconds(self.ref_period_mu)))
@kernel @kernel
def set_config(self, flags, length, freq, cs): def set_config(self, flags: int32, length: int32, freq: float, cs: int32):
"""Set the configuration register. """Set the configuration register.
* If ``SPI_CS_POLARITY`` is cleared (``cs`` active low, the default), * If ``SPI_CS_POLARITY`` is cleared (``cs`` active low, the default),
@ -149,7 +154,7 @@ class SPIMaster:
self.set_config_mu(flags, length, self.frequency_to_div(freq), cs) self.set_config_mu(flags, length, self.frequency_to_div(freq), cs)
@kernel @kernel
def set_config_mu(self, flags, length, div, cs): def set_config_mu(self, flags: int32, length: int32, div: int32, cs: int32):
"""Set the ``config`` register (in SPI bus machine units). """Set the ``config`` register (in SPI bus machine units).
.. seealso:: :meth:`set_config` .. seealso:: :meth:`set_config`
@ -176,7 +181,7 @@ class SPIMaster:
delay_mu(self.ref_period_mu) delay_mu(self.ref_period_mu)
@portable @portable
def update_xfer_duration_mu(self, div, length): def update_xfer_duration_mu(self, div: int32, length: int32):
"""Calculate and set the transfer duration. """Calculate and set the transfer duration.
This method updates the SPI transfer duration which is used This method updates the SPI transfer duration which is used
@ -199,10 +204,10 @@ class SPIMaster:
:param div: SPI clock divider (see: :meth:`set_config_mu`) :param div: SPI clock divider (see: :meth:`set_config_mu`)
:param length: SPI transfer length (see: :meth:`set_config_mu`) :param length: SPI transfer length (see: :meth:`set_config_mu`)
""" """
self.xfer_duration_mu = ((length + 1)*div + 1)*self.ref_period_mu self.xfer_duration_mu = int64((length + 1)*div + 1)*self.ref_period_mu
@kernel @kernel
def write(self, data): def write(self, data: int32):
"""Write SPI data to shift register register and start transfer. """Write SPI data to shift register register and start transfer.
* The ``data`` register and the shift register are 32 bits wide. * The ``data`` register and the shift register are 32 bits wide.
@ -230,7 +235,7 @@ class SPIMaster:
delay_mu(self.xfer_duration_mu) delay_mu(self.xfer_duration_mu)
@kernel @kernel
def read(self): def read(self) -> int32:
"""Read SPI data submitted by the SPI core. """Read SPI data submitted by the SPI core.
For bit alignment and bit ordering see :meth:`set_config`. For bit alignment and bit ordering see :meth:`set_config`.
@ -242,21 +247,22 @@ class SPIMaster:
return rtio_input_data(self.channel) return rtio_input_data(self.channel)
@syscall(flags={"nounwind", "nowrite"}) @extern
def spi_set_config(busno: TInt32, flags: TInt32, length: TInt32, div: TInt32, cs: TInt32) -> TNone: def spi_set_config(busno: int32, flags: int32, length: int32, div: int32, cs: int32):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) @extern
def spi_write(busno: TInt32, data: TInt32) -> TNone: def spi_write(busno: int32, data: int32):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) @extern
def spi_read(busno: TInt32) -> TInt32: def spi_read(busno: int32) -> int32:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@nac3
class NRTSPIMaster: class NRTSPIMaster:
"""Core device non-realtime Serial Peripheral Interface (SPI) bus master. """Core device non-realtime Serial Peripheral Interface (SPI) bus master.
Owns one non-realtime SPI bus. Owns one non-realtime SPI bus.
@ -269,12 +275,15 @@ class NRTSPIMaster:
See :class:`SPIMaster` for a description of the methods. See :class:`SPIMaster` for a description of the methods.
""" """
core: KernelInvariant[Core]
busno: KernelInvariant[int32]
def __init__(self, dmgr, busno=0, core_device="core"): def __init__(self, dmgr, busno=0, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
self.busno = busno self.busno = busno
@kernel @kernel
def set_config_mu(self, flags=0, length=8, div=6, cs=1): def set_config_mu(self, flags: int32 = 0, length: int32 = 8, div: int32 = 6, cs: int32 = 1):
"""Set the ``config`` register. """Set the ``config`` register.
In many cases, the SPI configuration is already set by the firmware In many cases, the SPI configuration is already set by the firmware
@ -283,9 +292,9 @@ class NRTSPIMaster:
spi_set_config(self.busno, flags, length, div, cs) spi_set_config(self.busno, flags, length, div, cs)
@kernel @kernel
def write(self, data=0): def write(self, data: int32 = 0):
spi_write(self.busno, data) spi_write(self.busno, data)
@kernel @kernel
def read(self): def read(self) -> int32:
return spi_read(self.busno) return spi_read(self.busno)

View File

@ -1,8 +1,13 @@
from artiq.language.core import kernel, delay, delay_mu, portable from numpy import int32, int64
from artiq.language.core import *
from artiq.language.units import us, ns from artiq.language.units import us, ns
from artiq.coredevice.core import Core
from artiq.coredevice.rtio import rtio_output, rtio_input_data from artiq.coredevice.rtio import rtio_output, rtio_input_data
from artiq.coredevice import spi2 as spi from artiq.coredevice.spi2 import SPI_END, SPIMaster
from artiq.coredevice import urukul, sampler from artiq.coredevice.urukul import CFG_MASK_NU, CPLD
from artiq.coredevice.ad9910 import AD9910
from artiq.coredevice.sampler import adc_mu_to_volt as sampler_adc_mu_to_volt, SPI_CONFIG as SAMPLER_SPI_CONFIG, SPI_CS_PGIA as SAMPLER_SPI_CS_PGIA
COEFF_WIDTH = 18 COEFF_WIDTH = 18
@ -17,20 +22,21 @@ COEFF_SHIFT = 11
@portable @portable
def y_mu_to_full_scale(y): def y_mu_to_full_scale(y: int32) -> float:
"""Convert servo Y data from machine units to units of full scale.""" """Convert servo Y data from machine units to units of full scale."""
return y / Y_FULL_SCALE_MU return float(y) / float(Y_FULL_SCALE_MU)
@portable @portable
def adc_mu_to_volts(x, gain, corrected_fs=True): def adc_mu_to_volts(x: int32, gain: int32, corrected_fs: bool = True) -> float:
"""Convert servo ADC data from machine units to Volt.""" """Convert servo ADC data from machine units to Volt."""
val = (x >> 1) & 0xffff val = (x >> 1) & 0xffff
mask = 1 << 15 mask = 1 << 15
val = -(val & mask) + (val & ~mask) val = -(val & mask) + (val & ~mask)
return sampler.adc_mu_to_volt(val, gain, corrected_fs) return sampler_adc_mu_to_volt(val, gain, corrected_fs)
@nac3
class SUServo: class SUServo:
"""Sampler-Urukul Servo parent and configuration device. """Sampler-Urukul Servo parent and configuration device.
@ -65,8 +71,15 @@ class SUServo:
:param sampler_hw_rev: Sampler's revision string :param sampler_hw_rev: Sampler's revision string
:param core_device: Core device name :param core_device: Core device name
""" """
kernel_invariants = {"channel", "core", "pgia", "cplds", "ddses",
"ref_period_mu", "corrected_fs"} core: KernelInvariant[Core]
pgia: KernelInvariant[SPIMaster]
ddses: KernelInvariant[list[AD9910]]
cplds: KernelInvariant[list[CPLD]]
channel: KernelInvariant[int32]
gains: Kernel[int32]
ref_period_mu: KernelInvariant[int64]
corrected_fs: KernelInvariant[bool]
def __init__(self, dmgr, channel, pgia_device, def __init__(self, dmgr, channel, pgia_device,
cpld_devices, dds_devices, cpld_devices, dds_devices,
@ -102,12 +115,12 @@ class SUServo:
This method does not alter the profile configuration memory This method does not alter the profile configuration memory
or the channel controls. or the channel controls.
""" """
self.set_config(enable=0) self.set_config(enable=False)
delay(3*us) # pipeline flush self.core.delay(3.*us) # pipeline flush
self.pgia.set_config_mu( self.pgia.set_config_mu(
sampler.SPI_CONFIG | spi.SPI_END, SAMPLER_SPI_CONFIG | SPI_END,
16, 4, sampler.SPI_CS_PGIA) 16, 4, SAMPLER_SPI_CS_PGIA)
for i in range(len(self.cplds)): for i in range(len(self.cplds)):
cpld = self.cplds[i] cpld = self.cplds[i]
@ -115,12 +128,12 @@ class SUServo:
cpld.init(blind=True) cpld.init(blind=True)
prev_cpld_cfg = cpld.cfg_reg prev_cpld_cfg = cpld.cfg_reg
cpld.cfg_write(prev_cpld_cfg | (0xf << urukul.CFG_MASK_NU)) cpld.cfg_write(prev_cpld_cfg | (0xf << CFG_MASK_NU))
dds.init(blind=True) dds.init(blind=True)
cpld.cfg_write(prev_cpld_cfg) cpld.cfg_write(prev_cpld_cfg)
@kernel @kernel
def write(self, addr, value): def write(self, addr: int32, value: int32):
"""Write to servo memory. """Write to servo memory.
This method advances the timeline by one coarse RTIO cycle. This method advances the timeline by one coarse RTIO cycle.
@ -136,7 +149,7 @@ class SUServo:
delay_mu(self.ref_period_mu) delay_mu(self.ref_period_mu)
@kernel @kernel
def read(self, addr): def read(self, addr: int32) -> int32:
"""Read from servo memory. """Read from servo memory.
This method does not advance the timeline but consumes all slack. This method does not advance the timeline but consumes all slack.
@ -149,7 +162,7 @@ class SUServo:
return rtio_input_data(self.channel) return rtio_input_data(self.channel)
@kernel @kernel
def set_config(self, enable): def set_config(self, enable: bool):
"""Set SU Servo configuration. """Set SU Servo configuration.
This method advances the timeline by one servo memory access. This method advances the timeline by one servo memory access.
@ -164,10 +177,10 @@ class SUServo:
Disabling takes up to two servo cycles (~2.3 µs) to clear the Disabling takes up to two servo cycles (~2.3 µs) to clear the
processing pipeline. processing pipeline.
""" """
self.write(CONFIG_ADDR, enable) self.write(CONFIG_ADDR, int32(enable))
@kernel @kernel
def get_status(self): def get_status(self) -> int32:
"""Get current SU Servo status. """Get current SU Servo status.
This method does not advance the timeline but consumes all slack. This method does not advance the timeline but consumes all slack.
@ -188,7 +201,7 @@ class SUServo:
return self.read(CONFIG_ADDR) return self.read(CONFIG_ADDR)
@kernel @kernel
def get_adc_mu(self, adc): def get_adc_mu(self, adc: int32) -> int32:
"""Get the latest ADC reading (IIR filter input X0) in machine units. """Get the latest ADC reading (IIR filter input X0) in machine units.
This method does not advance the timeline but consumes all slack. This method does not advance the timeline but consumes all slack.
@ -206,7 +219,7 @@ class SUServo:
return self.read(STATE_SEL | (adc << 1) | (1 << 8)) return self.read(STATE_SEL | (adc << 1) | (1 << 8))
@kernel @kernel
def set_pgia_mu(self, channel, gain): def set_pgia_mu(self, channel: int32, gain: int32):
"""Set instrumentation amplifier gain of a ADC channel. """Set instrumentation amplifier gain of a ADC channel.
The four gain settings (0, 1, 2, 3) corresponds to gains of The four gain settings (0, 1, 2, 3) corresponds to gains of
@ -222,7 +235,7 @@ class SUServo:
self.gains = gains self.gains = gains
@kernel @kernel
def get_adc(self, channel): def get_adc(self, channel: int32) -> float:
"""Get the latest ADC reading (IIR filter input X0). """Get the latest ADC reading (IIR filter input X0).
This method does not advance the timeline but consumes all slack. This method does not advance the timeline but consumes all slack.
@ -243,13 +256,19 @@ class SUServo:
return adc_mu_to_volts(val, gain, self.corrected_fs) return adc_mu_to_volts(val, gain, self.corrected_fs)
@nac3
class Channel: class Channel:
"""Sampler-Urukul Servo channel """Sampler-Urukul Servo channel
:param channel: RTIO channel number :param channel: RTIO channel number
:param servo_device: Name of the parent SUServo device :param servo_device: Name of the parent SUServo device
""" """
kernel_invariants = {"channel", "core", "servo", "servo_channel"}
core: KernelInvariant[Core]
servo: KernelInvariant[SUServo]
channel: KernelInvariant[int32]
servo_channel: KernelInvariant[int32]
dds: KernelInvariant[AD9910]
def __init__(self, dmgr, channel, servo_device): def __init__(self, dmgr, channel, servo_device):
self.servo = dmgr.get(servo_device) self.servo = dmgr.get(servo_device)
@ -266,7 +285,7 @@ class Channel:
return [(channel, None)] return [(channel, None)]
@kernel @kernel
def set(self, en_out, en_iir=0, profile=0): def set(self, en_out: bool, en_iir: bool = False, profile: int32 = 0):
"""Operate channel. """Operate channel.
This method does not advance the timeline. Output RF switch setting This method does not advance the timeline. Output RF switch setting
@ -282,10 +301,10 @@ class Channel:
:param profile: Active profile (0-31) :param profile: Active profile (0-31)
""" """
rtio_output(self.channel << 8, rtio_output(self.channel << 8,
en_out | (en_iir << 1) | (profile << 2)) int32(en_out) | (int32(en_iir) << 1) | (profile << 2))
@kernel @kernel
def set_dds_mu(self, profile, ftw, offs, pow_=0): def set_dds_mu(self, profile: int32, ftw: int32, offs: int32, pow_: int32 = 0):
"""Set profile DDS coefficients in machine units. """Set profile DDS coefficients in machine units.
.. seealso:: :meth:`set_amplitude` .. seealso:: :meth:`set_amplitude`
@ -302,7 +321,7 @@ class Channel:
self.servo.write(base + 2, pow_) self.servo.write(base + 2, pow_)
@kernel @kernel
def set_dds(self, profile, frequency, offset, phase=0.): def set_dds(self, profile: int32, frequency: float, offset: float, phase: float = 0.):
"""Set profile DDS coefficients. """Set profile DDS coefficients.
This method advances the timeline by four servo memory accesses. This method advances the timeline by four servo memory accesses.
@ -321,7 +340,7 @@ class Channel:
self.set_dds_mu(profile, ftw, offs, pow_) self.set_dds_mu(profile, ftw, offs, pow_)
@kernel @kernel
def set_dds_offset_mu(self, profile, offs): def set_dds_offset_mu(self, profile: int32, offs: int32):
"""Set only IIR offset in DDS coefficient profile. """Set only IIR offset in DDS coefficient profile.
See :meth:`set_dds_mu` for setting the complete DDS profile. See :meth:`set_dds_mu` for setting the complete DDS profile.
@ -333,7 +352,7 @@ class Channel:
self.servo.write(base + 4, offs) self.servo.write(base + 4, offs)
@kernel @kernel
def set_dds_offset(self, profile, offset): def set_dds_offset(self, profile: int32, offset: float):
"""Set only IIR offset in DDS coefficient profile. """Set only IIR offset in DDS coefficient profile.
See :meth:`set_dds` for setting the complete DDS profile. See :meth:`set_dds` for setting the complete DDS profile.
@ -344,7 +363,7 @@ class Channel:
self.set_dds_offset_mu(profile, self.dds_offset_to_mu(offset)) self.set_dds_offset_mu(profile, self.dds_offset_to_mu(offset))
@portable @portable
def dds_offset_to_mu(self, offset): def dds_offset_to_mu(self, offset: float) -> int32:
"""Convert IIR offset (negative setpoint) from units of full scale to """Convert IIR offset (negative setpoint) from units of full scale to
machine units (see :meth:`set_dds_mu`, :meth:`set_dds_offset_mu`). machine units (see :meth:`set_dds_mu`, :meth:`set_dds_offset_mu`).
@ -352,10 +371,10 @@ class Channel:
rounding and representation as two's complement, ``offset=1`` can not rounding and representation as two's complement, ``offset=1`` can not
be represented while ``offset=-1`` can. be represented while ``offset=-1`` can.
""" """
return int(round(offset * (1 << COEFF_WIDTH - 1))) return round(offset * float(1 << COEFF_WIDTH - 1))
@kernel @kernel
def set_iir_mu(self, profile, adc, a1, b0, b1, dly=0): def set_iir_mu(self, profile: int32, adc: int32, a1: int32, b0: int32, b1: int32, dly: int32 = 0):
"""Set profile IIR coefficients in machine units. """Set profile IIR coefficients in machine units.
The recurrence relation is (all data signed and MSB aligned): The recurrence relation is (all data signed and MSB aligned):
@ -395,7 +414,7 @@ class Channel:
self.servo.write(base + 7, b0) self.servo.write(base + 7, b0)
@kernel @kernel
def set_iir(self, profile, adc, kp, ki=0., g=0., delay=0.): def set_iir(self, profile: int32, adc: int32, kp: float, ki: float = 0., g: float = 0., delay: float = 0.):
"""Set profile IIR coefficients. """Set profile IIR coefficients.
This method advances the timeline by four servo memory accesses. This method advances the timeline by four servo memory accesses.
@ -437,23 +456,23 @@ class Channel:
A_NORM = 1 << COEFF_SHIFT A_NORM = 1 << COEFF_SHIFT
COEFF_MAX = 1 << COEFF_WIDTH - 1 COEFF_MAX = 1 << COEFF_WIDTH - 1
kp *= B_NORM kp *= float(B_NORM)
if ki == 0.: if ki == 0.:
# pure P # pure P
a1 = 0 a1 = 0
b1 = 0 b1 = 0
b0 = int(round(kp)) b0 = round(kp)
else: else:
# I or PI # I or PI
ki *= B_NORM*T_CYCLE/2. ki *= float(B_NORM)*T_CYCLE/2.
if g == 0.: if g == 0.:
c = 1. c = 1.
a1 = A_NORM a1 = A_NORM
else: else:
c = 1./(1. + ki/(g*B_NORM)) c = 1./(1. + ki/(g*float(B_NORM)))
a1 = int(round((2.*c - 1.)*A_NORM)) a1 = round((2.*c - 1.)*float(A_NORM))
b0 = int(round(kp + ki*c)) b0 = round(kp + ki*c)
b1 = int(round(kp + (ki - 2.*kp)*c)) b1 = round(kp + (ki - 2.*kp)*c)
if b1 == -b0: if b1 == -b0:
raise ValueError("low integrator gain and/or gain limit") raise ValueError("low integrator gain and/or gain limit")
@ -461,11 +480,11 @@ class Channel:
b1 >= COEFF_MAX or b1 < -COEFF_MAX): b1 >= COEFF_MAX or b1 < -COEFF_MAX):
raise ValueError("high gains") raise ValueError("high gains")
dly = int(round(delay/T_CYCLE)) dly = round(delay/T_CYCLE)
self.set_iir_mu(profile, adc, a1, b0, b1, dly) self.set_iir_mu(profile, adc, a1, b0, b1, dly)
@kernel @kernel
def get_profile_mu(self, profile, data): def get_profile_mu(self, profile: int32, data: list[int32]):
"""Retrieve profile data. """Retrieve profile data.
Profile data is returned in the ``data`` argument in machine units Profile data is returned in the ``data`` argument in machine units
@ -483,10 +502,10 @@ class Channel:
base = (self.servo_channel << 8) | (profile << 3) base = (self.servo_channel << 8) | (profile << 3)
for i in range(len(data)): for i in range(len(data)):
data[i] = self.servo.read(base + i) data[i] = self.servo.read(base + i)
delay(4*us) self.core.delay(4.*us)
@kernel @kernel
def get_y_mu(self, profile): def get_y_mu(self, profile: int32) -> int32:
"""Get a profile's IIR state (filter output, Y0) in machine units. """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 know as the "integrator", or the DDS amplitude
@ -504,7 +523,7 @@ class Channel:
return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile) return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile)
@kernel @kernel
def get_y(self, profile): def get_y(self, profile: int32) -> float:
"""Get a profile's IIR state (filter output, Y0). """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 know as the "integrator", or the DDS amplitude
@ -522,7 +541,7 @@ class Channel:
return y_mu_to_full_scale(self.get_y_mu(profile)) return y_mu_to_full_scale(self.get_y_mu(profile))
@kernel @kernel
def set_y_mu(self, profile, y): def set_y_mu(self, profile: int32, y: int32):
"""Set a profile's IIR state (filter output, Y0) in machine units. """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 know as the "integrator", or the DDS amplitude
@ -542,7 +561,7 @@ class Channel:
self.servo.write(STATE_SEL | (self.servo_channel << 5) | profile, y) self.servo.write(STATE_SEL | (self.servo_channel << 5) | profile, y)
@kernel @kernel
def set_y(self, profile, y): def set_y(self, profile: int32, y: float) -> int32:
"""Set a profile's IIR state (filter output, Y0). """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 know as the "integrator", or the DDS amplitude
@ -557,7 +576,7 @@ class Channel:
:param profile: Profile number (0-31) :param profile: Profile number (0-31)
:param y: IIR state in units of full scale :param y: IIR state in units of full scale
""" """
y_mu = int(round(y * Y_FULL_SCALE_MU)) y_mu = round(y * float(Y_FULL_SCALE_MU))
if y_mu < 0 or y_mu > (1 << 17) - 1: if y_mu < 0 or y_mu > (1 << 17) - 1:
raise ValueError("Invalid SUServo y-value!") raise ValueError("Invalid SUServo y-value!")
self.set_y_mu(profile, y_mu) self.set_y_mu(profile, y_mu)

View File

@ -6,10 +6,10 @@ replacement. For example, pulses of "zero" length (e.g. :meth:`TTLInOut.on`
immediately followed by :meth:`TTLInOut.off`, without a delay) are suppressed. immediately followed by :meth:`TTLInOut.off`, without a delay) are suppressed.
""" """
import numpy from numpy import int32, int64
from artiq.language.core import * from artiq.language.core import *
from artiq.language.types import * from artiq.coredevice.core import Core
from artiq.coredevice.rtio import (rtio_output, rtio_input_timestamp, from artiq.coredevice.rtio import (rtio_output, rtio_input_timestamp,
rtio_input_data) rtio_input_data)
from artiq.coredevice.exceptions import RTIOOverflow from artiq.coredevice.exceptions import RTIOOverflow
@ -22,6 +22,7 @@ from artiq.coredevice.exceptions import RTIOOverflow
# 3 Set input sensitivity and sample # 3 Set input sensitivity and sample
@nac3
class TTLOut: class TTLOut:
"""RTIO TTL output driver. """RTIO TTL output driver.
@ -29,7 +30,9 @@ class TTLOut:
:param channel: channel number :param channel: channel number
""" """
kernel_invariants = {"core", "channel", "target_o"} core: KernelInvariant[Core]
channel: KernelInvariant[int32]
target_o: KernelInvariant[int32]
def __init__(self, dmgr, channel, core_device="core"): def __init__(self, dmgr, channel, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -45,7 +48,7 @@ class TTLOut:
pass pass
@kernel @kernel
def set_o(self, o): def set_o(self, o: bool):
rtio_output(self.target_o, 1 if o else 0) rtio_output(self.target_o, 1 if o else 0)
@kernel @kernel
@ -65,7 +68,7 @@ class TTLOut:
self.set_o(False) self.set_o(False)
@kernel @kernel
def pulse_mu(self, duration): def pulse_mu(self, duration: int64):
"""Pulse the output high for the specified duration """Pulse the output high for the specified duration
(in machine units). (in machine units).
@ -75,16 +78,17 @@ class TTLOut:
self.off() self.off()
@kernel @kernel
def pulse(self, duration): def pulse(self, duration: float):
"""Pulse the output high for the specified duration """Pulse the output high for the specified duration
(in seconds). (in seconds).
The time cursor is advanced by the specified duration.""" The time cursor is advanced by the specified duration."""
self.on() self.on()
delay(duration) self.core.delay(duration)
self.off() self.off()
@nac3
class TTLInOut: class TTLInOut:
"""RTIO TTL input/output driver. """RTIO TTL input/output driver.
@ -111,8 +115,13 @@ class TTLInOut:
:param channel: channel number :param channel: channel number
""" """
kernel_invariants = {"core", "channel", "gate_latency_mu", core: KernelInvariant[Core]
"target_o", "target_oe", "target_sens", "target_sample"} channel: KernelInvariant[int32]
gate_latency_mu: KernelInvariant[int32]
target_o: KernelInvariant[int32]
target_oe: KernelInvariant[int32]
target_sens: KernelInvariant[int32]
target_sample: KernelInvariant[int32]
def __init__(self, dmgr, channel, gate_latency_mu=None, def __init__(self, dmgr, channel, gate_latency_mu=None,
core_device="core"): core_device="core"):
@ -137,7 +146,7 @@ class TTLInOut:
return [(channel, None)] return [(channel, None)]
@kernel @kernel
def set_oe(self, oe): def set_oe(self, oe: bool):
rtio_output(self.target_oe, 1 if oe else 0) rtio_output(self.target_oe, 1 if oe else 0)
@kernel @kernel
@ -167,7 +176,7 @@ class TTLInOut:
self.set_oe(False) self.set_oe(False)
@kernel @kernel
def set_o(self, o): def set_o(self, o: bool):
rtio_output(self.target_o, 1 if o else 0) rtio_output(self.target_o, 1 if o else 0)
@kernel @kernel
@ -191,7 +200,7 @@ class TTLInOut:
self.set_o(False) self.set_o(False)
@kernel @kernel
def pulse_mu(self, duration): def pulse_mu(self, duration: int64):
"""Pulse the output high for the specified duration """Pulse the output high for the specified duration
(in machine units). (in machine units).
@ -201,22 +210,22 @@ class TTLInOut:
self.off() self.off()
@kernel @kernel
def pulse(self, duration): def pulse(self, duration: float):
"""Pulse the output high for the specified duration """Pulse the output high for the specified duration
(in seconds). (in seconds).
The time cursor is advanced by the specified duration.""" The time cursor is advanced by the specified duration."""
self.on() self.on()
delay(duration) self.core.delay(duration)
self.off() self.off()
# Input API: gating # Input API: gating
@kernel @kernel
def _set_sensitivity(self, value): def _set_sensitivity(self, value: int32):
rtio_output(self.target_sens, value) rtio_output(self.target_sens, value)
@kernel @kernel
def gate_rising_mu(self, duration): def gate_rising_mu(self, duration: int64) -> int64:
"""Register rising edge events for the specified duration """Register rising edge events for the specified duration
(in machine units). (in machine units).
@ -231,7 +240,7 @@ class TTLInOut:
return now_mu() return now_mu()
@kernel @kernel
def gate_falling_mu(self, duration): def gate_falling_mu(self, duration: int64) -> int64:
"""Register falling edge events for the specified duration """Register falling edge events for the specified duration
(in machine units). (in machine units).
@ -246,7 +255,7 @@ class TTLInOut:
return now_mu() return now_mu()
@kernel @kernel
def gate_both_mu(self, duration): def gate_both_mu(self, duration: int64) -> int64:
"""Register both rising and falling edge events for the specified """Register both rising and falling edge events for the specified
duration (in machine units). duration (in machine units).
@ -261,7 +270,7 @@ class TTLInOut:
return now_mu() return now_mu()
@kernel @kernel
def gate_rising(self, duration): def gate_rising(self, duration: float) -> int64:
"""Register rising edge events for the specified duration """Register rising edge events for the specified duration
(in seconds). (in seconds).
@ -271,12 +280,12 @@ class TTLInOut:
convenience when used with :meth:`count`/:meth:`timestamp_mu`. convenience when used with :meth:`count`/:meth:`timestamp_mu`.
""" """
self._set_sensitivity(1) self._set_sensitivity(1)
delay(duration) self.core.delay(duration)
self._set_sensitivity(0) self._set_sensitivity(0)
return now_mu() return now_mu()
@kernel @kernel
def gate_falling(self, duration): def gate_falling(self, duration: float) -> int64:
"""Register falling edge events for the specified duration """Register falling edge events for the specified duration
(in seconds). (in seconds).
@ -287,12 +296,12 @@ class TTLInOut:
""" """
self._set_sensitivity(2) self._set_sensitivity(2)
delay(duration) self.core.delay(duration)
self._set_sensitivity(0) self._set_sensitivity(0)
return now_mu() return now_mu()
@kernel @kernel
def gate_both(self, duration): def gate_both(self, duration: float) -> int64:
"""Register both rising and falling edge events for the specified """Register both rising and falling edge events for the specified
duration (in seconds). duration (in seconds).
@ -302,12 +311,12 @@ class TTLInOut:
convenience when used with :meth:`count`/:meth:`timestamp_mu`. convenience when used with :meth:`count`/:meth:`timestamp_mu`.
""" """
self._set_sensitivity(3) self._set_sensitivity(3)
delay(duration) self.core.delay(duration)
self._set_sensitivity(0) self._set_sensitivity(0)
return now_mu() return now_mu()
@kernel @kernel
def count(self, up_to_timestamp_mu): def count(self, up_to_timestamp_mu: int64) -> int32:
"""Consume RTIO input events until the hardware timestamp counter has """Consume RTIO input events until the hardware timestamp counter has
reached the specified timestamp and return the number of observed reached the specified timestamp and return the number of observed
events. events.
@ -355,12 +364,12 @@ class TTLInOut:
ttl_input.count(ttl_input.gate_rising(100 * us)) ttl_input.count(ttl_input.gate_rising(100 * us))
""" """
count = 0 count = 0
while rtio_input_timestamp(up_to_timestamp_mu + self.gate_latency_mu, self.channel) >= 0: while rtio_input_timestamp(up_to_timestamp_mu + int64(self.gate_latency_mu), self.channel) >= int64(0):
count += 1 count += 1
return count return count
@kernel @kernel
def timestamp_mu(self, up_to_timestamp_mu): def timestamp_mu(self, up_to_timestamp_mu: int64) -> int64:
"""Return the timestamp of the next RTIO input event, or -1 if the """Return the timestamp of the next RTIO input event, or -1 if the
hardware timestamp counter reaches the given value before an event is hardware timestamp counter reaches the given value before an event is
received. received.
@ -378,7 +387,7 @@ class TTLInOut:
:return: The timestamp (in machine units) of the first event received; :return: The timestamp (in machine units) of the first event received;
-1 on timeout. -1 on timeout.
""" """
return rtio_input_timestamp(up_to_timestamp_mu + self.gate_latency_mu, self.channel) return rtio_input_timestamp(up_to_timestamp_mu + int64(self.gate_latency_mu), self.channel)
# Input API: sampling # Input API: sampling
@kernel @kernel
@ -390,7 +399,7 @@ class TTLInOut:
rtio_output(self.target_sample, 0) rtio_output(self.target_sample, 0)
@kernel @kernel
def sample_get(self): def sample_get(self) -> int32:
"""Returns the value of a sample previously obtained with """Returns the value of a sample previously obtained with
:meth:`sample_input`. :meth:`sample_input`.
@ -402,7 +411,7 @@ class TTLInOut:
return rtio_input_data(self.channel) return rtio_input_data(self.channel)
@kernel @kernel
def sample_get_nonrt(self): def sample_get_nonrt(self) -> int32:
"""Convenience function that obtains the value of a sample """Convenience function that obtains the value of a sample
at the position of the time cursor, breaks realtime, and at the position of the time cursor, breaks realtime, and
returns the sample value.""" returns the sample value."""
@ -413,7 +422,7 @@ class TTLInOut:
# Input API: watching # Input API: watching
@kernel @kernel
def watch_stay_on(self): def watch_stay_on(self) -> bool:
"""Checks that the input is at a high level at the position """Checks that the input is at a high level at the position
of the time cursor and keep checking until :meth:`watch_done` of the time cursor and keep checking until :meth:`watch_done`
is called. is called.
@ -428,13 +437,13 @@ class TTLInOut:
return rtio_input_data(self.channel) == 1 return rtio_input_data(self.channel) == 1
@kernel @kernel
def watch_stay_off(self): def watch_stay_off(self) -> bool:
"""Like :meth:`watch_stay_on`, but for low levels.""" """Like :meth:`watch_stay_on`, but for low levels."""
rtio_output(self.target_sample, 1) # gate rising rtio_output(self.target_sample, 1) # gate rising
return rtio_input_data(self.channel) == 0 return rtio_input_data(self.channel) == 0
@kernel @kernel
def watch_done(self): def watch_done(self) -> bool:
"""Stop watching the input at the position of the time cursor. """Stop watching the input at the position of the time cursor.
Returns ``True`` if the input has not changed state while it Returns ``True`` if the input has not changed state while it
@ -446,13 +455,14 @@ class TTLInOut:
rtio_output(self.target_sens, 0) rtio_output(self.target_sens, 0)
success = True success = True
try: try:
while rtio_input_timestamp(now_mu() + self.gate_latency_mu, self.channel) != -1: while rtio_input_timestamp(now_mu() + int64(self.gate_latency_mu), self.channel) != int64(-1):
success = False success = False
except RTIOOverflow: except RTIOOverflow:
success = False success = False
return success return success
@nac3
class TTLClockGen: class TTLClockGen:
"""RTIO TTL clock generator driver. """RTIO TTL clock generator driver.
@ -464,35 +474,37 @@ class TTLClockGen:
:param channel: channel number :param channel: channel number
:param acc_width: accumulator width in bits :param acc_width: accumulator width in bits
""" """
kernel_invariants = {"core", "channel", "target", "acc_width"} core: KernelInvariant[Core]
channel: KernelInvariant[int32]
target: KernelInvariant[int32]
acc_width: KernelInvariant[int32]
def __init__(self, dmgr, channel, acc_width=24, core_device="core"): def __init__(self, dmgr, channel, acc_width=24, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
self.channel = channel self.channel = channel
self.target = channel << 8 self.target = channel << 8
self.acc_width = acc_width
self.acc_width = numpy.int64(acc_width)
@staticmethod @staticmethod
def get_rtio_channels(channel, **kwargs): def get_rtio_channels(channel, **kwargs):
return [(channel, None)] return [(channel, None)]
@portable @portable
def frequency_to_ftw(self, frequency): def frequency_to_ftw(self, frequency: float) -> int32:
"""Returns the frequency tuning word corresponding to the given """Returns the frequency tuning word corresponding to the given
frequency. frequency.
""" """
return round(2**self.acc_width*frequency*self.core.coarse_ref_period) return round(2.**float(self.acc_width)*frequency*self.core.coarse_ref_period)
@portable @portable
def ftw_to_frequency(self, ftw): def ftw_to_frequency(self, ftw: int32) -> float:
"""Returns the frequency corresponding to the given frequency tuning """Returns the frequency corresponding to the given frequency tuning
word. word.
""" """
return ftw/self.core.coarse_ref_period/2**self.acc_width return float(ftw)/self.core.coarse_ref_period/2.**float(self.acc_width)
@kernel @kernel
def set_mu(self, frequency): def set_mu(self, frequency: int32):
"""Set the frequency of the clock, in machine units, at the current """Set the frequency of the clock, in machine units, at the current
position of the time cursor. position of the time cursor.
@ -512,7 +524,7 @@ class TTLClockGen:
rtio_output(self.target, frequency) rtio_output(self.target, frequency)
@kernel @kernel
def set(self, frequency): def set(self, frequency: float):
"""Like :meth:`set_mu`, but using Hz.""" """Like :meth:`set_mu`, but using Hz."""
self.set_mu(self.frequency_to_ftw(frequency)) self.set_mu(self.frequency_to_ftw(frequency))

View File

@ -1,15 +1,19 @@
from __future__ import annotations
from numpy import int32, int64 from numpy import int32, int64
from artiq.language.core import kernel, delay, portable, at_mu, now_mu from artiq.language.core import *
from artiq.language.units import us, ms from artiq.language.units import us, ms
from artiq.language.types import TInt32, TFloat, TBool
from artiq.coredevice import spi2 as spi from artiq.coredevice.core import Core
from artiq.coredevice.spi2 import *
from artiq.coredevice.ttl import TTLOut, TTLClockGen
SPI_CONFIG = (0 * spi.SPI_OFFLINE | 0 * spi.SPI_END |
0 * spi.SPI_INPUT | 1 * spi.SPI_CS_POLARITY | SPI_CONFIG = (0 * SPI_OFFLINE | 0 * SPI_END |
0 * spi.SPI_CLK_POLARITY | 0 * spi.SPI_CLK_PHASE | 0 * SPI_INPUT | 1 * SPI_CS_POLARITY |
0 * spi.SPI_LSB_FIRST | 0 * spi.SPI_HALF_DUPLEX) 0 * SPI_CLK_POLARITY | 0 * SPI_CLK_PHASE |
0 * SPI_LSB_FIRST | 0 * SPI_HALF_DUPLEX)
# SPI clock write and read dividers # SPI clock write and read dividers
SPIT_CFG_WR = 2 SPIT_CFG_WR = 2
@ -57,8 +61,8 @@ DEFAULT_PROFILE = 7
@portable @portable
def urukul_cfg(rf_sw, led, profile, io_update, mask_nu, def urukul_cfg(rf_sw: int32, led: int32, profile: int32, io_update: int32, mask_nu: int32,
clk_sel, sync_sel, rst, io_rst, clk_div): clk_sel: int32, sync_sel: int32, rst: int32, io_rst: int32, clk_div: int32) -> int32:
"""Build Urukul CPLD configuration register""" """Build Urukul CPLD configuration register"""
return ((rf_sw << CFG_RF_SW) | return ((rf_sw << CFG_RF_SW) |
(led << CFG_LED) | (led << CFG_LED) |
@ -74,56 +78,36 @@ def urukul_cfg(rf_sw, led, profile, io_update, mask_nu,
@portable @portable
def urukul_sta_rf_sw(sta): def urukul_sta_rf_sw(sta: int32) -> int32:
"""Return the RF switch status from Urukul status register value.""" """Return the RF switch status from Urukul status register value."""
return (sta >> STA_RF_SW) & 0xf return (sta >> STA_RF_SW) & 0xf
@portable @portable
def urukul_sta_smp_err(sta): def urukul_sta_smp_err(sta: int32) -> int32:
"""Return the SMP_ERR status from Urukul status register value.""" """Return the SMP_ERR status from Urukul status register value."""
return (sta >> STA_SMP_ERR) & 0xf return (sta >> STA_SMP_ERR) & 0xf
@portable @portable
def urukul_sta_pll_lock(sta): def urukul_sta_pll_lock(sta: int32) -> int32:
"""Return the PLL_LOCK status from Urukul status register value.""" """Return the PLL_LOCK status from Urukul status register value."""
return (sta >> STA_PLL_LOCK) & 0xf return (sta >> STA_PLL_LOCK) & 0xf
@portable @portable
def urukul_sta_ifc_mode(sta): def urukul_sta_ifc_mode(sta: int32) -> int32:
"""Return the IFC_MODE status from Urukul status register value.""" """Return the IFC_MODE status from Urukul status register value."""
return (sta >> STA_IFC_MODE) & 0xf return (sta >> STA_IFC_MODE) & 0xf
@portable @portable
def urukul_sta_proto_rev(sta): def urukul_sta_proto_rev(sta: int32) -> int32:
"""Return the PROTO_REV value from Urukul status register value.""" """Return the PROTO_REV value from Urukul status register value."""
return (sta >> STA_PROTO_REV) & 0x7f return (sta >> STA_PROTO_REV) & 0x7f
class _RegIOUpdate: @nac3
def __init__(self, cpld):
self.cpld = cpld
@kernel
def pulse(self, t: TFloat):
cfg = self.cpld.cfg_reg
self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE))
delay(t)
self.cpld.cfg_write(cfg)
class _DummySync:
def __init__(self, cpld):
self.cpld = cpld
@kernel
def set_mu(self, ftw: TInt32):
pass
class CPLD: class CPLD:
"""Urukul CPLD SPI router and configuration interface. """Urukul CPLD SPI router and configuration interface.
@ -162,7 +146,17 @@ class CPLD:
front panel SMA with no clock connected), then the ``init()`` method of front panel SMA with no clock connected), then the ``init()`` method of
the DDS channels can fail with the error message ``PLL lock timeout``. the DDS channels can fail with the error message ``PLL lock timeout``.
""" """
kernel_invariants = {"refclk", "bus", "core", "io_update", "clk_div"}
core: KernelInvariant[Core]
refclk: KernelInvariant[float]
bus: KernelInvariant[SPIMaster]
io_update: KernelInvariant[TTLOut]
clk_div: KernelInvariant[int32]
dds_reset: KernelInvariant[Option[TTLOut]]
sync: KernelInvariant[Option[TTLClockGen]]
cfg_reg: Kernel[int32]
att_reg: Kernel[int32]
sync_div: Kernel[int32]
def __init__(self, dmgr, spi_device, io_update_device=None, def __init__(self, dmgr, spi_device, io_update_device=None,
dds_reset_device=None, sync_device=None, dds_reset_device=None, sync_device=None,
@ -179,15 +173,19 @@ class CPLD:
if io_update_device is not None: if io_update_device is not None:
self.io_update = dmgr.get(io_update_device) self.io_update = dmgr.get(io_update_device)
else: else:
self.io_update = _RegIOUpdate(self) self.io_update = _RegIOUpdate(self.core, self)
# NAC3TODO
raise NotImplementedError
if dds_reset_device is not None: if dds_reset_device is not None:
self.dds_reset = dmgr.get(dds_reset_device) self.dds_reset = Some(dmgr.get(dds_reset_device))
else:
self.dds_reset = none
if sync_device is not None: if sync_device is not None:
self.sync = dmgr.get(sync_device) self.sync = Some(dmgr.get(sync_device))
if sync_div is None: if sync_div is None:
sync_div = 2 sync_div = 2
else: else:
self.sync = _DummySync(self) self.sync = none
assert sync_div is None assert sync_div is None
sync_div = 0 sync_div = 0
@ -199,7 +197,7 @@ class CPLD:
self.sync_div = sync_div self.sync_div = sync_div
@kernel @kernel
def cfg_write(self, cfg: TInt32): def cfg_write(self, cfg: int32):
"""Write to the configuration register. """Write to the configuration register.
See :func:`urukul_cfg` for possible flags. See :func:`urukul_cfg` for possible flags.
@ -207,13 +205,13 @@ class CPLD:
:param cfg: 24 bit data to be written. Will be stored at :param cfg: 24 bit data to be written. Will be stored at
:attr:`cfg_reg`. :attr:`cfg_reg`.
""" """
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24,
SPIT_CFG_WR, CS_CFG) SPIT_CFG_WR, CS_CFG)
self.bus.write(cfg << 8) self.bus.write(cfg << 8)
self.cfg_reg = cfg self.cfg_reg = cfg
@kernel @kernel
def sta_read(self) -> TInt32: def sta_read(self) -> int32:
"""Read the status register. """Read the status register.
Use any of the following functions to extract values: Use any of the following functions to extract values:
@ -226,13 +224,13 @@ class CPLD:
:return: The status register value. :return: The status register value.
""" """
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 24, self.bus.set_config_mu(SPI_CONFIG | SPI_END | SPI_INPUT, 24,
SPIT_CFG_RD, CS_CFG) SPIT_CFG_RD, CS_CFG)
self.bus.write(self.cfg_reg << 8) self.bus.write(self.cfg_reg << 8)
return self.bus.read() return self.bus.read()
@kernel @kernel
def init(self, blind: TBool = False): def init(self, blind: bool = False):
"""Initialize and detect Urukul. """Initialize and detect Urukul.
Resets the DDS I/O interface and verifies correct CPLD gateware Resets the DDS I/O interface and verifies correct CPLD gateware
@ -250,12 +248,12 @@ class CPLD:
proto_rev = urukul_sta_proto_rev(self.sta_read()) proto_rev = urukul_sta_proto_rev(self.sta_read())
if proto_rev != STA_PROTO_REV_MATCH: if proto_rev != STA_PROTO_REV_MATCH:
raise ValueError("Urukul proto_rev mismatch") raise ValueError("Urukul proto_rev mismatch")
delay(100 * us) # reset, slack self.core.delay(100. * us) # reset, slack
self.cfg_write(cfg) self.cfg_write(cfg)
if self.sync_div: if self.sync_div != 0:
at_mu(now_mu() & ~0xf) # align to RTIO/2 at_mu(now_mu() & ~int64(0xf)) # align to RTIO/2
self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16 self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16
delay(1 * ms) # DDS wake up self.core.delay(1. * ms) # DDS wake up
@kernel @kernel
def io_rst(self): def io_rst(self):
@ -264,7 +262,7 @@ class CPLD:
self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST)) self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST))
@kernel @kernel
def cfg_sw(self, channel: TInt32, on: TBool): def cfg_sw(self, channel: int32, on: bool):
"""Configure the RF switches through the configuration register. """Configure the RF switches through the configuration register.
These values are logically OR-ed with the LVDS lines on EEM1. These values are logically OR-ed with the LVDS lines on EEM1.
@ -280,15 +278,15 @@ class CPLD:
self.cfg_write(c) self.cfg_write(c)
@kernel @kernel
def cfg_switches(self, state: TInt32): def cfg_switches(self, state: int32):
"""Configure all four RF switches through the configuration register. """Configure all four RF switches through the configuration register.
:param state: RF switch state as a 4 bit integer. :param state: RF switch state as a 4 bit integer.
""" """
self.cfg_write((self.cfg_reg & ~0xf) | state) self.cfg_write((self.cfg_reg & ~0xf) | state)
@portable(flags={"fast-math"}) @portable
def mu_to_att(self, att_mu: TInt32) -> TFloat: def mu_to_att(self, att_mu: int32) -> float:
"""Convert a digital attenuation setting to dB. """Convert a digital attenuation setting to dB.
:param att_mu: Digital attenuation setting. :param att_mu: Digital attenuation setting.
@ -296,20 +294,20 @@ class CPLD:
""" """
return (255 - (att_mu & 0xff)) / 8 return (255 - (att_mu & 0xff)) / 8
@portable(flags={"fast-math"}) @portable
def att_to_mu(self, att: TFloat) -> TInt32: def att_to_mu(self, att: float) -> int32:
"""Convert an attenuation setting in dB to machine units. """Convert an attenuation setting in dB to machine units.
:param att: Attenuation setting in dB. :param att: Attenuation setting in dB.
:return: Digital attenuation setting. :return: Digital attenuation setting.
""" """
code = int32(255) - int32(round(att * 8)) code = 255 - round(att * 8.)
if code < 0 or code > 255: if code < 0 or code > 255:
raise ValueError("Invalid urukul.CPLD attenuation!") raise ValueError("Invalid urukul.CPLD attenuation!")
return code return code
@kernel @kernel
def set_att_mu(self, channel: TInt32, att: TInt32): def set_att_mu(self, channel: int32, att: int32):
"""Set digital step attenuator in machine units. """Set digital step attenuator in machine units.
This method will also write the attenuator settings of the three This method will also write the attenuator settings of the three
@ -325,20 +323,20 @@ class CPLD:
self.set_all_att_mu(a) self.set_all_att_mu(a)
@kernel @kernel
def set_all_att_mu(self, att_reg: TInt32): def set_all_att_mu(self, att_reg: int32):
"""Set all four digital step attenuators (in machine units). """Set all four digital step attenuators (in machine units).
.. seealso:: :meth:`set_att_mu` .. seealso:: :meth:`set_att_mu`
:param att_reg: Attenuator setting string (32 bit) :param att_reg: Attenuator setting string (32 bit)
""" """
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32, self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
SPIT_ATT_WR, CS_ATT) SPIT_ATT_WR, CS_ATT)
self.bus.write(att_reg) self.bus.write(att_reg)
self.att_reg = att_reg self.att_reg = att_reg
@kernel @kernel
def set_att(self, channel: TInt32, att: TFloat): def set_att(self, channel: int32, att: float):
"""Set digital step attenuator in SI units. """Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels. This method will write the attenuator settings of all four channels.
@ -353,7 +351,7 @@ class CPLD:
self.set_att_mu(channel, self.att_to_mu(att)) self.set_att_mu(channel, self.att_to_mu(att))
@kernel @kernel
def get_att_mu(self) -> TInt32: def get_att_mu(self) -> int32:
"""Return the digital step attenuator settings in machine units. """Return the digital step attenuator settings in machine units.
The result is stored and will be used in future calls of The result is stored and will be used in future calls of
@ -363,18 +361,18 @@ class CPLD:
:return: 32 bit attenuator settings :return: 32 bit attenuator settings
""" """
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 32, self.bus.set_config_mu(SPI_CONFIG | SPI_INPUT, 32,
SPIT_ATT_RD, CS_ATT) SPIT_ATT_RD, CS_ATT)
self.bus.write(0) # shift in zeros, shift out current value self.bus.write(0) # shift in zeros, shift out current value
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32, self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
SPIT_ATT_WR, CS_ATT) SPIT_ATT_WR, CS_ATT)
delay(10 * us) self.core.delay(10. * us)
self.att_reg = self.bus.read() self.att_reg = self.bus.read()
self.bus.write(self.att_reg) # shift in current value again and latch self.bus.write(self.att_reg) # shift in current value again and latch
return self.att_reg return self.att_reg
@kernel @kernel
def get_channel_att_mu(self, channel: TInt32) -> TInt32: def get_channel_att_mu(self, channel: int32) -> int32:
"""Get digital step attenuator value for a channel in machine units. """Get digital step attenuator value for a channel in machine units.
The result is stored and will be used in future calls of The result is stored and will be used in future calls of
@ -389,7 +387,7 @@ class CPLD:
return int32((self.get_att_mu() >> (channel * 8)) & 0xff) return int32((self.get_att_mu() >> (channel * 8)) & 0xff)
@kernel @kernel
def get_channel_att(self, channel: TInt32) -> TFloat: def get_channel_att(self, channel: int32) -> float:
"""Get digital step attenuator value for a channel in SI units. """Get digital step attenuator value for a channel in SI units.
.. seealso:: :meth:`get_channel_att_mu` .. seealso:: :meth:`get_channel_att_mu`
@ -402,7 +400,7 @@ class CPLD:
return self.mu_to_att(self.get_channel_att_mu(channel)) return self.mu_to_att(self.get_channel_att_mu(channel))
@kernel @kernel
def set_sync_div(self, div: TInt32): def set_sync_div(self, div: int32):
"""Set the SYNC_IN AD9910 pulse generator frequency """Set the SYNC_IN AD9910 pulse generator frequency
and align it to the current RTIO timestamp. and align it to the current RTIO timestamp.
@ -416,10 +414,11 @@ class CPLD:
ftw_max = 1 << 4 ftw_max = 1 << 4
ftw = ftw_max // div ftw = ftw_max // div
assert ftw * div == ftw_max assert ftw * div == ftw_max
self.sync.set_mu(ftw) if self.sync.is_some():
self.sync.unwrap().set_mu(ftw)
@kernel @kernel
def set_profile(self, profile: TInt32): def set_profile(self, profile: int32):
"""Set the PROFILE pins. """Set the PROFILE pins.
The PROFILE pins are common to all four DDS channels. The PROFILE pins are common to all four DDS channels.
@ -429,3 +428,24 @@ class CPLD:
cfg = self.cfg_reg & ~(7 << CFG_PROFILE) cfg = self.cfg_reg & ~(7 << CFG_PROFILE)
cfg |= (profile & 7) << CFG_PROFILE cfg |= (profile & 7) << CFG_PROFILE
self.cfg_write(cfg) self.cfg_write(cfg)
@nac3
class _RegIOUpdate:
core: KernelInvariant[Core]
cpld: KernelInvariant[CPLD]
def __init__(self, core, cpld):
self.core = core
self.cpld = cpld
@kernel
def pulse_mu(self, t: int64):
cfg = self.cpld.cfg_reg
self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE))
delay_mu(t)
self.cpld.cfg_write(cfg)
@kernel
def pulse(self, t: float):
self.pulse_mu(self.core.seconds_to_mu(t))

View File

@ -4,19 +4,23 @@ Output event replacement is not supported and issuing commands at the same
time is an error. time is an error.
""" """
from artiq.language.core import kernel from numpy import int32
from artiq.coredevice import spi2 as spi
from artiq.language.core import nac3, kernel
from artiq.coredevice.spi2 import *
from artiq.coredevice.ad53xx import SPI_AD53XX_CONFIG, AD53xx from artiq.coredevice.ad53xx import SPI_AD53XX_CONFIG, AD53xx
_SPI_SR_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | _SPI_SR_CONFIG = (0*SPI_OFFLINE | 1*SPI_END |
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | 0*SPI_INPUT | 0*SPI_CS_POLARITY |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) 0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE |
0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
_SPI_CS_DAC = 1 _SPI_CS_DAC = 1
_SPI_CS_SR = 2 _SPI_CS_SR = 2
@nac3
class Zotino(AD53xx): class Zotino(AD53xx):
""" Zotino 32-channel, 16-bit 1MSPS DAC. """ Zotino 32-channel, 16-bit 1MSPS DAC.
@ -42,8 +46,8 @@ class Zotino(AD53xx):
div_read=div_read, core=core) div_read=div_read, core=core)
@kernel @kernel
def set_leds(self, leds): def set_leds(self, leds: int32):
""" Sets the states of the 8 user LEDs. """Sets the states of the 8 user LEDs.
:param leds: 8-bit word with LED state :param leds: 8-bit word with LED state
""" """

View File

@ -13,7 +13,6 @@ from artiq.gui.entries import procdesc_to_entry, ScanEntry
from artiq.gui.fuzzy_select import FuzzySelectWidget from artiq.gui.fuzzy_select import FuzzySelectWidget
from artiq.gui.tools import (LayoutWidget, WheelFilter, from artiq.gui.tools import (LayoutWidget, WheelFilter,
log_level_to_name, get_open_file_name) log_level_to_name, get_open_file_name)
from artiq.tools import parse_devarg_override, unparse_devarg_override
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -306,7 +305,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
flush = self.flush flush = self.flush
flush.setToolTip("Flush the pipeline (of current- and higher-priority " flush.setToolTip("Flush the pipeline (of current- and higher-priority "
"experiments) before starting the experiment") "experiments) before starting the experiment")
self.layout.addWidget(flush, 2, 2) self.layout.addWidget(flush, 2, 2, 1, 2)
flush.setChecked(scheduling["flush"]) flush.setChecked(scheduling["flush"])
@ -314,20 +313,6 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
scheduling["flush"] = bool(checked) scheduling["flush"] = bool(checked)
flush.stateChanged.connect(update_flush) flush.stateChanged.connect(update_flush)
devarg_override = QtWidgets.QComboBox()
devarg_override.setEditable(True)
devarg_override.lineEdit().setPlaceholderText("Override device arguments")
devarg_override.lineEdit().setClearButtonEnabled(True)
devarg_override.insertItem(0, "core:analyze_at_run_end=True")
self.layout.addWidget(devarg_override, 2, 3)
devarg_override.setCurrentText(options["devarg_override"])
def update_devarg_override(text):
options["devarg_override"] = text
devarg_override.editTextChanged.connect(update_devarg_override)
self.devarg_override = devarg_override
log_level = QtWidgets.QComboBox() log_level = QtWidgets.QComboBox()
log_level.addItems(log_levels) log_level.addItems(log_levels)
log_level.setCurrentIndex(1) log_level.setCurrentIndex(1)
@ -348,7 +333,6 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
if "repo_rev" in options: if "repo_rev" in options:
repo_rev = QtWidgets.QLineEdit() repo_rev = QtWidgets.QLineEdit()
repo_rev.setPlaceholderText("current") repo_rev.setPlaceholderText("current")
repo_rev.setClearButtonEnabled(True)
repo_rev_label = QtWidgets.QLabel("Revision:") repo_rev_label = QtWidgets.QLabel("Revision:")
repo_rev_label.setToolTip("Experiment repository revision " repo_rev_label.setToolTip("Experiment repository revision "
"(commit ID) to use") "(commit ID) to use")
@ -481,9 +465,6 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
return return
try: try:
if "devarg_override" in expid:
self.devarg_override.setCurrentText(
unparse_devarg_override(expid["devarg_override"]))
self.log_level.setCurrentIndex(log_levels.index( self.log_level.setCurrentIndex(log_levels.index(
log_level_to_name(expid["log_level"]))) log_level_to_name(expid["log_level"])))
if ("repo_rev" in expid and if ("repo_rev" in expid and
@ -657,8 +638,7 @@ class ExperimentManager:
else: else:
# mutated by _ExperimentDock # mutated by _ExperimentDock
options = { options = {
"log_level": logging.WARNING, "log_level": logging.WARNING
"devarg_override": ""
} }
if expurl[:5] == "repo:": if expurl[:5] == "repo:":
options["repo_rev"] = None options["repo_rev"] = None
@ -753,14 +733,7 @@ class ExperimentManager:
entry_cls = procdesc_to_entry(argument["desc"]) entry_cls = procdesc_to_entry(argument["desc"])
argument_values[name] = entry_cls.state_to_value(argument["state"]) argument_values[name] = entry_cls.state_to_value(argument["state"])
try:
devarg_override = parse_devarg_override(options["devarg_override"])
except:
logger.error("Failed to parse device argument overrides for %s", expurl)
return
expid = { expid = {
"devarg_override": devarg_override,
"log_level": options["log_level"], "log_level": options["log_level"],
"file": file, "file": file,
"class_name": class_name, "class_name": class_name,

View File

@ -1,249 +0,0 @@
# Tester device database
core_addr = "192.168.1.70"
device_db = {
"core": {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {
"host": core_addr,
"ref_period": 1e-9,
"analyzer_proxy": "core_analyzer"
}
},
"core_log": {
"type": "controller",
"host": "::1",
"port": 1068,
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
},
"core_moninj": {
"type": "controller",
"host": "::1",
"port_proxy": 1383,
"port": 1384,
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_analyzer": {
"type": "controller",
"host": "::1",
"port_proxy": 1385,
"port": 1386,
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",
"class": "CoreCache"
},
"core_dma": {
"type": "local",
"module": "artiq.coredevice.dma",
"class": "CoreDMA"
},
"i2c_switch0": {
"type": "local",
"module": "artiq.coredevice.i2c",
"class": "I2CSwitch",
"arguments": {"address": 0xe0}
},
"i2c_switch1": {
"type": "local",
"module": "artiq.coredevice.i2c",
"class": "I2CSwitch",
"arguments": {"address": 0xe2}
},
}
# DIO (EEM5) starting at RTIO channel 0
for i in range(8):
device_db["ttl" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLInOut" if i < 4 else "TTLOut",
"arguments": {"channel": i},
}
device_db["ttl{}_counter".format(i)] = {
"type": "local",
"module": "artiq.coredevice.edge_counter",
"class": "EdgeCounter",
"arguments": {"channel": 8 + i},
}
# Urukul (EEM1) starting at RTIO channel 12
device_db.update(
eeprom_urukul0={
"type": "local",
"module": "artiq.coredevice.kasli_i2c",
"class": "KasliEEPROM",
"arguments": {"port": "EEM1"}
},
spi_urukul0={
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 12}
},
ttl_urukul0_sync={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLClockGen",
"arguments": {"channel": 13, "acc_width": 4}
},
ttl_urukul0_io_update={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 14}
},
ttl_urukul0_sw0={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 15}
},
ttl_urukul0_sw1={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 16}
},
ttl_urukul0_sw2={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 17}
},
ttl_urukul0_sw3={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 18}
},
urukul0_cpld={
"type": "local",
"module": "artiq.coredevice.urukul",
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul0",
"io_update_device": "ttl_urukul0_io_update",
"sync_device": "ttl_urukul0_sync",
"refclk": 125e6,
"clk_sel": 2
}
}
)
for i in range(4):
device_db["urukul0_ch" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 32,
"chip_select": 4 + i,
"cpld_device": "urukul0_cpld",
"sw_device": "ttl_urukul0_sw" + str(i),
"sync_delay_seed": "eeprom_urukul0:" + str(64 + 4*i),
"io_update_delay": "eeprom_urukul0:" + str(64 + 4*i),
}
}
# Sampler (EEM3) starting at RTIO channel 19
device_db["spi_sampler0_adc"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 19}
}
device_db["spi_sampler0_pgia"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 20}
}
device_db["spi_sampler0_cnv"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 21},
}
device_db["sampler0"] = {
"type": "local",
"module": "artiq.coredevice.sampler",
"class": "Sampler",
"arguments": {
"spi_adc_device": "spi_sampler0_adc",
"spi_pgia_device": "spi_sampler0_pgia",
"cnv_device": "spi_sampler0_cnv"
}
}
# Zotino (EEM4) starting at RTIO channel 22
device_db["spi_zotino0"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 22}
}
device_db["ttl_zotino0_ldac"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 23}
}
device_db["ttl_zotino0_clr"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 24}
}
device_db["zotino0"] = {
"type": "local",
"module": "artiq.coredevice.zotino",
"class": "Zotino",
"arguments": {
"spi_device": "spi_zotino0",
"ldac_device": "ttl_zotino0_ldac",
"clr_device": "ttl_zotino0_clr"
}
}
device_db.update(
led0={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 25}
},
led1={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 26}
},
)
device_db.update(
i2c_switch="i2c_switch0",
ttl_out="ttl4",
ttl_out_serdes="ttl4",
loop_out="ttl4",
loop_in="ttl0",
loop_in_counter="ttl0_counter",
# Urukul CPLD with sync and io_update, IFC MODE 0b1000
urukul_cpld="urukul0_cpld",
# Urukul AD9910 with switch TTL, internal 125 MHz MMCX connection
urukul_ad9910="urukul0_ch0",
)

View File

@ -1,21 +0,0 @@
from artiq.experiment import *
class IdleKernel(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("led0")
@kernel
def run(self):
start_time = now_mu() + self.core.seconds_to_mu(500*ms)
while self.core.get_rtio_counter_mu() < start_time:
pass
self.core.reset()
while True:
self.led0.pulse(250*ms)
delay(125*ms)
self.led0.pulse(125*ms)
delay(125*ms)
self.led0.pulse(125*ms)
delay(250*ms)

View File

@ -1,52 +0,0 @@
core_addr = "192.168.1.70"
device_db = {
"core": {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {
"host": core_addr,
"ref_period": 1e-9,
"analyzer_proxy": "core_analyzer"
}
},
"core_log": {
"type": "controller",
"host": "::1",
"port": 1068,
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
},
"core_moninj": {
"type": "controller",
"host": "::1",
"port_proxy": 1383,
"port": 1384,
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_analyzer": {
"type": "controller",
"host": "::1",
"port_proxy": 1385,
"port": 1386,
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",
"class": "CoreCache"
},
"core_dma": {
"type": "local",
"module": "artiq.coredevice.dma",
"class": "CoreDMA"
},
}
for i in range(3):
device_db["led" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": i << 16},
}

View File

@ -1,16 +0,0 @@
from artiq.experiment import *
class Blink(EnvExperiment):
def build(self):
self.setattr_device("core")
self.leds = [self.get_device("led0"), self.get_device("led2")]
@kernel
def run(self):
self.core.reset()
while True:
for led in self.leds:
led.pulse(200*ms)
delay(200*ms)

View File

@ -1,61 +1,82 @@
from artiq.experiment import * from numpy import int32, int64
from artiq.coredevice.shuttler import shuttler_volt_to_mu
DAC_Fs_MHZ = 125 from artiq.experiment import *
from artiq.coredevice.core import Core
from artiq.coredevice.ttl import TTLOut
from artiq.coredevice.shuttler import (
shuttler_volt_to_mu,
Config as ShuttlerConfig,
Trigger as ShuttlerTrigger,
DCBias as ShuttlerDCBias,
DDS as ShuttlerDDS,
Relay as ShuttlerRelay,
ADC as ShuttlerADC)
DAC_Fs_MHZ = 125.
CORDIC_GAIN = 1.64676 CORDIC_GAIN = 1.64676
@portable @portable
def shuttler_phase_offset(offset_degree): def shuttler_phase_offset(offset_degree: float) -> int32:
return round(offset_degree / 360 * (2 ** 16)) return round(offset_degree / 360. * float(2 ** 16))
@portable @portable
def shuttler_freq_mu(freq_mhz): def shuttler_freq_mu(freq_mhz: float) -> int32:
return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz) return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz)
@portable @portable
def shuttler_chirp_rate_mu(freq_mhz_per_us): def shuttler_chirp_rate_mu(freq_mhz_per_us: float) -> int32:
return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2)) return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2))
@portable @portable
def shuttler_freq_sweep(start_f_MHz, end_f_MHz, time_us): def shuttler_freq_sweep(start_f_MHz: float, end_f_MHz: float, time_us: float) -> int32:
return shuttler_chirp_rate_mu((end_f_MHz - start_f_MHz)/(time_us)) return shuttler_chirp_rate_mu((end_f_MHz - start_f_MHz)/time_us)
@portable @portable
def shuttler_volt_amp_mu(volt): def shuttler_volt_amp_mu(volt: float) -> int32:
return shuttler_volt_to_mu(volt) return shuttler_volt_to_mu(volt)
@portable @portable
def shuttler_volt_damp_mu(volt_per_us): def shuttler_volt_damp_mu(volt_per_us: float) -> int32:
return round(float(2) ** 32 * (volt_per_us / 20) / DAC_Fs_MHZ) return round(float(2) ** 32 * (volt_per_us / 20.) / DAC_Fs_MHZ)
@portable @portable
def shuttler_volt_ddamp_mu(volt_per_us_square): def shuttler_volt_ddamp_mu(volt_per_us_square: float) -> int64:
return round(float(2) ** 48 * (volt_per_us_square / 20) * 2 / (DAC_Fs_MHZ ** 2)) return round64(float(2) ** 48 * (volt_per_us_square / 20.) * 2. / (DAC_Fs_MHZ ** 2))
@portable @portable
def shuttler_volt_dddamp_mu(volt_per_us_cube): def shuttler_volt_dddamp_mu(volt_per_us_cube: float) -> int64:
return round(float(2) ** 48 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3)) return round64(float(2) ** 48 * (volt_per_us_cube / 20.) * 6. / (DAC_Fs_MHZ ** 3))
@portable @portable
def shuttler_dds_amp_mu(volt): def shuttler_dds_amp_mu(volt: float) -> int32:
return shuttler_volt_amp_mu(volt / CORDIC_GAIN) return shuttler_volt_amp_mu(volt / CORDIC_GAIN)
@portable @portable
def shuttler_dds_damp_mu(volt_per_us): def shuttler_dds_damp_mu(volt_per_us: float) -> int32:
return shuttler_volt_damp_mu(volt_per_us / CORDIC_GAIN) return shuttler_volt_damp_mu(volt_per_us / CORDIC_GAIN)
@portable @portable
def shuttler_dds_ddamp_mu(volt_per_us_square): def shuttler_dds_ddamp_mu(volt_per_us_square: float) -> int64:
return shuttler_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN) return shuttler_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN)
@portable @portable
def shuttler_dds_dddamp_mu(volt_per_us_cube): def shuttler_dds_dddamp_mu(volt_per_us_cube: float) -> int64:
return shuttler_volt_dddamp_mu(volt_per_us_cube / CORDIC_GAIN) return shuttler_volt_dddamp_mu(volt_per_us_cube / CORDIC_GAIN)
@nac3
class Shuttler(EnvExperiment): class Shuttler(EnvExperiment):
core: KernelInvariant[Core]
shuttler0_leds: KernelInvariant[list[TTLOut]]
shuttler0_config: KernelInvariant[ShuttlerConfig]
shuttler0_trigger: KernelInvariant[ShuttlerTrigger]
shuttler0_dcbias: KernelInvariant[list[ShuttlerDCBias]]
shuttler0_dds: KernelInvariant[list[ShuttlerDDS]]
shuttler0_relay: KernelInvariant[ShuttlerRelay]
shuttler0_adc: KernelInvariant[ShuttlerADC]
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("core_dma")
self.setattr_device("scheduler") self.setattr_device("scheduler")
self.shuttler0_leds = [ self.get_device("shuttler0_led{}".format(i)) for i in range(2) ] self.shuttler0_leds = [ self.get_device("shuttler0_led{}".format(i)) for i in range(2) ]
self.setattr_device("shuttler0_config") self.setattr_device("shuttler0_config")
@ -65,12 +86,6 @@ class Shuttler(EnvExperiment):
self.setattr_device("shuttler0_relay") self.setattr_device("shuttler0_relay")
self.setattr_device("shuttler0_adc") self.setattr_device("shuttler0_adc")
@kernel
def record(self):
with self.core_dma.record("example_waveform"):
self.example_waveform()
@kernel @kernel
def init(self): def init(self):
self.led() self.led()
@ -84,35 +99,32 @@ class Shuttler(EnvExperiment):
self.core.break_realtime() self.core.break_realtime()
self.init() self.init()
self.record() print_rpc("Example Waveforms are on OUT0 and OUT1")
example_waveform_handle = self.core_dma.get_handle("example_waveform")
print("Example Waveforms are on OUT0 and OUT1")
self.core.break_realtime() self.core.break_realtime()
while not(self.scheduler.check_termination()): while not(self.scheduler.check_termination()):
delay(1*s) self.core.delay(1.*s)
self.core_dma.playback_handle(example_waveform_handle) self.example_waveform()
@kernel @kernel
def shuttler_reset(self): def shuttler_reset(self):
for i in range(16): for i in range(16):
self.shuttler_channel_reset(i) self.shuttler_channel_reset(i)
# To avoid RTIO Underflow # To avoid RTIO Underflow
delay(50*us) self.core.delay(50.*us)
@kernel @kernel
def shuttler_channel_reset(self, ch): def shuttler_channel_reset(self, ch: int32):
self.shuttler0_dcbias[ch].set_waveform( self.shuttler0_dcbias[ch].set_waveform(
a0=0, a0=0,
a1=0, a1=0,
a2=0, a2=int64(0),
a3=0, a3=int64(0),
) )
self.shuttler0_dds[ch].set_waveform( self.shuttler0_dds[ch].set_waveform(
b0=0, b0=0,
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=0, c1=0,
c2=0, c2=0,
@ -163,13 +175,13 @@ class Shuttler(EnvExperiment):
## Step 2 ## ## Step 2 ##
start_f_MHz = 0.01 start_f_MHz = 0.01
end_f_MHz = 0.05 end_f_MHz = 0.05
duration_us = 500 duration_us = 500.
# OUT0 and OUT1 have their frequency and phase aligned at 500us # OUT0 and OUT1 have their frequency and phase aligned at 500us
self.shuttler0_dds[0].set_waveform( self.shuttler0_dds[0].set_waveform(
b0=shuttler_dds_amp_mu(1.0), b0=shuttler_dds_amp_mu(1.0),
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=shuttler_freq_mu(start_f_MHz), c1=shuttler_freq_mu(start_f_MHz),
c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us), c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us),
@ -177,22 +189,22 @@ class Shuttler(EnvExperiment):
self.shuttler0_dds[1].set_waveform( self.shuttler0_dds[1].set_waveform(
b0=shuttler_dds_amp_mu(1.0), b0=shuttler_dds_amp_mu(1.0),
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=shuttler_freq_mu(end_f_MHz), c1=shuttler_freq_mu(end_f_MHz),
c2=0, c2=0,
) )
self.shuttler0_trigger.trigger(0b11) self.shuttler0_trigger.trigger(0b11)
delay(500*us) self.core.delay(500.*us)
## Step 3 ## ## Step 3 ##
# OUT0 and OUT1 has 180 degree phase difference # OUT0 and OUT1 has 180 degree phase difference
self.shuttler0_dds[0].set_waveform( self.shuttler0_dds[0].set_waveform(
b0=shuttler_dds_amp_mu(1.0), b0=shuttler_dds_amp_mu(1.0),
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=shuttler_phase_offset(180.0), c0=shuttler_phase_offset(180.0),
c1=shuttler_freq_mu(end_f_MHz), c1=shuttler_freq_mu(end_f_MHz),
c2=0, c2=0,
@ -200,7 +212,7 @@ class Shuttler(EnvExperiment):
# Phase and Output Setting of OUT1 is retained # Phase and Output Setting of OUT1 is retained
# if the channel is not triggered or config is not cleared # if the channel is not triggered or config is not cleared
self.shuttler0_trigger.trigger(0b1) self.shuttler0_trigger.trigger(0b1)
delay(500*us) self.core.delay(500.*us)
## Step 4 ## ## Step 4 ##
# b(0) = 0, b(250) = 8.545, b(500) = 0 # b(0) = 0, b(250) = 8.545, b(500) = 0
@ -208,7 +220,7 @@ class Shuttler(EnvExperiment):
b0=0, b0=0,
b1=shuttler_dds_damp_mu(0.06835937), b1=shuttler_dds_damp_mu(0.06835937),
b2=shuttler_dds_ddamp_mu(-0.0001367187), b2=shuttler_dds_ddamp_mu(-0.0001367187),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=shuttler_freq_mu(end_f_MHz), c1=shuttler_freq_mu(end_f_MHz),
c2=0, c2=0,
@ -217,26 +229,26 @@ class Shuttler(EnvExperiment):
b0=0, b0=0,
b1=shuttler_dds_damp_mu(0.06835937), b1=shuttler_dds_damp_mu(0.06835937),
b2=shuttler_dds_ddamp_mu(-0.0001367187), b2=shuttler_dds_ddamp_mu(-0.0001367187),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=0, c1=0,
c2=0, c2=0,
) )
self.shuttler0_trigger.trigger(0b11) self.shuttler0_trigger.trigger(0b11)
delay(500*us) self.core.delay(500.*us)
## Step 5 ## ## Step 5 ##
self.shuttler0_dcbias[0].set_waveform( self.shuttler0_dcbias[0].set_waveform(
a0=shuttler_volt_amp_mu(-5.0), a0=shuttler_volt_amp_mu(-5.0),
a1=int32(shuttler_volt_damp_mu(0.01)), a1=int32(shuttler_volt_damp_mu(0.01)),
a2=0, a2=int64(0),
a3=0, a3=int64(0),
) )
self.shuttler0_dds[0].set_waveform( self.shuttler0_dds[0].set_waveform(
b0=shuttler_dds_amp_mu(1.0), b0=shuttler_dds_amp_mu(1.0),
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=shuttler_freq_mu(end_f_MHz), c1=shuttler_freq_mu(end_f_MHz),
c2=0, c2=0,
@ -244,59 +256,59 @@ class Shuttler(EnvExperiment):
self.shuttler0_dcbias[1].set_waveform( self.shuttler0_dcbias[1].set_waveform(
a0=shuttler_volt_amp_mu(-5.0), a0=shuttler_volt_amp_mu(-5.0),
a1=int32(shuttler_volt_damp_mu(0.01)), a1=int32(shuttler_volt_damp_mu(0.01)),
a2=0, a2=int64(0),
a3=0, a3=int64(0),
) )
self.shuttler0_dds[1].set_waveform( self.shuttler0_dds[1].set_waveform(
b0=0, b0=0,
b1=0, b1=0,
b2=0, b2=int64(0),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=0, c1=0,
c2=0, c2=0,
) )
self.shuttler0_trigger.trigger(0b11) self.shuttler0_trigger.trigger(0b11)
delay(1000*us) self.core.delay(1000.*us)
## Step 6 ## ## Step 6 ##
self.shuttler0_dcbias[0].set_waveform( self.shuttler0_dcbias[0].set_waveform(
a0=shuttler_volt_amp_mu(-2.5), a0=shuttler_volt_amp_mu(-2.5),
a1=int32(shuttler_volt_damp_mu(0.01)), a1=int32(shuttler_volt_damp_mu(0.01)),
a2=0, a2=int64(0),
a3=0, a3=int64(0),
) )
self.shuttler0_dds[0].set_waveform( self.shuttler0_dds[0].set_waveform(
b0=0, b0=0,
b1=shuttler_dds_damp_mu(0.06835937), b1=shuttler_dds_damp_mu(0.06835937),
b2=shuttler_dds_ddamp_mu(-0.0001367187), b2=shuttler_dds_ddamp_mu(-0.0001367187),
b3=0, b3=int64(0),
c0=0, c0=0,
c1=shuttler_freq_mu(start_f_MHz), c1=shuttler_freq_mu(start_f_MHz),
c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us), c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us),
) )
self.shuttler0_trigger.trigger(0b1) self.shuttler0_trigger.trigger(0b1)
self.shuttler_channel_reset(1) self.shuttler_channel_reset(1)
delay(500*us) self.core.delay(500.*us)
## Step 7 ## ## Step 7 ##
self.shuttler0_dcbias[0].set_waveform( self.shuttler0_dcbias[0].set_waveform(
a0=shuttler_volt_amp_mu(2.5), a0=shuttler_volt_amp_mu(2.5),
a1=int32(shuttler_volt_damp_mu(-0.01)), a1=int32(shuttler_volt_damp_mu(-0.01)),
a2=0, a2=int64(0),
a3=0, a3=int64(0),
) )
self.shuttler0_dds[0].set_waveform( self.shuttler0_dds[0].set_waveform(
b0=0, b0=0,
b1=shuttler_dds_damp_mu(-0.06835937), b1=shuttler_dds_damp_mu(-0.06835937),
b2=shuttler_dds_ddamp_mu(0.0001367187), b2=shuttler_dds_ddamp_mu(0.0001367187),
b3=0, b3=int64(0),
c0=shuttler_phase_offset(180.0), c0=shuttler_phase_offset(180.0),
c1=shuttler_freq_mu(end_f_MHz), c1=shuttler_freq_mu(end_f_MHz),
c2=shuttler_freq_sweep(end_f_MHz, start_f_MHz, duration_us), c2=shuttler_freq_sweep(end_f_MHz, start_f_MHz, duration_us),
) )
self.shuttler0_trigger.trigger(0b1) self.shuttler0_trigger.trigger(0b1)
delay(500*us) self.core.delay(500.*us)
## Step 8 ## ## Step 8 ##
self.shuttler0_relay.enable(0) self.shuttler0_relay.enable(0)
@ -308,7 +320,7 @@ class Shuttler(EnvExperiment):
for i in range(2): for i in range(2):
for j in range(3): for j in range(3):
self.shuttler0_leds[i].pulse(.1*s) self.shuttler0_leds[i].pulse(.1*s)
delay(.1*s) self.core.delay(.1*s)
@kernel @kernel
def relay_init(self): def relay_init(self):

View File

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

View File

@ -1,7 +1,24 @@
from numpy import int32
from artiq.experiment import * from artiq.experiment import *
from artiq.coredevice.core import Core
from artiq.coredevice.ttl import TTLOut
from artiq.coredevice.suservo import SUServo, Channel as SUServoChannel
@nac3
class SUServoDemo(EnvExperiment):
core: KernelInvariant[Core]
led0: KernelInvariant[TTLOut]
suservo0: KernelInvariant[SUServo]
suservo0_ch0: KernelInvariant[SUServoChannel]
suservo0_ch1: KernelInvariant[SUServoChannel]
suservo0_ch2: KernelInvariant[SUServoChannel]
suservo0_ch3: KernelInvariant[SUServoChannel]
suservo0_ch4: KernelInvariant[SUServoChannel]
suservo0_ch5: KernelInvariant[SUServoChannel]
suservo0_ch6: KernelInvariant[SUServoChannel]
suservo0_ch7: KernelInvariant[SUServoChannel]
class SUServo(EnvExperiment):
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("led0") self.setattr_device("led0")
@ -9,40 +26,37 @@ class SUServo(EnvExperiment):
for i in range(8): for i in range(8):
self.setattr_device("suservo0_ch{}".format(i)) self.setattr_device("suservo0_ch{}".format(i))
def run(self): @rpc
self.init() def p(self, d: list[int32]):
def p(self, d):
mask = 1 << 18 - 1 mask = 1 << 18 - 1
for name, val in zip("ftw1 b1 pow cfg offset a1 ftw0 b0".split(), d): for name, val in zip("ftw1 b1 pow cfg offset a1 ftw0 b0".split(), d):
val = -(val & mask) + (val & ~mask) val = -(val & mask) + (val & ~mask)
print("{}: {:#x} = {}".format(name, val, val)) print("{}: {:#x} = {}".format(name, val, val))
@rpc(flags={"async"}) @rpc # NAC3TODO (flags={"async"})
def p1(self, adc, asf, st): def p1(self, adc: float, asf: float, st: int32):
print("ADC: {:10s}, ASF: {:10s}, clipped: {}".format( print("ADC: {:10s}, ASF: {:10s}, clipped: {}".format(
"#"*int(adc), "#"*int(asf*10), (st >> 8) & 1), end="\r") "#"*int(adc), "#"*int(asf*10), (st >> 8) & 1), end="\r")
@kernel @kernel
def init(self): def run(self):
self.core.break_realtime()
self.core.reset() self.core.reset()
self.led() self.led()
self.suservo0.init() self.suservo0.init()
delay(1*us) self.core.delay(1.*us)
# ADC PGIA gain # ADC PGIA gain
for i in range(8): for i in range(8):
self.suservo0.set_pgia_mu(i, 0) self.suservo0.set_pgia_mu(i, 0)
delay(10*us) self.core.delay(10.*us)
# DDS attenuator # DDS attenuator
self.suservo0.cpld0.set_att(0, 10.) self.suservo0.cplds[0].set_att(0, 10.)
delay(1*us) self.core.delay(1.*us)
# Servo is done and disabled # Servo is done and disabled
assert self.suservo0.get_status() & 0xff == 2 assert self.suservo0.get_status() & 0xff == 2
# set up profile 0 on channel 0: # set up profile 0 on channel 0:
delay(120*us) self.core.delay(120.*us)
self.suservo0_ch0.set_y( self.suservo0_ch0.set_y(
profile=0, profile=0,
y=0. # clear integrator y=0. # clear integrator
@ -61,39 +75,39 @@ class SUServo(EnvExperiment):
self.suservo0_ch0.set_dds( self.suservo0_ch0.set_dds(
profile=0, profile=0,
offset=-.5, # 5 V with above PGIA settings offset=-.5, # 5 V with above PGIA settings
frequency=71*MHz, frequency=71.*MHz,
phase=0.) phase=0.)
# enable RF, IIR updates and profile 0 # enable RF, IIR updates and profile 0
self.suservo0_ch0.set(en_out=1, en_iir=1, profile=0) self.suservo0_ch0.set(en_out=True, en_iir=True, profile=0)
# enable global servo iterations # enable global servo iterations
self.suservo0.set_config(enable=1) self.suservo0.set_config(enable=True)
# check servo enabled # check servo enabled
assert self.suservo0.get_status() & 0x01 == 1 assert self.suservo0.get_status() & 0x01 == 1
delay(10*us) self.core.delay(10.*us)
# read back profile data # read back profile data
data = [0] * 8 data = [0 for _ in range(8)]
self.suservo0_ch0.get_profile_mu(0, data) self.suservo0_ch0.get_profile_mu(0, data)
self.p(data) self.p(data)
delay(10*ms) self.core.delay(10.*ms)
while True: while True:
self.suservo0.set_config(0) self.suservo0.set_config(False)
delay(10*us) self.core.delay(10.*us)
v = self.suservo0.get_adc(7) v = self.suservo0.get_adc(7)
delay(30*us) self.core.delay(30.*us)
w = self.suservo0_ch0.get_y(0) w = self.suservo0_ch0.get_y(0)
delay(20*us) self.core.delay(20.*us)
x = self.suservo0.get_status() x = self.suservo0.get_status()
delay(10*us) self.core.delay(10.*us)
self.suservo0.set_config(1) self.suservo0.set_config(True)
self.p1(v, w, x) self.p1(v, w, x)
delay(20*ms) self.core.delay(20.*ms)
@kernel @kernel
def led(self): def led(self):
self.core.break_realtime() self.core.break_realtime()
for i in range(3): for i in range(3):
self.led0.pulse(.1*s) self.led0.pulse(.1*s)
delay(.1*s) self.core.delay(.1*s)

View File

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

View File

@ -1,21 +1,26 @@
from artiq.experiment import * from artiq.experiment import *
from artiq.coredevice.core import Core
from artiq.coredevice.ttl import TTLOut
@nac3
class IdleKernel(EnvExperiment): class IdleKernel(EnvExperiment):
core: KernelInvariant[Core]
led: KernelInvariant[TTLOut]
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("led") self.setattr_device("led")
@kernel @kernel
def run(self): def run(self):
start_time = now_mu() + self.core.seconds_to_mu(500*ms) start_time = now_mu() + self.core.seconds_to_mu(500.*ms)
while self.core.get_rtio_counter_mu() < start_time: while self.core.get_rtio_counter_mu() < start_time:
pass pass
self.core.reset() self.core.reset()
while True: while True:
self.led.pulse(250*ms) self.led.pulse(250.*ms)
delay(125*ms) self.core.delay(125.*ms)
self.led.pulse(125*ms) self.led.pulse(125.*ms)
delay(125*ms) self.core.delay(125.*ms)
self.led.pulse(125*ms) self.led.pulse(125.*ms)
delay(250*ms) self.core.delay(250.*ms)

View File

@ -1,7 +1,12 @@
from artiq.experiment import * from artiq.experiment import *
from artiq.coredevice.core import Core
from artiq.coredevice.ttl import TTLOut
@nac3
class BlinkForever(EnvExperiment): class BlinkForever(EnvExperiment):
core: KernelInvariant[Core]
led: KernelInvariant[TTLOut]
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("led") self.setattr_device("led")
@ -10,5 +15,5 @@ class BlinkForever(EnvExperiment):
def run(self): def run(self):
self.core.reset() self.core.reset()
while True: while True:
self.led.pulse(100*ms) self.led.pulse(100.*ms)
delay(100*ms) self.core.delay(100.*ms)

View File

@ -1,20 +1,30 @@
from time import sleep from time import sleep
from artiq.experiment import * from artiq.experiment import *
from artiq.coredevice.core import Core
# NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/282
@rpc
def sleep_rpc():
sleep(1)
@nac3
class CorePause(EnvExperiment): class CorePause(EnvExperiment):
core: KernelInvariant[Core]
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("scheduler") self.setattr_device("scheduler")
@kernel @kernel
def k(self): def k(self):
print("kernel starting") print_rpc("kernel starting")
while not self.scheduler.check_pause(): while not self.scheduler.check_pause():
print("main kernel loop running...") print_rpc("main kernel loop running...")
sleep(1) sleep_rpc()
print("kernel exiting") print_rpc("kernel exiting")
def run(self): def run(self):
while True: while True:

View File

@ -1,10 +1,16 @@
from operator import itemgetter from operator import itemgetter
from artiq.experiment import * from artiq.experiment import *
from artiq.coredevice.core import Core
from artiq.coredevice.ad9914 import AD9914
@nac3
class DDSSetter(EnvExperiment): class DDSSetter(EnvExperiment):
"""DDS Setter""" """DDS Setter"""
core: KernelInvariant[Core]
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
@ -24,10 +30,10 @@ class DDSSetter(EnvExperiment):
} }
@kernel @kernel
def set_dds(self, dds, frequency): def set_dds(self, dds: AD9914, frequency: float):
self.core.break_realtime() self.core.break_realtime()
dds.set(frequency) dds.set(frequency)
delay(200*ms) self.core.delay(200.*ms)
def run(self): def run(self):
for k, v in self.dds.items(): for k, v in self.dds.items():

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