mirror of https://github.com/m-labs/artiq
Compare commits
689 Commits
Author | SHA1 | Date |
---|---|---|
Egor Savkin | 2b31d38084 | |
mwojcik | c2d645ed0a | |
linuswck | 4de3273e7a | |
linuswck | 531640fa91 | |
mwojcik | c5d656ba32 | |
mwojcik | 688e643078 | |
mwojcik | c33c1df07f | |
Sebastien Bourdeauducq | 6d0821ecaf | |
Mikołaj Sowiński | 16e4b616ca | |
Simon Renblad | 7dff78e849 | |
Florian Agbuya | a8157cd5c9 | |
Sebastien Bourdeauducq | 193962f31e | |
Simon Renblad | 5fe47129ed | |
Sebastien Bourdeauducq | 24fe885b5c | |
mwojcik | 7204feae1f | |
mwojcik | acebc3d691 | |
mwojcik | a49ba3e350 | |
mwojcik | b1c305fd11 | |
mwojcik | b6ac052e9f | |
mwojcik | 76d704ac33 | |
Norman Krackow | baa58343ac | |
Sébastien Bourdeauducq | 1bcbee988d | |
Sébastien Bourdeauducq | ab206ac154 | |
Simon Renblad | 4a2352c2df | |
Simon Renblad | f9a447e8e0 | |
Simon Renblad | c4892cf285 | |
Simon Renblad | c1e6ae2193 | |
Simon Renblad | 4f302ee675 | |
Simon Renblad | 3ecd115252 | |
Simon Renblad | 400c1644b0 | |
Simon Renblad | 1b2a18c9c8 | |
Simon Renblad | 7d9199a2ee | |
Simon Renblad | 43edffc67e | |
Simon Renblad | 49930a2df2 | |
Simon Renblad | 9d3509d7b0 | |
Simon Renblad | b555f08ed8 | |
Simon Renblad | 65005ed45a | |
Simon Renblad | 6ac532a00e | |
Simon Renblad | 856e43fd61 | |
Simon Renblad | af11dc6b74 | |
Sebastien Bourdeauducq | 0fb31ddbb1 | |
Simon Renblad | 9bf5695ab2 | |
Sébastien Bourdeauducq | 5f49e582c8 | |
Simon Renblad | fddff13842 | |
Simon Renblad | 915d3613f1 | |
Simon Renblad | d463ccb218 | |
Simon Renblad | b4d070fa1b | |
Simon Renblad | 9934c756b2 | |
Simon Renblad | 47716badef | |
Simon Renblad | 8e68501081 | |
Simon Renblad | 19b652d4c0 | |
Florian Agbuya | dc0b803b19 | |
Florian Agbuya | bc8bc952d7 | |
Simon Renblad | aea5f04d74 | |
Sébastien Bourdeauducq | d0f893c01c | |
Simon Renblad | 7fa770fba9 | |
Simon Renblad | 5a8bc17e4d | |
Sébastien Bourdeauducq | 329e7189cc | |
Simon Renblad | 13a36bf911 | |
Simon Renblad | 88438e2d76 | |
Simon Renblad | 1a41b16fb6 | |
Simon Renblad | 6978101b1f | |
Simon Renblad | 244c73a592 | |
Simon Renblad | c4323e1179 | |
morgan | 609684664a | |
Simon Renblad | 7e6ed1655f | |
Simon Renblad | 332c9c0fcd | |
Simon Renblad | 27178c1478 | |
Simon Renblad | e56331248e | |
Sébastien Bourdeauducq | 692572a3b9 | |
Sébastien Bourdeauducq | 18f55bb196 | |
Sébastien Bourdeauducq | 3e8a853e53 | |
Sébastien Bourdeauducq | de29db0b35 | |
mwojcik | 42d3c3b4b2 | |
Sébastien Bourdeauducq | 450fe91e93 | |
Simon Renblad | 002325be17 | |
Sébastien Bourdeauducq | 92eb3947a4 | |
Florian Agbuya | 3609f95207 | |
Sébastien Bourdeauducq | 5e01661443 | |
Simon Renblad | a21805598a | |
Simon Renblad | c151f0c3ce | |
Simon Renblad | c794e51c1c | |
Sébastien Bourdeauducq | bafa69098a | |
Sébastien Bourdeauducq | b2ba087acd | |
Sébastien Bourdeauducq | a8a5fc213b | |
Sébastien Bourdeauducq | 7688f380b1 | |
Sébastien Bourdeauducq | a0450555e2 | |
Sébastien Bourdeauducq | b142428607 | |
Sébastien Bourdeauducq | 750fdf89b3 | |
Simon Renblad | 0a24d72b9f | |
Sébastien Bourdeauducq | 7c1274f254 | |
Sébastien Bourdeauducq | 716d0f556d | |
Charles Baynham | 20d7604f87 | |
Simon Renblad | 4c142ec3f1 | |
Simon Renblad | c49600a2fc | |
Simon Renblad | cda758ef53 | |
Simon Renblad | bd9e8b3977 | |
Simon Renblad | 779b7704ed | |
Simon Renblad | edd23977f8 | |
Simon Renblad | f460af3a6a | |
Simon Renblad | 1b0fd2e2d3 | |
Simon Renblad | 652bcc22c6 | |
Simon Renblad | de539a4d33 | |
Simon Renblad | 1749fa661f | |
Simon Renblad | 6ed6fb0bce | |
morgan | fc282d4e17 | |
Simon Renblad | 795b8ae4c6 | |
Simon Renblad | 21b77567f2 | |
Simon Renblad | d085c1e4a4 | |
Simon Renblad | 720cbb4490 | |
Simon Renblad | efb8aaf9f9 | |
Sebastien Bourdeauducq | 7c583b9c04 | |
Simon Renblad | 7f43c5c31a | |
Simon Renblad | 40cea30285 | |
Simon Renblad | 8b503c3b4f | |
Simon Renblad | 1e9070a2af | |
Simon Renblad | dcf1bba8c6 | |
Simon Renblad | a7b045a478 | |
Sebastien Bourdeauducq | 3aaa7e04f2 | |
mwojcik | b648a2930b | |
mwojcik | b64c75fd71 | |
mwojcik | 392533f8ee | |
mwojcik | 7fee68ede0 | |
mwojcik | 849b77fbf2 | |
mwojcik | 502204cab2 | |
mwojcik | d1ee0ffb83 | |
Simon Renblad | cbe7ac1cfd | |
Simon Renblad | 2d8de3ed93 | |
Simon Renblad | 5f3126f393 | |
mwojcik | 09462442f7 | |
mwojcik | 726cb092ca | |
mwojcik | fbbc8d3dd1 | |
mwojcik | 0ba0330b53 | |
mwojcik | 7d3bcc7cac | |
mwojcik | 171c7a6e11 | |
Simon Renblad | c087a47e45 | |
Simon Renblad | 28dfe1f9c6 | |
Simon Renblad | 3861d58749 | |
Simon Renblad | 6c9f1cbf7c | |
Simon Renblad | 06b908fd18 | |
Simon Renblad | e72f37eb4e | |
Simon Renblad | 847b4ee2a3 | |
Simon Renblad | 863daca2da | |
Simon Renblad | fcaf4a8af0 | |
Simon Renblad | 466d865e58 | |
Simon Renblad | 5036230ff3 | |
Simon Renblad | 12a44fad3c | |
Simon Renblad | 096664c1ba | |
Simon Renblad | 8a9b6a449b | |
Simon Renblad | 73be2257d3 | |
Simon Renblad | 9088ffa2ca | |
Simon Renblad | d44f55c6d9 | |
Simon Renblad | e393b3ab37 | |
Simon Renblad | 3af4c9d517 | |
Simon Renblad | 64567bc26f | |
Sebastien Bourdeauducq | da15e94c22 | |
Charles Baynham | 669edf17c5 | |
Simon Renblad | b215df2d25 | |
mwojcik | 6c0ff9a912 | |
mwojcik | c9e3771cd5 | |
mwojcik | c876acd5a5 | |
mwojcik | 4363cdf9fa | |
mwojcik | 95b92a178b | |
mwojcik | 1cc7398bc0 | |
mwojcik | 4956fac861 | |
mwojcik | 9bc66e5c14 | |
mwojcik | 4495f6035e | |
mwojcik | e556c29b40 | |
Simon Renblad | 76fba538b1 | |
Sebastien Bourdeauducq | 8dd8cfa6b0 | |
Sebastien Bourdeauducq | 5df0721811 | |
Sebastien Bourdeauducq | 6326051052 | |
Sebastien Bourdeauducq | 44a95b5dda | |
Sebastien Bourdeauducq | 645b9b8c5f | |
Sebastien Bourdeauducq | 858f0479ba | |
Sebastien Bourdeauducq | 133b26b6ce | |
Sebastien Bourdeauducq | d96213dbbc | |
Sebastien Bourdeauducq | 413d33c3d1 | |
Sebastien Bourdeauducq | c2b53ecb43 | |
Sebastien Bourdeauducq | ede0b37c6e | |
Sebastien Bourdeauducq | 795c4372fa | |
Sebastien Bourdeauducq | 402a5d3376 | |
Sebastien Bourdeauducq | 85850ad9e8 | |
Sebastien Bourdeauducq | 7a863b4f5e | |
Sebastien Bourdeauducq | a26cee6ca7 | |
Sebastien Bourdeauducq | be08862606 | |
Sebastien Bourdeauducq | 05a9422e67 | |
Simon Renblad | b09a39c82e | |
mwojcik | 49267671f9 | |
Sebastien Bourdeauducq | 8ca75a3fb9 | |
Florian Agbuya | 8381b34a79 | |
Sebastien Bourdeauducq | d458fc27bf | |
mwojcik | 9f4b8db2de | |
Florian Agbuya | 1108cebd75 | |
Florian Agbuya | cf7cbd0c3b | |
mwojcik | 1a28069aa2 | |
Sebastien Bourdeauducq | 56418e342e | |
Sebastien Bourdeauducq | 77c6553725 | |
Sebastien Bourdeauducq | e81e8f28cf | |
mwojcik | de10e584f6 | |
Florian Agbuya | 875666f3ec | |
Sebastien Bourdeauducq | 3ad3fac828 | |
Simon Renblad | 49afa116b3 | |
Simon Renblad | 363afb5fc9 | |
Simon Renblad | e7af219505 | |
linuswck | ec2b86b08d | |
linuswck | 8f7d138dbd | |
Sebastien Bourdeauducq | bbe6ff8cac | |
Sebastien Bourdeauducq | c0a6252e77 | |
mwojcik | 6640bf0e82 | |
mwojcik | b3c0d084d4 | |
linuswck | bb0b8a6c00 | |
Sebastien Bourdeauducq | ce80bf5717 | |
Florian Agbuya | 378dd0e5ca | |
jfniedermeyer | 9c68451cae | |
linuswck | 93c9d8bcdf | |
mwojcik | e480bbe8d8 | |
mwojcik | b168f0bb4b | |
Sebastien Bourdeauducq | 6705c9fbfb | |
mwojcik | 5f445f6b92 | |
occheung | 363f7327f1 | |
Sebastien Bourdeauducq | f7abc156cb | |
linuswck | de41bd6655 | |
Simon Renblad | 96941d7c04 | |
mwojcik | f3c79e71e1 | |
Simon Renblad | 333b81f789 | |
Sebastien Bourdeauducq | d070826911 | |
Sebastien Bourdeauducq | 9c90f923d2 | |
Sebastien Bourdeauducq | e23e4d39d7 | |
David Nadlinger | 08eea09d44 | |
mwojcik | 7ab52af603 | |
mwojcik | 973fd88b27 | |
mwojcik | 8d7194941e | |
mwojcik | 0a750c77e8 | |
mwojcik | 1a0fc317df | |
mwojcik | e05be2f8e4 | |
mwojcik | 6f4b8c641e | |
mwojcik | b42816582e | |
Hartmann Michael (IFAG PSS SIS SCE QSE) | 76f1318bc0 | |
Sebastien Bourdeauducq | 0131a8bef2 | |
mwojcik | e63e2a2897 | |
Simon Renblad | 47fc640f75 | |
Simon Renblad | bb7caacb5f | |
Simon Renblad | da9f7cb58a | |
occheung | 43926574da | |
Simon Renblad | 4f3e58db52 | |
Simon Renblad | 13271cea64 | |
occheung | 0e8fa8933f | |
David Nadlinger | 2eb89cb168 | |
occheung | a772dee1cc | |
Simon Renblad | bafb85a274 | |
mwojcik | 0e8aa33979 | |
mwojcik | fcf6c90ba2 | |
linuswck | 0c1b572872 | |
linuswck | ab0d4c41c3 | |
Jonathan Coates | 6eb81494c5 | |
Jonathan Coates | 586d97c6cb | |
David Nadlinger | 892b0eaca2 | |
linuswck | eedac7cf71 | |
linuswck | a61bbf5618 | |
occheung | b7b8f0efa2 | |
occheung | b52f253dbd | |
occheung | 73ab71f443 | |
linuswck | ab8247b3d7 | |
mwojcik | 36b3678853 | |
mwojcik | af77885dfc | |
mwojcik | eb57b3b393 | |
Simon Renblad | 40ac2e03ab | |
occheung | a2fbcb8bfd | |
occheung | 5c64eac8d2 | |
occheung | 477a7b693c | |
occheung | f2694f25eb | |
occheung | 9e1447d104 | |
occheung | 870020bc9f | |
occheung | c2d136f669 | |
occheung | 06426e0ed9 | |
occheung | e443e06e62 | |
occheung | 55150ebdbb | |
occheung | eb08c55abe | |
occheung | 67b6588d95 | |
occheung | 1bb7e9ceef | |
Florian Agbuya | c02a14ba37 | |
Simon Renblad | 1f3b2ef645 | |
linuswck | 372008cb66 | |
linuswck | 85abb1da2c | |
David Nadlinger | 9e5b62a6b1 | |
David Nadlinger | 22ab62324c | |
David Nadlinger | fc74b78a45 | |
Simon Renblad | f01e654b9c | |
David Nadlinger | e45dc948e9 | |
David Mak | 460cbf4499 | |
Florian Agbuya | 6df85478e4 | |
Jonathan Coates | 5c85cef0c2 | |
linuswck | ccb140a929 | |
linuswck | 7c8073c1ce | |
Florian Agbuya | 2f3329181c | |
Sebastien Bourdeauducq | 1ec1ab0502 | |
linuswck | b49fb841ce | |
Florian Agbuya | a619c9f3c2 | |
Florian Agbuya | 0188f31f3a | |
Florian Agbuya | 4e770509db | |
occheung | 7f63bb322d | |
occheung | 5e5d671f4c | |
occheung | 98904ef4c3 | |
Sebastien Bourdeauducq | 73ac414912 | |
occheung | 838cc80922 | |
Simon Renblad | 904afe1632 | |
Simon Renblad | 01d777c977 | |
Sebastien Bourdeauducq | 9556ca53de | |
occheung | df99450faa | |
Sebastien Bourdeauducq | 1f58cd505c | |
linuswck | ddb2b5e3a1 | |
linuswck | b56f7e429a | |
Sebastien Bourdeauducq | 3452d0c423 | |
Sebastien Bourdeauducq | 2139456f80 | |
Sebastien Bourdeauducq | a2a780a3f2 | |
Sebastien Bourdeauducq | 3620358f12 | |
Sebastien Bourdeauducq | 72b0a17542 | |
Sebastien Bourdeauducq | f5cbca9c29 | |
linuswck | 737ff79ae7 | |
linuswck | dc97d3aee6 | |
Sebastien Bourdeauducq | 5d38db19d0 | |
Sebastien Bourdeauducq | 9bee4b9697 | |
linuswck | cd22e42cb4 | |
linuswck | b7bac8c9d8 | |
mwojcik | e8818c812c | |
occheung | 68dd0e029f | |
occheung | 64d3f867a0 | |
Sebastien Bourdeauducq | df662c4262 | |
Sebastien Bourdeauducq | d2ac6aceb3 | |
Sebastien Bourdeauducq | 9b94a09477 | |
David Nadlinger | efbae51f9d | |
David Nadlinger | 8acfa82586 | |
David Nadlinger | 4d636ea593 | |
Sebastien Bourdeauducq | 3ed7e0ed06 | |
Simon Renblad | c4259dab18 | |
mwojcik | c46ac6f87d | |
linuswck | 758b97426a | |
linuswck | c206e92f29 | |
linuswck | cb547c8a46 | |
linuswck | 72a5231493 | |
Denis Ovchinnikov | 07714be8a7 | |
Simon Renblad | 361088ae72 | |
Simon Renblad | a384df17a4 | |
Simon Renblad | 6592b6ea1d | |
Simon Renblad | 2fb085f1a2 | |
Simon Renblad | a7569a0b2d | |
Simon Renblad | 4fbff1648c | |
Simon Renblad | 8f4c8387f9 | |
Simon Renblad | a2d62e6006 | |
Simon Renblad | 3d0feef614 | |
Simon Renblad | 59ad873831 | |
Simon Renblad | 8589da0723 | |
Simon Renblad | 94e076e976 | |
Simon Renblad | a0094aafbb | |
Simon Renblad | 0befadee96 | |
sven-oxionics | b3dc199e6a | |
Florian Agbuya | d73889fb27 | |
Simon Renblad | 9f8bb6445f | |
Simon Renblad | 068a2d1663 | |
Simon Renblad | 6c588b83d7 | |
Simon Renblad | c17f69a51b | |
Simon Renblad | ac504069d2 | |
Simon Renblad | b6a83904b5 | |
Simon Renblad | 25959d0cd6 | |
Simon Renblad | 5695e9f77e | |
Simon Renblad | fe0f6d8a2c | |
Simon Renblad | d1f2727126 | |
Simon Renblad | 16a3ce274f | |
Simon Renblad | af7622d7ab | |
Jonathan Coates | 9a84575649 | |
Simon Renblad | faf85e815a | |
Simon Renblad | 3663a6b8e8 | |
Simon Renblad | 91442e2914 | |
Simon Renblad | 50a6dac178 | |
Simon Renblad | 5292a8de82 | |
Sebastien Bourdeauducq | 7791f85a1a | |
Sebastien Bourdeauducq | 48bc8a2ecc | |
Denis Ovchinnikov | 93882eb3ce | |
Simon Renblad | 7ca02a119d | |
Simon Renblad | 373fe3dbe7 | |
Simon Renblad | 1af98727b7 | |
Simon Renblad | 376f36c965 | |
Simon Renblad | e710d4badd | |
Simon Renblad | bfbe13e51b | |
Simon Renblad | bf38fc8b0f | |
Simon Renblad | 337273acb6 | |
Simon Renblad | 748707e157 | |
Leon Riesebos | 833fd8760e | |
Florian Agbuya | 454597915a | |
Sebastien Bourdeauducq | 77293d53e3 | |
Sebastien Bourdeauducq | a792bc5456 | |
Sebastien Bourdeauducq | 20d4712815 | |
Spaqin | 82bd913f63 | |
Sebastien Bourdeauducq | 115415d120 | |
Florian Agbuya | d140c960bb | |
Egor Savkin | c25c0bd55a | |
Egor Savkin | 30ef8d8cb4 | |
Florian Agbuya | 7ad32d903a | |
Florian Agbuya | bf46ce4a92 | |
den512is | 1f306a2859 | |
Florian Agbuya | 150d325fc1 | |
Florian Agbuya | c298ec4c2e | |
Sebastien Bourdeauducq | 69bf2dfb81 | |
mwojcik | 29cb7e785d | |
Sebastien Bourdeauducq | b97f6a9e44 | |
Sebastien Bourdeauducq | e0ebc1b21d | |
Sebastien Bourdeauducq | c6ddd3af17 | |
Florian Agbuya | e12219e803 | |
Sebastien Bourdeauducq | ff11b5df71 | |
Sebastien Bourdeauducq | c8dc2cbf09 | |
Sebastien Bourdeauducq | c6b29b30fb | |
Sebastien Bourdeauducq | b20d09aad5 | |
Sebastien Bourdeauducq | 6276182c96 | |
Sebastien Bourdeauducq | d103cbea31 | |
Sebastien Bourdeauducq | 9a6bc6dc7b | |
Sebastien Bourdeauducq | fabe88065b | |
Egor Savkin | 748969c21e | |
Sebastien Bourdeauducq | 75f6bdb6a1 | |
Sebastien Bourdeauducq | 41caec797e | |
Sebastien Bourdeauducq | 953a8a9555 | |
Sebastien Bourdeauducq | 444bab2186 | |
Sebastien Bourdeauducq | 0941d3a29a | |
Denis Ovchinnikov | 22e2514ce6 | |
mwojcik | a4895b591a | |
Sebastien Bourdeauducq | ef2cc2cc12 | |
Sebastien Bourdeauducq | 779810163f | |
Sebastien Bourdeauducq | b9c7905b20 | |
Charles Baynham | c2b0c97640 | |
Sebastien Bourdeauducq | 58cc3b8d0a | |
Sebastien Bourdeauducq | 598c7b1d25 | |
Jonathan Coates | ea9fe9b4e1 | |
mwojcik | c1d6fd4bbe | |
mwojcik | ab52748cac | |
mwojcik | ddfe51e7ac | |
mwojcik | 6c96033d41 | |
mwojcik | 0b03126038 | |
mwojcik | fdca1ab7fc | |
mwojcik | c36b6b3b65 | |
mwojcik | c0ca27e6cf | |
Jonathan Coates | 3ca47537b8 | |
Hartmann Michael (IFAG PSS SIS SCE QSE) | df15f53ee9 | |
Sebastien Bourdeauducq | e015483e48 | |
Sebastien Bourdeauducq | c53d333d46 | |
Sebastien Bourdeauducq | 5b94ce82e4 | |
Sebastien Bourdeauducq | 45cd438fb8 | |
Sebastien Bourdeauducq | 0e7e30d46e | |
Sebastien Bourdeauducq | d5a7755584 | |
Sebastien Bourdeauducq | 3ff0be6540 | |
Sebastien Bourdeauducq | 8409a6bb94 | |
Sebastien Bourdeauducq | 2c1438c4b9 | |
Egor Savkin | 5199bea353 | |
mwojcik | a533f2a0cd | |
Jonathan Coates | 0bf57f4ebd | |
Sebastien Bourdeauducq | 4417acd13b | |
Sebastien Bourdeauducq | 4056168875 | |
Egor Savkin | 9331911139 | |
Spaqin | 2f35869eb1 | |
Egor Savkin | aed47d79ff | |
mwojcik | 918d30b900 | |
Egor Savkin | b5d9062ba9 | |
Egor Savkin | 8984f5104a | |
Egor Savkin | d0b8818688 | |
Sebastien Bourdeauducq | 757c00b0fe | |
Sebastien Bourdeauducq | c1474c134a | |
Sebastien Bourdeauducq | dc3db8bb66 | |
Sebastien Bourdeauducq | 97161a3df2 | |
Ikko Eltociear Ashimine | 7ba06bfe61 | |
Spaqin | b225717ddb | |
mwojcik | 696bda5c03 | |
mwojcik | 9150230ea7 | |
Spaqin | e9a153b985 | |
David Nadlinger | 8b1f38b015 | |
Egor Savkin | bbf80875fb | |
Egor Savkin | 1ca09b9484 | |
Spaqin | 84e7515721 | |
Ikko Eltociear Ashimine | 15c18bdc81 | |
Sebastien Bourdeauducq | a9360823b1 | |
Egor Savkin | 1ec0abbfcf | |
mwojcik | 90a6fe1c35 | |
mwojcik | d0437f5672 | |
Michael Hartmann | 07d684a35d | |
Michael Hartmann | 2371c825f5 | |
Egor Savkin | 394138f00f | |
Sebastien Bourdeauducq | 3f5cc4aa10 | |
Sebastien Bourdeauducq | e9c65abebe | |
Sebastien Bourdeauducq | 20e8f17b3d | |
Sebastien Bourdeauducq | 57e87c9717 | |
Sebastien Bourdeauducq | 248cd69673 | |
Sebastien Bourdeauducq | b8968262d7 | |
Sebastien Bourdeauducq | babbbfadb3 | |
Sebastien Bourdeauducq | 514ac953ce | |
Sebastien Bourdeauducq | 0a37a1a4c1 | |
Sebastien Bourdeauducq | 6d37d9d52c | |
Sebastien Bourdeauducq | 5f77d4f5fa | |
Sebastien Bourdeauducq | 2f289c552f | |
Sebastien Bourdeauducq | 9e8bb3c701 | |
Sebastien Bourdeauducq | d872c3ab4d | |
Sebastien Bourdeauducq | f8d93813e9 | |
Sebastien Bourdeauducq | 628b671433 | |
Sebastien Bourdeauducq | daad3d263a | |
Sebastien Bourdeauducq | 80f261437a | |
Sebastien Bourdeauducq | 7fd6dead8f | |
Sebastien Bourdeauducq | 73a4ef89ec | |
mwojcik | 70edc9c5c6 | |
mwojcik | 9042426872 | |
mwojcik | cd860beda2 | |
mwojcik | 627504b60e | |
Sebastien Bourdeauducq | c8ab6c1b2b | |
Sebastien Bourdeauducq | a96bbd8508 | |
Sebastien Bourdeauducq | 6cfd1480a7 | |
Sebastien Bourdeauducq | c401559ed5 | |
Sebastien Bourdeauducq | ea21f474a7 | |
Sebastien Bourdeauducq | cee9f3f44e | |
Sebastien Bourdeauducq | b9bfe090f4 | |
mwojcik | eb3742fb08 | |
Egor Savkin | 070fed755b | |
Sebastien Bourdeauducq | 63f1a6d197 | |
Sebastien Bourdeauducq | 7dafdfe2f7 | |
Sebastien Bourdeauducq | ec893222a4 | |
Sebastien Bourdeauducq | 573a895c1e | |
Sebastien Bourdeauducq | cf2a4972f7 | |
Sebastien Bourdeauducq | 668997a451 | |
Sebastien Bourdeauducq | 5da9794895 | |
Spaqin | 3838dfc1d1 | |
Sebastien Bourdeauducq | 1be7e2a2e1 | |
Sebastien Bourdeauducq | 1bf7188dec | |
mwojcik | bdae594c79 | |
mwojcik | 8dc6902c23 | |
Norman Krackow | dbb77b5356 | |
Sebastien Bourdeauducq | 1fc127c770 | |
David Nadlinger | 88684dbd2a | |
David Nadlinger | b9f13d48aa | |
David Nadlinger | 4bb2a3b9e0 | |
David Nadlinger | f5c408d8d9 | |
Sebastien Bourdeauducq | 4be7f302e4 | |
Spaqin | 17efc28dbe | |
David Nadlinger | 1e0102379b | |
David Nadlinger | ceabeb8d84 | |
SingularitySurfer | 8e476dd502 | |
David Nadlinger | 874d298ceb | |
Egor Savkin | d75ade7be6 | |
Egor Savkin | 2a58981822 | |
Egor Savkin | e80442811e | |
Egor Savkin | 12649720f1 | |
Egor Savkin | 454ae39c5d | |
David Nadlinger | 3c7a394eff | |
David Nadlinger | 740543d4e2 | |
Egor Savkin | b2b559e73b | |
Egor Savkin | 1852491102 | |
Egor Savkin | c591e7e305 | |
David Nadlinger | 261dc6b933 | |
David Nadlinger | 1abedba6dc | |
Egor Savkin | aa2febca53 | |
Egor Savkin | d60a96a715 | |
wlph17 | 3f93f16955 | |
Sebastien Bourdeauducq | 3735b7ea9d | |
Sebastien Bourdeauducq | 195d2aea6a | |
Sebastien Bourdeauducq | 6d179b2bf5 | |
Sebastien Bourdeauducq | 275b00bfc2 | |
Jonathan Coates | b8b6ce14cc | |
Nico Pulido | 88c5109627 | |
David Nadlinger | dee154b35b | |
David Nadlinger | 950b9ac4d6 | |
Egor Savkin | 6c47aac760 | |
mwojcik | f2c1e663a7 | |
Egor Savkin | f7f027001e | |
David Nadlinger | 0b3c232819 | |
Etienne Wodey | d45f9b6950 | |
Sebastien Bourdeauducq | 2fe02cee6f | |
Sebastien Bourdeauducq | 404f24af6b | |
David Nadlinger | 3d25092cbd | |
David Nadlinger | dbbe8e8ed4 | |
David Nadlinger | 8740ec3dd5 | |
David Nadlinger | 6caa779c74 | |
David Nadlinger | 4819016a3c | |
David Nadlinger | 00a27b105a | |
David Nadlinger | beff15de5e | |
火焚 富良 | defc69d9c3 | |
火焚 富良 | e2178f6c86 | |
Sebastien Bourdeauducq | f3f068036a | |
mwojcik | ad000609ce | |
mwojcik | af0b94bb34 | |
mwojcik | 5cd57e8688 | |
mwojcik | f8eb695c0f | |
mwojcik | 458bd8a927 | |
mwojcik | a6856a5e4a | |
mwojcik | 1eb87164be | |
Sebastien Bourdeauducq | f75ddf78b0 | |
Sebastien Bourdeauducq | e0b1098bc0 | |
Robert Jördens | e5c621751f | |
Robert Jördens | 07db770423 | |
Robert Jördens | eb7a0714b3 | |
Robert Jördens | e15b5b50d8 | |
Robert Jördens | 1820e1f715 | |
Robert Jördens | 118b7aca1d | |
Fabian Schwartau | d5e267fadf | |
Sebastien Bourdeauducq | 286f151d9a | |
Sebastien Bourdeauducq | 19b8d28a2e | |
Sebastien Bourdeauducq | 3ffbc5681e | |
Sebastien Bourdeauducq | 192cab887f | |
wlph17 | 9846ee653c | |
fanmingyu212 | 56e6b1428c | |
Michael Birtwell | b895846322 | |
Robert Jördens | a1a4545ed4 | |
Robert Jördens | a0053f7a2b | |
Robert Jördens | 740f3d220b | |
Robert Jördens | 513f9f00f3 | |
Robert Jördens | 5cfa8d9a42 | |
Robert Jördens | 0e4a87826c | |
Sebastien Bourdeauducq | 1709cf9717 | |
Sebastien Bourdeauducq | 4266beeb9c | |
mwojcik | c955ac15ed | |
mwojcik | 81ef484864 | |
mwojcik | f2c3f95040 | |
mwojcik | 616ed3dcc2 | |
Robert Jördens | aedcf205c7 | |
Robert Jördens | 14ab1d4bbc | |
Sebastien Bourdeauducq | a028b5c9f7 | |
Sebastien Bourdeauducq | 6085fe3319 | |
Robert Jördens | af28bf3550 | |
Robert Jördens | 4df880faf6 | |
Robert Jördens | 857fb4ecec | |
Robert Jördens | a91836e5fe | |
Robert Jördens | c5c5c30617 | |
Robert Jördens | 27e3c044ed | |
Robert Jördens | c26fa5eb90 | |
Sebastien Bourdeauducq | 411afbdc23 | |
Sebastien Bourdeauducq | b4287ac9f4 | |
Robert Jördens | 1cc57e2345 | |
Robert Jördens | 263c2751b3 | |
Robert Jördens | 876f26ee30 | |
Robert Jördens | fa3678f8a3 | |
Robert Jördens | f4d325112c | |
Robert Jördens | b6586cd7e4 | |
Robert Jördens | 3809ac5470 | |
Robert Jördens | b9727fdfce | |
Robert Jördens | d6d0c2c866 | |
Robert Jördens | 0df2cadcd3 | |
Robert Jördens | 25c0dc4688 | |
Robert Jördens | cf48232a90 | |
Robert Jördens | a20087848d | |
Robert Jördens | 31663556b8 | |
Robert Jördens | 47f90a58cc | |
Mikołaj Sowiński | 3c7ab498d1 | |
Deepskyhunter | 7c306d5609 | |
mwojcik | b705862ecd | |
fanmingyu212 | 20cb99061e | |
Sebastien Bourdeauducq | 5ef94d30dd | |
kk1050 | 3c72b8d646 | |
Sebastien Bourdeauducq | 27397625ba | |
cc78078 | 3535d0f1ae | |
cc78078 | 185c91f522 | |
Deepskyhunter | f31279411e | |
Alex Wong Tat Hang | a3ae82502c | |
Deepskyhunter | 0cdb06fdf5 | |
Deepskyhunter | 2a7a72b27a | |
kk1050 | 748e28be38 | |
Sebastien Bourdeauducq | 4b1715c80b | |
Robert Jördens | 5985595845 | |
Robert Jördens | a8f498b478 | |
Sebastien Bourdeauducq | db4bccda7e | |
Robert Jördens | 4ea11f4609 | |
SingularitySurfer | 57ac6ec003 | |
Robert Jördens | d2dacc6433 | |
SingularitySurfer | 9c8ffa54b2 | |
SingularitySurfer | 953dd899fd | |
SingularitySurfer | 689a2ef8ba | |
SingularitySurfer | d8cfe22501 | |
SingularitySurfer | 2e834cf406 | |
SingularitySurfer | 3f8a221c76 | |
SingularitySurfer | ab097b8ef9 | |
SingularitySurfer | 24b4ec46bd | |
Norman Krackow | 56c59e38f0 | |
SingularitySurfer | c0581178d6 | |
SingularitySurfer | 43c94577ce | |
SingularitySurfer | ce4055db3b | |
SingularitySurfer | b67a70392d | |
SingularitySurfer | 57176fedb2 | |
SingularitySurfer | 8bea821f93 | |
SingularitySurfer | 0388161754 | |
SingularitySurfer | 751af3144e | |
SingularitySurfer | 5df766e6da | |
SingularitySurfer | d09153411f | |
Norman Krackow | dc49372d57 | |
Norman Krackow | 2044dc3ae5 | |
SingularitySurfer | ae3f1c1c71 | |
SingularitySurfer | 1bddadc6e2 | |
SingularitySurfer | b0f9fd9c4c |
|
@ -29,6 +29,7 @@ __pycache__/
|
|||
/repository/
|
||||
/results
|
||||
/last_rid.pyon
|
||||
/dataset_db.pyon
|
||||
/dataset_db.mdb
|
||||
/dataset_db.mdb-lock
|
||||
/device_db*.py
|
||||
/test*
|
||||
|
|
|
@ -26,7 +26,6 @@ report if possible:
|
|||
* Operating System
|
||||
* ARTIQ version (with recent versions of ARTIQ, run ``artiq_client --version``)
|
||||
* Version of the gateware and runtime loaded in the core device (in the output of ``artiq_coremgmt -D .... log``)
|
||||
* If using Conda, output of `conda list`
|
||||
* Hardware involved
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardwar
|
|||
ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers.
|
||||
Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and of a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort.
|
||||
|
||||
ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and Conda packages (for Windows and Linux). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions.
|
||||
ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions.
|
||||
Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration.
|
||||
Like any open source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_.
|
||||
|
||||
|
@ -29,7 +29,7 @@ Website: https://m-labs.hk/artiq
|
|||
License
|
||||
=======
|
||||
|
||||
Copyright (C) 2014-2022 M-Labs Limited.
|
||||
Copyright (C) 2014-2024 M-Labs Limited.
|
||||
|
||||
ARTIQ is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
|
|
|
@ -3,6 +3,107 @@
|
|||
Release notes
|
||||
=============
|
||||
|
||||
ARTIQ-8 (Unreleased)
|
||||
--------------------
|
||||
|
||||
Highlights:
|
||||
|
||||
* New hardware support:
|
||||
- Support for Shuttler, a 16-channel 125MSPS DAC card intended for ion transport.
|
||||
Waveform generator and user API are similar to the NIST PDQ.
|
||||
- Implemented Phaser-servo. This requires recent gateware on Phaser.
|
||||
- Almazny v1.2 with finer RF switch control.
|
||||
- Metlino and Sayma support has been dropped due to complications with synchronous RTIO clocking.
|
||||
- More user LEDs are exposed to RTIO on Kasli.
|
||||
- Implemented Phaser-MIQRO support. This requires the proprietary Phaser MIQRO gateware
|
||||
variant from QUARTIQ.
|
||||
- Sampler: fixed ADC MU to Volt conversion factor for Sampler v2.2+.
|
||||
For earlier hardware versions, specify the hardware version in the device
|
||||
database file (e.g. ``"hw_rev": "v2.1"``) to use the correct conversion factor.
|
||||
* Support for distributed DMA, where DMA is run directly on satellites for corresponding
|
||||
RTIO events, increasing bandwidth in scenarios with heavy satellite usage.
|
||||
* Support for subkernels, where kernels are run on satellite device CPUs to offload some
|
||||
of the processing and RTIO operations.
|
||||
* CPU (on softcore platforms) and AXI bus (on Zynq) are now clocked synchronously with the RTIO
|
||||
clock, to facilitate implementation of local processing on DRTIO satellites, and to slightly
|
||||
reduce RTIO latency.
|
||||
* Support for DRTIO-over-EEM, used with Shuttler.
|
||||
* Enabled event spreading on DRTIO satellites, using high watermark for lane switching.
|
||||
* Added channel names to RTIO error messages.
|
||||
* GUI:
|
||||
- Implemented Applet Request Interfaces which allow applets to modify datasets and set the
|
||||
current values of widgets in the dashboard's experiment windows.
|
||||
- Implemented a new EntryArea widget which allows argument entry widgets to be used in applets.
|
||||
- The "Close all applets" command (shortcut: Ctrl-Alt-W) now ignores docked applets,
|
||||
making it a convenient way to clean up after exploratory work without destroying a
|
||||
carefully arranged default workspace.
|
||||
- Hotkeys now organize experiment windows in the order they were last interacted with:
|
||||
+ CTRL+SHIFT+T tiles experiment windows
|
||||
+ CTRL+SHIFT+C cascades experiment windows
|
||||
- By enabling the ``quickstyle`` option, ``EnumerationValue`` entry widgets can now alternatively display
|
||||
its choices as buttons that submit the experiment on click.
|
||||
* Datasets can now be associated with units and scale factors, and displayed accordingly in the dashboard
|
||||
including applets, like widgets such as ``NumberValue`` already did in earlier ARTIQ versions.
|
||||
* Experiments can now request arguments interactively from the user at any time.
|
||||
* Persistent datasets are now stored in a LMDB database for improved performance.
|
||||
* Python's built-in types (such as ``float``, or ``List[...]``) can now be used in type annotations on
|
||||
kernel functions.
|
||||
* Full Python 3.11 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.
|
||||
* Experiments can now be submitted with revisions set to a branch / tag name instead of only git hashes.
|
||||
* Grabber image input now has an optional timeout.
|
||||
|
||||
Breaking changes:
|
||||
|
||||
* ``SimpleApplet`` now calls widget constructors with an additional ``ctl`` parameter for control
|
||||
operations, which includes dataset operations. It can be ignored if not needed. For an example usage,
|
||||
refer to the ``big_number.py`` applet.
|
||||
* ``SimpleApplet`` and ``TitleApplet`` now call ``data_changed`` with additional parameters. Derived applets
|
||||
should change the function signature as below:
|
||||
|
||||
::
|
||||
|
||||
# SimpleApplet
|
||||
def data_changed(self, value, metadata, persist, mods)
|
||||
# SimpleApplet (old version)
|
||||
def data_changed(self, data, mods)
|
||||
# TitleApplet
|
||||
def data_changed(self, value, metadata, persist, mods, title)
|
||||
# TitleApplet (old version)
|
||||
def data_changed(self, data, mods, title)
|
||||
|
||||
Accesses to the data argument should be replaced as below:
|
||||
|
||||
::
|
||||
|
||||
data[key][0] ==> persist[key]
|
||||
data[key][1] ==> value[key]
|
||||
|
||||
* The ``ndecimals`` parameter in ``NumberValue`` and ``Scannable`` has been renamed to ``precision``.
|
||||
Parameters after and including ``scale`` in both constructors are now keyword-only.
|
||||
Refer to the updated ``no_hardware/arguments_demo.py`` example for current usage.
|
||||
* Almazny v1.2 is incompatible with the legacy versions and is the default.
|
||||
To use legacy versions, specify ``almazny_hw_rev`` in the JSON description.
|
||||
* kasli_generic.py has been merged into kasli.py, and the demonstration designs without JSON descriptions
|
||||
have been removed. The base classes remain present in kasli.py to support third-party flows without
|
||||
JSON descriptions.
|
||||
* Legacy PYON databases should be converted to LMDB with the script below:
|
||||
|
||||
::
|
||||
|
||||
from sipyco import pyon
|
||||
import lmdb
|
||||
|
||||
old = pyon.load_file("dataset_db.pyon")
|
||||
new = lmdb.open("dataset_db.mdb", subdir=False, map_size=2**30)
|
||||
with new.begin(write=True) as txn:
|
||||
for key, value in old.items():
|
||||
txn.put(key.encode(), pyon.encode((value, {})).encode())
|
||||
new.close()
|
||||
|
||||
* ``artiq.wavesynth`` has been removed.
|
||||
|
||||
ARTIQ-7
|
||||
-------
|
||||
|
||||
|
@ -17,7 +118,7 @@ Highlights:
|
|||
- Almazny mezzanine board for Mirny
|
||||
- Phaser: improved documentation, exposed the DAC coarse mixer and ``sif_sync``, exposed upconverter calibration
|
||||
and enabling/disabling of upconverter LO & RF outputs, added helpers to align Phaser updates to the
|
||||
RTIO timeline (``get_next_frame_mu()``
|
||||
RTIO timeline (``get_next_frame_mu()``).
|
||||
- Urukul: ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912.
|
||||
* Softcore targets now use the RISC-V architecture (VexRiscv) instead of OR1K (mor1kx).
|
||||
* Gateware FPU is supported on KC705 and Kasli 2.0.
|
||||
|
@ -67,9 +168,9 @@ Breaking changes:
|
|||
generated for some configurations.
|
||||
* Phaser: fixed coarse mixer frequency configuration
|
||||
* Mirny: Added extra delays in ``ADF5356.sync()``. This avoids the need of an extra delay before
|
||||
calling `ADF5356.init()`.
|
||||
calling ``ADF5356.init()``.
|
||||
* The deprecated ``set_dataset(..., save=...)`` is no longer supported.
|
||||
* The ``PCA9548`` I2C switch class was renamed to ``I2CSwitch``, to accomodate support for PCA9547,
|
||||
* The ``PCA9548`` I2C switch class was renamed to ``I2CSwitch``, to accommodate support for PCA9547,
|
||||
and possibly other switches in future. Readback has been removed, and now only one channel per
|
||||
switch is supported.
|
||||
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
import os
|
||||
|
||||
def get_rev():
|
||||
return os.getenv("VERSIONEER_REV", default="unknown")
|
||||
|
||||
def get_version():
|
||||
return os.getenv("VERSIONEER_OVERRIDE", default="7.0.beta")
|
||||
return os.getenv("VERSIONEER_OVERRIDE", default="8.0+unknown.beta")
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
-----BEGIN CERTIFICATE-----
|
||||
MIID0zCCArugAwIBAgIUPkNfEUx/uau3z8SD4mgMbCK/DEgwDQYJKoZIhvcNAQEL
|
||||
BQAweTELMAkGA1UEBhMCSEsxEzARBgNVBAgMClNvbWUtU3RhdGUxFzAVBgNVBAoM
|
||||
Dk0tTGFicyBMaW1pdGVkMRkwFwYDVQQDDBBuaXhibGQubS1sYWJzLmhrMSEwHwYJ
|
||||
KoZIhvcNAQkBFhJoZWxwZGVza0BtLWxhYnMuaGswHhcNMjIwMjA2MTA1ODQ0WhcN
|
||||
MjUwMjA1MTA1ODQ0WjB5MQswCQYDVQQGEwJISzETMBEGA1UECAwKU29tZS1TdGF0
|
||||
ZTEXMBUGA1UECgwOTS1MYWJzIExpbWl0ZWQxGTAXBgNVBAMMEG5peGJsZC5tLWxh
|
||||
YnMuaGsxITAfBgkqhkiG9w0BCQEWEmhlbHBkZXNrQG0tbGFicy5oazCCASIwDQYJ
|
||||
KoZIhvcNAQEBBQADggEPADCCAQoCggEBAPWetZhoggPR2ae7waGzv1AQ8NQO3noW
|
||||
8DofVjusNpX5i/YB0waAr1bm1tALLJoHV2r/gTxujlXCe/L/WG1DLseCf6NO9sHg
|
||||
t0FLhDpF9kPMWBgauVVLepd2Y2yU1G8eFuEVGnsiQSu0IzsZP5FQBJSyxvxJ+V/L
|
||||
EW9ox91VGOP9VZR9jqdlYjGhcwClHA/nHe0q1fZq42+9rG466I5yIlNSoa7ilhTU
|
||||
2C2doxy6Sr6VJYnLEMQqoIF65t3MkKi9iaqN7az0OCrj6XR0P5iKBzUhIgMUd2qs
|
||||
7Id0XUdbQvaoaRI67vhGkNr+f4rdAUNCDGcbbokuBnmE7/gva6BAABUCAwEAAaNT
|
||||
MFEwHQYDVR0OBBYEFM2e2FmcytXhKyfC1KEjVJ2mPSy3MB8GA1UdIwQYMBaAFM2e
|
||||
2FmcytXhKyfC1KEjVJ2mPSy3MA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQEL
|
||||
BQADggEBAKH0z5vlbfTghjYWwd2yEEFBbZx5XxaLHboFQpFpxu9sZoidVs047tco
|
||||
MOr1py9juiNGGM8G35sw9306f+thDFwqlQfSExUwp5pRQNq+mxglMSF05HWDqBwb
|
||||
wnItKi/WXpkMQXgpQJFVeflz4B4ZFNlH1UQl5bwacXOM9NM9zO7duCjVXmGE0yxi
|
||||
VQyApfPQYu9whCSowDYYaA0toJeikMzGfWxhlAH79/2Qmit8KcSCbX1fK/QoRZLa
|
||||
5NeUi/OlJbBpkgTrfzfMLphmsPWPAVMeUKzqd/vXfG6ZBOZZm6e6sl8RBycBezII
|
||||
15WekikTE5+T54/E0xiu+zIW/Xhhk14=
|
||||
-----END CERTIFICATE-----
|
|
@ -1,22 +1,96 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
from PyQt5 import QtWidgets
|
||||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
from artiq.applets.simple import SimpleApplet
|
||||
from artiq.tools import scale_from_metadata
|
||||
from artiq.gui.tools import LayoutWidget
|
||||
|
||||
|
||||
class NumberWidget(QtWidgets.QLCDNumber):
|
||||
def __init__(self, args):
|
||||
QtWidgets.QLCDNumber.__init__(self)
|
||||
self.setDigitCount(args.digit_count)
|
||||
class QResponsiveLCDNumber(QtWidgets.QLCDNumber):
|
||||
doubleClicked = QtCore.pyqtSignal()
|
||||
|
||||
def mouseDoubleClickEvent(self, event):
|
||||
self.doubleClicked.emit()
|
||||
|
||||
|
||||
class QCancellableLineEdit(QtWidgets.QLineEdit):
|
||||
editCancelled = QtCore.pyqtSignal()
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == QtCore.Qt.Key_Escape:
|
||||
self.editCancelled.emit()
|
||||
else:
|
||||
super().keyPressEvent(event)
|
||||
|
||||
|
||||
class NumberWidget(LayoutWidget):
|
||||
def __init__(self, args, req):
|
||||
LayoutWidget.__init__(self)
|
||||
self.dataset_name = args.dataset
|
||||
self.req = req
|
||||
self.metadata = dict()
|
||||
|
||||
def data_changed(self, data, mods):
|
||||
self.number_area = QtWidgets.QStackedWidget()
|
||||
self.addWidget(self.number_area, 0, 0)
|
||||
|
||||
self.unit_area = QtWidgets.QLabel()
|
||||
self.unit_area.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop)
|
||||
self.addWidget(self.unit_area, 0, 1)
|
||||
|
||||
self.lcd_widget = QResponsiveLCDNumber()
|
||||
self.lcd_widget.setDigitCount(args.digit_count)
|
||||
self.lcd_widget.doubleClicked.connect(self.start_edit)
|
||||
self.number_area.addWidget(self.lcd_widget)
|
||||
|
||||
self.edit_widget = QCancellableLineEdit()
|
||||
self.edit_widget.setValidator(QtGui.QDoubleValidator())
|
||||
self.edit_widget.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.edit_widget.editCancelled.connect(self.cancel_edit)
|
||||
self.edit_widget.returnPressed.connect(self.confirm_edit)
|
||||
self.number_area.addWidget(self.edit_widget)
|
||||
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(60)
|
||||
self.edit_widget.setFont(font)
|
||||
|
||||
unit_font = QtGui.QFont()
|
||||
unit_font.setPointSize(20)
|
||||
self.unit_area.setFont(unit_font)
|
||||
|
||||
self.number_area.setCurrentWidget(self.lcd_widget)
|
||||
|
||||
def start_edit(self):
|
||||
# QLCDNumber value property contains the value of zero
|
||||
# if the displayed value is not a number.
|
||||
self.edit_widget.setText(str(self.lcd_widget.value()))
|
||||
self.edit_widget.selectAll()
|
||||
self.edit_widget.setFocus()
|
||||
self.number_area.setCurrentWidget(self.edit_widget)
|
||||
|
||||
def confirm_edit(self):
|
||||
scale = scale_from_metadata(self.metadata)
|
||||
val = float(self.edit_widget.text())
|
||||
val *= scale
|
||||
self.req.set_dataset(self.dataset_name, val, **self.metadata)
|
||||
self.number_area.setCurrentWidget(self.lcd_widget)
|
||||
|
||||
def cancel_edit(self):
|
||||
self.number_area.setCurrentWidget(self.lcd_widget)
|
||||
|
||||
def data_changed(self, value, metadata, persist, mods):
|
||||
try:
|
||||
n = float(data[self.dataset_name][1])
|
||||
self.metadata = metadata[self.dataset_name]
|
||||
# This applet will degenerate other scalar types to native float on edit
|
||||
# Use the dashboard ChangeEditDialog for consistent type casting
|
||||
val = float(value[self.dataset_name])
|
||||
scale = scale_from_metadata(self.metadata)
|
||||
val /= scale
|
||||
except (KeyError, ValueError, TypeError):
|
||||
n = "---"
|
||||
self.display(n)
|
||||
val = "---"
|
||||
|
||||
unit = self.metadata.get("unit", "")
|
||||
self.unit_area.setText(unit)
|
||||
self.lcd_widget.display(val)
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
@ -7,13 +7,13 @@ from artiq.applets.simple import SimpleApplet
|
|||
|
||||
|
||||
class Image(pyqtgraph.ImageView):
|
||||
def __init__(self, args):
|
||||
def __init__(self, args, req):
|
||||
pyqtgraph.ImageView.__init__(self)
|
||||
self.args = args
|
||||
|
||||
def data_changed(self, data, mods):
|
||||
def data_changed(self, value, metadata, persist, mods):
|
||||
try:
|
||||
img = data[self.args.img][1]
|
||||
img = value[self.args.img]
|
||||
except KeyError:
|
||||
return
|
||||
self.setImage(img)
|
||||
|
|
|
@ -8,20 +8,20 @@ from artiq.applets.simple import TitleApplet
|
|||
|
||||
|
||||
class HistogramPlot(pyqtgraph.PlotWidget):
|
||||
def __init__(self, args):
|
||||
def __init__(self, args, req):
|
||||
pyqtgraph.PlotWidget.__init__(self)
|
||||
self.args = args
|
||||
self.timer = QTimer()
|
||||
self.timer.setSingleShot(True)
|
||||
self.timer.timeout.connect(self.length_warning)
|
||||
|
||||
def data_changed(self, data, mods, title):
|
||||
def data_changed(self, value, metadata, persist, mods, title):
|
||||
try:
|
||||
y = data[self.args.y][1]
|
||||
y = value[self.args.y]
|
||||
if self.args.x is None:
|
||||
x = None
|
||||
else:
|
||||
x = data[self.args.x][1]
|
||||
x = value[self.args.x]
|
||||
except KeyError:
|
||||
return
|
||||
if x is None:
|
||||
|
|
|
@ -9,7 +9,7 @@ from artiq.applets.simple import TitleApplet
|
|||
|
||||
|
||||
class XYPlot(pyqtgraph.PlotWidget):
|
||||
def __init__(self, args):
|
||||
def __init__(self, args, req):
|
||||
pyqtgraph.PlotWidget.__init__(self)
|
||||
self.args = args
|
||||
self.timer = QTimer()
|
||||
|
@ -19,16 +19,16 @@ class XYPlot(pyqtgraph.PlotWidget):
|
|||
'Error bars': False,
|
||||
'Fit values': False}
|
||||
|
||||
def data_changed(self, data, mods, title):
|
||||
def data_changed(self, value, metadata, persist, mods, title):
|
||||
try:
|
||||
y = data[self.args.y][1]
|
||||
y = value[self.args.y]
|
||||
except KeyError:
|
||||
return
|
||||
x = data.get(self.args.x, (False, None))[1]
|
||||
x = value.get(self.args.x, (False, None))
|
||||
if x is None:
|
||||
x = np.arange(len(y))
|
||||
error = data.get(self.args.error, (False, None))[1]
|
||||
fit = data.get(self.args.fit, (False, None))[1]
|
||||
error = value.get(self.args.error, (False, None))
|
||||
fit = value.get(self.args.fit, (False, None))
|
||||
|
||||
if not len(y) or len(y) != len(x):
|
||||
self.mismatch['X values'] = True
|
||||
|
|
|
@ -22,7 +22,7 @@ def _compute_ys(histogram_bins, histograms_counts):
|
|||
# pyqtgraph.GraphicsWindow fails to behave like a regular Qt widget
|
||||
# and breaks embedding. Do not use as top widget.
|
||||
class XYHistPlot(QtWidgets.QSplitter):
|
||||
def __init__(self, args):
|
||||
def __init__(self, args, req):
|
||||
QtWidgets.QSplitter.__init__(self)
|
||||
self.resize(1000, 600)
|
||||
self.setWindowTitle("XY/Histogram")
|
||||
|
@ -124,11 +124,11 @@ class XYHistPlot(QtWidgets.QSplitter):
|
|||
return False
|
||||
return True
|
||||
|
||||
def data_changed(self, data, mods):
|
||||
def data_changed(self, value, metadata, persist, mods):
|
||||
try:
|
||||
xs = data[self.args.xs][1]
|
||||
histogram_bins = data[self.args.histogram_bins][1]
|
||||
histograms_counts = data[self.args.histograms_counts][1]
|
||||
xs = value[self.args.xs]
|
||||
histogram_bins = value[self.args.histogram_bins]
|
||||
histograms_counts = value[self.args.histograms_counts]
|
||||
except KeyError:
|
||||
return
|
||||
if len(xs) != histograms_counts.shape[0]:
|
||||
|
|
|
@ -6,18 +6,18 @@ from artiq.applets.simple import SimpleApplet
|
|||
|
||||
|
||||
class ProgressWidget(QtWidgets.QProgressBar):
|
||||
def __init__(self, args):
|
||||
def __init__(self, args, req):
|
||||
QtWidgets.QProgressBar.__init__(self)
|
||||
self.setMinimum(args.min)
|
||||
self.setMaximum(args.max)
|
||||
self.dataset_value = args.value
|
||||
|
||||
def data_changed(self, data, mods):
|
||||
def data_changed(self, value, metadata, persist, mods):
|
||||
try:
|
||||
value = round(data[self.dataset_value][1])
|
||||
val = round(value[self.dataset_value])
|
||||
except (KeyError, ValueError, TypeError):
|
||||
value = 0
|
||||
self.setValue(value)
|
||||
val = 0
|
||||
self.setValue(val)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -7,13 +7,112 @@ import string
|
|||
from qasync import QEventLoop, QtWidgets, QtCore
|
||||
|
||||
from sipyco.sync_struct import Subscriber, process_mod
|
||||
from sipyco.pc_rpc import AsyncioClient as RPCClient
|
||||
from sipyco import pyon
|
||||
from sipyco.pipe_ipc import AsyncioChildComm
|
||||
|
||||
from artiq.language.scan import ScanObject
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _AppletRequestInterface:
|
||||
def __init__(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
|
||||
"""
|
||||
Set a dataset.
|
||||
See documentation of ``artiq.language.environment.set_dataset``.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def mutate_dataset(self, key, index, value):
|
||||
"""
|
||||
Mutate a dataset.
|
||||
See documentation of ``artiq.language.environment.mutate_dataset``.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def append_to_dataset(self, key, value):
|
||||
"""
|
||||
Append to a dataset.
|
||||
See documentation of ``artiq.language.environment.append_to_dataset``.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def set_argument_value(self, expurl, key, value):
|
||||
"""
|
||||
Temporarily set the value of an argument in a experiment in the dashboard.
|
||||
The value resets to default value when recomputing the argument.
|
||||
|
||||
:param expurl: Experiment URL identifying the experiment in the dashboard. Example: 'repo:ArgumentsDemo'.
|
||||
:param key: Name of the argument in the experiment.
|
||||
:param value: Object representing the new temporary value of the argument. For ``Scannable`` arguments, this parameter
|
||||
should be a ``ScanObject``. The type of the ``ScanObject`` will be set as the selected type when this function is called.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class AppletRequestIPC(_AppletRequestInterface):
|
||||
def __init__(self, ipc):
|
||||
self.ipc = ipc
|
||||
|
||||
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
|
||||
metadata = {}
|
||||
if unit is not None:
|
||||
metadata["unit"] = unit
|
||||
if scale is not None:
|
||||
metadata["scale"] = scale
|
||||
if precision is not None:
|
||||
metadata["precision"] = precision
|
||||
self.ipc.set_dataset(key, value, metadata, persist)
|
||||
|
||||
def mutate_dataset(self, key, index, value):
|
||||
mod = {"action": "setitem", "path": [key, 1], "key": index, "value": value}
|
||||
self.ipc.update_dataset(mod)
|
||||
|
||||
def append_to_dataset(self, key, value):
|
||||
mod = {"action": "append", "path": [key, 1], "x": value}
|
||||
self.ipc.update_dataset(mod)
|
||||
|
||||
def set_argument_value(self, expurl, key, value):
|
||||
if isinstance(value, ScanObject):
|
||||
value = value.describe()
|
||||
self.ipc.set_argument_value(expurl, key, value)
|
||||
|
||||
|
||||
class AppletRequestRPC(_AppletRequestInterface):
|
||||
def __init__(self, loop, dataset_ctl):
|
||||
self.loop = loop
|
||||
self.dataset_ctl = dataset_ctl
|
||||
self.background_tasks = set()
|
||||
|
||||
def _background(self, coro, *args, **kwargs):
|
||||
task = self.loop.create_task(coro(*args, **kwargs))
|
||||
self.background_tasks.add(task)
|
||||
task.add_done_callback(self.background_tasks.discard)
|
||||
|
||||
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
|
||||
metadata = {}
|
||||
if unit is not None:
|
||||
metadata["unit"] = unit
|
||||
if scale is not None:
|
||||
metadata["scale"] = scale
|
||||
if precision is not None:
|
||||
metadata["precision"] = precision
|
||||
self._background(self.dataset_ctl.set, key, value, metadata=metadata, persist=persist)
|
||||
|
||||
def mutate_dataset(self, key, index, value):
|
||||
mod = {"action": "setitem", "path": [key, 1], "key": index, "value": value}
|
||||
self._background(self.dataset_ctl.update, mod)
|
||||
|
||||
def append_to_dataset(self, key, value):
|
||||
mod = {"action": "append", "path": [key, 1], "x": value}
|
||||
self._background(self.dataset_ctl.update, mod)
|
||||
|
||||
|
||||
class AppletIPCClient(AsyncioChildComm):
|
||||
def set_close_cb(self, close_cb):
|
||||
self.close_cb = close_cb
|
||||
|
@ -64,13 +163,30 @@ class AppletIPCClient(AsyncioChildComm):
|
|||
exc_info=True)
|
||||
self.close_cb()
|
||||
|
||||
def subscribe(self, datasets, init_cb, mod_cb, dataset_prefixes=[]):
|
||||
def subscribe(self, datasets, init_cb, mod_cb, dataset_prefixes=[], *, loop):
|
||||
self.write_pyon({"action": "subscribe",
|
||||
"datasets": datasets,
|
||||
"dataset_prefixes": dataset_prefixes})
|
||||
self.init_cb = init_cb
|
||||
self.mod_cb = mod_cb
|
||||
asyncio.ensure_future(self.listen())
|
||||
self.listen_task = loop.create_task(self.listen())
|
||||
|
||||
def set_dataset(self, key, value, metadata, persist=None):
|
||||
self.write_pyon({"action": "set_dataset",
|
||||
"key": key,
|
||||
"value": value,
|
||||
"metadata": metadata,
|
||||
"persist": persist})
|
||||
|
||||
def update_dataset(self, mod):
|
||||
self.write_pyon({"action": "update_dataset",
|
||||
"mod": mod})
|
||||
|
||||
def set_argument_value(self, expurl, key, value):
|
||||
self.write_pyon({"action": "set_argument_value",
|
||||
"expurl": expurl,
|
||||
"key": key,
|
||||
"value": value})
|
||||
|
||||
|
||||
class SimpleApplet:
|
||||
|
@ -92,8 +208,11 @@ class SimpleApplet:
|
|||
"for dataset notifications "
|
||||
"(ignored in embedded mode)")
|
||||
group.add_argument(
|
||||
"--port", default=3250, type=int,
|
||||
help="TCP port to connect to")
|
||||
"--port-notify", default=3250, type=int,
|
||||
help="TCP port to connect to for notifications (ignored in embedded mode)")
|
||||
group.add_argument(
|
||||
"--port-control", default=3251, type=int,
|
||||
help="TCP port to connect to for control (ignored in embedded mode)")
|
||||
|
||||
self._arggroup_datasets = self.argparser.add_argument_group("datasets")
|
||||
|
||||
|
@ -132,8 +251,21 @@ class SimpleApplet:
|
|||
if self.embed is not None:
|
||||
self.ipc.close()
|
||||
|
||||
def req_init(self):
|
||||
if self.embed is None:
|
||||
dataset_ctl = RPCClient()
|
||||
self.loop.run_until_complete(dataset_ctl.connect_rpc(
|
||||
self.args.server, self.args.port_control, "dataset_db"))
|
||||
self.req = AppletRequestRPC(self.loop, dataset_ctl)
|
||||
else:
|
||||
self.req = AppletRequestIPC(self.ipc)
|
||||
|
||||
def req_close(self):
|
||||
if self.embed is None:
|
||||
self.req.dataset_ctl.close_rpc()
|
||||
|
||||
def create_main_widget(self):
|
||||
self.main_widget = self.main_widget_class(self.args)
|
||||
self.main_widget = self.main_widget_class(self.args, self.req)
|
||||
if self.embed is not None:
|
||||
self.ipc.set_close_cb(self.main_widget.close)
|
||||
if os.name == "nt":
|
||||
|
@ -189,7 +321,12 @@ class SimpleApplet:
|
|||
return False
|
||||
|
||||
def emit_data_changed(self, data, mod_buffer):
|
||||
self.main_widget.data_changed(data, mod_buffer)
|
||||
persist = dict()
|
||||
value = dict()
|
||||
metadata = dict()
|
||||
for k, d in data.items():
|
||||
persist[k], value[k], metadata[k] = d
|
||||
self.main_widget.data_changed(value, metadata, persist, mod_buffer)
|
||||
|
||||
def flush_mod_buffer(self):
|
||||
self.emit_data_changed(self.data, self.mod_buffer)
|
||||
|
@ -204,8 +341,8 @@ class SimpleApplet:
|
|||
self.mod_buffer.append(mod)
|
||||
else:
|
||||
self.mod_buffer = [mod]
|
||||
asyncio.get_event_loop().call_later(self.args.update_delay,
|
||||
self.flush_mod_buffer)
|
||||
self.loop.call_later(self.args.update_delay,
|
||||
self.flush_mod_buffer)
|
||||
else:
|
||||
self.emit_data_changed(self.data, [mod])
|
||||
|
||||
|
@ -214,10 +351,11 @@ class SimpleApplet:
|
|||
self.subscriber = Subscriber("datasets",
|
||||
self.sub_init, self.sub_mod)
|
||||
self.loop.run_until_complete(self.subscriber.connect(
|
||||
self.args.server, self.args.port))
|
||||
self.args.server, self.args.port_notify))
|
||||
else:
|
||||
self.ipc.subscribe(self.datasets, self.sub_init, self.sub_mod,
|
||||
dataset_prefixes=self.dataset_prefixes)
|
||||
dataset_prefixes=self.dataset_prefixes,
|
||||
loop=self.loop)
|
||||
|
||||
def unsubscribe(self):
|
||||
if self.embed is None:
|
||||
|
@ -229,12 +367,16 @@ class SimpleApplet:
|
|||
try:
|
||||
self.ipc_init()
|
||||
try:
|
||||
self.create_main_widget()
|
||||
self.subscribe()
|
||||
self.req_init()
|
||||
try:
|
||||
self.loop.run_forever()
|
||||
self.create_main_widget()
|
||||
self.subscribe()
|
||||
try:
|
||||
self.loop.run_forever()
|
||||
finally:
|
||||
self.unsubscribe()
|
||||
finally:
|
||||
self.unsubscribe()
|
||||
self.req_close()
|
||||
finally:
|
||||
self.ipc_close()
|
||||
finally:
|
||||
|
@ -273,4 +415,9 @@ class TitleApplet(SimpleApplet):
|
|||
title = self.args.title
|
||||
else:
|
||||
title = None
|
||||
self.main_widget.data_changed(data, mod_buffer, title)
|
||||
persist = dict()
|
||||
value = dict()
|
||||
metadata = dict()
|
||||
for k, d in data.items():
|
||||
persist[k], value[k], metadata[k] = d
|
||||
self.main_widget.data_changed(value, metadata, persist, mod_buffer, title)
|
||||
|
|
|
@ -20,11 +20,46 @@ class Model(DictSyncTreeSepModel):
|
|||
DictSyncTreeSepModel.__init__(self, ".", ["Dataset", "Value"], init)
|
||||
|
||||
def convert(self, k, v, column):
|
||||
return short_format(v[1])
|
||||
return short_format(v[1], v[2])
|
||||
|
||||
|
||||
class DatasetCtl:
|
||||
def __init__(self, master_host, master_port):
|
||||
self.master_host = master_host
|
||||
self.master_port = master_port
|
||||
|
||||
async def _execute_rpc(self, op_name, key_or_mod, value=None, persist=None, metadata=None):
|
||||
logger.info("Starting %s operation on %s", op_name, key_or_mod)
|
||||
try:
|
||||
remote = RPCClient()
|
||||
await remote.connect_rpc(self.master_host, self.master_port,
|
||||
"dataset_db")
|
||||
try:
|
||||
if op_name == "set":
|
||||
await remote.set(key_or_mod, value, persist, metadata)
|
||||
elif op_name == "update":
|
||||
await remote.update(key_or_mod)
|
||||
else:
|
||||
logger.error("Invalid operation: %s", op_name)
|
||||
return
|
||||
finally:
|
||||
remote.close_rpc()
|
||||
except:
|
||||
logger.error("Failed %s operation on %s", op_name,
|
||||
key_or_mod, exc_info=True)
|
||||
else:
|
||||
logger.info("Finished %s operation on %s", op_name,
|
||||
key_or_mod)
|
||||
|
||||
async def set(self, key, value, persist=None, metadata=None):
|
||||
await self._execute_rpc("set", key, value, persist, metadata)
|
||||
|
||||
async def update(self, mod):
|
||||
await self._execute_rpc("update", mod)
|
||||
|
||||
|
||||
class DatasetsDock(QtWidgets.QDockWidget):
|
||||
def __init__(self, datasets_sub, master_host, master_port):
|
||||
def __init__(self, dataset_sub, dataset_ctl):
|
||||
QtWidgets.QDockWidget.__init__(self, "Datasets")
|
||||
self.setObjectName("Datasets")
|
||||
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
|
||||
|
@ -62,10 +97,9 @@ class DatasetsDock(QtWidgets.QDockWidget):
|
|||
self.table.addAction(upload_action)
|
||||
|
||||
self.set_model(Model(dict()))
|
||||
datasets_sub.add_setmodel_callback(self.set_model)
|
||||
dataset_sub.add_setmodel_callback(self.set_model)
|
||||
|
||||
self.master_host = master_host
|
||||
self.master_port = master_port
|
||||
self.dataset_ctl = dataset_ctl
|
||||
|
||||
def _search_datasets(self):
|
||||
if hasattr(self, "table_model_filter"):
|
||||
|
@ -82,30 +116,14 @@ class DatasetsDock(QtWidgets.QDockWidget):
|
|||
self.table_model_filter.setSourceModel(self.table_model)
|
||||
self.table.setModel(self.table_model_filter)
|
||||
|
||||
async def _upload_dataset(self, name, value,):
|
||||
logger.info("Uploading dataset '%s' to master...", name)
|
||||
try:
|
||||
remote = RPCClient()
|
||||
await remote.connect_rpc(self.master_host, self.master_port,
|
||||
"master_dataset_db")
|
||||
try:
|
||||
await remote.set(name, value)
|
||||
finally:
|
||||
remote.close_rpc()
|
||||
except:
|
||||
logger.error("Failed uploading dataset '%s'",
|
||||
name, exc_info=True)
|
||||
else:
|
||||
logger.info("Finished uploading dataset '%s'", name)
|
||||
|
||||
def upload_clicked(self):
|
||||
idx = self.table.selectedIndexes()
|
||||
if idx:
|
||||
idx = self.table_model_filter.mapToSource(idx[0])
|
||||
key = self.table_model.index_to_key(idx)
|
||||
if key is not None:
|
||||
persist, value = self.table_model.backing_store[key]
|
||||
asyncio.ensure_future(self._upload_dataset(key, value))
|
||||
persist, value, metadata = self.table_model.backing_store[key]
|
||||
asyncio.ensure_future(self.dataset_ctl.set(key, value, metadata=metadata))
|
||||
|
||||
def save_state(self):
|
||||
return bytes(self.table.header().saveState())
|
||||
|
|
|
@ -10,95 +10,26 @@ import h5py
|
|||
from sipyco import pyon
|
||||
|
||||
from artiq import __artiq_dir__ as artiq_dir
|
||||
from artiq.gui.tools import LayoutWidget, log_level_to_name, get_open_file_name
|
||||
from artiq.gui.entries import procdesc_to_entry
|
||||
from artiq.gui.tools import (LayoutWidget, log_level_to_name, get_open_file_name)
|
||||
from artiq.gui.entries import procdesc_to_entry, EntryTreeWidget
|
||||
from artiq.master.worker import Worker, log_worker_exception
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _WheelFilter(QtCore.QObject):
|
||||
def eventFilter(self, obj, event):
|
||||
if (event.type() == QtCore.QEvent.Wheel and
|
||||
event.modifiers() != QtCore.Qt.NoModifier):
|
||||
event.ignore()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class _ArgumentEditor(QtWidgets.QTreeWidget):
|
||||
class _ArgumentEditor(EntryTreeWidget):
|
||||
def __init__(self, dock):
|
||||
QtWidgets.QTreeWidget.__init__(self)
|
||||
self.setColumnCount(3)
|
||||
self.header().setStretchLastSection(False)
|
||||
try:
|
||||
set_resize_mode = self.header().setSectionResizeMode
|
||||
except AttributeError:
|
||||
set_resize_mode = self.header().setResizeMode
|
||||
set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents)
|
||||
set_resize_mode(1, QtWidgets.QHeaderView.Stretch)
|
||||
set_resize_mode(2, QtWidgets.QHeaderView.ResizeToContents)
|
||||
self.header().setVisible(False)
|
||||
self.setSelectionMode(self.NoSelection)
|
||||
self.setHorizontalScrollMode(self.ScrollPerPixel)
|
||||
self.setVerticalScrollMode(self.ScrollPerPixel)
|
||||
|
||||
self.setStyleSheet("QTreeWidget {background: " +
|
||||
self.palette().midlight().color().name() + " ;}")
|
||||
|
||||
self.viewport().installEventFilter(_WheelFilter(self.viewport()))
|
||||
|
||||
self._groups = dict()
|
||||
self._arg_to_widgets = dict()
|
||||
EntryTreeWidget.__init__(self)
|
||||
self._dock = dock
|
||||
|
||||
if not self._dock.arguments:
|
||||
self.addTopLevelItem(QtWidgets.QTreeWidgetItem(["No arguments"]))
|
||||
gradient = QtGui.QLinearGradient(
|
||||
0, 0, 0, QtGui.QFontMetrics(self.font()).lineSpacing()*2.5)
|
||||
gradient.setColorAt(0, self.palette().base().color())
|
||||
gradient.setColorAt(1, self.palette().midlight().color())
|
||||
self.insertTopLevelItem(0, QtWidgets.QTreeWidgetItem(["No arguments"]))
|
||||
|
||||
for name, argument in self._dock.arguments.items():
|
||||
widgets = dict()
|
||||
self._arg_to_widgets[name] = widgets
|
||||
self.set_argument(name, argument)
|
||||
|
||||
entry = procdesc_to_entry(argument["desc"])(argument)
|
||||
widget_item = QtWidgets.QTreeWidgetItem([name])
|
||||
if argument["tooltip"]:
|
||||
widget_item.setToolTip(0, argument["tooltip"])
|
||||
widgets["entry"] = entry
|
||||
widgets["widget_item"] = widget_item
|
||||
self.quickStyleClicked.connect(self._dock._run_clicked)
|
||||
|
||||
for col in range(3):
|
||||
widget_item.setBackground(col, gradient)
|
||||
font = widget_item.font(0)
|
||||
font.setBold(True)
|
||||
widget_item.setFont(0, font)
|
||||
|
||||
if argument["group"] is None:
|
||||
self.addTopLevelItem(widget_item)
|
||||
else:
|
||||
self._get_group(argument["group"]).addChild(widget_item)
|
||||
fix_layout = LayoutWidget()
|
||||
widgets["fix_layout"] = fix_layout
|
||||
fix_layout.addWidget(entry)
|
||||
self.setItemWidget(widget_item, 1, fix_layout)
|
||||
|
||||
recompute_argument = QtWidgets.QToolButton()
|
||||
recompute_argument.setToolTip("Re-run the experiment's build "
|
||||
"method and take the default value")
|
||||
recompute_argument.setIcon(
|
||||
QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_BrowserReload))
|
||||
recompute_argument.clicked.connect(
|
||||
partial(self._recompute_argument_clicked, name))
|
||||
fix_layout = LayoutWidget()
|
||||
fix_layout.addWidget(recompute_argument)
|
||||
self.setItemWidget(widget_item, 2, fix_layout)
|
||||
|
||||
widget_item = QtWidgets.QTreeWidgetItem()
|
||||
self.addTopLevelItem(widget_item)
|
||||
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
|
||||
recompute_arguments.setIcon(
|
||||
QtWidgets.QApplication.style().standardIcon(
|
||||
|
@ -108,7 +39,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
|||
load = QtWidgets.QPushButton("Set arguments from HDF5")
|
||||
load.setToolTip("Set arguments from currently selected HDF5 file")
|
||||
load.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_DialogApplyButton))
|
||||
QtWidgets.QStyle.SP_DialogApplyButton))
|
||||
load.clicked.connect(self._load_clicked)
|
||||
|
||||
buttons = LayoutWidget()
|
||||
|
@ -116,21 +47,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
|||
buttons.addWidget(load, 1, 2)
|
||||
for i, s in enumerate((1, 0, 0, 1)):
|
||||
buttons.layout.setColumnStretch(i, s)
|
||||
self.setItemWidget(widget_item, 1, buttons)
|
||||
|
||||
def _get_group(self, name):
|
||||
if name in self._groups:
|
||||
return self._groups[name]
|
||||
group = QtWidgets.QTreeWidgetItem([name])
|
||||
for col in range(3):
|
||||
group.setBackground(col, self.palette().mid())
|
||||
group.setForeground(col, self.palette().brightText())
|
||||
font = group.font(col)
|
||||
font.setBold(True)
|
||||
group.setFont(col, font)
|
||||
self.addTopLevelItem(group)
|
||||
self._groups[name] = group
|
||||
return group
|
||||
self.setItemWidget(self.bottom_item, 1, buttons)
|
||||
|
||||
def _load_clicked(self):
|
||||
asyncio.ensure_future(self._dock.load_hdf5_task())
|
||||
|
@ -138,8 +55,8 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
|||
def _recompute_arguments_clicked(self):
|
||||
asyncio.ensure_future(self._dock._recompute_arguments())
|
||||
|
||||
def _recompute_argument_clicked(self, name):
|
||||
asyncio.ensure_future(self._recompute_argument(name))
|
||||
def reset_entry(self, key):
|
||||
asyncio.ensure_future(self._recompute_argument(key))
|
||||
|
||||
async def _recompute_argument(self, name):
|
||||
try:
|
||||
|
@ -154,29 +71,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
|||
state = procdesc_to_entry(procdesc).default_state(procdesc)
|
||||
argument["desc"] = procdesc
|
||||
argument["state"] = state
|
||||
|
||||
widgets = self._arg_to_widgets[name]
|
||||
|
||||
widgets["entry"].deleteLater()
|
||||
widgets["entry"] = procdesc_to_entry(procdesc)(argument)
|
||||
widgets["fix_layout"] = LayoutWidget()
|
||||
widgets["fix_layout"].addWidget(widgets["entry"])
|
||||
self.setItemWidget(widgets["widget_item"], 1, widgets["fix_layout"])
|
||||
self.updateGeometries()
|
||||
|
||||
def save_state(self):
|
||||
expanded = []
|
||||
for k, v in self._groups.items():
|
||||
if v.isExpanded():
|
||||
expanded.append(k)
|
||||
return {"expanded": expanded}
|
||||
|
||||
def restore_state(self, state):
|
||||
for e in state["expanded"]:
|
||||
try:
|
||||
self._groups[e].setExpanded(True)
|
||||
except KeyError:
|
||||
pass
|
||||
self.update_argument(name, argument)
|
||||
|
||||
|
||||
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
||||
|
@ -285,8 +180,8 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
state = self.argeditor.save_state()
|
||||
self.argeditor.deleteLater()
|
||||
self.argeditor = _ArgumentEditor(self)
|
||||
self.argeditor.restore_state(state)
|
||||
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
||||
self.argeditor.restore_state(state)
|
||||
|
||||
async def load_hdf5_task(self, filename=None):
|
||||
if filename is None:
|
||||
|
@ -378,9 +273,9 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
|
||||
|
||||
class LocalDatasetDB:
|
||||
def __init__(self, datasets_sub):
|
||||
self.datasets_sub = datasets_sub
|
||||
datasets_sub.add_setmodel_callback(self.init)
|
||||
def __init__(self, dataset_sub):
|
||||
self.dataset_sub = dataset_sub
|
||||
dataset_sub.add_setmodel_callback(self.init)
|
||||
|
||||
def init(self, data):
|
||||
self._data = data
|
||||
|
@ -389,11 +284,11 @@ class LocalDatasetDB:
|
|||
return self._data.backing_store[key][1]
|
||||
|
||||
def update(self, mod):
|
||||
self.datasets_sub.update(mod)
|
||||
self.dataset_sub.update(mod)
|
||||
|
||||
|
||||
class ExperimentsArea(QtWidgets.QMdiArea):
|
||||
def __init__(self, root, datasets_sub):
|
||||
def __init__(self, root, dataset_sub):
|
||||
QtWidgets.QMdiArea.__init__(self)
|
||||
self.pixmap = QtGui.QPixmap(os.path.join(
|
||||
artiq_dir, "gui", "logo_ver.svg"))
|
||||
|
@ -402,11 +297,11 @@ class ExperimentsArea(QtWidgets.QMdiArea):
|
|||
|
||||
self.open_experiments = []
|
||||
|
||||
self._ddb = LocalDatasetDB(datasets_sub)
|
||||
self._ddb = LocalDatasetDB(dataset_sub)
|
||||
|
||||
self.worker_handlers = {
|
||||
"get_device_db": lambda: {},
|
||||
"get_device": lambda k: {"type": "dummy"},
|
||||
"get_device": lambda key, resolve_alias=False: {"type": "dummy"},
|
||||
"get_dataset": self._ddb.get,
|
||||
"update_dataset": self._ddb.update,
|
||||
}
|
||||
|
@ -474,6 +369,8 @@ class ExperimentsArea(QtWidgets.QMdiArea):
|
|||
def initialize_submission_arguments(self, arginfo):
|
||||
arguments = OrderedDict()
|
||||
for name, (procdesc, group, tooltip) in arginfo.items():
|
||||
if procdesc["ty"] == "EnumerationValue" and procdesc["quickstyle"]:
|
||||
procdesc["quickstyle"] = False
|
||||
state = procdesc_to_entry(procdesc).default_state(procdesc)
|
||||
arguments[name] = {
|
||||
"desc": procdesc,
|
||||
|
@ -516,5 +413,9 @@ class ExperimentsArea(QtWidgets.QMdiArea):
|
|||
self.open_experiments.append(dock)
|
||||
return dock
|
||||
|
||||
def set_argument_value(self, expurl, name, value):
|
||||
logger.warning("Unable to set argument '%s', dropping change. "
|
||||
"'set_argument_value' not supported in browser.", name)
|
||||
|
||||
def on_dock_closed(self, dock):
|
||||
self.open_experiments.remove(dock)
|
||||
|
|
|
@ -102,13 +102,14 @@ class Hdf5FileSystemModel(QtWidgets.QFileSystemModel):
|
|||
h5 = open_h5(info)
|
||||
if h5 is not None:
|
||||
try:
|
||||
expid = pyon.decode(h5["expid"][()])
|
||||
start_time = datetime.fromtimestamp(h5["start_time"][()])
|
||||
expid = pyon.decode(h5["expid"][()]) if "expid" in h5 else dict()
|
||||
start_time = datetime.fromtimestamp(h5["start_time"][()]) if "start_time" in h5 else "<none>"
|
||||
v = ("artiq_version: {}\nrepo_rev: {}\nfile: {}\n"
|
||||
"class_name: {}\nrid: {}\nstart_time: {}").format(
|
||||
h5["artiq_version"][()], expid["repo_rev"],
|
||||
expid.get("file", "<none>"), expid["class_name"],
|
||||
h5["rid"][()], start_time)
|
||||
h5["artiq_version"].asstr()[()] if "artiq_version" in h5 else "<none>",
|
||||
expid.get("repo_rev", "<none>"),
|
||||
expid.get("file", "<none>"), expid.get("class_name", "<none>"),
|
||||
h5["rid"][()] if "rid" in h5 else "<none>", start_time)
|
||||
return v
|
||||
except:
|
||||
logger.warning("unable to read metadata from %s",
|
||||
|
@ -174,14 +175,14 @@ class FilesDock(QtWidgets.QDockWidget):
|
|||
logger.debug("loading datasets from %s", info.filePath())
|
||||
with f:
|
||||
try:
|
||||
expid = pyon.decode(f["expid"][()])
|
||||
start_time = datetime.fromtimestamp(f["start_time"][()])
|
||||
expid = pyon.decode(f["expid"][()]) if "expid" in f else dict()
|
||||
start_time = datetime.fromtimestamp(f["start_time"][()]) if "start_time" in f else "<none>"
|
||||
v = {
|
||||
"artiq_version": f["artiq_version"][()],
|
||||
"repo_rev": expid["repo_rev"],
|
||||
"artiq_version": f["artiq_version"].asstr()[()] if "artiq_version" in f else "<none>",
|
||||
"repo_rev": expid.get("repo_rev", "<none>"),
|
||||
"file": expid.get("file", "<none>"),
|
||||
"class_name": expid["class_name"],
|
||||
"rid": f["rid"][()],
|
||||
"class_name": expid.get("class_name", "<none>"),
|
||||
"rid": f["rid"][()] if "rid" in f else "<none>",
|
||||
"start_time": start_time,
|
||||
}
|
||||
self.metadata_changed.emit(v)
|
||||
|
@ -193,7 +194,9 @@ class FilesDock(QtWidgets.QDockWidget):
|
|||
if "archive" in f:
|
||||
def visitor(k, v):
|
||||
if isinstance(v, h5py.Dataset):
|
||||
rd[k] = (True, v[()])
|
||||
# v.attrs is a non-serializable h5py.AttributeManager, need to convert to dict
|
||||
# See https://docs.h5py.org/en/stable/high/attr.html#h5py.AttributeManager
|
||||
rd[k] = (True, v[()], dict(v.attrs))
|
||||
|
||||
f["archive"].visititems(visitor)
|
||||
|
||||
|
@ -203,7 +206,9 @@ class FilesDock(QtWidgets.QDockWidget):
|
|||
if k in rd:
|
||||
logger.warning("dataset '%s' is both in archive "
|
||||
"and outputs", k)
|
||||
rd[k] = (True, v[()])
|
||||
# v.attrs is a non-serializable h5py.AttributeManager, need to convert to dict
|
||||
# See https://docs.h5py.org/en/stable/high/attr.html#h5py.AttributeManager
|
||||
rd[k] = (True, v[()], dict(v.attrs))
|
||||
|
||||
f["datasets"].visititems(visitor)
|
||||
|
||||
|
|
|
@ -59,19 +59,18 @@ def build_artiq_soc(soc, argdict):
|
|||
builder.software_packages = []
|
||||
builder.add_software_package("bootloader", os.path.join(firmware_dir, "bootloader"))
|
||||
is_kasli_v1 = isinstance(soc.platform, kasli.Platform) and soc.platform.hw_rev in ("v1.0", "v1.1")
|
||||
if isinstance(soc, AMPSoC):
|
||||
kernel_cpu_type = "vexriscv" if is_kasli_v1 else "vexriscv-g"
|
||||
builder.add_software_package("libm", cpu_type=kernel_cpu_type)
|
||||
builder.add_software_package("libprintf", cpu_type=kernel_cpu_type)
|
||||
builder.add_software_package("libunwind", cpu_type=kernel_cpu_type)
|
||||
builder.add_software_package("ksupport", os.path.join(firmware_dir, "ksupport"), cpu_type=kernel_cpu_type)
|
||||
# Generate unwinder for soft float target (ARTIQ runtime)
|
||||
# If the kernel lacks FPU, then the runtime unwinder is already generated
|
||||
if not is_kasli_v1:
|
||||
builder.add_software_package("libunwind")
|
||||
kernel_cpu_type = "vexriscv" if is_kasli_v1 else "vexriscv-g"
|
||||
builder.add_software_package("libm", cpu_type=kernel_cpu_type)
|
||||
builder.add_software_package("libprintf", cpu_type=kernel_cpu_type)
|
||||
builder.add_software_package("libunwind", cpu_type=kernel_cpu_type)
|
||||
builder.add_software_package("ksupport", os.path.join(firmware_dir, "ksupport"), cpu_type=kernel_cpu_type)
|
||||
# Generate unwinder for soft float target (ARTIQ runtime)
|
||||
# If the kernel lacks FPU, then the runtime unwinder is already generated
|
||||
if not is_kasli_v1:
|
||||
builder.add_software_package("libunwind")
|
||||
if not soc.config["DRTIO_ROLE"] == "satellite":
|
||||
builder.add_software_package("runtime", os.path.join(firmware_dir, "runtime"))
|
||||
else:
|
||||
# Assume DRTIO satellite.
|
||||
builder.add_software_package("satman", os.path.join(firmware_dir, "satman"))
|
||||
try:
|
||||
builder.build()
|
||||
|
|
|
@ -21,13 +21,19 @@ class scoped(object):
|
|||
set of variables resolved as globals
|
||||
"""
|
||||
|
||||
class remote(object):
|
||||
"""
|
||||
:ivar remote_fn: (bool) whether function is ran on a remote device,
|
||||
meaning arguments are received remotely and return is sent remotely
|
||||
"""
|
||||
|
||||
# Typed versions of untyped nodes
|
||||
class argT(ast.arg, commontyped):
|
||||
pass
|
||||
|
||||
class ClassDefT(ast.ClassDef):
|
||||
_types = ("constructor_type",)
|
||||
class FunctionDefT(ast.FunctionDef, scoped):
|
||||
class FunctionDefT(ast.FunctionDef, scoped, remote):
|
||||
_types = ("signature_type",)
|
||||
class QuotedFunctionDefT(FunctionDefT):
|
||||
"""
|
||||
|
@ -58,7 +64,7 @@ class BinOpT(ast.BinOp, commontyped):
|
|||
pass
|
||||
class BoolOpT(ast.BoolOp, commontyped):
|
||||
pass
|
||||
class CallT(ast.Call, commontyped):
|
||||
class CallT(ast.Call, commontyped, remote):
|
||||
"""
|
||||
:ivar iodelay: (:class:`iodelay.Expr`)
|
||||
:ivar arg_exprs: (dict of str to :class:`iodelay.Expr`)
|
||||
|
|
|
@ -38,6 +38,9 @@ class TInt(types.TMono):
|
|||
def one():
|
||||
return 1
|
||||
|
||||
def TInt8():
|
||||
return TInt(types.TValue(8))
|
||||
|
||||
def TInt32():
|
||||
return TInt(types.TValue(32))
|
||||
|
||||
|
@ -244,6 +247,18 @@ def fn_at_mu():
|
|||
def fn_rtio_log():
|
||||
return types.TBuiltinFunction("rtio_log")
|
||||
|
||||
def fn_subkernel_await():
|
||||
return types.TBuiltinFunction("subkernel_await")
|
||||
|
||||
def fn_subkernel_preload():
|
||||
return types.TBuiltinFunction("subkernel_preload")
|
||||
|
||||
def fn_subkernel_send():
|
||||
return types.TBuiltinFunction("subkernel_send")
|
||||
|
||||
def fn_subkernel_recv():
|
||||
return types.TBuiltinFunction("subkernel_recv")
|
||||
|
||||
# Accessors
|
||||
|
||||
def is_none(typ):
|
||||
|
@ -326,7 +341,7 @@ def get_iterable_elt(typ):
|
|||
# n-dimensional arrays, rather than the n-1 dimensional result of iterating over
|
||||
# the first axis, which makes the name a bit misleading.
|
||||
if is_str(typ) or is_bytes(typ) or is_bytearray(typ):
|
||||
return TInt(types.TValue(8))
|
||||
return TInt8()
|
||||
elif types._is_pointer(typ) or is_iterable(typ):
|
||||
return typ.find()["elt"].find()
|
||||
else:
|
||||
|
@ -342,5 +357,5 @@ def is_allocated(typ):
|
|||
is_float(typ) or is_range(typ) or
|
||||
types._is_pointer(typ) or types.is_function(typ) or
|
||||
types.is_external_function(typ) or types.is_rpc(typ) or
|
||||
types.is_method(typ) or types.is_tuple(typ) or
|
||||
types.is_value(typ))
|
||||
types.is_subkernel(typ) or types.is_method(typ) or
|
||||
types.is_tuple(typ) or types.is_value(typ))
|
||||
|
|
|
@ -5,6 +5,7 @@ the references to the host objects and translates the functions
|
|||
annotated as ``@kernel`` when they are referenced.
|
||||
"""
|
||||
|
||||
import typing
|
||||
import os, re, linecache, inspect, textwrap, types as pytypes, numpy
|
||||
from collections import OrderedDict, defaultdict
|
||||
|
||||
|
@ -18,6 +19,13 @@ from . import types, builtins, asttyped, math_fns, prelude
|
|||
from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer, TypedtreePrinter
|
||||
from .transforms.asttyped_rewriter import LocalExtractor
|
||||
|
||||
try:
|
||||
# From numpy=1.25.0 dispatching for `__array_function__` is done via
|
||||
# a C wrapper: https://github.com/numpy/numpy/pull/23020
|
||||
from numpy.core._multiarray_umath import _ArrayFunctionDispatcher
|
||||
except ImportError:
|
||||
_ArrayFunctionDispatcher = None
|
||||
|
||||
|
||||
class SpecializedFunction:
|
||||
def __init__(self, instance_type, host_function):
|
||||
|
@ -39,17 +47,47 @@ class SpecializedFunction:
|
|||
return hash((self.instance_type, self.host_function))
|
||||
|
||||
|
||||
class SubkernelMessageType:
|
||||
def __init__(self, name, value_type):
|
||||
self.name = name
|
||||
self.value_type = value_type
|
||||
self.send_loc = None
|
||||
self.recv_loc = None
|
||||
|
||||
class EmbeddingMap:
|
||||
def __init__(self):
|
||||
def __init__(self, old_embedding_map=None):
|
||||
self.object_current_key = 0
|
||||
self.object_forward_map = {}
|
||||
self.object_reverse_map = {}
|
||||
self.module_map = {}
|
||||
|
||||
# type_map connects the host Python `type` to the pair of associated
|
||||
# `(TInstance, TConstructor)`s. The `used_…_names` sets cache the
|
||||
# respective `.name`s for O(1) collision avoidance.
|
||||
self.type_map = {}
|
||||
self.used_instance_type_names = set()
|
||||
self.used_constructor_type_names = set()
|
||||
|
||||
self.function_map = {}
|
||||
self.str_forward_map = {}
|
||||
self.str_reverse_map = {}
|
||||
|
||||
# mapping `name` to object ID
|
||||
self.subkernel_message_map = {}
|
||||
|
||||
# subkernels: dict of ID: function, just like object_forward_map
|
||||
# allow the embedding map to be aware of subkernels from other kernels
|
||||
if not old_embedding_map is None:
|
||||
for key, obj_ref in old_embedding_map.subkernels().items():
|
||||
self.object_forward_map[key] = obj_ref
|
||||
obj_id = id(obj_ref)
|
||||
self.object_reverse_map[obj_id] = key
|
||||
for msg_id, msg_type in old_embedding_map.subkernel_messages().items():
|
||||
self.object_forward_map[msg_id] = msg_type
|
||||
obj_id = id(msg_type)
|
||||
self.subkernel_message_map[msg_type.name] = msg_id
|
||||
self.object_reverse_map[obj_id] = msg_id
|
||||
|
||||
self.preallocate_runtime_exception_names(["RuntimeError",
|
||||
"RTIOUnderflow",
|
||||
"RTIOOverflow",
|
||||
|
@ -59,7 +97,9 @@ class EmbeddingMap:
|
|||
"CacheError",
|
||||
"SPIError",
|
||||
"0:ZeroDivisionError",
|
||||
"0:IndexError"])
|
||||
"0:IndexError",
|
||||
"UnwrapNoneError",
|
||||
"SubkernelError"])
|
||||
|
||||
def preallocate_runtime_exception_names(self, names):
|
||||
for i, name in enumerate(names):
|
||||
|
@ -91,16 +131,6 @@ class EmbeddingMap:
|
|||
|
||||
# Types
|
||||
def store_type(self, host_type, instance_type, constructor_type):
|
||||
self._rename_type(instance_type)
|
||||
self.type_map[host_type] = (instance_type, constructor_type)
|
||||
|
||||
def retrieve_type(self, host_type):
|
||||
return self.type_map[host_type]
|
||||
|
||||
def has_type(self, host_type):
|
||||
return host_type in self.type_map
|
||||
|
||||
def _rename_type(self, new_instance_type):
|
||||
# Generally, user-defined types that have exact same name (which is to say, classes
|
||||
# defined inside functions) do not pose a problem to the compiler. The two places which
|
||||
# cannot handle this are:
|
||||
|
@ -109,12 +139,29 @@ class EmbeddingMap:
|
|||
# Since handling #2 requires renaming on ARTIQ side anyway, it's more straightforward
|
||||
# to do it once when embedding (since non-embedded code cannot define classes in
|
||||
# functions). Also, easier to debug.
|
||||
n = 0
|
||||
for host_type in self.type_map:
|
||||
instance_type, constructor_type = self.type_map[host_type]
|
||||
if instance_type.name == new_instance_type.name:
|
||||
n += 1
|
||||
new_instance_type.name = "{}.{}".format(new_instance_type.name, n)
|
||||
suffix = 0
|
||||
new_instance_name = instance_type.name
|
||||
new_constructor_name = constructor_type.name
|
||||
while True:
|
||||
if (new_instance_name not in self.used_instance_type_names
|
||||
and new_constructor_name not in self.used_constructor_type_names):
|
||||
break
|
||||
suffix += 1
|
||||
new_instance_name = f"{instance_type.name}.{suffix}"
|
||||
new_constructor_name = f"{constructor_type.name}.{suffix}"
|
||||
|
||||
self.used_instance_type_names.add(new_instance_name)
|
||||
instance_type.name = new_instance_name
|
||||
self.used_constructor_type_names.add(new_constructor_name)
|
||||
constructor_type.name = new_constructor_name
|
||||
|
||||
self.type_map[host_type] = (instance_type, constructor_type)
|
||||
|
||||
def retrieve_type(self, host_type):
|
||||
return self.type_map[host_type]
|
||||
|
||||
def has_type(self, host_type):
|
||||
return host_type in self.type_map
|
||||
|
||||
def attribute_count(self):
|
||||
count = 0
|
||||
|
@ -141,6 +188,11 @@ class EmbeddingMap:
|
|||
return self.object_reverse_map[obj_id]
|
||||
|
||||
self.object_current_key += 1
|
||||
while self.object_forward_map.get(self.object_current_key):
|
||||
# make sure there's no collisions with previously inserted subkernels
|
||||
# their identifiers must be consistent across all kernels/subkernels
|
||||
self.object_current_key += 1
|
||||
|
||||
self.object_forward_map[self.object_current_key] = obj_ref
|
||||
self.object_reverse_map[obj_id] = self.object_current_key
|
||||
return self.object_current_key
|
||||
|
@ -153,7 +205,7 @@ class EmbeddingMap:
|
|||
obj_ref = self.object_forward_map[obj_id]
|
||||
if isinstance(obj_ref, (pytypes.FunctionType, pytypes.MethodType,
|
||||
pytypes.BuiltinFunctionType, pytypes.ModuleType,
|
||||
SpecializedFunction)):
|
||||
SpecializedFunction, SubkernelMessageType)):
|
||||
continue
|
||||
elif isinstance(obj_ref, type):
|
||||
_, obj_typ = self.type_map[obj_ref]
|
||||
|
@ -161,14 +213,55 @@ class EmbeddingMap:
|
|||
obj_typ, _ = self.type_map[type(obj_ref)]
|
||||
yield obj_id, obj_ref, obj_typ
|
||||
|
||||
def subkernels(self):
|
||||
subkernels = {}
|
||||
for k, v in self.object_forward_map.items():
|
||||
if hasattr(v, "artiq_embedded"):
|
||||
if v.artiq_embedded.destination is not None:
|
||||
subkernels[k] = v
|
||||
return subkernels
|
||||
|
||||
def store_subkernel_message(self, name, value_type, function_type, function_loc):
|
||||
if name in self.subkernel_message_map:
|
||||
msg_id = self.subkernel_message_map[name]
|
||||
else:
|
||||
msg_id = self.store_object(SubkernelMessageType(name, value_type))
|
||||
self.subkernel_message_map[name] = msg_id
|
||||
subkernel_msg = self.retrieve_object(msg_id)
|
||||
if function_type == "send":
|
||||
subkernel_msg.send_loc = function_loc
|
||||
elif function_type == "recv":
|
||||
subkernel_msg.recv_loc = function_loc
|
||||
else:
|
||||
assert False
|
||||
return msg_id, subkernel_msg
|
||||
|
||||
def subkernel_messages(self):
|
||||
messages = {}
|
||||
for msg_id in self.subkernel_message_map.values():
|
||||
messages[msg_id] = self.retrieve_object(msg_id)
|
||||
return messages
|
||||
|
||||
def subkernel_messages_unpaired(self):
|
||||
unpaired = []
|
||||
for msg_id in self.subkernel_message_map.values():
|
||||
msg_obj = self.retrieve_object(msg_id)
|
||||
if msg_obj.send_loc is None or msg_obj.recv_loc is None:
|
||||
unpaired.append(msg_obj)
|
||||
return unpaired
|
||||
|
||||
def has_rpc(self):
|
||||
return any(filter(lambda x: inspect.isfunction(x) or inspect.ismethod(x),
|
||||
self.object_forward_map.values()))
|
||||
return any(filter(
|
||||
lambda x: (inspect.isfunction(x) or inspect.ismethod(x)) and \
|
||||
(not hasattr(x, "artiq_embedded") or x.artiq_embedded.destination is None),
|
||||
self.object_forward_map.values()
|
||||
))
|
||||
|
||||
|
||||
class ASTSynthesizer:
|
||||
def __init__(self, embedding_map, value_map, quote_function=None, expanded_from=None):
|
||||
self.source = ""
|
||||
self.source_last_new_line = 0
|
||||
self.source_buffer = source.Buffer(self.source, "<synthesized>")
|
||||
self.embedding_map = embedding_map
|
||||
self.value_map = value_map
|
||||
|
@ -187,6 +280,14 @@ class ASTSynthesizer:
|
|||
return source.Range(self.source_buffer, range_from, range_to,
|
||||
expanded_from=self.expanded_from)
|
||||
|
||||
def _add_iterable(self, fragment):
|
||||
# Since DILocation points on the beginning of the piece of source
|
||||
# we don't care if the fragment's end will overflow LLVM's limit.
|
||||
if len(self.source) - self.source_last_new_line >= 2**16:
|
||||
fragment = "\\\n" + fragment
|
||||
self.source_last_new_line = len(self.source) + 2
|
||||
return self._add(fragment)
|
||||
|
||||
def fast_quote_list(self, value):
|
||||
elts = [None] * len(value)
|
||||
is_T = False
|
||||
|
@ -245,7 +346,7 @@ class ASTSynthesizer:
|
|||
for index, elt in enumerate(value):
|
||||
elts[index] = self.quote(elt)
|
||||
if index < len(value) - 1:
|
||||
self._add(", ")
|
||||
self._add_iterable(", ")
|
||||
return elts
|
||||
|
||||
def quote(self, value):
|
||||
|
@ -296,28 +397,28 @@ class ASTSynthesizer:
|
|||
loc=self._add(repr(value)))
|
||||
elif isinstance(value, str):
|
||||
return asttyped.StrT(s=value, ctx=None, type=builtins.TStr(),
|
||||
loc=self._add(repr(value)))
|
||||
loc=self._add_iterable(repr(value)))
|
||||
elif isinstance(value, bytes):
|
||||
return asttyped.StrT(s=value, ctx=None, type=builtins.TBytes(),
|
||||
loc=self._add(repr(value)))
|
||||
loc=self._add_iterable(repr(value)))
|
||||
elif isinstance(value, bytearray):
|
||||
quote_loc = self._add('`')
|
||||
repr_loc = self._add(repr(value))
|
||||
unquote_loc = self._add('`')
|
||||
quote_loc = self._add_iterable('`')
|
||||
repr_loc = self._add_iterable(repr(value))
|
||||
unquote_loc = self._add_iterable('`')
|
||||
loc = quote_loc.join(unquote_loc)
|
||||
|
||||
return asttyped.QuoteT(value=value, type=builtins.TByteArray(), loc=loc)
|
||||
elif isinstance(value, list):
|
||||
begin_loc = self._add("[")
|
||||
begin_loc = self._add_iterable("[")
|
||||
elts = self.fast_quote_list(value)
|
||||
end_loc = self._add("]")
|
||||
end_loc = self._add_iterable("]")
|
||||
return asttyped.ListT(elts=elts, ctx=None, type=builtins.TList(),
|
||||
begin_loc=begin_loc, end_loc=end_loc,
|
||||
loc=begin_loc.join(end_loc))
|
||||
elif isinstance(value, tuple):
|
||||
begin_loc = self._add("(")
|
||||
begin_loc = self._add_iterable("(")
|
||||
elts = self.fast_quote_list(value)
|
||||
end_loc = self._add(")")
|
||||
end_loc = self._add_iterable(")")
|
||||
return asttyped.TupleT(elts=elts, ctx=None,
|
||||
type=types.TTuple([e.type for e in elts]),
|
||||
begin_loc=begin_loc, end_loc=end_loc,
|
||||
|
@ -327,7 +428,9 @@ class ASTSynthesizer:
|
|||
elif inspect.isfunction(value) or inspect.ismethod(value) or \
|
||||
isinstance(value, pytypes.BuiltinFunctionType) or \
|
||||
isinstance(value, SpecializedFunction) or \
|
||||
isinstance(value, numpy.ufunc):
|
||||
isinstance(value, numpy.ufunc) or \
|
||||
(isinstance(value, _ArrayFunctionDispatcher) if
|
||||
_ArrayFunctionDispatcher is not None else False):
|
||||
if inspect.ismethod(value):
|
||||
quoted_self = self.quote(value.__self__)
|
||||
function_type = self.quote_function(value.__func__, self.expanded_from)
|
||||
|
@ -436,7 +539,7 @@ class ASTSynthesizer:
|
|||
return asttyped.QuoteT(value=value, type=instance_type,
|
||||
loc=loc)
|
||||
|
||||
def call(self, callee, args, kwargs, callback=None):
|
||||
def call(self, callee, args, kwargs, callback=None, remote_fn=False):
|
||||
"""
|
||||
Construct an AST fragment calling a function specified by
|
||||
an AST node `function_node`, with given arguments.
|
||||
|
@ -480,7 +583,7 @@ class ASTSynthesizer:
|
|||
starargs=None, kwargs=None,
|
||||
type=types.TVar(), iodelay=None, arg_exprs={},
|
||||
begin_loc=begin_loc, end_loc=end_loc, star_loc=None, dstar_loc=None,
|
||||
loc=callee_node.loc.join(end_loc))
|
||||
loc=callee_node.loc.join(end_loc), remote_fn=remote_fn)
|
||||
|
||||
if callback is not None:
|
||||
node = asttyped.CallT(
|
||||
|
@ -515,7 +618,7 @@ class StitchingASTTypedRewriter(ASTTypedRewriter):
|
|||
arg=node.arg, annotation=None,
|
||||
arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc)
|
||||
|
||||
def visit_quoted_function(self, node, function):
|
||||
def visit_quoted_function(self, node, function, remote_fn):
|
||||
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
|
||||
extractor.visit(node)
|
||||
|
||||
|
@ -532,11 +635,11 @@ class StitchingASTTypedRewriter(ASTTypedRewriter):
|
|||
node = asttyped.QuotedFunctionDefT(
|
||||
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
|
||||
signature_type=types.TVar(), return_type=types.TVar(),
|
||||
name=node.name, args=node.args, returns=node.returns,
|
||||
name=node.name, args=node.args, returns=None,
|
||||
body=node.body, decorator_list=node.decorator_list,
|
||||
keyword_loc=node.keyword_loc, name_loc=node.name_loc,
|
||||
arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs,
|
||||
loc=node.loc)
|
||||
loc=node.loc, remote_fn=remote_fn)
|
||||
|
||||
try:
|
||||
self.env_stack.append(node.typing_env)
|
||||
|
@ -744,7 +847,7 @@ class TypedtreeHasher(algorithm.Visitor):
|
|||
return hash(tuple(freeze(getattr(node, field_name)) for field_name in fields))
|
||||
|
||||
class Stitcher:
|
||||
def __init__(self, core, dmgr, engine=None, print_as_rpc=True):
|
||||
def __init__(self, core, dmgr, engine=None, print_as_rpc=True, destination=0, subkernel_arg_types=[], old_embedding_map=None):
|
||||
self.core = core
|
||||
self.dmgr = dmgr
|
||||
if engine is None:
|
||||
|
@ -766,15 +869,23 @@ class Stitcher:
|
|||
|
||||
self.functions = {}
|
||||
|
||||
self.embedding_map = EmbeddingMap()
|
||||
self.embedding_map = EmbeddingMap(old_embedding_map)
|
||||
self.value_map = defaultdict(lambda: [])
|
||||
self.definitely_changed = False
|
||||
|
||||
self.destination = destination
|
||||
self.first_call = True
|
||||
# for non-annotated subkernels:
|
||||
# main kernel inferencer output with types of arguments
|
||||
self.subkernel_arg_types = subkernel_arg_types
|
||||
|
||||
def stitch_call(self, function, args, kwargs, callback=None):
|
||||
# We synthesize source code for the initial call so that
|
||||
# diagnostics would have something meaningful to display to the user.
|
||||
synthesizer = self._synthesizer(self._function_loc(function.artiq_embedded.function))
|
||||
call_node = synthesizer.call(function, args, kwargs, callback)
|
||||
# first call of a subkernel will get its arguments from remote (DRTIO)
|
||||
remote_fn = self.destination != 0
|
||||
call_node = synthesizer.call(function, args, kwargs, callback, remote_fn=remote_fn)
|
||||
synthesizer.finalize()
|
||||
self.typedtree.append(call_node)
|
||||
|
||||
|
@ -886,6 +997,10 @@ class Stitcher:
|
|||
return [diagnostic.Diagnostic("note",
|
||||
"in kernel function here", {},
|
||||
call_loc)]
|
||||
elif fn_kind == 'subkernel':
|
||||
return [diagnostic.Diagnostic("note",
|
||||
"in subkernel call here", {},
|
||||
call_loc)]
|
||||
else:
|
||||
assert False
|
||||
else:
|
||||
|
@ -905,7 +1020,7 @@ class Stitcher:
|
|||
self._function_loc(function),
|
||||
notes=self._call_site_note(loc, fn_kind))
|
||||
self.engine.process(diag)
|
||||
elif fn_kind == 'rpc' and param.default is not inspect.Parameter.empty:
|
||||
elif fn_kind == 'rpc' or fn_kind == 'subkernel' and param.default is not inspect.Parameter.empty:
|
||||
notes = []
|
||||
notes.append(diagnostic.Diagnostic("note",
|
||||
"expanded from here while trying to infer a type for an"
|
||||
|
@ -924,11 +1039,18 @@ class Stitcher:
|
|||
Inferencer(engine=self.engine).visit(ast)
|
||||
IntMonomorphizer(engine=self.engine).visit(ast)
|
||||
return ast.type
|
||||
else:
|
||||
# Let the rest of the program decide.
|
||||
return types.TVar()
|
||||
elif fn_kind == 'kernel' and self.first_call and self.destination != 0:
|
||||
# subkernels do not have access to the main kernel code to infer
|
||||
# arg types - so these are cached and passed onto subkernel
|
||||
# compilation, to avoid having to annotate them fully
|
||||
for name, typ in self.subkernel_arg_types:
|
||||
if param.name == name:
|
||||
return typ
|
||||
|
||||
def _quote_embedded_function(self, function, flags):
|
||||
# Let the rest of the program decide.
|
||||
return types.TVar()
|
||||
|
||||
def _quote_embedded_function(self, function, flags, remote_fn=False):
|
||||
# we are now parsing new functions... definitely changed the type
|
||||
self.definitely_changed = True
|
||||
|
||||
|
@ -1027,7 +1149,7 @@ class Stitcher:
|
|||
engine=self.engine, prelude=self.prelude,
|
||||
globals=self.globals, host_environment=host_environment,
|
||||
quote=self._quote)
|
||||
function_node = asttyped_rewriter.visit_quoted_function(function_node, embedded_function)
|
||||
function_node = asttyped_rewriter.visit_quoted_function(function_node, embedded_function, remote_fn)
|
||||
function_node.flags = flags
|
||||
|
||||
# Add it into our typedtree so that it gets inferenced and codegen'd.
|
||||
|
@ -1039,9 +1161,6 @@ class Stitcher:
|
|||
return function_node
|
||||
|
||||
def _extract_annot(self, function, annot, kind, call_loc, fn_kind):
|
||||
if annot is None:
|
||||
annot = builtins.TNone()
|
||||
|
||||
if isinstance(function, SpecializedFunction):
|
||||
host_function = function.host_function
|
||||
else:
|
||||
|
@ -1055,9 +1174,20 @@ class Stitcher:
|
|||
if isinstance(embedded_function, str):
|
||||
embedded_function = host_function
|
||||
|
||||
return self._to_artiq_type(
|
||||
annot,
|
||||
function=function,
|
||||
kind=kind,
|
||||
eval_in_scope=lambda x: eval(x, embedded_function.__globals__),
|
||||
call_loc=call_loc,
|
||||
fn_kind=fn_kind)
|
||||
|
||||
def _to_artiq_type(
|
||||
self, annot, *, function, kind: str, eval_in_scope, call_loc: str, fn_kind: str
|
||||
) -> types.Type:
|
||||
if isinstance(annot, str):
|
||||
try:
|
||||
annot = eval(annot, embedded_function.__globals__)
|
||||
annot = eval_in_scope(annot)
|
||||
except Exception:
|
||||
diag = diagnostic.Diagnostic(
|
||||
"error",
|
||||
|
@ -1067,23 +1197,72 @@ class Stitcher:
|
|||
notes=self._call_site_note(call_loc, fn_kind))
|
||||
self.engine.process(diag)
|
||||
|
||||
if not isinstance(annot, types.Type):
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"type annotation for {kind}, '{annot}', is not an ARTIQ type",
|
||||
{"kind": kind, "annot": repr(annot)},
|
||||
self._function_loc(function),
|
||||
notes=self._call_site_note(call_loc, fn_kind))
|
||||
self.engine.process(diag)
|
||||
|
||||
return types.TVar()
|
||||
else:
|
||||
if isinstance(annot, types.Type):
|
||||
return annot
|
||||
|
||||
# Convert built-in Python types to ARTIQ ones.
|
||||
if annot is None:
|
||||
return builtins.TNone()
|
||||
elif annot is numpy.int64:
|
||||
return builtins.TInt64()
|
||||
elif annot is numpy.int32:
|
||||
return builtins.TInt32()
|
||||
elif annot is float:
|
||||
return builtins.TFloat()
|
||||
elif annot is bool:
|
||||
return builtins.TBool()
|
||||
elif annot is str:
|
||||
return builtins.TStr()
|
||||
elif annot is bytes:
|
||||
return builtins.TBytes()
|
||||
elif annot is bytearray:
|
||||
return builtins.TByteArray()
|
||||
|
||||
# Convert generic Python types to ARTIQ ones.
|
||||
generic_ty = typing.get_origin(annot)
|
||||
if generic_ty is not None:
|
||||
type_args = typing.get_args(annot)
|
||||
artiq_args = [
|
||||
self._to_artiq_type(
|
||||
x,
|
||||
function=function,
|
||||
kind=kind,
|
||||
eval_in_scope=eval_in_scope,
|
||||
call_loc=call_loc,
|
||||
fn_kind=fn_kind)
|
||||
for x in type_args
|
||||
]
|
||||
|
||||
if generic_ty is list and len(artiq_args) == 1:
|
||||
return builtins.TList(artiq_args[0])
|
||||
elif generic_ty is tuple:
|
||||
return types.TTuple(artiq_args)
|
||||
|
||||
# Otherwise report an unknown type and just use a fresh tyvar.
|
||||
|
||||
if annot is int:
|
||||
message = (
|
||||
"type annotation for {kind}, 'int' cannot be used as an ARTIQ type. "
|
||||
"Use numpy's int32 or int64 instead."
|
||||
)
|
||||
ty = builtins.TInt()
|
||||
else:
|
||||
message = "type annotation for {kind}, '{annot}', is not an ARTIQ type"
|
||||
ty = types.TVar()
|
||||
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
message,
|
||||
{"kind": kind, "annot": repr(annot)},
|
||||
self._function_loc(function),
|
||||
notes=self._call_site_note(call_loc, fn_kind))
|
||||
self.engine.process(diag)
|
||||
|
||||
return ty
|
||||
|
||||
def _quote_syscall(self, function, loc):
|
||||
signature = inspect.signature(function)
|
||||
|
||||
arg_types = OrderedDict()
|
||||
optarg_types = OrderedDict()
|
||||
for param in signature.parameters.values():
|
||||
if param.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
|
@ -1121,6 +1300,40 @@ class Stitcher:
|
|||
self.functions[function] = function_type
|
||||
return function_type
|
||||
|
||||
def _quote_subkernel(self, function, loc):
|
||||
if isinstance(function, SpecializedFunction):
|
||||
host_function = function.host_function
|
||||
else:
|
||||
host_function = function
|
||||
ret_type = builtins.TNone()
|
||||
signature = inspect.signature(host_function)
|
||||
|
||||
if signature.return_annotation is not inspect.Signature.empty:
|
||||
ret_type = self._extract_annot(host_function, signature.return_annotation,
|
||||
"return type", loc, fn_kind='subkernel')
|
||||
arg_types = OrderedDict()
|
||||
optarg_types = OrderedDict()
|
||||
for param in signature.parameters.values():
|
||||
if param.kind != inspect.Parameter.POSITIONAL_OR_KEYWORD:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"subkernels must only use positional arguments; '{argument}' isn't",
|
||||
{"argument": param.name},
|
||||
self._function_loc(function),
|
||||
notes=self._call_site_note(loc, fn_kind='subkernel'))
|
||||
self.engine.process(diag)
|
||||
|
||||
arg_type = self._type_of_param(function, loc, param, fn_kind='subkernel')
|
||||
if param.default is inspect.Parameter.empty:
|
||||
arg_types[param.name] = arg_type
|
||||
else:
|
||||
optarg_types[param.name] = arg_type
|
||||
|
||||
function_type = types.TSubkernel(arg_types, optarg_types, ret_type,
|
||||
sid=self.embedding_map.store_object(host_function),
|
||||
destination=host_function.artiq_embedded.destination)
|
||||
self.functions[function] = function_type
|
||||
return function_type
|
||||
|
||||
def _quote_rpc(self, function, loc):
|
||||
if isinstance(function, SpecializedFunction):
|
||||
host_function = function.host_function
|
||||
|
@ -1180,8 +1393,18 @@ class Stitcher:
|
|||
(host_function.artiq_embedded.core_name is None and
|
||||
host_function.artiq_embedded.portable is False and
|
||||
host_function.artiq_embedded.syscall is None and
|
||||
host_function.artiq_embedded.destination is None and
|
||||
host_function.artiq_embedded.forbidden is False):
|
||||
self._quote_rpc(function, loc)
|
||||
elif host_function.artiq_embedded.destination is not None and \
|
||||
host_function.artiq_embedded.destination != self.destination:
|
||||
# treat subkernels as kernels if running on the same device
|
||||
if not 0 < host_function.artiq_embedded.destination <= 255:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"subkernel destination must be between 1 and 255 (inclusive)", {},
|
||||
self._function_loc(host_function))
|
||||
self.engine.process(diag)
|
||||
self._quote_subkernel(function, loc)
|
||||
elif host_function.artiq_embedded.function is not None:
|
||||
if host_function.__name__ == "<lambda>":
|
||||
note = diagnostic.Diagnostic("note",
|
||||
|
@ -1205,8 +1428,13 @@ class Stitcher:
|
|||
notes=[note])
|
||||
self.engine.process(diag)
|
||||
|
||||
destination = host_function.artiq_embedded.destination
|
||||
# remote_fn only for first call in subkernels
|
||||
remote_fn = destination is not None and self.first_call
|
||||
self._quote_embedded_function(function,
|
||||
flags=host_function.artiq_embedded.flags)
|
||||
flags=host_function.artiq_embedded.flags,
|
||||
remote_fn=remote_fn)
|
||||
self.first_call = False
|
||||
elif host_function.artiq_embedded.syscall is not None:
|
||||
# Insert a storage-less global whose type instructs the compiler
|
||||
# to perform a system call instead of a regular call.
|
||||
|
|
|
@ -706,6 +706,81 @@ class SetLocal(Instruction):
|
|||
def value(self):
|
||||
return self.operands[1]
|
||||
|
||||
class GetArgFromRemote(Instruction):
|
||||
"""
|
||||
An instruction that receives function arguments from remote
|
||||
(ie. subkernel in DRTIO context)
|
||||
|
||||
:ivar arg_name: (string) argument name
|
||||
:ivar arg_type: argument type
|
||||
"""
|
||||
|
||||
"""
|
||||
:param arg_name: (string) argument name
|
||||
:param arg_type: argument type
|
||||
"""
|
||||
def __init__(self, arg_name, arg_type, name=""):
|
||||
assert isinstance(arg_name, str)
|
||||
super().__init__([], arg_type, name)
|
||||
self.arg_name = arg_name
|
||||
self.arg_type = arg_type
|
||||
|
||||
def copy(self, mapper):
|
||||
self_copy = super().copy(mapper)
|
||||
self_copy.arg_name = self.arg_name
|
||||
self_copy.arg_type = self.arg_type
|
||||
return self_copy
|
||||
|
||||
def opcode(self):
|
||||
return "getargfromremote({})".format(repr(self.arg_name))
|
||||
|
||||
class GetOptArgFromRemote(GetArgFromRemote):
|
||||
"""
|
||||
An instruction that may or may not retrieve an optional function argument
|
||||
from remote, depending on number of values received by firmware.
|
||||
|
||||
:ivar rcv_count: number of received values,
|
||||
determined by firmware
|
||||
:ivar index: (integer) index of the current argument,
|
||||
in reference to remote arguments
|
||||
"""
|
||||
|
||||
"""
|
||||
:param rcv_count: number of received valuese
|
||||
:param index: (integer) index of the current argument,
|
||||
in reference to remote arguments
|
||||
"""
|
||||
def __init__(self, arg_name, arg_type, rcv_count, index, name=""):
|
||||
super().__init__(arg_name, arg_type, name)
|
||||
self.rcv_count = rcv_count
|
||||
self.index = index
|
||||
|
||||
def copy(self, mapper):
|
||||
self_copy = super().copy(mapper)
|
||||
self_copy.rcv_count = self.rcv_count
|
||||
self_copy.index = self.index
|
||||
return self_copy
|
||||
|
||||
def opcode(self):
|
||||
return "getoptargfromremote({})".format(repr(self.arg_name))
|
||||
|
||||
class SubkernelAwaitArgs(Instruction):
|
||||
"""
|
||||
A builtin instruction that takes min and max received messages as operands,
|
||||
and a list of received types.
|
||||
|
||||
:ivar arg_types: (list of types) types of passed arguments (including optional)
|
||||
"""
|
||||
|
||||
"""
|
||||
:param arg_types: (list of types) types of passed arguments (including optional)
|
||||
"""
|
||||
|
||||
def __init__(self, operands, arg_types, name=None):
|
||||
assert isinstance(arg_types, list)
|
||||
self.arg_types = arg_types
|
||||
super().__init__(operands, builtins.TNone(), name)
|
||||
|
||||
class GetAttr(Instruction):
|
||||
"""
|
||||
An intruction that loads an attribute from an object,
|
||||
|
@ -728,7 +803,7 @@ class GetAttr(Instruction):
|
|||
typ = obj.type.attributes[attr]
|
||||
else:
|
||||
typ = obj.type.constructor.attributes[attr]
|
||||
if types.is_function(typ) or types.is_rpc(typ):
|
||||
if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(typ):
|
||||
typ = types.TMethod(obj.type, typ)
|
||||
super().__init__([obj], typ, name)
|
||||
self.attr = attr
|
||||
|
@ -1190,14 +1265,18 @@ class IndirectBranch(Terminator):
|
|||
class Return(Terminator):
|
||||
"""
|
||||
A return instruction.
|
||||
:param remote_return: (bool)
|
||||
marks a return in subkernel context,
|
||||
where the return value is sent back through DRTIO
|
||||
"""
|
||||
|
||||
"""
|
||||
:param value: (:class:`Value`) return value
|
||||
"""
|
||||
def __init__(self, value, name=""):
|
||||
def __init__(self, value, remote_return=False, name=""):
|
||||
assert isinstance(value, Value)
|
||||
super().__init__([value], builtins.TNone(), name)
|
||||
self.remote_return = remote_return
|
||||
|
||||
def opcode(self):
|
||||
return "return"
|
||||
|
|
|
@ -33,9 +33,19 @@ SECTIONS
|
|||
KEEP(*(.eh_frame_hdr))
|
||||
} : text : eh_frame
|
||||
|
||||
.got :
|
||||
{
|
||||
*(.got)
|
||||
} : text
|
||||
|
||||
.got.plt :
|
||||
{
|
||||
*(.got.plt)
|
||||
} : text
|
||||
|
||||
.data :
|
||||
{
|
||||
*(.data)
|
||||
*(.data .data.*)
|
||||
} : data
|
||||
|
||||
.dynamic :
|
||||
|
@ -51,6 +61,10 @@ SECTIONS
|
|||
_end = .;
|
||||
}
|
||||
|
||||
/* Kernel stack grows downward from end of memory, so put guard page after
|
||||
* all the program contents. Note: This requires all loaded sections (at
|
||||
* least those accessed) to be explicitly listed in the above!
|
||||
*/
|
||||
. = ALIGN(0x1000);
|
||||
_sstack_guard = .;
|
||||
}
|
||||
|
|
|
@ -84,6 +84,8 @@ class Module:
|
|||
constant_hoister.process(self.artiq_ir)
|
||||
if remarks:
|
||||
invariant_detection.process(self.artiq_ir)
|
||||
# for subkernels: main kernel inferencer output, to be passed to further compilations
|
||||
self.subkernel_arg_types = inferencer.subkernel_arg_types
|
||||
|
||||
def build_llvm_ir(self, target):
|
||||
"""Compile the module to LLVM IR for the specified target."""
|
||||
|
|
|
@ -37,6 +37,7 @@ def globals():
|
|||
|
||||
# ARTIQ decorators
|
||||
"kernel": builtins.fn_kernel(),
|
||||
"subkernel": builtins.fn_kernel(),
|
||||
"portable": builtins.fn_kernel(),
|
||||
"rpc": builtins.fn_kernel(),
|
||||
|
||||
|
@ -54,4 +55,10 @@ def globals():
|
|||
# ARTIQ utility functions
|
||||
"rtio_log": builtins.fn_rtio_log(),
|
||||
"core_log": builtins.fn_print(),
|
||||
|
||||
# ARTIQ subkernel utility functions
|
||||
"subkernel_await": builtins.fn_subkernel_await(),
|
||||
"subkernel_preload": builtins.fn_subkernel_preload(),
|
||||
"subkernel_send": builtins.fn_subkernel_send(),
|
||||
"subkernel_recv": builtins.fn_subkernel_recv(),
|
||||
}
|
||||
|
|
|
@ -91,11 +91,12 @@ class Target:
|
|||
|
||||
tool_ld = "ld.lld"
|
||||
tool_strip = "llvm-strip"
|
||||
tool_addr2line = "llvm-addr2line"
|
||||
tool_symbolizer = "llvm-symbolizer"
|
||||
tool_cxxfilt = "llvm-cxxfilt"
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self, subkernel_id=None):
|
||||
self.llcontext = ll.Context()
|
||||
self.subkernel_id = subkernel_id
|
||||
|
||||
def target_machine(self):
|
||||
lltarget = llvm.Target.from_triple(self.triple)
|
||||
|
@ -148,7 +149,8 @@ class Target:
|
|||
ir.BasicBlock._dump_loc = False
|
||||
|
||||
type_printer = types.TypePrinter()
|
||||
_dump(os.getenv("ARTIQ_DUMP_IR"), "ARTIQ IR", ".txt",
|
||||
suffix = "_subkernel_{}".format(self.subkernel_id) if self.subkernel_id is not None else ""
|
||||
_dump(os.getenv("ARTIQ_DUMP_IR"), "ARTIQ IR", suffix + ".txt",
|
||||
lambda: "\n".join(fn.as_entity(type_printer) for fn in module.artiq_ir))
|
||||
|
||||
llmod = module.build_llvm_ir(self)
|
||||
|
@ -160,12 +162,12 @@ class Target:
|
|||
_dump("", "LLVM IR (broken)", ".ll", lambda: str(llmod))
|
||||
raise
|
||||
|
||||
_dump(os.getenv("ARTIQ_DUMP_UNOPT_LLVM"), "LLVM IR (generated)", "_unopt.ll",
|
||||
_dump(os.getenv("ARTIQ_DUMP_UNOPT_LLVM"), "LLVM IR (generated)", suffix + "_unopt.ll",
|
||||
lambda: str(llparsedmod))
|
||||
|
||||
self.optimize(llparsedmod)
|
||||
|
||||
_dump(os.getenv("ARTIQ_DUMP_LLVM"), "LLVM IR (optimized)", ".ll",
|
||||
_dump(os.getenv("ARTIQ_DUMP_LLVM"), "LLVM IR (optimized)", suffix + ".ll",
|
||||
lambda: str(llparsedmod))
|
||||
|
||||
return llparsedmod
|
||||
|
@ -218,8 +220,8 @@ class Target:
|
|||
# the backtrace entry should point at.
|
||||
last_inlined = None
|
||||
offset_addresses = [hex(addr - 1) for addr in addresses]
|
||||
with RunTool([self.tool_addr2line, "--addresses", "--functions", "--inlines",
|
||||
"--demangle", "--exe={library}"] + offset_addresses,
|
||||
with RunTool([self.tool_symbolizer, "--addresses", "--functions", "--inlines",
|
||||
"--demangle", "--output-style=GNU", "--exe={library}"] + offset_addresses,
|
||||
library=library) \
|
||||
as results:
|
||||
lines = iter(results["__stdout__"].read().rstrip().split("\n"))
|
||||
|
@ -256,6 +258,8 @@ class Target:
|
|||
return backtrace
|
||||
|
||||
def demangle(self, names):
|
||||
if not any(names):
|
||||
return names
|
||||
with RunTool([self.tool_cxxfilt] + names) as results:
|
||||
return results["__stdout__"].read().rstrip().split("\n")
|
||||
|
||||
|
@ -263,43 +267,43 @@ class NativeTarget(Target):
|
|||
def __init__(self):
|
||||
super().__init__()
|
||||
self.triple = llvm.get_default_triple()
|
||||
host_data_layout = str(llvm.targets.Target.from_default_triple().create_target_machine().target_data)
|
||||
self.data_layout = str(llvm.targets.Target.from_default_triple().create_target_machine().target_data)
|
||||
|
||||
class RV32IMATarget(Target):
|
||||
triple = "riscv32-unknown-linux"
|
||||
data_layout = "e-m:e-p:32:32-i64:64-n32-S128"
|
||||
features = ["m", "a"]
|
||||
additional_linker_options = []
|
||||
additional_linker_options = ["-m", "elf32lriscv"]
|
||||
print_function = "core_log"
|
||||
now_pinning = True
|
||||
|
||||
tool_ld = "ld.lld"
|
||||
tool_strip = "llvm-strip"
|
||||
tool_addr2line = "llvm-addr2line"
|
||||
tool_symbolizer = "llvm-symbolizer"
|
||||
tool_cxxfilt = "llvm-cxxfilt"
|
||||
|
||||
class RV32GTarget(Target):
|
||||
triple = "riscv32-unknown-linux"
|
||||
data_layout = "e-m:e-p:32:32-i64:64-n32-S128"
|
||||
features = ["m", "a", "f", "d"]
|
||||
additional_linker_options = []
|
||||
additional_linker_options = ["-m", "elf32lriscv"]
|
||||
print_function = "core_log"
|
||||
now_pinning = True
|
||||
|
||||
tool_ld = "ld.lld"
|
||||
tool_strip = "llvm-strip"
|
||||
tool_addr2line = "llvm-addr2line"
|
||||
tool_symbolizer = "llvm-symbolizer"
|
||||
tool_cxxfilt = "llvm-cxxfilt"
|
||||
|
||||
class CortexA9Target(Target):
|
||||
triple = "armv7-unknown-linux-gnueabihf"
|
||||
data_layout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
features = ["dsp", "fp16", "neon", "vfp3"]
|
||||
additional_linker_options = ["--target2=rel"]
|
||||
additional_linker_options = ["-m", "armelf_linux_eabi", "--target2=rel"]
|
||||
print_function = "core_log"
|
||||
now_pinning = False
|
||||
|
||||
tool_ld = "ld.lld"
|
||||
tool_strip = "llvm-strip"
|
||||
tool_addr2line = "llvm-addr2line"
|
||||
tool_symbolizer = "llvm-symbolizer"
|
||||
tool_cxxfilt = "llvm-cxxfilt"
|
||||
|
|
|
@ -30,8 +30,9 @@ def main():
|
|||
device_db_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py")
|
||||
device_mgr = DeviceManager(DeviceDB(device_db_path))
|
||||
|
||||
dataset_db_path = os.path.join(os.path.dirname(sys.argv[1]), "dataset_db.pyon")
|
||||
dataset_mgr = DatasetManager(DatasetDB(dataset_db_path))
|
||||
dataset_db_path = os.path.join(os.path.dirname(sys.argv[1]), "dataset_db.mdb")
|
||||
dataset_db = DatasetDB(dataset_db_path)
|
||||
dataset_mgr = DatasetManager()
|
||||
|
||||
argument_mgr = ProcessArgumentManager({})
|
||||
|
||||
|
@ -68,5 +69,7 @@ def main():
|
|||
benchmark(lambda: target.strip(elf_shlib),
|
||||
"Stripping debug information")
|
||||
|
||||
dataset_db.close_db()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -108,6 +108,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
|||
self.current_args = None
|
||||
self.current_assign = None
|
||||
self.current_exception = None
|
||||
self.current_remote_fn = False
|
||||
self.break_target = None
|
||||
self.continue_target = None
|
||||
self.return_target = None
|
||||
|
@ -211,7 +212,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
|||
old_priv_env, self.current_private_env = self.current_private_env, priv_env
|
||||
|
||||
self.generic_visit(node)
|
||||
self.terminate(ir.Return(ir.Constant(None, builtins.TNone())))
|
||||
self.terminate(ir.Return(ir.Constant(None, builtins.TNone()),
|
||||
remote_return=self.current_remote_fn))
|
||||
|
||||
return self.functions
|
||||
finally:
|
||||
|
@ -294,6 +296,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
|||
old_block, self.current_block = self.current_block, entry
|
||||
|
||||
old_globals, self.current_globals = self.current_globals, node.globals_in_scope
|
||||
old_remote_fn = self.current_remote_fn
|
||||
self.current_remote_fn = getattr(node, "remote_fn", False)
|
||||
|
||||
env_without_globals = \
|
||||
{var: node.typing_env[var]
|
||||
|
@ -326,7 +330,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
|||
self.terminate(ir.Return(result))
|
||||
elif builtins.is_none(typ.ret):
|
||||
if not self.current_block.is_terminated():
|
||||
self.current_block.append(ir.Return(ir.Constant(None, builtins.TNone())))
|
||||
self.current_block.append(ir.Return(ir.Constant(None, builtins.TNone()),
|
||||
remote_return=self.current_remote_fn))
|
||||
else:
|
||||
if not self.current_block.is_terminated():
|
||||
if len(self.current_block.predecessors()) != 0:
|
||||
|
@ -345,6 +350,7 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
|||
self.current_block = old_block
|
||||
self.current_globals = old_globals
|
||||
self.current_env = old_env
|
||||
self.current_remote_fn = old_remote_fn
|
||||
if not is_lambda:
|
||||
self.current_private_env = old_priv_env
|
||||
|
||||
|
@ -367,7 +373,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
|||
return_value = self.visit(node.value)
|
||||
|
||||
if self.return_target is None:
|
||||
self.append(ir.Return(return_value))
|
||||
self.append(ir.Return(return_value,
|
||||
remote_return=self.current_remote_fn))
|
||||
else:
|
||||
self.append(ir.SetLocal(self.current_private_env, "$return", return_value))
|
||||
self.append(ir.Branch(self.return_target))
|
||||
|
@ -1198,7 +1205,27 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
|||
finally:
|
||||
self.current_assign = old_assign
|
||||
|
||||
if isinstance(node.slice, ast.Index):
|
||||
if types.is_tuple(node.value.type):
|
||||
assert isinstance(node.slice, ast.Index), \
|
||||
"Internal compiler error: tuple index should be an Index"
|
||||
assert isinstance(node.slice.value, ast.Num), \
|
||||
"Internal compiler error: tuple index should be a constant"
|
||||
|
||||
if self.current_assign is not None:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"cannot assign to a tuple element",
|
||||
{}, node.loc)
|
||||
self.engine.process(diag)
|
||||
|
||||
index = node.slice.value.n
|
||||
indexed = self.append(
|
||||
ir.GetAttr(value, index, name="{}.e{}".format(value.name, index)),
|
||||
loc=node.loc
|
||||
)
|
||||
|
||||
return indexed
|
||||
|
||||
elif isinstance(node.slice, ast.Index):
|
||||
try:
|
||||
old_assign, self.current_assign = self.current_assign, None
|
||||
index = self.visit(node.slice.value)
|
||||
|
@ -2504,6 +2531,70 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
|||
or types.is_builtin(typ, "at_mu"):
|
||||
return self.append(ir.Builtin(typ.name,
|
||||
[self.visit(arg) for arg in node.args], node.type))
|
||||
elif types.is_builtin(typ, "subkernel_await"):
|
||||
if len(node.args) == 2 and len(node.keywords) == 0:
|
||||
fn = node.args[0].type
|
||||
timeout = self.visit(node.args[1])
|
||||
elif len(node.args) == 1 and len(node.keywords) == 0:
|
||||
fn = node.args[0].type
|
||||
timeout = ir.Constant(-1, builtins.TInt64())
|
||||
else:
|
||||
assert False
|
||||
if types.is_method(fn):
|
||||
fn = types.get_method_function(fn)
|
||||
sid = ir.Constant(fn.sid, builtins.TInt32())
|
||||
if not builtins.is_none(fn.ret):
|
||||
ret = self.append(ir.Builtin("subkernel_retrieve_return", [sid, timeout], fn.ret))
|
||||
else:
|
||||
ret = ir.Constant(None, builtins.TNone())
|
||||
self.append(ir.Builtin("subkernel_await_finish", [sid, timeout], builtins.TNone()))
|
||||
return ret
|
||||
elif types.is_builtin(typ, "subkernel_preload"):
|
||||
if len(node.args) == 1 and len(node.keywords) == 0:
|
||||
fn = node.args[0].type
|
||||
else:
|
||||
assert False
|
||||
if types.is_method(fn):
|
||||
fn = types.get_method_function(fn)
|
||||
sid = ir.Constant(fn.sid, builtins.TInt32())
|
||||
dest = ir.Constant(fn.destination, builtins.TInt32())
|
||||
return self.append(ir.Builtin("subkernel_preload", [sid, dest], builtins.TNone()))
|
||||
elif types.is_builtin(typ, "subkernel_send"):
|
||||
if len(node.args) == 3 and len(node.keywords) == 0:
|
||||
dest = self.visit(node.args[0])
|
||||
name = node.args[1].s
|
||||
value = self.visit(node.args[2])
|
||||
else:
|
||||
assert False
|
||||
msg_id, msg = self.embedding_map.store_subkernel_message(name, value.type, "send", node.loc)
|
||||
msg_id = ir.Constant(msg_id, builtins.TInt32())
|
||||
if value.type != msg.value_type:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"type mismatch for subkernel message '{name}', receiver expects {recv} while sending {send}",
|
||||
{"name": name, "recv": msg.value_type, "send": value.type},
|
||||
node.loc)
|
||||
self.engine.process(diag)
|
||||
return self.append(ir.Builtin("subkernel_send", [msg_id, dest, value], builtins.TNone()))
|
||||
elif types.is_builtin(typ, "subkernel_recv"):
|
||||
if len(node.args) == 2 and len(node.keywords) == 0:
|
||||
name = node.args[0].s
|
||||
vartype = node.args[1].value
|
||||
timeout = ir.Constant(-1, builtins.TInt64())
|
||||
elif len(node.args) == 3 and len(node.keywords) == 0:
|
||||
name = node.args[0].s
|
||||
vartype = node.args[1].value
|
||||
timeout = self.visit(node.args[2])
|
||||
else:
|
||||
assert False
|
||||
msg_id, msg = self.embedding_map.store_subkernel_message(name, vartype, "recv", node.loc)
|
||||
msg_id = ir.Constant(msg_id, builtins.TInt32())
|
||||
if vartype != msg.value_type:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"type mismatch for subkernel message '{name}', receiver expects {recv} while sending {send}",
|
||||
{"name": name, "recv": vartype, "send": msg.value_type},
|
||||
node.loc)
|
||||
self.engine.process(diag)
|
||||
return self.append(ir.Builtin("subkernel_recv", [msg_id, timeout], vartype))
|
||||
elif types.is_exn_constructor(typ):
|
||||
return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args])
|
||||
elif types.is_constructor(typ):
|
||||
|
@ -2515,8 +2606,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
|||
node.loc)
|
||||
self.engine.process(diag)
|
||||
|
||||
def _user_call(self, callee, positional, keywords, arg_exprs={}):
|
||||
if types.is_function(callee.type) or types.is_rpc(callee.type):
|
||||
def _user_call(self, callee, positional, keywords, arg_exprs={}, remote_fn=False):
|
||||
if types.is_function(callee.type) or types.is_rpc(callee.type) or types.is_subkernel(callee.type):
|
||||
func = callee
|
||||
self_arg = None
|
||||
fn_typ = callee.type
|
||||
|
@ -2531,16 +2622,51 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
|||
else:
|
||||
assert False
|
||||
|
||||
if types.is_rpc(fn_typ):
|
||||
if self_arg is None:
|
||||
if types.is_rpc(fn_typ) or types.is_subkernel(fn_typ):
|
||||
if self_arg is None or types.is_subkernel(fn_typ):
|
||||
# self is not passed to subkernels by remote
|
||||
args = positional
|
||||
else:
|
||||
elif self_arg is not None:
|
||||
args = [self_arg] + positional
|
||||
|
||||
for keyword in keywords:
|
||||
arg = keywords[keyword]
|
||||
args.append(self.append(ir.Alloc([ir.Constant(keyword, builtins.TStr()), arg],
|
||||
ir.TKeyword(arg.type))))
|
||||
elif remote_fn:
|
||||
assert self_arg is None
|
||||
assert len(fn_typ.args) >= len(positional)
|
||||
assert len(keywords) == 0 # no keyword support
|
||||
args = [None] * fn_typ.arity()
|
||||
index = 0
|
||||
# fill in first available args
|
||||
for arg in positional:
|
||||
args[index] = arg
|
||||
index += 1
|
||||
|
||||
# remaining args are received through DRTIO
|
||||
if index < len(args):
|
||||
# min/max args received remotely (minus already filled)
|
||||
offset = index
|
||||
min_args = ir.Constant(len(fn_typ.args)-offset, builtins.TInt8())
|
||||
max_args = ir.Constant(fn_typ.arity()-offset, builtins.TInt8())
|
||||
|
||||
arg_types = list(fn_typ.args.items())[offset:]
|
||||
arg_type_list = [a[1] for a in arg_types] + [a[1] for a in fn_typ.optargs.items()]
|
||||
rcvd_count = self.append(ir.SubkernelAwaitArgs([min_args, max_args], arg_type_list))
|
||||
# obligatory arguments
|
||||
for arg_name, arg_type in arg_types:
|
||||
args[index] = self.append(ir.GetArgFromRemote(arg_name, arg_type,
|
||||
name="ARG.{}".format(arg_name)))
|
||||
index += 1
|
||||
|
||||
# optional arguments
|
||||
for optarg_name, optarg_type in fn_typ.optargs.items():
|
||||
idx = ir.Constant(index-offset, builtins.TInt8())
|
||||
args[index] = \
|
||||
self.append(ir.GetOptArgFromRemote(optarg_name, optarg_type, rcvd_count, idx))
|
||||
index += 1
|
||||
|
||||
else:
|
||||
args = [None] * (len(fn_typ.args) + len(fn_typ.optargs))
|
||||
|
||||
|
@ -2626,7 +2752,8 @@ class ARTIQIRGenerator(algorithm.Visitor):
|
|||
else:
|
||||
assert False, "Broadcasting for {} arguments not implemented".format(len)
|
||||
else:
|
||||
insn = self._user_call(callee, args, keywords, node.arg_exprs)
|
||||
remote_fn = getattr(node, "remote_fn", False)
|
||||
insn = self._user_call(callee, args, keywords, node.arg_exprs, remote_fn)
|
||||
if isinstance(node.func, asttyped.AttributeT):
|
||||
attr_node = node.func
|
||||
self.method_map[(attr_node.value.type.find(),
|
||||
|
|
|
@ -238,7 +238,7 @@ class ASTTypedRewriter(algorithm.Transformer):
|
|||
body=node.body, decorator_list=node.decorator_list,
|
||||
keyword_loc=node.keyword_loc, name_loc=node.name_loc,
|
||||
arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs,
|
||||
loc=node.loc)
|
||||
loc=node.loc, remote_fn=False)
|
||||
|
||||
try:
|
||||
self.env_stack.append(node.typing_env)
|
||||
|
@ -439,8 +439,9 @@ class ASTTypedRewriter(algorithm.Transformer):
|
|||
|
||||
def visit_Call(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.CallT(type=types.TVar(), iodelay=None, arg_exprs={},
|
||||
func=node.func, args=node.args, keywords=node.keywords,
|
||||
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)
|
||||
|
|
|
@ -46,6 +46,7 @@ class Inferencer(algorithm.Visitor):
|
|||
self.function = None # currently visited function, for Return inference
|
||||
self.in_loop = False
|
||||
self.has_return = False
|
||||
self.subkernel_arg_types = dict()
|
||||
|
||||
def _unify(self, typea, typeb, loca, locb, makenotes=None, when=""):
|
||||
try:
|
||||
|
@ -178,7 +179,7 @@ class Inferencer(algorithm.Visitor):
|
|||
# Convert to a method.
|
||||
attr_type = types.TMethod(object_type, attr_type)
|
||||
self._unify_method_self(attr_type, attr_name, attr_loc, loc, value_node.loc)
|
||||
elif types.is_rpc(attr_type):
|
||||
elif types.is_rpc(attr_type) or types.is_subkernel(attr_type):
|
||||
# Convert to a method. We don't have to bother typechecking
|
||||
# the self argument, since for RPCs anything goes.
|
||||
attr_type = types.TMethod(object_type, attr_type)
|
||||
|
@ -259,7 +260,31 @@ class Inferencer(algorithm.Visitor):
|
|||
|
||||
def visit_SubscriptT(self, node):
|
||||
self.generic_visit(node)
|
||||
if isinstance(node.slice, ast.Index):
|
||||
|
||||
if types.is_tuple(node.value.type):
|
||||
if (not isinstance(node.slice, ast.Index) or
|
||||
not isinstance(node.slice.value, ast.Num)):
|
||||
diag = diagnostic.Diagnostic(
|
||||
"error", "tuples can only be indexed by a constant", {},
|
||||
node.slice.loc, []
|
||||
)
|
||||
self.engine.process(diag)
|
||||
return
|
||||
|
||||
tuple_type = node.value.type.find()
|
||||
index = node.slice.value.n
|
||||
if index < 0 or index >= len(tuple_type.elts):
|
||||
diag = diagnostic.Diagnostic(
|
||||
"error",
|
||||
"index {index} is out of range for tuple of size {size}",
|
||||
{"index": index, "size": len(tuple_type.elts)},
|
||||
node.slice.loc, []
|
||||
)
|
||||
self.engine.process(diag)
|
||||
return
|
||||
|
||||
self._unify(node.type, tuple_type.elts[index], node.loc, node.value.loc)
|
||||
elif isinstance(node.slice, ast.Index):
|
||||
if types.is_tuple(node.slice.value.type):
|
||||
if types.is_var(node.value.type):
|
||||
return
|
||||
|
@ -1269,6 +1294,106 @@ class Inferencer(algorithm.Visitor):
|
|||
# Ignored.
|
||||
self._unify(node.type, builtins.TNone(),
|
||||
node.loc, None)
|
||||
elif types.is_builtin(typ, "subkernel_await"):
|
||||
valid_forms = lambda: [
|
||||
valid_form("subkernel_await(f: subkernel) -> f return type"),
|
||||
valid_form("subkernel_await(f: subkernel, timeout: numpy.int64) -> f return type")
|
||||
]
|
||||
if 1 <= len(node.args) <= 2:
|
||||
arg0 = node.args[0].type
|
||||
if types.is_var(arg0):
|
||||
pass # undetermined yet
|
||||
else:
|
||||
if types.is_method(arg0):
|
||||
fn = types.get_method_function(arg0)
|
||||
elif types.is_function(arg0) or types.is_subkernel(arg0):
|
||||
fn = arg0
|
||||
else:
|
||||
diagnose(valid_forms())
|
||||
self._unify(node.type, fn.ret,
|
||||
node.loc, None)
|
||||
if len(node.args) == 2:
|
||||
arg1 = node.args[1]
|
||||
if types.is_var(arg1.type):
|
||||
pass
|
||||
elif builtins.is_int(arg1.type):
|
||||
# promote to TInt64
|
||||
self._unify(arg1.type, builtins.TInt64(),
|
||||
arg1.loc, None)
|
||||
else:
|
||||
diagnose(valid_forms())
|
||||
else:
|
||||
diagnose(valid_forms())
|
||||
elif types.is_builtin(typ, "subkernel_preload"):
|
||||
valid_forms = lambda: [
|
||||
valid_form("subkernel_preload(f: subkernel) -> None")
|
||||
]
|
||||
if len(node.args) == 1:
|
||||
arg0 = node.args[0].type
|
||||
if types.is_var(arg0):
|
||||
pass # undetermined yet
|
||||
else:
|
||||
if types.is_method(arg0):
|
||||
fn = types.get_method_function(arg0)
|
||||
elif types.is_function(arg0) or types.is_subkernel(arg0):
|
||||
fn = arg0
|
||||
else:
|
||||
diagnose(valid_forms())
|
||||
self._unify(node.type, fn.ret,
|
||||
node.loc, None)
|
||||
else:
|
||||
diagnose(valid_forms())
|
||||
elif types.is_builtin(typ, "subkernel_send"):
|
||||
valid_forms = lambda: [
|
||||
valid_form("subkernel_send(dest: numpy.int?, name: str, value: V) -> None"),
|
||||
]
|
||||
self._unify(node.type, builtins.TNone(),
|
||||
node.loc, None)
|
||||
if len(node.args) == 3:
|
||||
arg0 = node.args[0]
|
||||
if types.is_var(arg0.type):
|
||||
pass # undetermined yet
|
||||
else:
|
||||
if builtins.is_int(arg0.type):
|
||||
self._unify(arg0.type, builtins.TInt8(),
|
||||
arg0.loc, None)
|
||||
else:
|
||||
diagnose(valid_forms())
|
||||
arg1 = node.args[1]
|
||||
self._unify(arg1.type, builtins.TStr(),
|
||||
arg1.loc, None)
|
||||
else:
|
||||
diagnose(valid_forms())
|
||||
elif types.is_builtin(typ, "subkernel_recv"):
|
||||
valid_forms = lambda: [
|
||||
valid_form("subkernel_recv(name: str, value_type: type) -> value_type"),
|
||||
valid_form("subkernel_recv(name: str, value_type: type, timeout: numpy.int64) -> value_type"),
|
||||
]
|
||||
if 2 <= len(node.args) <= 3:
|
||||
arg0 = node.args[0]
|
||||
if types.is_var(arg0.type):
|
||||
pass
|
||||
else:
|
||||
self._unify(arg0.type, builtins.TStr(),
|
||||
arg0.loc, None)
|
||||
arg1 = node.args[1]
|
||||
if types.is_var(arg1.type):
|
||||
pass
|
||||
else:
|
||||
self._unify(node.type, arg1.value,
|
||||
node.loc, None)
|
||||
if len(node.args) == 3:
|
||||
arg2 = node.args[2]
|
||||
if types.is_var(arg2.type):
|
||||
pass
|
||||
elif builtins.is_int(arg2.type):
|
||||
# promote to TInt64
|
||||
self._unify(arg2.type, builtins.TInt64(),
|
||||
arg2.loc, None)
|
||||
else:
|
||||
diagnose(valid_forms())
|
||||
else:
|
||||
diagnose(valid_forms())
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
@ -1307,6 +1432,7 @@ class Inferencer(algorithm.Visitor):
|
|||
typ_args = typ.args
|
||||
typ_optargs = typ.optargs
|
||||
typ_ret = typ.ret
|
||||
typ_func = typ
|
||||
else:
|
||||
typ_self = types.get_method_self(typ)
|
||||
typ_func = types.get_method_function(typ)
|
||||
|
@ -1364,12 +1490,23 @@ class Inferencer(algorithm.Visitor):
|
|||
other_node=node.args[0])
|
||||
self._unify(node.type, ret, node.loc, None)
|
||||
return
|
||||
if types.is_subkernel(typ_func) and typ_func.sid not in self.subkernel_arg_types:
|
||||
self.subkernel_arg_types[typ_func.sid] = []
|
||||
|
||||
for actualarg, (formalname, formaltyp) in \
|
||||
zip(node.args, list(typ_args.items()) + list(typ_optargs.items())):
|
||||
self._unify(actualarg.type, formaltyp,
|
||||
actualarg.loc, None)
|
||||
passed_args[formalname] = actualarg.loc
|
||||
if types.is_subkernel(typ_func):
|
||||
if types.is_instance(actualarg.type):
|
||||
# objects cannot be passed to subkernels, as rpc code doesn't support them
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"argument '{name}' of type: {typ} is not supported in subkernels",
|
||||
{"name": formalname, "typ": actualarg.type},
|
||||
actualarg.loc, [])
|
||||
self.engine.process(diag)
|
||||
self.subkernel_arg_types[typ_func.sid].append((formalname, formaltyp))
|
||||
|
||||
for keyword in node.keywords:
|
||||
if keyword.arg in passed_args:
|
||||
|
@ -1400,7 +1537,7 @@ class Inferencer(algorithm.Visitor):
|
|||
passed_args[keyword.arg] = keyword.arg_loc
|
||||
|
||||
for formalname in typ_args:
|
||||
if formalname not in passed_args:
|
||||
if formalname not in passed_args and not node.remote_fn:
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"the called function is of type {type}",
|
||||
{"type": types.TypePrinter().name(node.func.type)},
|
||||
|
|
|
@ -280,7 +280,7 @@ class IODelayEstimator(algorithm.Visitor):
|
|||
context="as an argument for delay_mu()")
|
||||
call_delay = value
|
||||
elif not types.is_builtin(typ):
|
||||
if types.is_function(typ) or types.is_rpc(typ):
|
||||
if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(typ):
|
||||
offset = 0
|
||||
elif types.is_method(typ):
|
||||
offset = 1
|
||||
|
@ -288,7 +288,7 @@ class IODelayEstimator(algorithm.Visitor):
|
|||
else:
|
||||
assert False
|
||||
|
||||
if types.is_rpc(typ):
|
||||
if types.is_rpc(typ) or types.is_subkernel(typ):
|
||||
call_delay = iodelay.Const(0)
|
||||
else:
|
||||
delay = typ.find().delay.find()
|
||||
|
@ -311,13 +311,20 @@ class IODelayEstimator(algorithm.Visitor):
|
|||
args[arg_name] = arg_node
|
||||
|
||||
free_vars = delay.duration.free_vars()
|
||||
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)
|
||||
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:
|
||||
|
|
|
@ -177,6 +177,15 @@ class LLVMIRGenerator:
|
|||
self.empty_metadata = self.llmodule.add_metadata([])
|
||||
self.quote_fail_msg = None
|
||||
|
||||
# Maximum alignment required according to the target platform ABI. As this is
|
||||
# not directly exposed by LLVM, just take the maximum across all the "big"
|
||||
# elementary types we use. (Vector types, should we ever support them, are
|
||||
# likely contenders for even larger alignment requirements.)
|
||||
self.max_target_alignment = max(map(
|
||||
lambda t: self.abi_layout_info.get_size_align(t)[1],
|
||||
[lli64, lldouble, llptr]
|
||||
))
|
||||
|
||||
def add_pred(self, pred, block):
|
||||
if block not in self.llpred_map:
|
||||
self.llpred_map[block] = set()
|
||||
|
@ -206,7 +215,7 @@ class LLVMIRGenerator:
|
|||
typ = typ.find()
|
||||
if types.is_tuple(typ):
|
||||
return ll.LiteralStructType([self.llty_of_type(eltty) for eltty in typ.elts])
|
||||
elif types.is_rpc(typ) or types.is_external_function(typ):
|
||||
elif types.is_rpc(typ) or types.is_external_function(typ) or types.is_subkernel(typ):
|
||||
if for_return:
|
||||
return llvoid
|
||||
else:
|
||||
|
@ -335,8 +344,8 @@ class LLVMIRGenerator:
|
|||
else:
|
||||
value = const.value
|
||||
|
||||
llptr = self.llstr_of_str(const.value, linkage="private", unnamed_addr=True)
|
||||
lllen = ll.Constant(lli32, len(const.value))
|
||||
llptr = self.llstr_of_str(value, linkage="private", unnamed_addr=True)
|
||||
lllen = ll.Constant(lli32, len(value))
|
||||
return ll.Constant(llty, (llptr, lllen))
|
||||
else:
|
||||
assert False
|
||||
|
@ -389,6 +398,15 @@ class LLVMIRGenerator:
|
|||
elif name == "rpc_recv":
|
||||
llty = ll.FunctionType(lli32, [llptr])
|
||||
|
||||
elif name == "subkernel_send_message":
|
||||
llty = ll.FunctionType(llvoid, [lli32, lli1, lli8, lli8, llsliceptr, llptrptr])
|
||||
elif name == "subkernel_load_run":
|
||||
llty = ll.FunctionType(llvoid, [lli32, lli8, lli1])
|
||||
elif name == "subkernel_await_finish":
|
||||
llty = ll.FunctionType(llvoid, [lli32, lli64])
|
||||
elif name == "subkernel_await_message":
|
||||
llty = ll.FunctionType(lli8, [lli32, lli64, llsliceptr, lli8, lli8])
|
||||
|
||||
# with now-pinning
|
||||
elif name == "now":
|
||||
llty = lli64
|
||||
|
@ -865,6 +883,53 @@ class LLVMIRGenerator:
|
|||
llvalue = self.llbuilder.bitcast(llvalue, llptr.type.pointee)
|
||||
return self.llbuilder.store(llvalue, llptr)
|
||||
|
||||
def process_GetArgFromRemote(self, insn):
|
||||
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
|
||||
name="subkernel.arg.stack")
|
||||
llval = self._build_rpc_recv(insn.arg_type, llstackptr)
|
||||
return llval
|
||||
|
||||
def process_GetOptArgFromRemote(self, insn):
|
||||
# optarg = index < rcv_count ? Some(rcv_recv()) : None
|
||||
llhead = self.llbuilder.basic_block
|
||||
llrcv = self.llbuilder.append_basic_block(name="optarg.get.{}".format(insn.arg_name))
|
||||
|
||||
# argument received
|
||||
self.llbuilder.position_at_end(llrcv)
|
||||
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
|
||||
name="subkernel.arg.stack")
|
||||
llval = self._build_rpc_recv(insn.arg_type, llstackptr)
|
||||
llrpcretblock = self.llbuilder.basic_block # 'return' from rpc_recv, will be needed later
|
||||
|
||||
# create the tail block, needs to be after the rpc recv tail block
|
||||
lltail = self.llbuilder.append_basic_block(name="optarg.tail.{}".format(insn.arg_name))
|
||||
self.llbuilder.branch(lltail)
|
||||
|
||||
# go back to head to add a branch to the tail
|
||||
self.llbuilder.position_at_end(llhead)
|
||||
llargrcvd = self.llbuilder.icmp_unsigned("<", self.map(insn.index), self.map(insn.rcv_count))
|
||||
self.llbuilder.cbranch(llargrcvd, llrcv, lltail)
|
||||
|
||||
# argument not received/after arg recvd
|
||||
self.llbuilder.position_at_end(lltail)
|
||||
|
||||
llargtype = self.llty_of_type(insn.arg_type)
|
||||
|
||||
llphi_arg_present = self.llbuilder.phi(lli1, name="optarg.phi.present.{}".format(insn.arg_name))
|
||||
llphi_arg = self.llbuilder.phi(llargtype, name="optarg.phi.{}".format(insn.arg_name))
|
||||
|
||||
llphi_arg_present.add_incoming(ll.Constant(lli1, 0), llhead)
|
||||
llphi_arg.add_incoming(ll.Constant(llargtype, ll.Undefined), llhead)
|
||||
|
||||
llphi_arg_present.add_incoming(ll.Constant(lli1, 1), llrpcretblock)
|
||||
llphi_arg.add_incoming(llval, llrpcretblock)
|
||||
|
||||
lloptarg = ll.Constant(ll.LiteralStructType([lli1, llargtype]), ll.Undefined)
|
||||
lloptarg = self.llbuilder.insert_value(lloptarg, llphi_arg_present, 0)
|
||||
lloptarg = self.llbuilder.insert_value(lloptarg, llphi_arg, 1)
|
||||
|
||||
return lloptarg
|
||||
|
||||
def attr_index(self, typ, attr):
|
||||
return list(typ.attributes.keys()).index(attr)
|
||||
|
||||
|
@ -889,8 +954,8 @@ class LLVMIRGenerator:
|
|||
def get_global_closure_ptr(self, typ, attr):
|
||||
closure_type = typ.attributes[attr]
|
||||
assert types.is_constructor(typ)
|
||||
assert types.is_function(closure_type) or types.is_rpc(closure_type)
|
||||
if types.is_external_function(closure_type) or types.is_rpc(closure_type):
|
||||
assert types.is_function(closure_type) or types.is_rpc(closure_type) or types.is_subkernel(closure_type)
|
||||
if types.is_external_function(closure_type) or types.is_rpc(closure_type) or types.is_subkernel(closure_type):
|
||||
return None
|
||||
|
||||
llty = self.llty_of_type(typ.attributes[attr])
|
||||
|
@ -1335,9 +1400,51 @@ class LLVMIRGenerator:
|
|||
return self.llbuilder.call(self.llbuiltin("delay_mu"), [llinterval])
|
||||
elif insn.op == "end_catch":
|
||||
return self.llbuilder.call(self.llbuiltin("__artiq_end_catch"), [])
|
||||
elif insn.op == "subkernel_await_finish":
|
||||
llsid = self.map(insn.operands[0])
|
||||
lltimeout = self.map(insn.operands[1])
|
||||
return self.llbuilder.call(self.llbuiltin("subkernel_await_finish"), [llsid, lltimeout],
|
||||
name="subkernel.await.finish")
|
||||
elif insn.op == "subkernel_retrieve_return":
|
||||
llsid = self.map(insn.operands[0])
|
||||
lltimeout = self.map(insn.operands[1])
|
||||
lltagptr = self._build_subkernel_tags([insn.type])
|
||||
self.llbuilder.call(self.llbuiltin("subkernel_await_message"),
|
||||
[llsid, lltimeout, lltagptr, ll.Constant(lli8, 1), ll.Constant(lli8, 1)],
|
||||
name="subkernel.await.message")
|
||||
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
|
||||
name="subkernel.arg.stack")
|
||||
return self._build_rpc_recv(insn.type, llstackptr)
|
||||
elif insn.op == "subkernel_preload":
|
||||
llsid = self.map(insn.operands[0])
|
||||
lldest = ll.Constant(lli8, insn.operands[1].value)
|
||||
return self.llbuilder.call(self.llbuiltin("subkernel_load_run"), [llsid, lldest, ll.Constant(lli1, 0)],
|
||||
name="subkernel.preload")
|
||||
elif insn.op == "subkernel_send":
|
||||
llmsgid = self.map(insn.operands[0])
|
||||
lldest = self.map(insn.operands[1])
|
||||
return self._build_subkernel_message(llmsgid, lldest, [insn.operands[2]])
|
||||
elif insn.op == "subkernel_recv":
|
||||
llmsgid = self.map(insn.operands[0])
|
||||
lltimeout = self.map(insn.operands[1])
|
||||
lltagptr = self._build_subkernel_tags([insn.type])
|
||||
self.llbuilder.call(self.llbuiltin("subkernel_await_message"),
|
||||
[llmsgid, lltimeout, lltagptr, ll.Constant(lli8, 1), ll.Constant(lli8, 1)],
|
||||
name="subkernel.await.message")
|
||||
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
|
||||
name="subkernel.arg.stack")
|
||||
return self._build_rpc_recv(insn.type, llstackptr)
|
||||
else:
|
||||
assert False
|
||||
|
||||
def process_SubkernelAwaitArgs(self, insn):
|
||||
llmin = self.map(insn.operands[0])
|
||||
llmax = self.map(insn.operands[1])
|
||||
lltagptr = self._build_subkernel_tags(insn.arg_types)
|
||||
return self.llbuilder.call(self.llbuiltin("subkernel_await_message"),
|
||||
[ll.Constant(lli32, -1), ll.Constant(lli64, 10_000), lltagptr, llmin, llmax],
|
||||
name="subkernel.await.args")
|
||||
|
||||
def process_Closure(self, insn):
|
||||
llenv = self.map(insn.environment())
|
||||
llenv = self.llbuilder.bitcast(llenv, llptr)
|
||||
|
@ -1417,11 +1524,78 @@ class LLVMIRGenerator:
|
|||
|
||||
return llfun, list(llargs), llarg_attrs, llcallstackptr
|
||||
|
||||
def _build_rpc(self, fun_loc, fun_type, args, llnormalblock, llunwindblock):
|
||||
llservice = ll.Constant(lli32, fun_type.service)
|
||||
def _build_subkernel_tags(self, tag_list):
|
||||
def ret_error_handler(typ):
|
||||
printer = types.TypePrinter()
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"value of type {type}",
|
||||
{"type": printer.name(typ)},
|
||||
fun_loc)
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"type {type} is not supported in subkernels",
|
||||
{"type": printer.name(fun_type.ret)},
|
||||
fun_loc, notes=[note])
|
||||
self.engine.process(diag)
|
||||
tag = b"".join([ir.rpc_tag(arg_type, ret_error_handler) for arg_type in tag_list])
|
||||
lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr()))
|
||||
lltagptr = self.llbuilder.alloca(lltag.type)
|
||||
self.llbuilder.store(lltag, lltagptr)
|
||||
return lltagptr
|
||||
|
||||
def _build_rpc_recv(self, ret, llstackptr, llnormalblock=None, llunwindblock=None):
|
||||
# T result = {
|
||||
# void *ret_ptr = alloca(sizeof(T));
|
||||
# void *ptr = ret_ptr;
|
||||
# loop: int size = rpc_recv(ptr);
|
||||
# // Non-zero: Provide `size` bytes of extra storage for variable-length data.
|
||||
# if(size) { ptr = alloca(size); goto loop; }
|
||||
# else *(T*)ret_ptr
|
||||
# }
|
||||
llprehead = self.llbuilder.basic_block
|
||||
llhead = self.llbuilder.append_basic_block(name="rpc.head")
|
||||
if llunwindblock:
|
||||
llheadu = self.llbuilder.append_basic_block(name="rpc.head.unwind")
|
||||
llalloc = self.llbuilder.append_basic_block(name="rpc.continue")
|
||||
lltail = self.llbuilder.append_basic_block(name="rpc.tail")
|
||||
|
||||
llretty = self.llty_of_type(ret)
|
||||
llslot = self.llbuilder.alloca(llretty, name="rpc.ret.alloc")
|
||||
llslotgen = self.llbuilder.bitcast(llslot, llptr, name="rpc.ret.ptr")
|
||||
self.llbuilder.branch(llhead)
|
||||
|
||||
self.llbuilder.position_at_end(llhead)
|
||||
llphi = self.llbuilder.phi(llslotgen.type, name="rpc.ptr")
|
||||
llphi.add_incoming(llslotgen, llprehead)
|
||||
if llunwindblock:
|
||||
llsize = self.llbuilder.invoke(self.llbuiltin("rpc_recv"), [llphi],
|
||||
llheadu, llunwindblock,
|
||||
name="rpc.size.next")
|
||||
self.llbuilder.position_at_end(llheadu)
|
||||
else:
|
||||
llsize = self.llbuilder.call(self.llbuiltin("rpc_recv"), [llphi],
|
||||
name="rpc.size.next")
|
||||
lldone = self.llbuilder.icmp_unsigned('==', llsize, ll.Constant(llsize.type, 0),
|
||||
name="rpc.done")
|
||||
self.llbuilder.cbranch(lldone, lltail, llalloc)
|
||||
|
||||
self.llbuilder.position_at_end(llalloc)
|
||||
llalloca = self.llbuilder.alloca(lli8, llsize, name="rpc.alloc")
|
||||
llalloca.align = self.max_target_alignment
|
||||
llphi.add_incoming(llalloca, llalloc)
|
||||
self.llbuilder.branch(llhead)
|
||||
|
||||
self.llbuilder.position_at_end(lltail)
|
||||
llret = self.llbuilder.load(llslot, name="rpc.ret")
|
||||
if not ret.fold(False, lambda r, t: r or builtins.is_allocated(t)):
|
||||
# We didn't allocate anything except the slot for the value itself.
|
||||
# Don't waste stack space.
|
||||
self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr])
|
||||
if llnormalblock:
|
||||
self.llbuilder.branch(llnormalblock)
|
||||
return llret
|
||||
|
||||
def _build_arg_tag(self, args, call_type):
|
||||
tag = b""
|
||||
|
||||
for arg in args:
|
||||
def arg_error_handler(typ):
|
||||
printer = types.TypePrinter()
|
||||
|
@ -1430,12 +1604,18 @@ class LLVMIRGenerator:
|
|||
{"type": printer.name(typ)},
|
||||
arg.loc)
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"type {type} is not supported in remote procedure calls",
|
||||
{"type": printer.name(arg.type)},
|
||||
"type {type} is not supported in {call_type} calls",
|
||||
{"type": printer.name(arg.type), "call_type": call_type},
|
||||
arg.loc, notes=[note])
|
||||
self.engine.process(diag)
|
||||
tag += ir.rpc_tag(arg.type, arg_error_handler)
|
||||
tag += b":"
|
||||
return tag
|
||||
|
||||
def _build_rpc(self, fun_loc, fun_type, args, llnormalblock, llunwindblock):
|
||||
llservice = ll.Constant(lli32, fun_type.service)
|
||||
|
||||
tag = self._build_arg_tag(args, call_type="remote procedure")
|
||||
|
||||
def ret_error_handler(typ):
|
||||
printer = types.TypePrinter()
|
||||
|
@ -1492,57 +1672,94 @@ class LLVMIRGenerator:
|
|||
|
||||
return ll.Undefined
|
||||
|
||||
# T result = {
|
||||
# void *ret_ptr = alloca(sizeof(T));
|
||||
# void *ptr = ret_ptr;
|
||||
# loop: int size = rpc_recv(ptr);
|
||||
# // Non-zero: Provide `size` bytes of extra storage for variable-length data.
|
||||
# if(size) { ptr = alloca(size); goto loop; }
|
||||
# else *(T*)ret_ptr
|
||||
# }
|
||||
llprehead = self.llbuilder.basic_block
|
||||
llhead = self.llbuilder.append_basic_block(name="rpc.head")
|
||||
if llunwindblock:
|
||||
llheadu = self.llbuilder.append_basic_block(name="rpc.head.unwind")
|
||||
llalloc = self.llbuilder.append_basic_block(name="rpc.continue")
|
||||
lltail = self.llbuilder.append_basic_block(name="rpc.tail")
|
||||
llret = self._build_rpc_recv(fun_type.ret, llstackptr, llnormalblock, llunwindblock)
|
||||
|
||||
llretty = self.llty_of_type(fun_type.ret)
|
||||
llslot = self.llbuilder.alloca(llretty, name="rpc.ret.alloc")
|
||||
llslotgen = self.llbuilder.bitcast(llslot, llptr, name="rpc.ret.ptr")
|
||||
self.llbuilder.branch(llhead)
|
||||
|
||||
self.llbuilder.position_at_end(llhead)
|
||||
llphi = self.llbuilder.phi(llslotgen.type, name="rpc.ptr")
|
||||
llphi.add_incoming(llslotgen, llprehead)
|
||||
if llunwindblock:
|
||||
llsize = self.llbuilder.invoke(self.llbuiltin("rpc_recv"), [llphi],
|
||||
llheadu, llunwindblock,
|
||||
name="rpc.size.next")
|
||||
self.llbuilder.position_at_end(llheadu)
|
||||
else:
|
||||
llsize = self.llbuilder.call(self.llbuiltin("rpc_recv"), [llphi],
|
||||
name="rpc.size.next")
|
||||
lldone = self.llbuilder.icmp_unsigned('==', llsize, ll.Constant(llsize.type, 0),
|
||||
name="rpc.done")
|
||||
self.llbuilder.cbranch(lldone, lltail, llalloc)
|
||||
|
||||
self.llbuilder.position_at_end(llalloc)
|
||||
llalloca = self.llbuilder.alloca(lli8, llsize, name="rpc.alloc")
|
||||
llalloca.align = 4 # maximum alignment required by OR1K ABI
|
||||
llphi.add_incoming(llalloca, llalloc)
|
||||
self.llbuilder.branch(llhead)
|
||||
|
||||
self.llbuilder.position_at_end(lltail)
|
||||
llret = self.llbuilder.load(llslot, name="rpc.ret")
|
||||
if not fun_type.ret.fold(False, lambda r, t: r or builtins.is_allocated(t)):
|
||||
# We didn't allocate anything except the slot for the value itself.
|
||||
# Don't waste stack space.
|
||||
self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr])
|
||||
if llnormalblock:
|
||||
self.llbuilder.branch(llnormalblock)
|
||||
return llret
|
||||
|
||||
def _build_subkernel_call(self, fun_loc, fun_type, args):
|
||||
llsid = ll.Constant(lli32, fun_type.sid)
|
||||
lldest = ll.Constant(lli8, fun_type.destination)
|
||||
# run the kernel first
|
||||
self.llbuilder.call(self.llbuiltin("subkernel_load_run"), [llsid, lldest, ll.Constant(lli1, 1)])
|
||||
|
||||
if args:
|
||||
# only send args if there's anything to send, 'self' is excluded
|
||||
self._build_subkernel_message(llsid, lldest, args)
|
||||
|
||||
return llsid
|
||||
|
||||
def _build_subkernel_message(self, llid, lldest, args):
|
||||
# args (or messages) are sent in the same vein as RPC
|
||||
tag = self._build_arg_tag(args, call_type="subkernel")
|
||||
|
||||
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [],
|
||||
name="subkernel.stack")
|
||||
lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr()))
|
||||
lltagptr = self.llbuilder.alloca(lltag.type)
|
||||
self.llbuilder.store(lltag, lltagptr)
|
||||
|
||||
llargs = self.llbuilder.alloca(llptr, ll.Constant(lli32, len(args)),
|
||||
name="subkernel.args")
|
||||
for index, arg in enumerate(args):
|
||||
if builtins.is_none(arg.type):
|
||||
llargslot = self.llbuilder.alloca(llunit,
|
||||
name="subkernel.arg{}".format(index))
|
||||
else:
|
||||
llarg = self.map(arg)
|
||||
llargslot = self.llbuilder.alloca(llarg.type,
|
||||
name="subkernel.arg{}".format(index))
|
||||
self.llbuilder.store(llarg, llargslot)
|
||||
llargslot = self.llbuilder.bitcast(llargslot, llptr)
|
||||
|
||||
llargptr = self.llbuilder.gep(llargs, [ll.Constant(lli32, index)])
|
||||
self.llbuilder.store(llargslot, llargptr)
|
||||
|
||||
llargcount = ll.Constant(lli8, len(args))
|
||||
|
||||
llisreturn = ll.Constant(lli1, False)
|
||||
self.llbuilder.call(self.llbuiltin("subkernel_send_message"),
|
||||
[llid, llisreturn, lldest, llargcount, lltagptr, llargs])
|
||||
return self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr])
|
||||
|
||||
def _build_subkernel_return(self, insn):
|
||||
# builds a remote return.
|
||||
# unlike args, return only sends one thing.
|
||||
if builtins.is_none(insn.value().type):
|
||||
# do not waste time and bandwidth on Nones
|
||||
return
|
||||
|
||||
def ret_error_handler(typ):
|
||||
printer = types.TypePrinter()
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"value of type {type}",
|
||||
{"type": printer.name(typ)},
|
||||
fun_loc)
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"return type {type} is not supported in subkernel returns",
|
||||
{"type": printer.name(fun_type.ret)},
|
||||
fun_loc, notes=[note])
|
||||
self.engine.process(diag)
|
||||
tag = ir.rpc_tag(insn.value().type, ret_error_handler)
|
||||
tag += b":"
|
||||
lltag = self.llconst_of_const(ir.Constant(tag, builtins.TStr()))
|
||||
lltagptr = self.llbuilder.alloca(lltag.type)
|
||||
self.llbuilder.store(lltag, lltagptr)
|
||||
|
||||
llrets = self.llbuilder.alloca(llptr, ll.Constant(lli32, 1),
|
||||
name="subkernel.return")
|
||||
llret = self.map(insn.value())
|
||||
llretslot = self.llbuilder.alloca(llret.type, name="subkernel.retval")
|
||||
self.llbuilder.store(llret, llretslot)
|
||||
llretslot = self.llbuilder.bitcast(llretslot, llptr)
|
||||
self.llbuilder.store(llretslot, llrets)
|
||||
|
||||
llsid = ll.Constant(lli32, 0) # return goes back to the caller, sid is ignored
|
||||
lltagcount = ll.Constant(lli8, 1) # only one thing is returned
|
||||
llisreturn = ll.Constant(lli1, True) # it's a return, so destination is ignored
|
||||
lldest = ll.Constant(lli8, 0)
|
||||
self.llbuilder.call(self.llbuiltin("subkernel_send_message"),
|
||||
[llsid, llisreturn, lldest, lltagcount, lltagptr, llrets])
|
||||
|
||||
def process_Call(self, insn):
|
||||
functiontyp = insn.target_function().type
|
||||
if types.is_rpc(functiontyp):
|
||||
|
@ -1550,6 +1767,10 @@ class LLVMIRGenerator:
|
|||
functiontyp,
|
||||
insn.arguments(),
|
||||
llnormalblock=None, llunwindblock=None)
|
||||
elif types.is_subkernel(functiontyp):
|
||||
return self._build_subkernel_call(insn.target_function().loc,
|
||||
functiontyp,
|
||||
insn.arguments())
|
||||
elif types.is_external_function(functiontyp):
|
||||
llfun, llargs, llarg_attrs, llcallstackptr = self._prepare_ffi_call(insn)
|
||||
else:
|
||||
|
@ -1586,6 +1807,11 @@ class LLVMIRGenerator:
|
|||
functiontyp,
|
||||
insn.arguments(),
|
||||
llnormalblock, llunwindblock)
|
||||
elif types.is_subkernel(functiontyp):
|
||||
return self._build_subkernel_call(insn.target_function().loc,
|
||||
functiontyp,
|
||||
insn.arguments(),
|
||||
llnormalblock, llunwindblock)
|
||||
elif types.is_external_function(functiontyp):
|
||||
llfun, llargs, llarg_attrs, llcallstackptr = self._prepare_ffi_call(insn)
|
||||
else:
|
||||
|
@ -1664,7 +1890,8 @@ class LLVMIRGenerator:
|
|||
attrvalue = getattr(value, attr)
|
||||
is_class_function = (types.is_constructor(typ) and
|
||||
types.is_function(typ.attributes[attr]) and
|
||||
not types.is_external_function(typ.attributes[attr]))
|
||||
not types.is_external_function(typ.attributes[attr]) and
|
||||
not types.is_subkernel(typ.attributes[attr]))
|
||||
if is_class_function:
|
||||
attrvalue = self.embedding_map.specialize_function(typ.instance, attrvalue)
|
||||
if not (types.is_instance(typ) and attr in typ.constant_attributes):
|
||||
|
@ -1749,7 +1976,8 @@ class LLVMIRGenerator:
|
|||
llelts = [self._quote(v, t, lambda: path() + [str(i)])
|
||||
for i, (v, t) in enumerate(zip(value, typ.elts))]
|
||||
return ll.Constant(llty, llelts)
|
||||
elif types.is_rpc(typ) or types.is_external_function(typ) or types.is_builtin_function(typ):
|
||||
elif types.is_rpc(typ) or types.is_external_function(typ) or \
|
||||
types.is_builtin_function(typ) or types.is_subkernel(typ):
|
||||
# RPC, C and builtin functions have no runtime representation.
|
||||
return ll.Constant(llty, ll.Undefined)
|
||||
elif types.is_function(typ):
|
||||
|
@ -1804,6 +2032,8 @@ class LLVMIRGenerator:
|
|||
return llinsn
|
||||
|
||||
def process_Return(self, insn):
|
||||
if insn.remote_return:
|
||||
self._build_subkernel_return(insn)
|
||||
if builtins.is_none(insn.value().type):
|
||||
return self.llbuilder.ret_void()
|
||||
else:
|
||||
|
|
|
@ -385,6 +385,50 @@ class TRPC(Type):
|
|||
def __hash__(self):
|
||||
return hash(self.service)
|
||||
|
||||
class TSubkernel(TFunction):
|
||||
"""
|
||||
A kernel to be run on a satellite.
|
||||
|
||||
:ivar args: (:class:`collections.OrderedDict` of string to :class:`Type`)
|
||||
function arguments
|
||||
:ivar ret: (:class:`Type`)
|
||||
return type
|
||||
:ivar sid: (int) subkernel ID number
|
||||
:ivar destination: (int) satellite destination number
|
||||
"""
|
||||
|
||||
attributes = OrderedDict()
|
||||
|
||||
def __init__(self, args, optargs, ret, sid, destination):
|
||||
assert isinstance(ret, Type)
|
||||
super().__init__(args, optargs, ret)
|
||||
self.sid, self.destination = sid, destination
|
||||
self.delay = TFixedDelay(iodelay.Const(0))
|
||||
|
||||
def unify(self, other):
|
||||
if other is self:
|
||||
return
|
||||
if isinstance(other, TSubkernel) and \
|
||||
self.sid == other.sid and \
|
||||
self.destination == other.destination:
|
||||
self.ret.unify(other.ret)
|
||||
elif isinstance(other, TVar):
|
||||
other.unify(self)
|
||||
else:
|
||||
raise UnificationError(self, other)
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
return "artiq.compiler.types.TSubkernel({})".format(repr(self.ret))
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, TSubkernel) and \
|
||||
self.sid == other.sid
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.sid)
|
||||
|
||||
class TBuiltin(Type):
|
||||
"""
|
||||
An instance of builtin type. Every instance of a builtin
|
||||
|
@ -644,6 +688,9 @@ def is_function(typ):
|
|||
def is_rpc(typ):
|
||||
return isinstance(typ.find(), TRPC)
|
||||
|
||||
def is_subkernel(typ):
|
||||
return isinstance(typ.find(), TSubkernel)
|
||||
|
||||
def is_external_function(typ, name=None):
|
||||
typ = typ.find()
|
||||
if name is None:
|
||||
|
@ -810,6 +857,10 @@ class TypePrinter(object):
|
|||
return "[rpc{} #{}](...)->{}".format(typ.service,
|
||||
" async" if typ.is_async else "",
|
||||
self.name(typ.ret, depth + 1))
|
||||
elif isinstance(typ, TSubkernel):
|
||||
return "<subkernel{} dest#{}>->{}".format(typ.sid,
|
||||
typ.destination,
|
||||
self.name(typ.ret, depth + 1))
|
||||
elif isinstance(typ, TBuiltinFunction):
|
||||
return "<function {}>".format(typ.name)
|
||||
elif isinstance(typ, (TConstructor, TExceptionConstructor)):
|
||||
|
|
|
@ -102,8 +102,20 @@ class RegionOf(algorithm.Visitor):
|
|||
if types.is_external_function(node.func.type, "cache_get"):
|
||||
# The cache is borrow checked dynamically
|
||||
return Global()
|
||||
else:
|
||||
self.visit_sometimes_allocating(node)
|
||||
|
||||
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
|
||||
|
|
|
@ -233,7 +233,7 @@ class AD53xx:
|
|||
def write_gain_mu(self, channel, gain=0xffff):
|
||||
"""Program the gain register for a DAC channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
|
||||
This method advances the timeline by the duration of one SPI transfer.
|
||||
|
||||
:param gain: 16-bit gain register value (default: 0xffff)
|
||||
|
@ -245,7 +245,7 @@ class AD53xx:
|
|||
def write_offset_mu(self, channel, offset=0x8000):
|
||||
"""Program the offset register for a DAC channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
|
||||
This method advances the timeline by the duration of one SPI transfer.
|
||||
|
||||
:param offset: 16-bit offset register value (default: 0x8000)
|
||||
|
@ -258,7 +258,7 @@ class AD53xx:
|
|||
"""Program the DAC offset voltage for a channel.
|
||||
|
||||
An offset of +V can be used to trim out a DAC offset error of -V.
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
|
||||
This method advances the timeline by the duration of one SPI transfer.
|
||||
|
||||
:param voltage: the offset voltage
|
||||
|
@ -270,7 +270,7 @@ class AD53xx:
|
|||
def write_dac_mu(self, channel, value):
|
||||
"""Program the DAC input register for a channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
|
||||
This method advances the timeline by the duration of one SPI transfer.
|
||||
"""
|
||||
self.bus.write(
|
||||
|
@ -280,7 +280,7 @@ class AD53xx:
|
|||
def write_dac(self, channel, voltage):
|
||||
"""Program the DAC output voltage for a channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
|
||||
This method advances the timeline by the duration of one SPI transfer.
|
||||
"""
|
||||
self.write_dac_mu(channel, voltage_to_mu(voltage, self.offset_dacs,
|
||||
|
@ -313,7 +313,7 @@ class AD53xx:
|
|||
|
||||
If no LDAC device was defined, the LDAC pulse is skipped.
|
||||
|
||||
See :meth load:.
|
||||
See :meth:`load`.
|
||||
|
||||
:param values: list of DAC values to program
|
||||
:param channels: list of DAC channels to program. If not specified,
|
||||
|
@ -355,7 +355,7 @@ class AD53xx:
|
|||
""" Two-point calibration of a DAC channel.
|
||||
|
||||
Programs the offset and gain register to trim out DAC errors. Does not
|
||||
take effect until LDAC is pulsed (see :meth load:).
|
||||
take effect until LDAC is pulsed (see :meth:`load`).
|
||||
|
||||
Calibration consists of measuring the DAC output voltage for a channel
|
||||
with the DAC set to zero-scale (0x0000) and full-scale (0xffff).
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,23 +0,0 @@
|
|||
from artiq.language.core import kernel
|
||||
|
||||
|
||||
class AD9154:
|
||||
"""Kernel interface to AD9154 registers, using non-realtime SPI."""
|
||||
|
||||
def __init__(self, dmgr, spi_device, chip_select):
|
||||
self.core = dmgr.get("core")
|
||||
self.bus = dmgr.get(spi_device)
|
||||
self.chip_select = chip_select
|
||||
|
||||
@kernel
|
||||
def setup_bus(self, div=16):
|
||||
self.bus.set_config_mu(0, 24, div, self.chip_select)
|
||||
|
||||
@kernel
|
||||
def write(self, addr, data):
|
||||
self.bus.write((addr << 16) | (data<< 8))
|
||||
|
||||
@kernel
|
||||
def read(self, addr):
|
||||
self.write((1 << 15) | addr, 0)
|
||||
return self.bus.read()
|
|
@ -999,7 +999,7 @@ class AD9910:
|
|||
"""
|
||||
if not self.cpld.sync_div:
|
||||
raise ValueError("parent cpld does not drive SYNC")
|
||||
search_span = 31
|
||||
search_span = 13
|
||||
# FIXME https://github.com/sinara-hw/Urukul/issues/16
|
||||
# should both be 2-4 once kasli sync_in jitter is identified
|
||||
min_window = 0
|
||||
|
|
|
@ -25,12 +25,14 @@ class AD9912:
|
|||
f_ref/clk_div*pll_n where f_ref is the reference frequency and clk_div
|
||||
is the reference clock divider (both set in the parent Urukul CPLD
|
||||
instance).
|
||||
:param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1).
|
||||
Note that when bypassing the PLL the red front panel LED may remain on.
|
||||
"""
|
||||
|
||||
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
|
||||
pll_n=10):
|
||||
pll_n=10, pll_en=1):
|
||||
self.kernel_invariants = {"cpld", "core", "bus", "chip_select",
|
||||
"pll_n", "ftw_per_hz"}
|
||||
"pll_n", "pll_en", "ftw_per_hz"}
|
||||
self.cpld = dmgr.get(cpld_device)
|
||||
self.core = self.cpld.core
|
||||
self.bus = self.cpld.bus
|
||||
|
@ -39,8 +41,12 @@ class AD9912:
|
|||
if sw_device:
|
||||
self.sw = dmgr.get(sw_device)
|
||||
self.kernel_invariants.add("sw")
|
||||
self.pll_en = pll_en
|
||||
self.pll_n = pll_n
|
||||
sysclk = self.cpld.refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n
|
||||
if pll_en:
|
||||
sysclk = self.cpld.refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n
|
||||
else:
|
||||
sysclk = self.cpld.refclk
|
||||
assert sysclk <= 1e9
|
||||
self.ftw_per_hz = 1 / sysclk * (int64(1) << 48)
|
||||
|
||||
|
@ -102,13 +108,15 @@ class AD9912:
|
|||
raise ValueError("Urukul AD9912 product id mismatch")
|
||||
delay(50 * us)
|
||||
# HSTL power down, CMOS power down
|
||||
self.write(AD9912_PWRCNTRL1, 0x80, length=1)
|
||||
self.cpld.io_update.pulse(2 * us)
|
||||
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1)
|
||||
self.cpld.io_update.pulse(2 * us)
|
||||
# I_cp = 375 µA, VCO high range
|
||||
self.write(AD9912_PLLCFG, 0b00000101, length=1)
|
||||
pwrcntrl1 = 0x80 | ((~self.pll_en & 1) << 4)
|
||||
self.write(AD9912_PWRCNTRL1, pwrcntrl1, length=1)
|
||||
self.cpld.io_update.pulse(2 * us)
|
||||
if self.pll_en:
|
||||
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1)
|
||||
self.cpld.io_update.pulse(2 * us)
|
||||
# I_cp = 375 µA, VCO high range
|
||||
self.write(AD9912_PLLCFG, 0b00000101, length=1)
|
||||
self.cpld.io_update.pulse(2 * us)
|
||||
delay(1 * ms)
|
||||
|
||||
@kernel
|
||||
|
|
|
@ -80,6 +80,13 @@ class AD9914:
|
|||
self.set_x_duration_mu = 7 * self.write_duration_mu
|
||||
self.exit_x_duration_mu = 3 * self.write_duration_mu
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(bus_channel, channel, **kwargs):
|
||||
# return only first entry, as there are several devices with the same RTIO channel
|
||||
if channel == 0:
|
||||
return [(bus_channel, None)]
|
||||
return []
|
||||
|
||||
@kernel
|
||||
def write(self, addr, data):
|
||||
rtio_output((self.bus_channel << 8) | addr, data)
|
||||
|
|
|
@ -73,6 +73,10 @@ class ADF5356:
|
|||
|
||||
self._init_registers()
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(**kwargs):
|
||||
return []
|
||||
|
||||
@kernel
|
||||
def init(self, blind=False):
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
from artiq.language.core import kernel, portable
|
||||
|
||||
from numpy import int32
|
||||
|
||||
|
||||
# almazny-specific data
|
||||
ALMAZNY_LEGACY_REG_BASE = 0x0C
|
||||
ALMAZNY_LEGACY_OE_SHIFT = 12
|
||||
|
||||
# higher SPI write divider to match almazny shift register timing
|
||||
# min SER time before SRCLK rise = 125ns
|
||||
# -> div=32 gives 125ns for data before clock rise
|
||||
# works at faster dividers too but could be less reliable
|
||||
ALMAZNY_LEGACY_SPIT_WR = 32
|
||||
|
||||
|
||||
class AlmaznyLegacy:
|
||||
"""
|
||||
Almazny (High frequency mezzanine board for Mirny)
|
||||
|
||||
This applies to Almazny hardware v1.1 and earlier.
|
||||
Use :class:`artiq.coredevice.almazny.AlmaznyChannel` for Almazny v1.2 and later.
|
||||
|
||||
:param host_mirny: Mirny device Almazny is connected to
|
||||
"""
|
||||
|
||||
def __init__(self, dmgr, host_mirny):
|
||||
self.mirny_cpld = dmgr.get(host_mirny)
|
||||
self.att_mu = [0x3f] * 4
|
||||
self.channel_sw = [0] * 4
|
||||
self.output_enable = False
|
||||
|
||||
@kernel
|
||||
def init(self):
|
||||
self.output_toggle(self.output_enable)
|
||||
|
||||
@kernel
|
||||
def att_to_mu(self, att):
|
||||
"""
|
||||
Convert an attenuator setting in dB to machine units.
|
||||
|
||||
:param att: attenuator setting in dB [0-31.5]
|
||||
:return: attenuator setting in machine units
|
||||
"""
|
||||
mu = round(att * 2.0)
|
||||
if mu > 63 or mu < 0:
|
||||
raise ValueError("Invalid Almazny attenuator settings!")
|
||||
return mu
|
||||
|
||||
@kernel
|
||||
def mu_to_att(self, att_mu):
|
||||
"""
|
||||
Convert a digital attenuator setting to dB.
|
||||
|
||||
:param att_mu: attenuator setting in machine units
|
||||
:return: attenuator setting in dB
|
||||
"""
|
||||
return att_mu / 2
|
||||
|
||||
@kernel
|
||||
def set_att(self, channel, att, rf_switch=True):
|
||||
"""
|
||||
Sets attenuators on chosen shift register (channel).
|
||||
|
||||
:param channel: index of the register [0-3]
|
||||
:param att: attenuation setting in dBm [0-31.5]
|
||||
:param rf_switch: rf switch (bool)
|
||||
"""
|
||||
self.set_att_mu(channel, self.att_to_mu(att), rf_switch)
|
||||
|
||||
@kernel
|
||||
def set_att_mu(self, channel, att_mu, rf_switch=True):
|
||||
"""
|
||||
Sets attenuators on chosen shift register (channel).
|
||||
|
||||
:param channel: index of the register [0-3]
|
||||
:param att_mu: attenuation setting in machine units [0-63]
|
||||
:param rf_switch: rf switch (bool)
|
||||
"""
|
||||
self.channel_sw[channel] = 1 if rf_switch else 0
|
||||
self.att_mu[channel] = att_mu
|
||||
self._update_register(channel)
|
||||
|
||||
@kernel
|
||||
def output_toggle(self, oe):
|
||||
"""
|
||||
Toggles output on all shift registers on or off.
|
||||
|
||||
:param oe: toggle output enable (bool)
|
||||
"""
|
||||
self.output_enable = oe
|
||||
cfg_reg = self.mirny_cpld.read_reg(1)
|
||||
en = 1 if self.output_enable else 0
|
||||
delay(100 * us)
|
||||
new_reg = (en << ALMAZNY_LEGACY_OE_SHIFT) | (cfg_reg & 0x3FF)
|
||||
self.mirny_cpld.write_reg(1, new_reg)
|
||||
delay(100 * us)
|
||||
|
||||
@kernel
|
||||
def _flip_mu_bits(self, mu):
|
||||
# in this form MSB is actually 0.5dB attenuator
|
||||
# unnatural for users, so we flip the six bits
|
||||
return (((mu & 0x01) << 5)
|
||||
| ((mu & 0x02) << 3)
|
||||
| ((mu & 0x04) << 1)
|
||||
| ((mu & 0x08) >> 1)
|
||||
| ((mu & 0x10) >> 3)
|
||||
| ((mu & 0x20) >> 5))
|
||||
|
||||
@kernel
|
||||
def _update_register(self, ch):
|
||||
self.mirny_cpld.write_ext(
|
||||
ALMAZNY_LEGACY_REG_BASE + ch,
|
||||
8,
|
||||
self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6),
|
||||
ALMAZNY_LEGACY_SPIT_WR
|
||||
)
|
||||
delay(100 * us)
|
||||
|
||||
@kernel
|
||||
def _update_all_registers(self):
|
||||
for i in range(4):
|
||||
self._update_register(i)
|
||||
|
||||
|
||||
class AlmaznyChannel:
|
||||
"""
|
||||
One Almazny channel
|
||||
Almazny is a mezzanine for the Quad PLL RF source Mirny that exposes and
|
||||
controls the frequency-doubled outputs.
|
||||
This driver requires Almazny hardware revision v1.2 or later
|
||||
and Mirny CPLD gateware v0.3 or later.
|
||||
Use :class:`artiq.coredevice.almazny.AlmaznyLegacy` for Almazny hardware v1.1 and earlier.
|
||||
|
||||
:param host_mirny: Mirny CPLD device name
|
||||
:param channel: channel index (0-3)
|
||||
"""
|
||||
|
||||
def __init__(self, dmgr, host_mirny, channel):
|
||||
self.channel = channel
|
||||
self.mirny_cpld = dmgr.get(host_mirny)
|
||||
|
||||
@portable
|
||||
def to_mu(self, att, enable, led):
|
||||
"""
|
||||
Convert an attenuation in dB, RF switch state and LED state to machine
|
||||
units.
|
||||
|
||||
:param att: attenuator setting in dB (0-31.5)
|
||||
:param enable: RF switch state (bool)
|
||||
:param led: LED state (bool)
|
||||
:return: channel setting in machine units
|
||||
"""
|
||||
mu = int32(round(att * 2.))
|
||||
if mu >= 64 or mu < 0:
|
||||
raise ValueError("Attenuation out of range")
|
||||
# unfortunate hardware design: bit reverse
|
||||
mu = ((mu & 0x15) << 1) | ((mu >> 1) & 0x15)
|
||||
mu = ((mu & 0x03) << 4) | (mu & 0x0c) | ((mu >> 4) & 0x03)
|
||||
if enable:
|
||||
mu |= 1 << 6
|
||||
if led:
|
||||
mu |= 1 << 7
|
||||
return mu
|
||||
|
||||
@kernel
|
||||
def set_mu(self, mu):
|
||||
"""
|
||||
Set channel state (machine units).
|
||||
|
||||
:param mu: channel state in machine units.
|
||||
"""
|
||||
self.mirny_cpld.write_ext(
|
||||
addr=0xc + self.channel, length=8, data=mu, ext_div=32)
|
||||
|
||||
@kernel
|
||||
def set(self, att, enable, led=False):
|
||||
"""
|
||||
Set attenuation, RF switch, and LED state (SI units).
|
||||
|
||||
:param att: attenuator setting in dB (0-31.5)
|
||||
:param enable: RF switch state (bool)
|
||||
:param led: LED state (bool)
|
||||
"""
|
||||
self.set_mu(self.to_mu(att, enable, led))
|
|
@ -1,79 +0,0 @@
|
|||
from artiq.language.core import kernel, portable, delay
|
||||
from artiq.language.units import us, ms
|
||||
from artiq.coredevice.shiftreg import ShiftReg
|
||||
|
||||
|
||||
@portable
|
||||
def to_mu(att):
|
||||
return round(att*2.0) ^ 0x3f
|
||||
|
||||
@portable
|
||||
def from_mu(att_mu):
|
||||
return 0.5*(att_mu ^ 0x3f)
|
||||
|
||||
|
||||
class BaseModAtt:
|
||||
def __init__(self, dmgr, rst_n, clk, le, mosi, miso):
|
||||
self.rst_n = dmgr.get(rst_n)
|
||||
self.shift_reg = ShiftReg(dmgr,
|
||||
clk=clk, ser=mosi, latch=le, ser_in=miso, n=8*4)
|
||||
|
||||
@kernel
|
||||
def reset(self):
|
||||
# HMC's incompetence in digital design and interfaces means that
|
||||
# the HMC542 needs a level low on RST_N and then a rising edge
|
||||
# on Latch Enable. Their "latch" isn't a latch but a DFF.
|
||||
# Of course, it also powers up with a random attenuation, and
|
||||
# that cannot be fixed with simple pull-ups/pull-downs.
|
||||
self.rst_n.off()
|
||||
self.shift_reg.latch.off()
|
||||
delay(1*us)
|
||||
self.shift_reg.latch.on()
|
||||
delay(1*us)
|
||||
self.shift_reg.latch.off()
|
||||
self.rst_n.on()
|
||||
delay(1*us)
|
||||
|
||||
@kernel
|
||||
def set_mu(self, att0, att1, att2, att3):
|
||||
"""
|
||||
Sets the four attenuators on BaseMod.
|
||||
The values are in half decibels, between 0 (no attenuation)
|
||||
and 63 (31.5dB attenuation).
|
||||
"""
|
||||
word = (
|
||||
(att0 << 2) |
|
||||
(att1 << 10) |
|
||||
(att2 << 18) |
|
||||
(att3 << 26)
|
||||
)
|
||||
self.shift_reg.set(word)
|
||||
|
||||
@kernel
|
||||
def get_mu(self):
|
||||
"""
|
||||
Retrieves the current settings of the four attenuators on BaseMod.
|
||||
"""
|
||||
word = self.shift_reg.get()
|
||||
att0 = (word >> 2) & 0x3f
|
||||
att1 = (word >> 10) & 0x3f
|
||||
att2 = (word >> 18) & 0x3f
|
||||
att3 = (word >> 26) & 0x3f
|
||||
return att0, att1, att2, att3
|
||||
|
||||
@kernel
|
||||
def set(self, att0, att1, att2, att3):
|
||||
"""
|
||||
Sets the four attenuators on BaseMod.
|
||||
The values are in decibels.
|
||||
"""
|
||||
self.set_mu(to_mu(att0), to_mu(att1), to_mu(att2), to_mu(att3))
|
||||
|
||||
@kernel
|
||||
def get(self):
|
||||
"""
|
||||
Retrieves the current settings of the four attenuators on BaseMod.
|
||||
The values are in decibels.
|
||||
"""
|
||||
att0, att1, att2, att3 = self.get_mu()
|
||||
return from_mu(att0), from_mu(att1), from_mu(att2), from_mu(att3)
|
|
@ -2,15 +2,22 @@ from operator import itemgetter
|
|||
from collections import namedtuple
|
||||
from itertools import count
|
||||
from contextlib import contextmanager
|
||||
from sipyco import keepalive
|
||||
import asyncio
|
||||
from enum import Enum
|
||||
import struct
|
||||
import logging
|
||||
import socket
|
||||
import math
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
DEFAULT_REF_PERIOD = 1e-9
|
||||
ANALYZER_MAGIC = b"ARTIQ Analyzer Proxy\n"
|
||||
|
||||
|
||||
class MessageType(Enum):
|
||||
output = 0b00
|
||||
input = 0b01
|
||||
|
@ -34,6 +41,13 @@ class ExceptionType(Enum):
|
|||
i_overflow = 0b100001
|
||||
|
||||
|
||||
class WaveformType(Enum):
|
||||
ANALOG = 0
|
||||
BIT = 1
|
||||
VECTOR = 2
|
||||
LOG = 3
|
||||
|
||||
|
||||
def get_analyzer_dump(host, port=1382):
|
||||
sock = socket.create_connection((host, port))
|
||||
try:
|
||||
|
@ -102,28 +116,98 @@ def decode_dump(data):
|
|||
# messages are big endian
|
||||
parts = struct.unpack(endian + "IQbbb", data[:15])
|
||||
(sent_bytes, total_byte_count,
|
||||
error_occured, log_channel, dds_onehot_sel) = parts
|
||||
error_occurred, log_channel, dds_onehot_sel) = parts
|
||||
|
||||
logger.debug("analyzer dump has length %d", sent_bytes)
|
||||
|
||||
expected_len = sent_bytes + 15
|
||||
if expected_len != len(data):
|
||||
raise ValueError("analyzer dump has incorrect length "
|
||||
"(got {}, expected {})".format(
|
||||
len(data), expected_len))
|
||||
if error_occured:
|
||||
logger.warning("error occured within the analyzer, "
|
||||
if error_occurred:
|
||||
logger.warning("error occurred within the analyzer, "
|
||||
"data may be corrupted")
|
||||
if total_byte_count > sent_bytes:
|
||||
logger.info("analyzer ring buffer has wrapped %d times",
|
||||
total_byte_count//sent_bytes)
|
||||
if sent_bytes == 0:
|
||||
logger.warning("analyzer dump is empty")
|
||||
|
||||
position = 15
|
||||
messages = []
|
||||
for _ in range(sent_bytes//32):
|
||||
messages.append(decode_message(data[position:position+32]))
|
||||
position += 32
|
||||
|
||||
if len(messages) == 1 and isinstance(messages[0], StoppedMessage):
|
||||
logger.warning("analyzer dump is empty aside from stop message")
|
||||
|
||||
return DecodedDump(log_channel, bool(dds_onehot_sel), messages)
|
||||
|
||||
|
||||
# simplified from sipyco broadcast Receiver
|
||||
class AnalyzerProxyReceiver:
|
||||
def __init__(self, receive_cb, disconnect_cb=None):
|
||||
self.receive_cb = receive_cb
|
||||
self.disconnect_cb = disconnect_cb
|
||||
|
||||
async def connect(self, host, port):
|
||||
self.reader, self.writer = \
|
||||
await keepalive.async_open_connection(host, port)
|
||||
try:
|
||||
line = await self.reader.readline()
|
||||
assert line == ANALYZER_MAGIC
|
||||
self.receive_task = asyncio.create_task(self._receive_cr())
|
||||
except:
|
||||
self.writer.close()
|
||||
del self.reader
|
||||
del self.writer
|
||||
raise
|
||||
|
||||
async def close(self):
|
||||
self.disconnect_cb = None
|
||||
try:
|
||||
self.receive_task.cancel()
|
||||
try:
|
||||
await self.receive_task
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
finally:
|
||||
self.writer.close()
|
||||
del self.reader
|
||||
del self.writer
|
||||
|
||||
async def _receive_cr(self):
|
||||
try:
|
||||
while True:
|
||||
endian_byte = await self.reader.read(1)
|
||||
if endian_byte == b"E":
|
||||
endian = '>'
|
||||
elif endian_byte == b"e":
|
||||
endian = '<'
|
||||
elif endian_byte == b"":
|
||||
# EOF reached, connection lost
|
||||
return
|
||||
else:
|
||||
raise ValueError
|
||||
payload_length_word = await self.reader.readexactly(4)
|
||||
payload_length = struct.unpack(endian + "I", payload_length_word)[0]
|
||||
if payload_length > 10 * 512 * 1024:
|
||||
# 10x buffer size of firmware
|
||||
raise ValueError
|
||||
|
||||
# The remaining header length is 11 bytes.
|
||||
remaining_data = await self.reader.readexactly(payload_length + 11)
|
||||
data = endian_byte + payload_length_word + remaining_data
|
||||
self.receive_cb(data)
|
||||
except Exception:
|
||||
logger.error("analyzer receiver connection terminating with exception", exc_info=True)
|
||||
finally:
|
||||
if self.disconnect_cb is not None:
|
||||
self.disconnect_cb()
|
||||
|
||||
|
||||
def vcd_codes():
|
||||
codechars = [chr(i) for i in range(33, 127)]
|
||||
for n in count():
|
||||
|
@ -150,38 +234,129 @@ class VCDChannel:
|
|||
integer_cast = struct.unpack(">Q", struct.pack(">d", x))[0]
|
||||
self.set_value("{:064b}".format(integer_cast))
|
||||
|
||||
def set_log(self, log_message):
|
||||
value = ""
|
||||
for c in log_message:
|
||||
value += "{:08b}".format(ord(c))
|
||||
self.set_value(value)
|
||||
|
||||
|
||||
class VCDManager:
|
||||
def __init__(self, fileobj):
|
||||
self.out = fileobj
|
||||
self.codes = vcd_codes()
|
||||
self.current_time = None
|
||||
self.start_time = 0
|
||||
|
||||
def set_timescale_ps(self, timescale):
|
||||
self.out.write("$timescale {}ps $end\n".format(round(timescale)))
|
||||
|
||||
def get_channel(self, name, width):
|
||||
def get_channel(self, name, width, ty, precision=0, unit=""):
|
||||
code = next(self.codes)
|
||||
self.out.write("$var wire {width} {code} {name} $end\n"
|
||||
.format(name=name, code=code, width=width))
|
||||
return VCDChannel(self.out, code)
|
||||
|
||||
@contextmanager
|
||||
def scope(self, name):
|
||||
self.out.write("$scope module {} $end\n".format(name))
|
||||
def scope(self, scope, name):
|
||||
self.out.write("$scope module {}/{} $end\n".format(scope, name))
|
||||
yield
|
||||
self.out.write("$upscope $end\n")
|
||||
|
||||
def set_time(self, time):
|
||||
time -= self.start_time
|
||||
if time != self.current_time:
|
||||
self.out.write("#{}\n".format(time))
|
||||
self.current_time = time
|
||||
|
||||
def set_start_time(self, time):
|
||||
self.start_time = time
|
||||
|
||||
def set_end_time(self, time):
|
||||
pass
|
||||
|
||||
|
||||
class WaveformManager:
|
||||
def __init__(self):
|
||||
self.current_time = 0
|
||||
self.start_time = 0
|
||||
self.end_time = 0
|
||||
self.channels = list()
|
||||
self.current_scope = ""
|
||||
self.trace = {"timescale": 1, "stopped_x": None, "logs": dict(), "data": dict()}
|
||||
|
||||
def set_timescale_ps(self, timescale):
|
||||
self.trace["timescale"] = int(timescale)
|
||||
|
||||
def get_channel(self, name, width, ty, precision=0, unit=""):
|
||||
if ty == WaveformType.LOG:
|
||||
self.trace["logs"][self.current_scope + name] = (ty, width, precision, unit)
|
||||
data = self.trace["data"][self.current_scope + name] = list()
|
||||
channel = WaveformChannel(data, self.current_time)
|
||||
self.channels.append(channel)
|
||||
return channel
|
||||
|
||||
@contextmanager
|
||||
def scope(self, scope, name):
|
||||
old_scope = self.current_scope
|
||||
self.current_scope = scope + "/"
|
||||
yield
|
||||
self.current_scope = old_scope
|
||||
|
||||
def set_time(self, time):
|
||||
time -= self.start_time
|
||||
for channel in self.channels:
|
||||
channel.set_time(time)
|
||||
|
||||
def set_start_time(self, time):
|
||||
self.start_time = time
|
||||
if self.trace["stopped_x"] is not None:
|
||||
self.trace["stopped_x"] = self.end_time - self.start_time
|
||||
|
||||
def set_end_time(self, time):
|
||||
self.end_time = time
|
||||
self.trace["stopped_x"] = self.end_time - self.start_time
|
||||
|
||||
|
||||
class WaveformChannel:
|
||||
def __init__(self, data, current_time):
|
||||
self.data = data
|
||||
self.current_time = current_time
|
||||
|
||||
def set_value(self, value):
|
||||
self.data.append((self.current_time, value))
|
||||
|
||||
def set_value_double(self, x):
|
||||
self.data.append((self.current_time, x))
|
||||
|
||||
def set_time(self, time):
|
||||
self.current_time = time
|
||||
|
||||
def set_log(self, log_message):
|
||||
self.data.append((self.current_time, log_message))
|
||||
|
||||
|
||||
class ChannelSignatureManager:
|
||||
def __init__(self):
|
||||
self.current_scope = ""
|
||||
self.channels = dict()
|
||||
|
||||
def get_channel(self, name, width, ty, precision=0, unit=""):
|
||||
self.channels[self.current_scope + name] = (ty, width, precision, unit)
|
||||
return None
|
||||
|
||||
@contextmanager
|
||||
def scope(self, scope, name):
|
||||
old_scope = self.current_scope
|
||||
self.current_scope = scope + "/"
|
||||
yield
|
||||
self.current_scope = old_scope
|
||||
|
||||
|
||||
class TTLHandler:
|
||||
def __init__(self, vcd_manager, name):
|
||||
def __init__(self, manager, name):
|
||||
self.name = name
|
||||
self.channel_value = vcd_manager.get_channel("ttl/" + name, 1)
|
||||
self.channel_value = manager.get_channel("ttl/" + name, 1, ty=WaveformType.BIT)
|
||||
self.last_value = "X"
|
||||
self.oe = True
|
||||
|
||||
|
@ -206,11 +381,12 @@ class TTLHandler:
|
|||
|
||||
|
||||
class TTLClockGenHandler:
|
||||
def __init__(self, vcd_manager, name, ref_period):
|
||||
def __init__(self, manager, name, ref_period):
|
||||
self.name = name
|
||||
self.ref_period = ref_period
|
||||
self.channel_frequency = vcd_manager.get_channel(
|
||||
"ttl_clkgen/" + name, 64)
|
||||
precision = max(0, math.ceil(math.log10(2**24 * ref_period) + 6))
|
||||
self.channel_frequency = manager.get_channel(
|
||||
"ttl_clkgen/" + name, 64, ty=WaveformType.ANALOG, precision=precision, unit="MHz")
|
||||
|
||||
def process_message(self, message):
|
||||
if isinstance(message, OutputMessage):
|
||||
|
@ -221,8 +397,8 @@ class TTLClockGenHandler:
|
|||
|
||||
|
||||
class DDSHandler:
|
||||
def __init__(self, vcd_manager, onehot_sel, sysclk):
|
||||
self.vcd_manager = vcd_manager
|
||||
def __init__(self, manager, onehot_sel, sysclk):
|
||||
self.manager = manager
|
||||
self.onehot_sel = onehot_sel
|
||||
self.sysclk = sysclk
|
||||
|
||||
|
@ -231,11 +407,18 @@ class DDSHandler:
|
|||
|
||||
def add_dds_channel(self, name, dds_channel_nr):
|
||||
dds_channel = dict()
|
||||
with self.vcd_manager.scope("dds/{}".format(name)):
|
||||
frequency_precision = max(0, math.ceil(math.log10(2**32 / self.sysclk) + 6))
|
||||
phase_precision = max(0, math.ceil(math.log10(2**16)))
|
||||
with self.manager.scope("dds", name):
|
||||
dds_channel["vcd_frequency"] = \
|
||||
self.vcd_manager.get_channel(name + "/frequency", 64)
|
||||
self.manager.get_channel(name + "/frequency", 64,
|
||||
ty=WaveformType.ANALOG,
|
||||
precision=frequency_precision,
|
||||
unit="MHz")
|
||||
dds_channel["vcd_phase"] = \
|
||||
self.vcd_manager.get_channel(name + "/phase", 64)
|
||||
self.manager.get_channel(name + "/phase", 64,
|
||||
ty=WaveformType.ANALOG,
|
||||
precision=phase_precision)
|
||||
dds_channel["ftw"] = [None, None]
|
||||
dds_channel["pow"] = None
|
||||
self.dds_channels[dds_channel_nr] = dds_channel
|
||||
|
@ -285,10 +468,10 @@ class DDSHandler:
|
|||
|
||||
|
||||
class WishboneHandler:
|
||||
def __init__(self, vcd_manager, name, read_bit):
|
||||
def __init__(self, manager, name, read_bit):
|
||||
self._reads = []
|
||||
self._read_bit = read_bit
|
||||
self.stb = vcd_manager.get_channel("{}/{}".format(name, "stb"), 1)
|
||||
self.stb = manager.get_channel(name + "/stb", 1, ty=WaveformType.BIT)
|
||||
|
||||
def process_message(self, message):
|
||||
self.stb.set_value("1")
|
||||
|
@ -318,16 +501,17 @@ class WishboneHandler:
|
|||
|
||||
|
||||
class SPIMasterHandler(WishboneHandler):
|
||||
def __init__(self, vcd_manager, name):
|
||||
def __init__(self, manager, name):
|
||||
self.channels = {}
|
||||
with vcd_manager.scope("spi/{}".format(name)):
|
||||
super().__init__(vcd_manager, name, read_bit=0b100)
|
||||
self.scope = "spi"
|
||||
with manager.scope("spi", name):
|
||||
super().__init__(manager, name, read_bit=0b100)
|
||||
for reg_name, reg_width in [
|
||||
("config", 32), ("chip_select", 16),
|
||||
("write_length", 8), ("read_length", 8),
|
||||
("write", 32), ("read", 32)]:
|
||||
self.channels[reg_name] = vcd_manager.get_channel(
|
||||
"{}/{}".format(name, reg_name), reg_width)
|
||||
self.channels[reg_name] = manager.get_channel(
|
||||
"{}/{}".format(name, reg_name), reg_width, ty=WaveformType.VECTOR)
|
||||
|
||||
def process_write(self, address, data):
|
||||
if address == 0:
|
||||
|
@ -352,11 +536,12 @@ class SPIMasterHandler(WishboneHandler):
|
|||
|
||||
|
||||
class SPIMaster2Handler(WishboneHandler):
|
||||
def __init__(self, vcd_manager, name):
|
||||
def __init__(self, manager, name):
|
||||
self._reads = []
|
||||
self.channels = {}
|
||||
with vcd_manager.scope("spi2/{}".format(name)):
|
||||
self.stb = vcd_manager.get_channel("{}/{}".format(name, "stb"), 1)
|
||||
self.scope = "spi2"
|
||||
with manager.scope("spi2", name):
|
||||
self.stb = manager.get_channel(name + "/stb", 1, ty=WaveformType.BIT)
|
||||
for reg_name, reg_width in [
|
||||
("flags", 8),
|
||||
("length", 5),
|
||||
|
@ -364,8 +549,8 @@ class SPIMaster2Handler(WishboneHandler):
|
|||
("chip_select", 8),
|
||||
("write", 32),
|
||||
("read", 32)]:
|
||||
self.channels[reg_name] = vcd_manager.get_channel(
|
||||
"{}/{}".format(name, reg_name), reg_width)
|
||||
self.channels[reg_name] = manager.get_channel(
|
||||
"{}/{}".format(name, reg_name), reg_width, ty=WaveformType.VECTOR)
|
||||
|
||||
def process_message(self, message):
|
||||
self.stb.set_value("1")
|
||||
|
@ -413,11 +598,12 @@ def _extract_log_chars(data):
|
|||
|
||||
|
||||
class LogHandler:
|
||||
def __init__(self, vcd_manager, vcd_log_channels):
|
||||
self.vcd_channels = dict()
|
||||
for name, maxlength in vcd_log_channels.items():
|
||||
self.vcd_channels[name] = vcd_manager.get_channel("log/" + name,
|
||||
maxlength*8)
|
||||
def __init__(self, manager, log_channels):
|
||||
self.channels = dict()
|
||||
for name, maxlength in log_channels.items():
|
||||
self.channels[name] = manager.get_channel("logs/" + name,
|
||||
maxlength * 8,
|
||||
ty=WaveformType.LOG)
|
||||
self.current_entry = ""
|
||||
|
||||
def process_message(self, message):
|
||||
|
@ -425,15 +611,12 @@ class LogHandler:
|
|||
self.current_entry += _extract_log_chars(message.data)
|
||||
if len(self.current_entry) > 1 and self.current_entry[-1] == "\x1D":
|
||||
channel_name, log_message = self.current_entry[:-1].split("\x1E", maxsplit=1)
|
||||
vcd_value = ""
|
||||
for c in log_message:
|
||||
vcd_value += "{:08b}".format(ord(c))
|
||||
self.vcd_channels[channel_name].set_value(vcd_value)
|
||||
self.channels[channel_name].set_log(log_message)
|
||||
self.current_entry = ""
|
||||
|
||||
|
||||
def get_vcd_log_channels(log_channel, messages):
|
||||
vcd_log_channels = dict()
|
||||
def get_log_channels(log_channel, messages):
|
||||
log_channels = dict()
|
||||
log_entry = ""
|
||||
for message in messages:
|
||||
if (isinstance(message, OutputMessage)
|
||||
|
@ -442,13 +625,13 @@ def get_vcd_log_channels(log_channel, messages):
|
|||
if len(log_entry) > 1 and log_entry[-1] == "\x1D":
|
||||
channel_name, log_message = log_entry[:-1].split("\x1E", maxsplit=1)
|
||||
l = len(log_message)
|
||||
if channel_name in vcd_log_channels:
|
||||
if vcd_log_channels[channel_name] < l:
|
||||
vcd_log_channels[channel_name] = l
|
||||
if channel_name in log_channels:
|
||||
if log_channels[channel_name] < l:
|
||||
log_channels[channel_name] = l
|
||||
else:
|
||||
vcd_log_channels[channel_name] = l
|
||||
log_channels[channel_name] = l
|
||||
log_entry = ""
|
||||
return vcd_log_channels
|
||||
return log_channels
|
||||
|
||||
|
||||
def get_single_device_argument(devices, module, cls, argument):
|
||||
|
@ -475,7 +658,7 @@ def get_dds_sysclk(devices):
|
|||
("AD9914",), "sysclk")
|
||||
|
||||
|
||||
def create_channel_handlers(vcd_manager, devices, ref_period,
|
||||
def create_channel_handlers(manager, devices, ref_period,
|
||||
dds_sysclk, dds_onehot_sel):
|
||||
channel_handlers = dict()
|
||||
for name, desc in sorted(devices.items(), key=itemgetter(0)):
|
||||
|
@ -483,11 +666,11 @@ def create_channel_handlers(vcd_manager, devices, ref_period,
|
|||
if (desc["module"] == "artiq.coredevice.ttl"
|
||||
and desc["class"] in {"TTLOut", "TTLInOut"}):
|
||||
channel = desc["arguments"]["channel"]
|
||||
channel_handlers[channel] = TTLHandler(vcd_manager, name)
|
||||
channel_handlers[channel] = TTLHandler(manager, name)
|
||||
if (desc["module"] == "artiq.coredevice.ttl"
|
||||
and desc["class"] == "TTLClockGen"):
|
||||
channel = desc["arguments"]["channel"]
|
||||
channel_handlers[channel] = TTLClockGenHandler(vcd_manager, name, ref_period)
|
||||
channel_handlers[channel] = TTLClockGenHandler(manager, name, ref_period)
|
||||
if (desc["module"] == "artiq.coredevice.ad9914"
|
||||
and desc["class"] == "AD9914"):
|
||||
dds_bus_channel = desc["arguments"]["bus_channel"]
|
||||
|
@ -495,37 +678,60 @@ def create_channel_handlers(vcd_manager, devices, ref_period,
|
|||
if dds_bus_channel in channel_handlers:
|
||||
dds_handler = channel_handlers[dds_bus_channel]
|
||||
else:
|
||||
dds_handler = DDSHandler(vcd_manager, dds_onehot_sel, dds_sysclk)
|
||||
dds_handler = DDSHandler(manager, dds_onehot_sel, dds_sysclk)
|
||||
channel_handlers[dds_bus_channel] = dds_handler
|
||||
dds_handler.add_dds_channel(name, dds_channel)
|
||||
if (desc["module"] == "artiq.coredevice.spi2" and
|
||||
desc["class"] == "SPIMaster"):
|
||||
channel = desc["arguments"]["channel"]
|
||||
channel_handlers[channel] = SPIMaster2Handler(
|
||||
vcd_manager, name)
|
||||
manager, name)
|
||||
return channel_handlers
|
||||
|
||||
|
||||
def get_channel_list(devices):
|
||||
manager = ChannelSignatureManager()
|
||||
create_channel_handlers(manager, devices, 1e-9, 3e9, False)
|
||||
ref_period = get_ref_period(devices)
|
||||
if ref_period is None:
|
||||
ref_period = DEFAULT_REF_PERIOD
|
||||
precision = max(0, math.ceil(math.log10(1 / ref_period) - 6))
|
||||
manager.get_channel("rtio_slack", 64, ty=WaveformType.ANALOG, precision=precision, unit="us")
|
||||
return manager.channels
|
||||
|
||||
|
||||
def get_message_time(message):
|
||||
return getattr(message, "timestamp", message.rtio_counter)
|
||||
|
||||
|
||||
def decoded_dump_to_vcd(fileobj, devices, dump, uniform_interval=False):
|
||||
vcd_manager = VCDManager(fileobj)
|
||||
decoded_dump_to_target(vcd_manager, devices, dump, uniform_interval)
|
||||
|
||||
|
||||
def decoded_dump_to_waveform_data(devices, dump, uniform_interval=False):
|
||||
manager = WaveformManager()
|
||||
decoded_dump_to_target(manager, devices, dump, uniform_interval)
|
||||
return manager.trace
|
||||
|
||||
|
||||
def decoded_dump_to_target(manager, devices, dump, uniform_interval):
|
||||
ref_period = get_ref_period(devices)
|
||||
|
||||
if ref_period is not None:
|
||||
if not uniform_interval:
|
||||
vcd_manager.set_timescale_ps(ref_period*1e12)
|
||||
else:
|
||||
if ref_period is None:
|
||||
logger.warning("unable to determine core device ref_period")
|
||||
ref_period = 1e-9 # guess
|
||||
ref_period = DEFAULT_REF_PERIOD
|
||||
if not uniform_interval:
|
||||
manager.set_timescale_ps(ref_period*1e12)
|
||||
dds_sysclk = get_dds_sysclk(devices)
|
||||
if dds_sysclk is None:
|
||||
logger.warning("unable to determine DDS sysclk")
|
||||
dds_sysclk = 3e9 # guess
|
||||
|
||||
if isinstance(dump.messages[-1], StoppedMessage):
|
||||
m = dump.messages[-1]
|
||||
end_time = get_message_time(m)
|
||||
manager.set_end_time(end_time)
|
||||
messages = dump.messages[:-1]
|
||||
else:
|
||||
logger.warning("StoppedMessage missing")
|
||||
|
@ -533,38 +739,39 @@ def decoded_dump_to_vcd(fileobj, devices, dump, uniform_interval=False):
|
|||
messages = sorted(messages, key=get_message_time)
|
||||
|
||||
channel_handlers = create_channel_handlers(
|
||||
vcd_manager, devices, ref_period,
|
||||
manager, devices, ref_period,
|
||||
dds_sysclk, dump.dds_onehot_sel)
|
||||
vcd_log_channels = get_vcd_log_channels(dump.log_channel, messages)
|
||||
log_channels = get_log_channels(dump.log_channel, messages)
|
||||
channel_handlers[dump.log_channel] = LogHandler(
|
||||
vcd_manager, vcd_log_channels)
|
||||
manager, log_channels)
|
||||
if uniform_interval:
|
||||
# RTIO event timestamp in machine units
|
||||
timestamp = vcd_manager.get_channel("timestamp", 64)
|
||||
timestamp = manager.get_channel("timestamp", 64, ty=WaveformType.VECTOR)
|
||||
# RTIO time interval between this and the next timed event
|
||||
# in SI seconds
|
||||
interval = vcd_manager.get_channel("interval", 64)
|
||||
slack = vcd_manager.get_channel("rtio_slack", 64)
|
||||
interval = manager.get_channel("interval", 64, ty=WaveformType.ANALOG)
|
||||
slack = manager.get_channel("rtio_slack", 64, ty=WaveformType.ANALOG)
|
||||
|
||||
vcd_manager.set_time(0)
|
||||
manager.set_time(0)
|
||||
start_time = 0
|
||||
for m in messages:
|
||||
start_time = get_message_time(m)
|
||||
if start_time:
|
||||
break
|
||||
|
||||
t0 = 0
|
||||
if not uniform_interval:
|
||||
manager.set_start_time(start_time)
|
||||
t0 = start_time
|
||||
for i, message in enumerate(messages):
|
||||
if message.channel in channel_handlers:
|
||||
t = get_message_time(message) - start_time
|
||||
t = get_message_time(message)
|
||||
if t >= 0:
|
||||
if uniform_interval:
|
||||
interval.set_value_double((t - t0)*ref_period)
|
||||
vcd_manager.set_time(i)
|
||||
manager.set_time(i)
|
||||
timestamp.set_value("{:064b}".format(t))
|
||||
t0 = t
|
||||
else:
|
||||
vcd_manager.set_time(t)
|
||||
manager.set_time(t)
|
||||
channel_handlers[message.channel].process_message(message)
|
||||
if isinstance(message, OutputMessage):
|
||||
slack.set_value_double(
|
||||
|
|
|
@ -23,6 +23,8 @@ class Request(Enum):
|
|||
RPCReply = 7
|
||||
RPCException = 8
|
||||
|
||||
SubkernelUpload = 9
|
||||
|
||||
|
||||
class Reply(Enum):
|
||||
SystemInfo = 2
|
||||
|
@ -208,6 +210,7 @@ class CommKernel:
|
|||
self.unpack_float64 = struct.Struct(self.endian + "d").unpack
|
||||
|
||||
self.pack_header = struct.Struct(self.endian + "lB").pack
|
||||
self.pack_int8 = struct.Struct(self.endian + "B").pack
|
||||
self.pack_int32 = struct.Struct(self.endian + "l").pack
|
||||
self.pack_int64 = struct.Struct(self.endian + "q").pack
|
||||
self.pack_float64 = struct.Struct(self.endian + "d").pack
|
||||
|
@ -322,7 +325,7 @@ class CommKernel:
|
|||
self._write(chunk)
|
||||
|
||||
def _write_int8(self, value):
|
||||
self._write(value)
|
||||
self._write(self.pack_int8(value))
|
||||
|
||||
def _write_int32(self, value):
|
||||
self._write(self.pack_int32(value))
|
||||
|
@ -382,6 +385,19 @@ class CommKernel:
|
|||
else:
|
||||
self._read_expect(Reply.LoadCompleted)
|
||||
|
||||
def upload_subkernel(self, kernel_library, id, destination):
|
||||
self._write_header(Request.SubkernelUpload)
|
||||
self._write_int32(id)
|
||||
self._write_int8(destination)
|
||||
self._write_bytes(kernel_library)
|
||||
self._flush()
|
||||
|
||||
self._read_header()
|
||||
if self._read_type == Reply.LoadFailed:
|
||||
raise LoadError(self._read_string())
|
||||
else:
|
||||
self._read_expect(Reply.LoadCompleted)
|
||||
|
||||
def run(self):
|
||||
self._write_empty(Request.RunKernel)
|
||||
self._flush()
|
||||
|
@ -686,8 +702,14 @@ class CommKernel:
|
|||
else:
|
||||
python_exn_type = embedding_map.retrieve_object(core_exn.id)
|
||||
|
||||
python_exn = python_exn_type(
|
||||
nested_exceptions[-1][1].format(*nested_exceptions[0][2]))
|
||||
try:
|
||||
python_exn = python_exn_type(
|
||||
nested_exceptions[-1][1].format(*nested_exceptions[0][2]))
|
||||
except Exception as ex:
|
||||
python_exn = RuntimeError(
|
||||
f"Exception type={python_exn_type}, which couldn't be "
|
||||
f"reconstructed ({ex})"
|
||||
)
|
||||
python_exn.artiq_core_exception = core_exn
|
||||
raise python_exn
|
||||
|
||||
|
|
|
@ -94,9 +94,7 @@ class CommMonInj:
|
|||
self.injection_status_cb(channel, override, value)
|
||||
else:
|
||||
raise ValueError("Unknown packet type", ty)
|
||||
except asyncio.CancelledError:
|
||||
raise
|
||||
except:
|
||||
except Exception:
|
||||
logger.error("Moninj connection terminating with exception", exc_info=True)
|
||||
finally:
|
||||
if self.disconnect_cb is not None:
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import os, sys
|
||||
import numpy
|
||||
from inspect import getfullargspec
|
||||
from functools import wraps
|
||||
|
||||
from pythonparser import diagnostic
|
||||
|
@ -53,6 +54,17 @@ def rtio_get_counter() -> TInt64:
|
|||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
def get_target_cls(target):
|
||||
if target == "rv32g":
|
||||
return RV32GTarget
|
||||
elif target == "rv32ima":
|
||||
return RV32IMATarget
|
||||
elif target == "cortexa9":
|
||||
return CortexA9Target
|
||||
else:
|
||||
raise ValueError("Unsupported target")
|
||||
|
||||
|
||||
class Core:
|
||||
"""Core device driver.
|
||||
|
||||
|
@ -66,58 +78,72 @@ class Core:
|
|||
:param ref_multiplier: ratio between the RTIO fine timestamp frequency
|
||||
and the RTIO coarse timestamp frequency (e.g. SERDES multiplication
|
||||
factor).
|
||||
:param analyzer_proxy: name of the core device analyzer proxy to trigger
|
||||
(optional).
|
||||
:param analyze_at_run_end: automatically trigger the core device analyzer
|
||||
proxy after the Experiment's run stage finishes.
|
||||
"""
|
||||
|
||||
kernel_invariants = {
|
||||
"core", "ref_period", "coarse_ref_period", "ref_multiplier",
|
||||
}
|
||||
|
||||
def __init__(self, dmgr, host, ref_period, ref_multiplier=8, target="rv32g"):
|
||||
def __init__(self, dmgr,
|
||||
host, ref_period,
|
||||
analyzer_proxy=None, analyze_at_run_end=False,
|
||||
ref_multiplier=8,
|
||||
target="rv32g", satellite_cpu_targets={}):
|
||||
self.ref_period = ref_period
|
||||
self.ref_multiplier = ref_multiplier
|
||||
if target == "rv32g":
|
||||
self.target_cls = RV32GTarget
|
||||
elif target == "rv32ima":
|
||||
self.target_cls = RV32IMATarget
|
||||
elif target == "cortexa9":
|
||||
self.target_cls = CortexA9Target
|
||||
else:
|
||||
raise ValueError("Unsupported target")
|
||||
self.satellite_cpu_targets = satellite_cpu_targets
|
||||
self.target_cls = get_target_cls(target)
|
||||
self.coarse_ref_period = ref_period*ref_multiplier
|
||||
if host is None:
|
||||
self.comm = CommKernelDummy()
|
||||
else:
|
||||
self.comm = CommKernel(host)
|
||||
self.analyzer_proxy_name = analyzer_proxy
|
||||
self.analyze_at_run_end = analyze_at_run_end
|
||||
|
||||
self.first_run = True
|
||||
self.dmgr = dmgr
|
||||
self.core = self
|
||||
self.comm.core = self
|
||||
self.analyzer_proxy = None
|
||||
|
||||
def notify_run_end(self):
|
||||
if self.analyze_at_run_end:
|
||||
self.trigger_analyzer_proxy()
|
||||
|
||||
def close(self):
|
||||
self.comm.close()
|
||||
|
||||
def compile(self, function, args, kwargs, set_result=None,
|
||||
attribute_writeback=True, print_as_rpc=True):
|
||||
attribute_writeback=True, print_as_rpc=True,
|
||||
target=None, destination=0, subkernel_arg_types=[],
|
||||
old_embedding_map=None):
|
||||
try:
|
||||
engine = _DiagnosticEngine(all_errors_are_fatal=True)
|
||||
|
||||
stitcher = Stitcher(engine=engine, core=self, dmgr=self.dmgr,
|
||||
print_as_rpc=print_as_rpc)
|
||||
print_as_rpc=print_as_rpc,
|
||||
destination=destination, subkernel_arg_types=subkernel_arg_types,
|
||||
old_embedding_map=old_embedding_map)
|
||||
stitcher.stitch_call(function, args, kwargs, set_result)
|
||||
stitcher.finalize()
|
||||
|
||||
module = Module(stitcher,
|
||||
ref_period=self.ref_period,
|
||||
attribute_writeback=attribute_writeback)
|
||||
target = self.target_cls()
|
||||
target = target if target is not None else self.target_cls()
|
||||
|
||||
library = target.compile_and_link([module])
|
||||
stripped_library = target.strip(library)
|
||||
|
||||
return stitcher.embedding_map, stripped_library, \
|
||||
lambda addresses: target.symbolize(library, addresses), \
|
||||
lambda symbols: target.demangle(symbols)
|
||||
lambda symbols: target.demangle(symbols), \
|
||||
module.subkernel_arg_types
|
||||
except diagnostic.Error as error:
|
||||
raise CompileError(error.diagnostic) from error
|
||||
|
||||
|
@ -135,11 +161,70 @@ class Core:
|
|||
def set_result(new_result):
|
||||
nonlocal result
|
||||
result = new_result
|
||||
embedding_map, kernel_library, symbolizer, demangler = \
|
||||
embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
|
||||
self.compile(function, args, kwargs, set_result)
|
||||
self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types)
|
||||
self._run_compiled(kernel_library, embedding_map, symbolizer, demangler)
|
||||
return result
|
||||
|
||||
def compile_subkernel(self, sid, subkernel_fn, embedding_map, args, subkernel_arg_types, subkernels):
|
||||
# pass self to subkernels (if applicable)
|
||||
# assuming the first argument is self
|
||||
subkernel_args = getfullargspec(subkernel_fn.artiq_embedded.function)
|
||||
self_arg = []
|
||||
if len(subkernel_args[0]) > 0:
|
||||
if subkernel_args[0][0] == 'self':
|
||||
self_arg = args[:1]
|
||||
destination = subkernel_fn.artiq_embedded.destination
|
||||
destination_tgt = self.satellite_cpu_targets[destination]
|
||||
target = get_target_cls(destination_tgt)(subkernel_id=sid)
|
||||
object_map, kernel_library, _, _, _ = \
|
||||
self.compile(subkernel_fn, self_arg, {}, attribute_writeback=False,
|
||||
print_as_rpc=False, target=target, destination=destination,
|
||||
subkernel_arg_types=subkernel_arg_types.get(sid, []),
|
||||
old_embedding_map=embedding_map)
|
||||
if object_map.has_rpc():
|
||||
raise ValueError("Subkernel must not use RPC")
|
||||
return destination, kernel_library, object_map
|
||||
|
||||
def compile_and_upload_subkernels(self, embedding_map, args, subkernel_arg_types):
|
||||
subkernels = embedding_map.subkernels()
|
||||
subkernels_compiled = []
|
||||
while True:
|
||||
new_subkernels = {}
|
||||
for sid, subkernel_fn in subkernels.items():
|
||||
if sid in subkernels_compiled:
|
||||
continue
|
||||
destination, kernel_library, embedding_map = \
|
||||
self.compile_subkernel(sid, subkernel_fn, embedding_map,
|
||||
args, subkernel_arg_types, subkernels)
|
||||
self.comm.upload_subkernel(kernel_library, sid, destination)
|
||||
new_subkernels.update(embedding_map.subkernels())
|
||||
subkernels_compiled.append(sid)
|
||||
if new_subkernels == subkernels:
|
||||
break
|
||||
subkernels.update(new_subkernels)
|
||||
# check for messages without a send/recv pair
|
||||
unpaired_messages = embedding_map.subkernel_messages_unpaired()
|
||||
if unpaired_messages:
|
||||
for unpaired_message in unpaired_messages:
|
||||
engine = _DiagnosticEngine(all_errors_are_fatal=False)
|
||||
# errors are non-fatal in order to display
|
||||
# all unpaired message errors before raising an excption
|
||||
if unpaired_message.send_loc is None:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"subkernel message '{name}' only has a receiver but no sender",
|
||||
{"name": unpaired_message.name},
|
||||
unpaired_message.recv_loc)
|
||||
else:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"subkernel message '{name}' only has a sender but no receiver",
|
||||
{"name": unpaired_message.name},
|
||||
unpaired_message.send_loc)
|
||||
engine.process(diag)
|
||||
raise ValueError("Found subkernel message(s) without a full send/recv pair")
|
||||
|
||||
|
||||
def precompile(self, function, *args, **kwargs):
|
||||
"""Precompile a kernel and return a callable that executes it on the core device
|
||||
at a later time.
|
||||
|
@ -148,7 +233,7 @@ class Core:
|
|||
as additional positional and keyword arguments.
|
||||
The returned callable accepts no arguments.
|
||||
|
||||
Precompiled kernels may use RPCs.
|
||||
Precompiled kernels may use RPCs and subkernels.
|
||||
|
||||
Object attributes at the beginning of a precompiled kernel execution have the
|
||||
values they had at precompilation time. If up-to-date values are required,
|
||||
|
@ -173,8 +258,9 @@ class Core:
|
|||
nonlocal result
|
||||
result = new_result
|
||||
|
||||
embedding_map, kernel_library, symbolizer, demangler = \
|
||||
embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
|
||||
self.compile(function, args, kwargs, set_result, attribute_writeback=False)
|
||||
self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types)
|
||||
|
||||
@wraps(function)
|
||||
def run_precompiled():
|
||||
|
@ -250,3 +336,21 @@ class Core:
|
|||
min_now = rtio_get_counter() + 125000
|
||||
if now_mu() < min_now:
|
||||
at_mu(min_now)
|
||||
|
||||
def trigger_analyzer_proxy(self):
|
||||
"""Causes the core analyzer proxy to retrieve a dump from the device,
|
||||
and distribute it to all connected clients (typically dashboards).
|
||||
|
||||
Returns only after the dump has been retrieved from the device.
|
||||
|
||||
Raises IOError if no analyzer proxy has been configured, or if the
|
||||
analyzer proxy fails. In the latter case, more details would be
|
||||
available in the proxy log.
|
||||
"""
|
||||
if self.analyzer_proxy is None:
|
||||
if self.analyzer_proxy_name is not None:
|
||||
self.analyzer_proxy = self.dmgr.get(self.analyzer_proxy_name)
|
||||
if self.analyzer_proxy is None:
|
||||
raise IOError("No analyzer proxy configured")
|
||||
else:
|
||||
self.analyzer_proxy.trigger()
|
||||
|
|
|
@ -19,16 +19,24 @@
|
|||
},
|
||||
"min_artiq_version": {
|
||||
"type": "string",
|
||||
"description": "Minimum required ARTIQ version"
|
||||
"description": "Minimum required ARTIQ version",
|
||||
"default": "0"
|
||||
},
|
||||
"hw_rev": {
|
||||
"type": "string",
|
||||
"description": "Hardware revision"
|
||||
},
|
||||
"base": {
|
||||
"type": "string",
|
||||
"enum": ["use_drtio_role", "standalone", "master", "satellite"],
|
||||
"description": "Deprecated, use drtio_role instead",
|
||||
"default": "use_drtio_role"
|
||||
},
|
||||
"drtio_role": {
|
||||
"type": "string",
|
||||
"enum": ["standalone", "master", "satellite"],
|
||||
"description": "SoC base; value depends on intended system topology"
|
||||
"description": "Role that this device takes in a DRTIO network; 'standalone' means no DRTIO",
|
||||
"default": "standalone"
|
||||
},
|
||||
"ext_ref_frequency": {
|
||||
"type": "number",
|
||||
|
@ -41,6 +49,10 @@
|
|||
"default": 125e6,
|
||||
"description": "RTIO frequency"
|
||||
},
|
||||
"enable_wrpll": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"core_addr": {
|
||||
"type": "string",
|
||||
"format": "ipv4",
|
||||
|
@ -122,7 +134,7 @@
|
|||
},
|
||||
"hw_rev": {
|
||||
"type": "string",
|
||||
"enum": ["v1.0"]
|
||||
"enum": ["v1.0", "v1.1"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +146,7 @@
|
|||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp"]
|
||||
"enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"]
|
||||
},
|
||||
"board": {
|
||||
"type": "string"
|
||||
|
@ -204,7 +216,8 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"default": "dio_spi"
|
||||
},
|
||||
"clk": {
|
||||
"type": "integer",
|
||||
|
@ -240,7 +253,8 @@
|
|||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
"type": "string",
|
||||
"default": "ttl"
|
||||
},
|
||||
"pin": {
|
||||
"type": "integer",
|
||||
|
@ -257,7 +271,8 @@
|
|||
}
|
||||
},
|
||||
"required": ["pin", "direction"]
|
||||
}
|
||||
},
|
||||
"default": []
|
||||
}
|
||||
},
|
||||
"required": ["ports", "spi"]
|
||||
|
@ -302,6 +317,12 @@
|
|||
"pll_n": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pll_en": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"default": 1
|
||||
},
|
||||
"pll_vco": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
@ -376,6 +397,11 @@
|
|||
"minItems": 2,
|
||||
"maxItems": 2
|
||||
},
|
||||
"sampler_hw_rev": {
|
||||
"type": "string",
|
||||
"pattern": "^v[0-9]+\\.[0-9]+",
|
||||
"default": "v2.2"
|
||||
},
|
||||
"urukul0_ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
|
@ -405,6 +431,12 @@
|
|||
"type": "integer",
|
||||
"default": 32
|
||||
},
|
||||
"pll_en": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"default": 1
|
||||
},
|
||||
"pll_vco": {
|
||||
"type": "integer"
|
||||
}
|
||||
|
@ -496,6 +528,11 @@
|
|||
"almazny": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"almazny_hw_rev": {
|
||||
"type": "string",
|
||||
"pattern": "^v[0-9]+\\.[0-9]+",
|
||||
"default": "v1.2"
|
||||
}
|
||||
},
|
||||
"required": ["ports"]
|
||||
|
@ -545,6 +582,11 @@
|
|||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 1
|
||||
},
|
||||
"mode": {
|
||||
"type": "string",
|
||||
"enum": ["base", "miqro"],
|
||||
"default": "base"
|
||||
}
|
||||
},
|
||||
"required": ["ports"]
|
||||
|
@ -571,6 +613,31 @@
|
|||
},
|
||||
"required": ["ports"]
|
||||
}
|
||||
},{
|
||||
"title": "Shuttler",
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "shuttler"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 2
|
||||
},
|
||||
"drtio_destination": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"required": ["ports"]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ alone could achieve.
|
|||
"""
|
||||
|
||||
from artiq.language.core import syscall, kernel
|
||||
from artiq.language.types import TInt32, TInt64, TStr, TNone, TTuple
|
||||
from artiq.language.types import TInt32, TInt64, TStr, TNone, TTuple, TBool
|
||||
from artiq.coredevice.exceptions import DMAError
|
||||
|
||||
from numpy import int64
|
||||
|
@ -17,7 +17,7 @@ def dma_record_start(name: TStr) -> TNone:
|
|||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall
|
||||
def dma_record_stop(duration: TInt64) -> TNone:
|
||||
def dma_record_stop(duration: TInt64, enable_ddma: TBool) -> TNone:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall
|
||||
|
@ -25,11 +25,11 @@ def dma_erase(name: TStr) -> TNone:
|
|||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall
|
||||
def dma_retrieve(name: TStr) -> TTuple([TInt64, TInt32]):
|
||||
def dma_retrieve(name: TStr) -> TTuple([TInt64, TInt32, TBool]):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall
|
||||
def dma_playback(timestamp: TInt64, ptr: TInt32) -> TNone:
|
||||
def dma_playback(timestamp: TInt64, ptr: TInt32, enable_ddma: TBool) -> TNone:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
|
@ -47,6 +47,7 @@ class DMARecordContextManager:
|
|||
def __init__(self):
|
||||
self.name = ""
|
||||
self.saved_now_mu = int64(0)
|
||||
self.enable_ddma = False
|
||||
|
||||
@kernel
|
||||
def __enter__(self):
|
||||
|
@ -56,7 +57,7 @@ class DMARecordContextManager:
|
|||
|
||||
@kernel
|
||||
def __exit__(self, type, value, traceback):
|
||||
dma_record_stop(now_mu()) # see above
|
||||
dma_record_stop(now_mu(), self.enable_ddma) # see above
|
||||
at_mu(self.saved_now_mu)
|
||||
|
||||
|
||||
|
@ -74,12 +75,20 @@ class CoreDMA:
|
|||
self.epoch = 0
|
||||
|
||||
@kernel
|
||||
def record(self, name):
|
||||
def record(self, name, enable_ddma=False):
|
||||
"""Returns a context manager that will record a DMA trace called ``name``.
|
||||
Any previously recorded trace with the same name is overwritten.
|
||||
The trace will persist across kernel switches."""
|
||||
The trace will persist across kernel switches.
|
||||
|
||||
In DRTIO context, distributed DMA can be toggled with ``enable_ddma``.
|
||||
Enabling it allows running DMA on satellites, rather than sending all
|
||||
events from the master.
|
||||
|
||||
Keeping it disabled it may improve performance in some scenarios,
|
||||
e.g. when there are many small satellite buffers."""
|
||||
self.epoch += 1
|
||||
self.recorder.name = name
|
||||
self.recorder.enable_ddma = enable_ddma
|
||||
return self.recorder
|
||||
|
||||
@kernel
|
||||
|
@ -92,24 +101,24 @@ class CoreDMA:
|
|||
def playback(self, name):
|
||||
"""Replays a previously recorded DMA trace. This function blocks until
|
||||
the entire trace is submitted to the RTIO FIFOs."""
|
||||
(advance_mu, ptr) = dma_retrieve(name)
|
||||
dma_playback(now_mu(), ptr)
|
||||
(advance_mu, ptr, uses_ddma) = dma_retrieve(name)
|
||||
dma_playback(now_mu(), ptr, uses_ddma)
|
||||
delay_mu(advance_mu)
|
||||
|
||||
@kernel
|
||||
def get_handle(self, name):
|
||||
"""Returns a handle to a previously recorded DMA trace. The returned handle
|
||||
is only valid until the next call to :meth:`record` or :meth:`erase`."""
|
||||
(advance_mu, ptr) = dma_retrieve(name)
|
||||
return (self.epoch, advance_mu, ptr)
|
||||
(advance_mu, ptr, uses_ddma) = dma_retrieve(name)
|
||||
return (self.epoch, advance_mu, ptr, uses_ddma)
|
||||
|
||||
@kernel
|
||||
def playback_handle(self, handle):
|
||||
"""Replays a handle obtained with :meth:`get_handle`. Using this function
|
||||
is much faster than :meth:`playback` for replaying a set of traces repeatedly,
|
||||
but incurs the overhead of managing the handles onto the programmer."""
|
||||
(epoch, advance_mu, ptr) = handle
|
||||
(epoch, advance_mu, ptr, uses_ddma) = handle
|
||||
if self.epoch != epoch:
|
||||
raise DMAError("Invalid handle")
|
||||
dma_playback(now_mu(), ptr)
|
||||
dma_playback(now_mu(), ptr, uses_ddma)
|
||||
delay_mu(advance_mu)
|
||||
|
|
|
@ -91,6 +91,10 @@ class EdgeCounter:
|
|||
self.channel = channel
|
||||
self.counter_max = (1 << (gateware_width - 1)) - 1
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(channel, **kwargs):
|
||||
return [(channel, None)]
|
||||
|
||||
@kernel
|
||||
def gate_rising(self, duration):
|
||||
"""Count rising edges for the given duration and request the total at
|
||||
|
|
|
@ -148,6 +148,13 @@ class DMAError(Exception):
|
|||
artiq_builtin = True
|
||||
|
||||
|
||||
class SubkernelError(Exception):
|
||||
"""Raised when an operation regarding a subkernel is invalid
|
||||
or cannot be completed.
|
||||
"""
|
||||
artiq_builtin = True
|
||||
|
||||
|
||||
class ClockFailure(Exception):
|
||||
"""Raised when RTIO PLL has lost lock."""
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ class Fastino:
|
|||
DAC updates synchronized to a frame edge.
|
||||
|
||||
The `log2_width=0` RTIO layout uses one DAC channel per RTIO address and a
|
||||
dense RTIO address space. The RTIO words are narrow. (32 bit) and
|
||||
dense RTIO address space. The RTIO words are narrow (32 bit) and
|
||||
few-channel updates are efficient. There is the least amount of DAC state
|
||||
tracking in kernels, at the cost of more DMA and RTIO data.
|
||||
The setting here and in the RTIO PHY (gateware) must match.
|
||||
|
@ -52,6 +52,10 @@ class Fastino:
|
|||
assert self.core.ref_period == 1*ns
|
||||
self.t_frame = int64(14*7*4)
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(channel, **kwargs):
|
||||
return [(channel, None)]
|
||||
|
||||
@kernel
|
||||
def init(self):
|
||||
"""Initialize the device.
|
||||
|
|
|
@ -2,7 +2,7 @@ from numpy import int32, int64
|
|||
|
||||
from artiq.language.core import *
|
||||
from artiq.language.types import *
|
||||
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
||||
from artiq.coredevice.rtio import rtio_output, rtio_input_timestamped_data
|
||||
|
||||
|
||||
class OutOfSyncException(Exception):
|
||||
|
@ -11,6 +11,11 @@ class OutOfSyncException(Exception):
|
|||
pass
|
||||
|
||||
|
||||
class GrabberTimeoutException(Exception):
|
||||
"""Raised when a timeout occurs while attempting to read Grabber RTIO input events."""
|
||||
pass
|
||||
|
||||
|
||||
class Grabber:
|
||||
"""Driver for the Grabber camera interface."""
|
||||
kernel_invariants = {"core", "channel_base", "sentinel"}
|
||||
|
@ -25,6 +30,10 @@ class Grabber:
|
|||
# ROI engine outputs for one video frame.
|
||||
self.sentinel = int32(int64(2**count_width))
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(channel_base, **kwargs):
|
||||
return [(channel_base, "ROI coordinates"), (channel_base + 1, "ROI mask")]
|
||||
|
||||
@kernel
|
||||
def setup_roi(self, n, x0, y0, x1, y1):
|
||||
"""
|
||||
|
@ -78,10 +87,10 @@ class Grabber:
|
|||
self.gate_roi(0)
|
||||
|
||||
@kernel
|
||||
def input_mu(self, data):
|
||||
def input_mu(self, data, timeout_mu=-1):
|
||||
"""
|
||||
Retrieves the accumulated values for one frame from the ROI engines.
|
||||
Blocks until values are available.
|
||||
Blocks until values are available or timeout is reached.
|
||||
|
||||
The input list must be a list of integers of the same length as there
|
||||
are enabled ROI engines. This method replaces the elements of the
|
||||
|
@ -91,15 +100,26 @@ class Grabber:
|
|||
If the number of elements in the list does not match the number of
|
||||
ROI engines that produced output, an exception will be raised during
|
||||
this call or the next.
|
||||
|
||||
If the timeout is reached before data is available, the exception
|
||||
GrabberTimeoutException is raised.
|
||||
|
||||
:param timeout_mu: Timestamp at which a timeout will occur. Set to -1
|
||||
(default) to disable timeout.
|
||||
"""
|
||||
channel = self.channel_base + 1
|
||||
|
||||
sentinel = rtio_input_data(channel)
|
||||
timestamp, sentinel = rtio_input_timestamped_data(timeout_mu, channel)
|
||||
if timestamp == -1:
|
||||
raise GrabberTimeoutException("Timeout before Grabber frame available")
|
||||
if sentinel != self.sentinel:
|
||||
raise OutOfSyncException
|
||||
|
||||
for i in range(len(data)):
|
||||
roi_output = rtio_input_data(channel)
|
||||
timestamp, roi_output = rtio_input_timestamped_data(timeout_mu, channel)
|
||||
if roi_output == self.sentinel:
|
||||
raise OutOfSyncException
|
||||
if timestamp == -1:
|
||||
raise GrabberTimeoutException(
|
||||
"Timeout retrieving ROIs (attempting to read more ROIs than enabled?)")
|
||||
data[i] = roi_output
|
||||
|
|
|
@ -161,6 +161,7 @@ class I2CSwitch:
|
|||
@kernel
|
||||
def set(self, channel):
|
||||
"""Enable one channel.
|
||||
|
||||
:param channel: channel number (0-7)
|
||||
"""
|
||||
i2c_switch_select(self.busno, self.address >> 1, 1 << channel)
|
||||
|
|
|
@ -32,4 +32,7 @@ def load(description_path):
|
|||
global validator
|
||||
validator.validate(result)
|
||||
|
||||
if result["base"] != "use_drtio_role":
|
||||
result["drtio_role"] = result["base"]
|
||||
|
||||
return result
|
||||
|
|
|
@ -25,14 +25,14 @@ port_mapping = {
|
|||
|
||||
|
||||
class KasliEEPROM:
|
||||
def __init__(self, dmgr, port, busno=0,
|
||||
def __init__(self, dmgr, port, address=0xa0, busno=0,
|
||||
core_device="core", sw0_device="i2c_switch0", sw1_device="i2c_switch1"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.sw0 = dmgr.get(sw0_device)
|
||||
self.sw1 = dmgr.get(sw1_device)
|
||||
self.busno = busno
|
||||
self.port = port_mapping[port]
|
||||
self.address = 0xa0 # i2c 8 bit
|
||||
self.address = address # i2c 8 bit
|
||||
|
||||
@kernel
|
||||
def select(self):
|
||||
|
|
|
@ -31,16 +31,6 @@ WE = 1 << 24
|
|||
# supported CPLD code version
|
||||
PROTO_REV_MATCH = 0x0
|
||||
|
||||
# almazny-specific data
|
||||
ALMAZNY_REG_BASE = 0x0C
|
||||
ALMAZNY_OE_SHIFT = 12
|
||||
|
||||
# higher SPI write divider to match almazny shift register timing
|
||||
# min SER time before SRCLK rise = 125ns
|
||||
# -> div=32 gives 125ns for data before clock rise
|
||||
# works at faster dividers too but could be less reliable
|
||||
ALMAZNY_SPIT_WR = 32
|
||||
|
||||
|
||||
class Mirny:
|
||||
"""
|
||||
|
@ -177,106 +167,3 @@ class Mirny:
|
|||
if length < 32:
|
||||
data <<= 32 - length
|
||||
self.bus.write(data)
|
||||
|
||||
|
||||
class Almazny:
|
||||
"""
|
||||
Almazny (High frequency mezzanine board for Mirny)
|
||||
|
||||
:param host_mirny - Mirny device Almazny is connected to
|
||||
"""
|
||||
|
||||
def __init__(self, dmgr, host_mirny):
|
||||
self.mirny_cpld = dmgr.get(host_mirny)
|
||||
self.att_mu = [0x3f] * 4
|
||||
self.channel_sw = [0] * 4
|
||||
self.output_enable = False
|
||||
|
||||
@kernel
|
||||
def init(self):
|
||||
self.output_toggle(self.output_enable)
|
||||
|
||||
@kernel
|
||||
def att_to_mu(self, att):
|
||||
"""
|
||||
Convert an attenuator setting in dB to machine units.
|
||||
|
||||
:param att: attenuator setting in dB [0-31.5]
|
||||
:return: attenuator setting in machine units
|
||||
"""
|
||||
mu = round(att * 2.0)
|
||||
if mu > 63 or mu < 0:
|
||||
raise ValueError("Invalid Almazny attenuator settings!")
|
||||
return mu
|
||||
|
||||
@kernel
|
||||
def mu_to_att(self, att_mu):
|
||||
"""
|
||||
Convert a digital attenuator setting to dB.
|
||||
|
||||
:param att_mu: attenuator setting in machine units
|
||||
:return: attenuator setting in dB
|
||||
"""
|
||||
return att_mu / 2
|
||||
|
||||
@kernel
|
||||
def set_att(self, channel, att, rf_switch=True):
|
||||
"""
|
||||
Sets attenuators on chosen shift register (channel).
|
||||
:param channel - index of the register [0-3]
|
||||
:param att_mu - attenuation setting in dBm [0-31.5]
|
||||
:param rf_switch - rf switch (bool)
|
||||
"""
|
||||
self.set_att_mu(channel, self.att_to_mu(att), rf_switch)
|
||||
|
||||
@kernel
|
||||
def set_att_mu(self, channel, att_mu, rf_switch=True):
|
||||
"""
|
||||
Sets attenuators on chosen shift register (channel).
|
||||
:param channel - index of the register [0-3]
|
||||
:param att_mu - attenuation setting in machine units [0-63]
|
||||
:param rf_switch - rf switch (bool)
|
||||
"""
|
||||
self.channel_sw[channel] = 1 if rf_switch else 0
|
||||
self.att_mu[channel] = att_mu
|
||||
self._update_register(channel)
|
||||
|
||||
@kernel
|
||||
def output_toggle(self, oe):
|
||||
"""
|
||||
Toggles output on all shift registers on or off.
|
||||
:param oe - toggle output enable (bool)
|
||||
"""
|
||||
self.output_enable = oe
|
||||
cfg_reg = self.mirny_cpld.read_reg(1)
|
||||
en = 1 if self.output_enable else 0
|
||||
delay(100 * us)
|
||||
new_reg = (en << ALMAZNY_OE_SHIFT) | (cfg_reg & 0x3FF)
|
||||
self.mirny_cpld.write_reg(1, new_reg)
|
||||
delay(100 * us)
|
||||
|
||||
@kernel
|
||||
def _flip_mu_bits(self, mu):
|
||||
# in this form MSB is actually 0.5dB attenuator
|
||||
# unnatural for users, so we flip the six bits
|
||||
return (((mu & 0x01) << 5)
|
||||
| ((mu & 0x02) << 3)
|
||||
| ((mu & 0x04) << 1)
|
||||
| ((mu & 0x08) >> 1)
|
||||
| ((mu & 0x10) >> 3)
|
||||
| ((mu & 0x20) >> 5))
|
||||
|
||||
@kernel
|
||||
def _update_register(self, ch):
|
||||
self.mirny_cpld.write_ext(
|
||||
ALMAZNY_REG_BASE + ch,
|
||||
8,
|
||||
self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6),
|
||||
ALMAZNY_SPIT_WR
|
||||
)
|
||||
delay(100 * us)
|
||||
|
||||
@kernel
|
||||
def _update_all_registers(self):
|
||||
for i in range(4):
|
||||
self._update_register(i)
|
|
@ -9,6 +9,10 @@ from artiq.coredevice.trf372017 import TRF372017
|
|||
|
||||
|
||||
PHASER_BOARD_ID = 19
|
||||
|
||||
PHASER_GW_BASE = 1
|
||||
PHASER_GW_MIQRO = 2
|
||||
|
||||
PHASER_ADDR_BOARD_ID = 0x00
|
||||
PHASER_ADDR_HW_REV = 0x01
|
||||
PHASER_ADDR_GW_REV = 0x02
|
||||
|
@ -40,6 +44,20 @@ PHASER_ADDR_DUC1_P = 0x26
|
|||
PHASER_ADDR_DAC1_DATA = 0x28
|
||||
PHASER_ADDR_DAC1_TEST = 0x2c
|
||||
|
||||
# servo registers
|
||||
PHASER_ADDR_SERVO_CFG0 = 0x30
|
||||
PHASER_ADDR_SERVO_CFG1 = 0x31
|
||||
|
||||
# 0x32 - 0x71 servo coefficients + offset data
|
||||
PHASER_ADDR_SERVO_DATA_BASE = 0x32
|
||||
|
||||
# 0x72 - 0x78 Miqro channel profile/window memories
|
||||
PHASER_ADDR_MIQRO_MEM_ADDR = 0x72
|
||||
PHASER_ADDR_MIQRO_MEM_DATA = 0x74
|
||||
|
||||
# Miqro profile memory select
|
||||
PHASER_MIQRO_SEL_PROFILE = 1 << 14
|
||||
|
||||
PHASER_SEL_DAC = 1 << 0
|
||||
PHASER_SEL_TRF0 = 1 << 1
|
||||
PHASER_SEL_TRF1 = 1 << 2
|
||||
|
@ -58,6 +76,11 @@ PHASER_DAC_SEL_TEST = 1
|
|||
|
||||
PHASER_HW_REV_VARIANT = 1 << 4
|
||||
|
||||
SERVO_COEFF_WIDTH = 16
|
||||
SERVO_DATA_WIDTH = 16
|
||||
SERVO_COEFF_SHIFT = 14
|
||||
SERVO_T_CYCLE = (32+12+192+24+4)*ns # Must match gateware ADC parameters
|
||||
|
||||
|
||||
class Phaser:
|
||||
"""Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver.
|
||||
|
@ -65,6 +88,26 @@ class Phaser:
|
|||
Phaser contains a 4 channel, 1 GS/s DAC chip with integrated upconversion,
|
||||
quadrature modulation compensation and interpolation features.
|
||||
|
||||
The coredevice RTIO PHY and the Phaser gateware come in different modes
|
||||
that have different features. Phaser mode and coredevice PHY mode are both
|
||||
selected at their respective gateware compile-time and need to match.
|
||||
|
||||
=============== ============== ===================================
|
||||
Phaser gateware Coredevice PHY Features per :class:`PhaserChannel`
|
||||
=============== ============== ===================================
|
||||
Base <= v0.5 Base Base (5 :class:`PhaserOscillator`)
|
||||
Base >= v0.6 Base Base + Servo
|
||||
Miqro >= v0.6 Miqro :class:`Miqro`
|
||||
=============== ============== ===================================
|
||||
|
||||
The coredevice driver (this class and :class:`PhaserChannel`) exposes
|
||||
the superset of all functionality regardless of the Coredevice RTIO PHY
|
||||
or Phaser gateware modes. This is to evade type unification limitations.
|
||||
Features absent in Coredevice PHY/Phaser gateware will not work and
|
||||
should not be accessed.
|
||||
|
||||
**Base mode**
|
||||
|
||||
The coredevice produces 2 IQ (in-phase and quadrature) data streams with 25
|
||||
MS/s and 14 bit per quadrature. Each data stream supports 5 independent
|
||||
numerically controlled IQ oscillators (NCOs, DDSs with 32 bit frequency, 16
|
||||
|
@ -95,6 +138,14 @@ class Phaser:
|
|||
absolute phase with respect to other RTIO input and output events
|
||||
(see `get_next_frame_mu()`).
|
||||
|
||||
**Miqro mode**
|
||||
|
||||
See :class:`Miqro`
|
||||
|
||||
Here the DAC operates in 4x interpolation.
|
||||
|
||||
**Analog flow**
|
||||
|
||||
The four analog DAC outputs are passed through anti-aliasing filters.
|
||||
|
||||
In the baseband variant, the even/in-phase DAC channels feed 31.5 dB range
|
||||
|
@ -112,6 +163,33 @@ class Phaser:
|
|||
configured through a shared SPI bus that is accessed and controlled via
|
||||
FPGA registers.
|
||||
|
||||
**Servo**
|
||||
|
||||
Each phaser output channel features a servo to control the RF output amplitude
|
||||
using feedback from an ADC. The servo consists of a first order IIR (infinite
|
||||
impulse response) filter fed by the ADC and a multiplier that scales the I
|
||||
and Q datastreams from the DUC by the IIR output. The IIR state is updated at
|
||||
the 3.788 MHz ADC sampling rate.
|
||||
|
||||
Each channel IIR features 4 profiles, each consisting of the [b0, b1, a1] filter
|
||||
coefficients as well as an output offset. The coefficients and offset can be
|
||||
set for each profile individually and the profiles each have their own ``y0``,
|
||||
``y1`` output registers (the ``x0``, ``x1`` inputs are shared). To avoid
|
||||
transient effects, care should be taken to not update the coefficents in the
|
||||
currently selected profile.
|
||||
|
||||
The servo can be en- or disabled for each channel. When disabled, the servo
|
||||
output multiplier is simply bypassed and the datastream reaches the DAC unscaled.
|
||||
|
||||
The IIR output can be put on hold for each channel. In hold mode, the filter
|
||||
still ingests samples and updates its input ``x0`` and ``x1`` registers, but
|
||||
does not update the ``y0``, ``y1`` output registers.
|
||||
|
||||
After power-up the servo is disabled, in profile 0, with coefficients [0, 0, 0]
|
||||
and hold is enabled. If older gateware without ther servo is loaded onto the
|
||||
Phaser FPGA, the device simply behaves as if the servo is disabled and none of
|
||||
the servo functions have any effect.
|
||||
|
||||
.. note:: Various register settings of the DAC and the quadrature
|
||||
upconverters are available to be modified through the `dac`, `trf0`,
|
||||
`trf1` dictionaries. These can be set through the device database
|
||||
|
@ -151,7 +229,7 @@ class Phaser:
|
|||
"dac_mmap"}
|
||||
|
||||
def __init__(self, dmgr, channel_base, miso_delay=1, tune_fifo_offset=True,
|
||||
clk_sel=0, sync_dly=0, dac=None, trf0=None, trf1=None,
|
||||
clk_sel=0, sync_dly=0, dac=None, trf0=None, trf1=None, gw_rev=PHASER_GW_BASE,
|
||||
core_device="core"):
|
||||
self.channel_base = channel_base
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -165,12 +243,25 @@ class Phaser:
|
|||
self.clk_sel = clk_sel
|
||||
self.tune_fifo_offset = tune_fifo_offset
|
||||
self.sync_dly = sync_dly
|
||||
self.gw_rev = gw_rev # verified in init()
|
||||
|
||||
self.dac_mmap = DAC34H84(dac).get_mmap()
|
||||
|
||||
self.channel = [PhaserChannel(self, ch, trf)
|
||||
for ch, trf in enumerate([trf0, trf1])]
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(channel_base, gw_rev=PHASER_GW_BASE, **kwargs):
|
||||
if gw_rev == PHASER_GW_MIQRO:
|
||||
return [(channel_base, "base"), (channel_base + 1, "ch0"), (channel_base + 2, "ch1")]
|
||||
elif gw_rev == PHASER_GW_BASE:
|
||||
return [(channel_base, "base"),
|
||||
(channel_base + 1, "ch0 frequency"),
|
||||
(channel_base + 2, "ch0 phase amplitude"),
|
||||
(channel_base + 3, "ch1 frequency"),
|
||||
(channel_base + 4, "ch1 phase amplitude")]
|
||||
raise ValueError("invalid gw_rev `{}`".format(gw_rev))
|
||||
|
||||
@kernel
|
||||
def init(self, debug=False):
|
||||
"""Initialize the board.
|
||||
|
@ -190,8 +281,9 @@ class Phaser:
|
|||
|
||||
gw_rev = self.read8(PHASER_ADDR_GW_REV)
|
||||
if debug:
|
||||
print("gw_rev:", gw_rev)
|
||||
print("gw_rev:", self.gw_rev)
|
||||
self.core.break_realtime()
|
||||
assert gw_rev == self.gw_rev
|
||||
delay(.1*ms) # slack
|
||||
|
||||
# allow a few errors during startup and alignment since boot
|
||||
|
@ -237,7 +329,7 @@ class Phaser:
|
|||
|
||||
for data in self.dac_mmap:
|
||||
self.dac_write(data >> 16, data)
|
||||
delay(40*us)
|
||||
delay(120*us)
|
||||
self.dac_sync()
|
||||
delay(40*us)
|
||||
|
||||
|
@ -308,34 +400,40 @@ class Phaser:
|
|||
if channel.get_att_mu() != 0x5a:
|
||||
raise ValueError("attenuator test failed")
|
||||
delay(.1*ms)
|
||||
channel.set_att_mu(0x00) # minimum attenuation
|
||||
channel.set_att_mu(0x00) # maximum attenuation
|
||||
|
||||
# test oscillators and DUC
|
||||
for i in range(len(channel.oscillator)):
|
||||
oscillator = channel.oscillator[i]
|
||||
asf = 0
|
||||
if i == 0:
|
||||
asf = 0x7fff
|
||||
# 6pi/4 phase
|
||||
oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=1)
|
||||
channel.set_servo(profile=0, enable=0, hold=1)
|
||||
|
||||
if self.gw_rev == PHASER_GW_BASE:
|
||||
# test oscillators and DUC
|
||||
for i in range(len(channel.oscillator)):
|
||||
oscillator = channel.oscillator[i]
|
||||
asf = 0
|
||||
if i == 0:
|
||||
asf = 0x7fff
|
||||
# 6pi/4 phase
|
||||
oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=1)
|
||||
delay(1*us)
|
||||
# 3pi/4
|
||||
channel.set_duc_phase_mu(0x6000)
|
||||
channel.set_duc_cfg(select=0, clr=1)
|
||||
self.duc_stb()
|
||||
delay(.1*ms) # settle link, pipeline and impulse response
|
||||
data = channel.get_dac_data()
|
||||
delay(1*us)
|
||||
# 3pi/4
|
||||
channel.set_duc_phase_mu(0x6000)
|
||||
channel.set_duc_cfg(select=0, clr=1)
|
||||
self.duc_stb()
|
||||
delay(.1*ms) # settle link, pipeline and impulse response
|
||||
data = channel.get_dac_data()
|
||||
delay(1*us)
|
||||
channel.oscillator[0].set_amplitude_phase_mu(asf=0, pow=0xc000,
|
||||
clr=1)
|
||||
delay(.1*ms)
|
||||
sqrt2 = 0x5a81 # 0x7fff/sqrt(2)
|
||||
data_i = data & 0xffff
|
||||
data_q = (data >> 16) & 0xffff
|
||||
# allow ripple
|
||||
if (data_i < sqrt2 - 30 or data_i > sqrt2 or
|
||||
abs(data_i - data_q) > 2):
|
||||
raise ValueError("DUC+oscillator phase/amplitude test failed")
|
||||
channel.oscillator[0].set_amplitude_phase_mu(asf=0, pow=0xc000,
|
||||
clr=1)
|
||||
delay(.1*ms)
|
||||
sqrt2 = 0x5a81 # 0x7fff/sqrt(2)
|
||||
data_i = data & 0xffff
|
||||
data_q = (data >> 16) & 0xffff
|
||||
# allow ripple
|
||||
if (data_i < sqrt2 - 30 or data_i > sqrt2 or
|
||||
abs(data_i - data_q) > 2):
|
||||
raise ValueError("DUC+oscillator phase/amplitude test failed")
|
||||
|
||||
if self.gw_rev == PHASER_GW_MIQRO:
|
||||
channel.miqro.reset()
|
||||
|
||||
if is_baseband:
|
||||
continue
|
||||
|
@ -382,6 +480,12 @@ class Phaser:
|
|||
response = rtio_input_data(self.channel_base)
|
||||
return response >> self.miso_delay
|
||||
|
||||
@kernel
|
||||
def write16(self, addr, data: TInt32):
|
||||
"""Write 16 bit to a sequence of FPGA registers."""
|
||||
self.write8(addr, data >> 8)
|
||||
self.write8(addr + 1, data)
|
||||
|
||||
@kernel
|
||||
def write32(self, addr, data: TInt32):
|
||||
"""Write 32 bit to a sequence of FPGA registers."""
|
||||
|
@ -616,7 +720,7 @@ class Phaser:
|
|||
.. note:: Synchronising the NCO clears the phase-accumulator
|
||||
"""
|
||||
config1f = self.dac_read(0x1f)
|
||||
delay(.1*ms)
|
||||
delay(.4*ms)
|
||||
self.dac_write(0x1f, config1f & ~int32(1 << 1))
|
||||
self.dac_write(0x1f, config1f | (1 << 1))
|
||||
|
||||
|
@ -736,6 +840,8 @@ class Phaser:
|
|||
if good & (1 << o):
|
||||
sum += o
|
||||
count += 1
|
||||
if count == 0:
|
||||
raise ValueError("no good fifo offset")
|
||||
best = ((sum // count) + offset) % 8
|
||||
self.dac_write(0x09, (config9 & 0x1fff) | (best << 13))
|
||||
return best
|
||||
|
@ -746,8 +852,9 @@ class PhaserChannel:
|
|||
|
||||
A Phaser channel contains:
|
||||
|
||||
* multiple oscillators (in the coredevice phy),
|
||||
* multiple :class:`PhaserOscillator` (in the coredevice phy),
|
||||
* an interpolation chain and digital upconverter (DUC) on Phaser,
|
||||
* a :class:`Miqro` instance on Phaser,
|
||||
* several channel-specific settings in the DAC:
|
||||
|
||||
* quadrature modulation compensation QMC
|
||||
|
@ -759,6 +866,7 @@ class PhaserChannel:
|
|||
Attributes:
|
||||
|
||||
* :attr:`oscillator`: List of five :class:`PhaserOscillator`.
|
||||
* :attr:`miqro`: A :class:`Miqro`.
|
||||
|
||||
.. note:: The amplitude sum of the oscillators must be less than one to
|
||||
avoid clipping or overflow. If any of the DDS or DUC frequencies are
|
||||
|
@ -771,6 +879,8 @@ class PhaserChannel:
|
|||
changes in oscillator parameters, the overshoot can lead to clipping
|
||||
or overflow after the interpolation. Either band-limit any changes
|
||||
in the oscillator parameters or back off the amplitude sufficiently.
|
||||
Miqro is not affected by this. But both the oscillators and Miqro can
|
||||
be affected by intrinsic overshoot of the interpolator on the DAC.
|
||||
"""
|
||||
kernel_invariants = {"index", "phaser", "trf_mmap"}
|
||||
|
||||
|
@ -780,6 +890,7 @@ class PhaserChannel:
|
|||
self.trf_mmap = TRF372017(trf).get_mmap()
|
||||
|
||||
self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)]
|
||||
self.miqro = Miqro(self)
|
||||
|
||||
@kernel
|
||||
def get_dac_data(self) -> TInt32:
|
||||
|
@ -1039,6 +1150,133 @@ class PhaserChannel:
|
|||
data = data ^ ((1 << 12) | (1 << 13))
|
||||
self.trf_write(data)
|
||||
|
||||
@kernel
|
||||
def set_servo(self, profile=0, enable=0, hold=0):
|
||||
"""Set the servo configuration.
|
||||
|
||||
:param enable: 1 to enable servo, 0 to disable servo (default). If disabled,
|
||||
the servo is bypassed and hold is enforced since the control loop is broken.
|
||||
:param hold: 1 to hold the servo IIR filter output constant, 0 for normal operation.
|
||||
:param profile: Profile index to select for channel. (0 to 3)
|
||||
"""
|
||||
if (profile < 0) or (profile > 3):
|
||||
raise ValueError("invalid profile index")
|
||||
addr = PHASER_ADDR_SERVO_CFG0 + self.index
|
||||
# enforce hold if the servo is disabled
|
||||
data = (profile << 2) | (((hold | ~enable) & 1) << 1) | (enable & 1)
|
||||
self.phaser.write8(addr, data)
|
||||
|
||||
@kernel
|
||||
def set_iir_mu(self, profile, b0, b1, a1, offset):
|
||||
"""Load a servo profile consiting of the three filter coefficients and an output offset.
|
||||
|
||||
Avoid setting the IIR parameters of the currently active profile.
|
||||
|
||||
The recurrence relation is (all data signed and MSB aligned):
|
||||
|
||||
.. math::
|
||||
a_0 y_n = a_1 y_{n - 1} + b_0 x_n + b_1 x_{n - 1} + o
|
||||
|
||||
Where:
|
||||
|
||||
* :math:`y_n` and :math:`y_{n-1}` are the current and previous
|
||||
filter outputs, clipped to :math:`[0, 1[`.
|
||||
* :math:`x_n` and :math:`x_{n-1}` are the current and previous
|
||||
filter inputs in :math:`[-1, 1[`.
|
||||
* :math:`o` is the offset
|
||||
* :math:`a_0` is the normalization factor :math:`2^{14}`
|
||||
* :math:`a_1` is the feedback gain
|
||||
* :math:`b_0` and :math:`b_1` are the feedforward gains for the two
|
||||
delays
|
||||
|
||||
.. seealso:: :meth:`set_iir`
|
||||
|
||||
:param profile: Profile to set (0 to 3)
|
||||
:param b0: b0 filter coefficient (16 bit signed)
|
||||
:param b1: b1 filter coefficient (16 bit signed)
|
||||
:param a1: a1 filter coefficient (16 bit signed)
|
||||
:param offset: Output offset (16 bit signed)
|
||||
"""
|
||||
if (profile < 0) or (profile > 3):
|
||||
raise ValueError("invalid profile index")
|
||||
# 32 byte-sized data registers per channel and 8 (2 bytes * (3 coefficients + 1 offset)) registers per profile
|
||||
addr = PHASER_ADDR_SERVO_DATA_BASE + (8 * profile) + (self.index * 32)
|
||||
for data in [b0, b1, a1, offset]:
|
||||
self.phaser.write16(addr, data)
|
||||
addr += 2
|
||||
|
||||
@kernel
|
||||
def set_iir(self, profile, kp, ki=0., g=0., x_offset=0., y_offset=0.):
|
||||
"""Set servo profile IIR coefficients.
|
||||
|
||||
Avoid setting the IIR parameters of the currently active profile.
|
||||
|
||||
Gains are given in units of output full per scale per input full scale.
|
||||
|
||||
.. note:: Due to inherent constraints of the fixed point datatypes and IIR
|
||||
filters, the ``x_offset`` (setpoint) resolution depends on the selected
|
||||
gains. Low ``ki`` gains will lead to a low ``x_offset`` resolution.
|
||||
|
||||
The transfer function is (up to time discretization and
|
||||
coefficient quantization errors):
|
||||
|
||||
.. math::
|
||||
H(s) = k_p + \\frac{k_i}{s + \\frac{k_i}{g}}
|
||||
|
||||
Where:
|
||||
* :math:`s = \\sigma + i\\omega` is the complex frequency
|
||||
* :math:`k_p` is the proportional gain
|
||||
* :math:`k_i` is the integrator gain
|
||||
* :math:`g` is the integrator gain limit
|
||||
|
||||
:param profile: Profile number (0-3)
|
||||
:param kp: Proportional gain. This is usually negative (closed
|
||||
loop, positive ADC voltage, positive setpoint). When 0, this
|
||||
implements a pure I controller.
|
||||
:param ki: Integrator gain (rad/s). Equivalent to the gain at 1 Hz.
|
||||
When 0 (the default) this implements a pure P controller.
|
||||
Same sign as ``kp``.
|
||||
:param g: Integrator gain limit (1). When 0 (the default) the
|
||||
integrator gain limit is infinite. Same sign as ``ki``.
|
||||
:param x_offset: IIR input offset. Used as the negative
|
||||
setpoint when stabilizing to a desired input setpoint. Will
|
||||
be converted to an equivalent output offset and added to y_offset.
|
||||
:param y_offset: IIR output offset.
|
||||
"""
|
||||
NORM = 1 << SERVO_COEFF_SHIFT
|
||||
COEFF_MAX = 1 << SERVO_COEFF_WIDTH - 1
|
||||
DATA_MAX = 1 << SERVO_DATA_WIDTH - 1
|
||||
|
||||
kp *= NORM
|
||||
if ki == 0.:
|
||||
# pure P
|
||||
a1 = 0
|
||||
b1 = 0
|
||||
b0 = int(round(kp))
|
||||
else:
|
||||
# I or PI
|
||||
ki *= NORM*SERVO_T_CYCLE/2.
|
||||
if g == 0.:
|
||||
c = 1.
|
||||
a1 = NORM
|
||||
else:
|
||||
c = 1./(1. + ki/(g*NORM))
|
||||
a1 = int(round((2.*c - 1.)*NORM))
|
||||
b0 = int(round(kp + ki*c))
|
||||
b1 = int(round(kp + (ki - 2.*kp)*c))
|
||||
if b1 == -b0:
|
||||
raise ValueError("low integrator gain and/or gain limit")
|
||||
|
||||
if (b0 >= COEFF_MAX or b0 < -COEFF_MAX or
|
||||
b1 >= COEFF_MAX or b1 < -COEFF_MAX):
|
||||
raise ValueError("high gains")
|
||||
|
||||
forward_gain = (b0 + b1) * (1 << SERVO_DATA_WIDTH - 1 - SERVO_COEFF_SHIFT)
|
||||
effective_offset = int(round(DATA_MAX * y_offset + forward_gain * x_offset))
|
||||
|
||||
self.set_iir_mu(profile, b0, b1, a1, effective_offset)
|
||||
|
||||
|
||||
|
||||
class PhaserOscillator:
|
||||
"""Phaser IQ channel oscillator (NCO/DDS).
|
||||
|
@ -1096,3 +1334,305 @@ class PhaserOscillator:
|
|||
raise ValueError("amplitude out of bounds")
|
||||
pow = int32(round(phase*(1 << 16)))
|
||||
self.set_amplitude_phase_mu(asf, pow, clr)
|
||||
|
||||
|
||||
class Miqro:
|
||||
"""
|
||||
Miqro pulse generator.
|
||||
|
||||
A Miqro instance represents one RF output. The DSP components are fully
|
||||
contained in the Phaser gateware. The output is generated by with
|
||||
the following data flow:
|
||||
|
||||
**Oscillators**
|
||||
|
||||
* There are n_osc = 16 oscillators with oscillator IDs 0..n_osc-1.
|
||||
* Each oscillator outputs one tone at any given time
|
||||
|
||||
* I/Q (quadrature, a.k.a. complex) 2x16 bit signed data
|
||||
at tau = 4 ns sample intervals, 250 MS/s, Nyquist 125 MHz, bandwidth 200 MHz
|
||||
(from f = -100..+100 MHz, taking into account the interpolation anti-aliasing
|
||||
filters in subsequent interpolators),
|
||||
* 32 bit frequency (f) resolution (~ 1/16 Hz),
|
||||
* 16 bit unsigned amplitude (a) resolution
|
||||
* 16 bit phase offset (p) resolution
|
||||
|
||||
* The output phase p' of each oscillator at time t (boot/reset/initialization of the
|
||||
device at t=0) is then p' = f*t + p (mod 1 turn) where f and p are the (currently
|
||||
active) profile frequency and phase offset.
|
||||
* Note: The terms "phase coherent" and "phase tracking" are defined to refer to this
|
||||
choice of oscillator output phase p'. Note that the phase offset p is not relative to
|
||||
(on top of previous phase/profiles/oscillator history).
|
||||
It is "absolute" in the sense that frequency f and phase offset p fully determine
|
||||
oscillator output phase p' at time t. This is unlike typical DDS behavior.
|
||||
* Frequency, phase, and amplitude of each oscillator are configurable by selecting one of
|
||||
n_profile = 32 profiles 0..n_profile-1. This selection is fast and can be done for
|
||||
each pulse. The phase coherence defined above is guaranteed for each
|
||||
profile individually.
|
||||
* Note: one profile per oscillator (usually profile index 0) should be reserved
|
||||
for the NOP (no operation, identity) profile, usually with zero amplitude.
|
||||
* Data for each profile for each oscillator can be configured
|
||||
individually. Storing profile data should be considered "expensive".
|
||||
* Note: The annotation that some operation is "expensive" does not mean it is
|
||||
impossible, just that it may take a significant amount of time and
|
||||
resources to execute such that it may be impractical when used often or
|
||||
during fast pulse sequences. They are intended for use in calibration and
|
||||
initialization.
|
||||
|
||||
**Summation**
|
||||
|
||||
* The oscillator outputs are added together (wrapping addition).
|
||||
* The user must ensure that the sum of oscillators outputs does not exceed the
|
||||
data range. In general that means that the sum of the amplitudes must not
|
||||
exceed one.
|
||||
|
||||
**Shaper**
|
||||
|
||||
* The summed complex output stream is then multiplied with a the complex-valued
|
||||
output of a triggerable shaper.
|
||||
* Triggering the shaper corresponds to passing a pulse from all oscillators to
|
||||
the RF output.
|
||||
* Selected profiles become active simultaneously (on the same output sample) when
|
||||
triggering the shaper with the first shaper output sample.
|
||||
* The shaper reads (replays) window samples from a memory of size n_window = 1 << 10.
|
||||
* The window memory can be segmented by choosing different start indices
|
||||
to support different windows.
|
||||
* Each window memory segment starts with a header determining segment
|
||||
length and interpolation parameters.
|
||||
* The window samples are interpolated by a factor (rate change) between 1 and
|
||||
r = 1 << 12.
|
||||
* The interpolation order is constant, linear, quadratic, or cubic. This
|
||||
corresponds to interpolation modes from rectangular window (1st order CIC)
|
||||
or zero order hold) to Parzen window (4th order CIC or cubic spline).
|
||||
* This results in support for single shot pulse lengths (envelope support) between
|
||||
tau and a bit more than r * n_window * tau = (1 << 12 + 10) tau ~ 17 ms.
|
||||
* Windows can be configured to be head-less and/or tail-less, meaning, they
|
||||
do not feed zero-amplitude samples into the shaper before and after
|
||||
each window respectively. This is used to implement pulses with arbitrary
|
||||
length or CW output.
|
||||
|
||||
**Overall properties**
|
||||
|
||||
* The DAC may upconvert the signal by applying a frequency offset f1 with
|
||||
phase p1.
|
||||
* In the Upconverter Phaser variant, the analog quadrature upconverter
|
||||
applies another frequency of f2 and phase p2.
|
||||
* The resulting phase of the signal from one oscillator at the SMA output is
|
||||
(f + f1 + f2)*t + p + s(t - t0) + p1 + p2 (mod 1 turn)
|
||||
where s(t - t0) is the phase of the interpolated
|
||||
shaper output, and t0 is the trigger time (fiducial of the shaper).
|
||||
Unsurprisingly the frequency is the derivative of the phase.
|
||||
* Group delays between pulse parameter updates are matched across oscillators,
|
||||
shapers, and channels.
|
||||
* The minimum time to change profiles and phase offsets is ~128 ns (estimate, TBC).
|
||||
This is the minimum pulse interval.
|
||||
The sustained pulse rate of the RTIO PHY/Fastlink is one pulse per Fastlink frame
|
||||
(may be increased, TBC).
|
||||
"""
|
||||
|
||||
def __init__(self, channel):
|
||||
self.channel = channel
|
||||
self.base_addr = (self.channel.phaser.channel_base + 1 +
|
||||
self.channel.index) << 8
|
||||
|
||||
@kernel
|
||||
def reset(self):
|
||||
"""Establish no-output profiles and no-output window and execute them.
|
||||
|
||||
This establishes the first profile (index 0) on all oscillators as zero
|
||||
amplitude, creates a trivial window (one sample with zero amplitude,
|
||||
minimal interpolation), and executes a corresponding pulse.
|
||||
"""
|
||||
for osc in range(16):
|
||||
self.set_profile_mu(osc, profile=0, ftw=0, asf=0)
|
||||
delay(20*us)
|
||||
self.set_window_mu(start=0, iq=[0], order=0)
|
||||
self.pulse(window=0, profiles=[0])
|
||||
|
||||
@kernel
|
||||
def set_profile_mu(self, oscillator, profile, ftw, asf, pow_=0):
|
||||
"""Store an oscillator profile (machine units).
|
||||
|
||||
:param oscillator: Oscillator index (0 to 15)
|
||||
:param profile: Profile index (0 to 31)
|
||||
:param ftw: Frequency tuning word (32 bit signed integer on a 250 MHz clock)
|
||||
:param asf: Amplitude scale factor (16 bit unsigned integer)
|
||||
:param pow_: Phase offset word (16 bit integer)
|
||||
"""
|
||||
if oscillator >= 16:
|
||||
raise ValueError("invalid oscillator index")
|
||||
if profile >= 32:
|
||||
raise ValueError("invalid profile index")
|
||||
self.channel.phaser.write16(PHASER_ADDR_MIQRO_MEM_ADDR,
|
||||
(self.channel.index << 15) | PHASER_MIQRO_SEL_PROFILE |
|
||||
(oscillator << 6) | (profile << 1))
|
||||
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA, ftw)
|
||||
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA,
|
||||
(asf & 0xffff) | (pow_ << 16))
|
||||
|
||||
@kernel
|
||||
def set_profile(self, oscillator, profile, frequency, amplitude, phase=0.):
|
||||
"""Store an oscillator profile.
|
||||
|
||||
:param oscillator: Oscillator index (0 to 15)
|
||||
:param profile: Profile index (0 to 31)
|
||||
:param frequency: Frequency in Hz (passband -100 to 100 MHz).
|
||||
Interpreted in the Nyquist sense, i.e. aliased.
|
||||
:param amplitude: Amplitude in units of full scale (0. to 1.)
|
||||
:param phase: Phase in turns. See :class:`Miqro` for a definition of
|
||||
phase in this context.
|
||||
:return: The quantized 32 bit frequency tuning word
|
||||
"""
|
||||
ftw = int32(round(frequency*((1 << 30)/(62.5*MHz))))
|
||||
asf = int32(round(amplitude*0xffff))
|
||||
if asf < 0 or asf > 0xffff:
|
||||
raise ValueError("amplitude out of bounds")
|
||||
pow_ = int32(round(phase*(1 << 16)))
|
||||
self.set_profile_mu(oscillator, profile, ftw, asf, pow_)
|
||||
return ftw
|
||||
|
||||
@kernel
|
||||
def set_window_mu(self, start, iq, rate=1, shift=0, order=3, head=1, tail=1):
|
||||
"""Store a window segment (machine units)
|
||||
|
||||
:param start: Window start address (0 to 0x3ff)
|
||||
:param iq: List of IQ window samples. Each window sample is an integer
|
||||
containing the signed I part in the 16 LSB and the signed Q part in
|
||||
the 16 MSB. The maximum window length is 0x3fe. The user must
|
||||
ensure that this window does not overlap with other windows in the
|
||||
memory.
|
||||
:param rate: Interpolation rate change (1 to 1 << 12)
|
||||
:param shift: Interpolator amplitude gain compensation in powers of 2 (0 to 63)
|
||||
:param order: Interpolation order from 0 (corresponding to
|
||||
constant/rectangular window/zero-order-hold/1st order CIC interpolation)
|
||||
to 3 (corresponding to cubic/Parzen window/4th order CIC interpolation)
|
||||
:param head: Update the interpolator settings and clear its state at the start
|
||||
of the window. This also implies starting the envelope from zero.
|
||||
:param tail: Feed zeros into the interpolator after the window samples.
|
||||
In the absence of further pulses this will return the output envelope
|
||||
to zero with the chosen interpolation.
|
||||
:return: Next available window memory address after this segment.
|
||||
"""
|
||||
if start >= 1 << 10:
|
||||
raise ValueError("start out of bounds")
|
||||
if len(iq) >= 1 << 10:
|
||||
raise ValueError("window length out of bounds")
|
||||
if rate < 1 or rate > 1 << 12:
|
||||
raise ValueError("rate out of bounds")
|
||||
if shift > 0x3f:
|
||||
raise ValueError("shift out of bounds")
|
||||
if order > 3:
|
||||
raise ValueError("order out of bounds")
|
||||
self.channel.phaser.write16(PHASER_ADDR_MIQRO_MEM_ADDR,
|
||||
(self.channel.index << 15) | start)
|
||||
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA,
|
||||
(len(iq) & 0x3ff) |
|
||||
((rate - 1) << 10) |
|
||||
(shift << 22) |
|
||||
(order << 28) |
|
||||
((head & 1) << 30) |
|
||||
((tail & 1) << 31)
|
||||
)
|
||||
for iqi in iq:
|
||||
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA, iqi)
|
||||
delay(20*us) # slack for long windows
|
||||
return (start + 1 + len(iq)) & 0x3ff
|
||||
|
||||
@kernel
|
||||
def set_window(self, start, iq, period=4*ns, order=3, head=1, tail=1):
|
||||
"""Store a window segment
|
||||
|
||||
:param start: Window start address (0 to 0x3ff)
|
||||
:param iq: List of IQ window samples. Each window sample is a pair of
|
||||
two float numbers -1 to 1, one for each I and Q in units of full scale.
|
||||
The maximum window length is 0x3fe. The user must ensure that this window
|
||||
does not overlap with other windows in the memory.
|
||||
:param period: Desired window sample period in SI units (4*ns to (4 << 12)*ns).
|
||||
:param order: Interpolation order from 0 (corresponding to
|
||||
constant/zero-order-hold/1st order CIC interpolation) to 3 (corresponding
|
||||
to cubic/Parzen/4th order CIC interpolation)
|
||||
:param head: Update the interpolator settings and clear its state at the start
|
||||
of the window. This also implies starting the envelope from zero.
|
||||
:param tail: Feed zeros into the interpolator after the window samples.
|
||||
In the absence of further pulses this will return the output envelope
|
||||
to zero with the chosen interpolation.
|
||||
:return: Actual sample period in SI units
|
||||
"""
|
||||
rate = int32(round(period/(4*ns)))
|
||||
gain = 1.
|
||||
for _ in range(order):
|
||||
gain *= rate
|
||||
shift = 0
|
||||
while gain >= 2.:
|
||||
shift += 1
|
||||
gain *= .5
|
||||
scale = ((1 << 15) - 1)/gain
|
||||
iq_mu = [
|
||||
(int32(round(iqi[0]*scale)) & 0xffff) |
|
||||
(int32(round(iqi[1]*scale)) << 16)
|
||||
for iqi in iq
|
||||
]
|
||||
self.set_window_mu(start, iq_mu, rate, shift, order, head, tail)
|
||||
return (len(iq) + order)*rate*4*ns
|
||||
|
||||
@kernel
|
||||
def encode(self, window, profiles, data):
|
||||
"""Encode window and profile selection
|
||||
|
||||
:param window: Window start address (0 to 0x3ff)
|
||||
:param profiles: List of profile indices for the oscillators. Maximum
|
||||
length 16. Unused oscillators will be set to profile 0.
|
||||
:param data: List of integers to store the encoded data words into.
|
||||
Unused entries will remain untouched. Must contain at least three
|
||||
lements if all oscillators are used and should be initialized to
|
||||
zeros.
|
||||
:return: Number of words from `data` used.
|
||||
"""
|
||||
if len(profiles) > 16:
|
||||
raise ValueError("too many oscillators")
|
||||
if window > 0x3ff:
|
||||
raise ValueError("window start out of bounds")
|
||||
data[0] = window
|
||||
word = 0
|
||||
idx = 10
|
||||
for profile in profiles:
|
||||
if profile > 0x1f:
|
||||
raise ValueError("profile out of bounds")
|
||||
if idx > 32 - 5:
|
||||
word += 1
|
||||
idx = 0
|
||||
data[word] |= profile << idx
|
||||
idx += 5
|
||||
return word + 1
|
||||
|
||||
@kernel
|
||||
def pulse_mu(self, data):
|
||||
"""Emit a pulse (encoded)
|
||||
|
||||
The pulse fiducial timing resolution is 4 ns.
|
||||
|
||||
:param data: List of up to 3 words containing an encoded MIQRO pulse as
|
||||
returned by :meth:`encode`.
|
||||
"""
|
||||
word = len(data)
|
||||
delay_mu(-8*word) # back shift to align
|
||||
while word > 0:
|
||||
word -= 1
|
||||
delay_mu(8)
|
||||
# final write sets pulse stb
|
||||
rtio_output(self.base_addr + word, data[word])
|
||||
|
||||
@kernel
|
||||
def pulse(self, window, profiles):
|
||||
"""Emit a pulse
|
||||
|
||||
This encodes the window and profiles (see :meth:`encode`) and emits them
|
||||
(see :meth:`pulse_mu`).
|
||||
|
||||
:param window: Window start address (0 to 0x3ff)
|
||||
:param profiles: List of profile indices for the oscillators. Maximum
|
||||
length 16. Unused oscillators will select profile 0.
|
||||
"""
|
||||
data = [0, 0, 0]
|
||||
words = self.encode(window, profiles, data)
|
||||
self.pulse_mu(data[:words])
|
||||
|
|
|
@ -15,24 +15,26 @@ SPI_CS_PGIA = 1 # separate SPI bus, CS used as RCLK
|
|||
|
||||
|
||||
@portable
|
||||
def adc_mu_to_volt(data, gain=0):
|
||||
def adc_mu_to_volt(data, gain=0, corrected_fs=True):
|
||||
"""Convert ADC data in machine units to Volts.
|
||||
|
||||
:param data: 16 bit signed ADC word
|
||||
:param gain: PGIA gain setting (0: 1, ..., 3: 1000)
|
||||
:param corrected_fs: use corrected ADC FS reference.
|
||||
Should be True for Samplers' revisions after v2.1. False for v2.1 and earlier.
|
||||
:return: Voltage in Volts
|
||||
"""
|
||||
if gain == 0:
|
||||
volt_per_lsb = 20./(1 << 16)
|
||||
volt_per_lsb = 20.48 / (1 << 16) if corrected_fs else 20. / (1 << 16)
|
||||
elif gain == 1:
|
||||
volt_per_lsb = 2./(1 << 16)
|
||||
volt_per_lsb = 2.048 / (1 << 16) if corrected_fs else 2. / (1 << 16)
|
||||
elif gain == 2:
|
||||
volt_per_lsb = .2/(1 << 16)
|
||||
volt_per_lsb = .2048 / (1 << 16) if corrected_fs else .2 / (1 << 16)
|
||||
elif gain == 3:
|
||||
volt_per_lsb = .02/(1 << 16)
|
||||
volt_per_lsb = 0.02048 / (1 << 16) if corrected_fs else .02 / (1 << 16)
|
||||
else:
|
||||
raise ValueError("invalid gain")
|
||||
return data*volt_per_lsb
|
||||
return data * volt_per_lsb
|
||||
|
||||
|
||||
class Sampler:
|
||||
|
@ -48,12 +50,13 @@ class Sampler:
|
|||
:param gains: Initial value for PGIA gains shift register
|
||||
(default: 0x0000). Knowledge of this state is not transferred
|
||||
between experiments.
|
||||
:param hw_rev: Sampler's hardware revision string (default 'v2.2')
|
||||
:param core_device: Core device name
|
||||
"""
|
||||
kernel_invariants = {"bus_adc", "bus_pgia", "core", "cnv", "div"}
|
||||
kernel_invariants = {"bus_adc", "bus_pgia", "core", "cnv", "div", "corrected_fs"}
|
||||
|
||||
def __init__(self, dmgr, spi_adc_device, spi_pgia_device, cnv_device,
|
||||
div=8, gains=0x0000, core_device="core"):
|
||||
div=8, gains=0x0000, hw_rev="v2.2", core_device="core"):
|
||||
self.bus_adc = dmgr.get(spi_adc_device)
|
||||
self.bus_adc.update_xfer_duration_mu(div, 32)
|
||||
self.bus_pgia = dmgr.get(spi_pgia_device)
|
||||
|
@ -62,6 +65,11 @@ class Sampler:
|
|||
self.cnv = dmgr.get(cnv_device)
|
||||
self.div = div
|
||||
self.gains = gains
|
||||
self.corrected_fs = self.use_corrected_fs(hw_rev)
|
||||
|
||||
@staticmethod
|
||||
def use_corrected_fs(hw_rev):
|
||||
return hw_rev != "v2.1"
|
||||
|
||||
@kernel
|
||||
def init(self):
|
||||
|
@ -144,4 +152,4 @@ class Sampler:
|
|||
for i in range(n):
|
||||
channel = i + 8 - len(data)
|
||||
gain = (self.gains >> (channel*2)) & 0b11
|
||||
data[i] = adc_mu_to_volt(adc_data[i], gain)
|
||||
data[i] = adc_mu_to_volt(adc_data[i], gain, self.corrected_fs)
|
||||
|
|
|
@ -1,372 +0,0 @@
|
|||
"""
|
||||
Driver for the Smart Arbitrary Waveform Generator (SAWG) on RTIO.
|
||||
|
||||
The SAWG is an "improved DDS" built in gateware and interfacing to
|
||||
high-speed DACs.
|
||||
|
||||
Output event replacement is supported except on the configuration channel.
|
||||
"""
|
||||
|
||||
|
||||
from artiq.language.types import TInt32, TFloat
|
||||
from numpy import int32, int64
|
||||
from artiq.language.core import kernel
|
||||
from artiq.coredevice.spline import Spline
|
||||
from artiq.coredevice.rtio import rtio_output
|
||||
|
||||
|
||||
# sawg.Config addresses
|
||||
_SAWG_DIV = 0
|
||||
_SAWG_CLR = 1
|
||||
_SAWG_IQ_EN = 2
|
||||
# _SAWF_PAD = 3 # reserved
|
||||
_SAWG_OUT_MIN = 4
|
||||
_SAWG_OUT_MAX = 5
|
||||
_SAWG_DUC_MIN = 6
|
||||
_SAWG_DUC_MAX = 7
|
||||
|
||||
|
||||
class Config:
|
||||
"""SAWG configuration.
|
||||
|
||||
Exposes the configurable quantities of a single SAWG channel.
|
||||
|
||||
Access to the configuration registers for a SAWG channel can not
|
||||
be concurrent. There must be at least :attr:`_rtio_interval` machine
|
||||
units of delay between accesses. Replacement is not supported and will be
|
||||
lead to an ``RTIOCollision`` as this is likely a programming error.
|
||||
All methods therefore advance the timeline by the duration of one
|
||||
configuration register transfer.
|
||||
|
||||
:param channel: RTIO channel number of the channel.
|
||||
:param core: Core device.
|
||||
"""
|
||||
kernel_invariants = {"channel", "core", "_out_scale", "_duc_scale",
|
||||
"_rtio_interval"}
|
||||
|
||||
def __init__(self, channel, core, cordic_gain=1.):
|
||||
self.channel = channel
|
||||
self.core = core
|
||||
# normalized DAC output
|
||||
self._out_scale = (1 << 15) - 1.
|
||||
# normalized DAC output including DUC cordic gain
|
||||
self._duc_scale = self._out_scale/cordic_gain
|
||||
# configuration channel access interval
|
||||
self._rtio_interval = int64(3*self.core.ref_multiplier)
|
||||
|
||||
@kernel
|
||||
def set_div(self, div: TInt32, n: TInt32=0):
|
||||
"""Set the spline evolution divider and current counter value.
|
||||
|
||||
The divider and the spline evolution are synchronized across all
|
||||
spline channels within a SAWG channel. The DDS/DUC phase accumulators
|
||||
always evolves at full speed.
|
||||
|
||||
.. note:: The spline evolution divider has not been tested extensively
|
||||
and is currently considered a technological preview only.
|
||||
|
||||
:param div: Spline evolution divider, such that
|
||||
``t_sawg_spline/t_rtio_coarse = div + 1``. Default: ``0``.
|
||||
:param n: Current value of the counter. Default: ``0``.
|
||||
"""
|
||||
rtio_output((self.channel << 8) | _SAWG_DIV, div | (n << 16))
|
||||
delay_mu(self._rtio_interval)
|
||||
|
||||
@kernel
|
||||
def set_clr(self, clr0: TInt32, clr1: TInt32, clr2: TInt32):
|
||||
"""Set the accumulator clear mode for the three phase accumulators.
|
||||
|
||||
When the ``clr`` bit for a given DDS/DUC phase accumulator is
|
||||
set, that phase accumulator will be cleared with every phase offset
|
||||
RTIO command and the output phase of the DDS/DUC will be
|
||||
exactly the phase RTIO value ("absolute phase update mode").
|
||||
|
||||
.. math::
|
||||
q^\prime(t) = p^\prime + (t - t^\prime) f^\prime
|
||||
|
||||
In turn, when the bit is cleared, the phase RTIO channels
|
||||
determine a phase offset to the current (carrier-) value of the
|
||||
DDS/DUC phase accumulator. This "relative phase update mode" is
|
||||
sometimes also called “continuous phase mode”.
|
||||
|
||||
.. math::
|
||||
q^\prime(t) = q(t^\prime) + (p^\prime - p) +
|
||||
(t - t^\prime) f^\prime
|
||||
|
||||
Where:
|
||||
|
||||
* :math:`q`, :math:`q^\prime`: old/new phase accumulator
|
||||
* :math:`p`, :math:`p^\prime`: old/new phase offset
|
||||
* :math:`f^\prime`: new frequency
|
||||
* :math:`t^\prime`: timestamp of setting new :math:`p`, :math:`f`
|
||||
* :math:`t`: running time
|
||||
|
||||
:param clr0: Auto-clear phase accumulator of the ``phase0``/
|
||||
``frequency0`` DUC. Default: ``True``
|
||||
:param clr1: Auto-clear phase accumulator of the ``phase1``/
|
||||
``frequency1`` DDS. Default: ``True``
|
||||
:param clr2: Auto-clear phase accumulator of the ``phase2``/
|
||||
``frequency2`` DDS. Default: ``True``
|
||||
"""
|
||||
rtio_output((self.channel << 8) | _SAWG_CLR, clr0 |
|
||||
(clr1 << 1) | (clr2 << 2))
|
||||
delay_mu(self._rtio_interval)
|
||||
|
||||
@kernel
|
||||
def set_iq_en(self, i_enable: TInt32, q_enable: TInt32):
|
||||
"""Enable I/Q data on this DAC channel.
|
||||
|
||||
Every pair of SAWG channels forms a buddy pair.
|
||||
The ``iq_en`` configuration controls which DDS data is emitted to the
|
||||
DACs.
|
||||
|
||||
Refer to the documentation of :class:`SAWG` for a mathematical
|
||||
description of ``i_enable`` and ``q_enable``.
|
||||
|
||||
.. note:: Quadrature data from the buddy channel is currently
|
||||
a technological preview only. The data is ignored in the SAWG
|
||||
gateware and not added to the DAC output.
|
||||
This is equivalent to the ``q_enable`` switch always being ``0``.
|
||||
|
||||
:param i_enable: Controls adding the in-phase
|
||||
DUC-DDS data of *this* SAWG channel to *this* DAC channel.
|
||||
Default: ``1``.
|
||||
:param q_enable: controls adding the quadrature
|
||||
DUC-DDS data of this SAWG's *buddy* channel to *this* DAC
|
||||
channel. Default: ``0``.
|
||||
"""
|
||||
rtio_output((self.channel << 8) | _SAWG_IQ_EN, i_enable |
|
||||
(q_enable << 1))
|
||||
delay_mu(self._rtio_interval)
|
||||
|
||||
@kernel
|
||||
def set_duc_max_mu(self, limit: TInt32):
|
||||
"""Set the digital up-converter (DUC) I and Q data summing junctions
|
||||
upper limit. In machine units.
|
||||
|
||||
The default limits are chosen to reach maximum and minimum DAC output
|
||||
amplitude.
|
||||
|
||||
For a description of the limiter functions in normalized units see:
|
||||
|
||||
.. seealso:: :meth:`set_duc_max`
|
||||
"""
|
||||
rtio_output((self.channel << 8) | _SAWG_DUC_MAX, limit)
|
||||
delay_mu(self._rtio_interval)
|
||||
|
||||
@kernel
|
||||
def set_duc_min_mu(self, limit: TInt32):
|
||||
""".. seealso:: :meth:`set_duc_max_mu`"""
|
||||
rtio_output((self.channel << 8) | _SAWG_DUC_MIN, limit)
|
||||
delay_mu(self._rtio_interval)
|
||||
|
||||
@kernel
|
||||
def set_out_max_mu(self, limit: TInt32):
|
||||
""".. seealso:: :meth:`set_duc_max_mu`"""
|
||||
rtio_output((self.channel << 8) | _SAWG_OUT_MAX, limit)
|
||||
delay_mu(self._rtio_interval)
|
||||
|
||||
@kernel
|
||||
def set_out_min_mu(self, limit: TInt32):
|
||||
""".. seealso:: :meth:`set_duc_max_mu`"""
|
||||
rtio_output((self.channel << 8) | _SAWG_OUT_MIN, limit)
|
||||
delay_mu(self._rtio_interval)
|
||||
|
||||
@kernel
|
||||
def set_duc_max(self, limit: TFloat):
|
||||
"""Set the digital up-converter (DUC) I and Q data summing junctions
|
||||
upper limit.
|
||||
|
||||
Each of the three summing junctions has a saturating adder with
|
||||
configurable upper and lower limits. The three summing junctions are:
|
||||
|
||||
* At the in-phase input to the ``phase0``/``frequency0`` fast DUC,
|
||||
after the anti-aliasing FIR filter.
|
||||
* At the quadrature input to the ``phase0``/``frequency0``
|
||||
fast DUC, after the anti-aliasing FIR filter. The in-phase and
|
||||
quadrature data paths both use the same limits.
|
||||
* Before the DAC, where the following three data streams
|
||||
are added together:
|
||||
|
||||
* the output of the ``offset`` spline,
|
||||
* (optionally, depending on ``i_enable``) the in-phase output
|
||||
of the ``phase0``/``frequency0`` fast DUC, and
|
||||
* (optionally, depending on ``q_enable``) the quadrature
|
||||
output of the ``phase0``/``frequency0`` fast DUC of the
|
||||
buddy channel.
|
||||
|
||||
Refer to the documentation of :class:`SAWG` for a mathematical
|
||||
description of the summing junctions.
|
||||
|
||||
:param limit: Limit value ``[-1, 1]``. The output of the limiter will
|
||||
never exceed this limit. The default limits are the full range
|
||||
``[-1, 1]``.
|
||||
|
||||
.. seealso::
|
||||
* :meth:`set_duc_max`: Upper limit of the in-phase and quadrature
|
||||
inputs to the DUC.
|
||||
* :meth:`set_duc_min`: Lower limit of the in-phase and quadrature
|
||||
inputs to the DUC.
|
||||
* :meth:`set_out_max`: Upper limit of the DAC output.
|
||||
* :meth:`set_out_min`: Lower limit of the DAC output.
|
||||
"""
|
||||
self.set_duc_max_mu(int32(round(limit*self._duc_scale)))
|
||||
|
||||
@kernel
|
||||
def set_duc_min(self, limit: TFloat):
|
||||
""".. seealso:: :meth:`set_duc_max`"""
|
||||
self.set_duc_min_mu(int32(round(limit*self._duc_scale)))
|
||||
|
||||
@kernel
|
||||
def set_out_max(self, limit: TFloat):
|
||||
""".. seealso:: :meth:`set_duc_max`"""
|
||||
self.set_out_max_mu(int32(round(limit*self._out_scale)))
|
||||
|
||||
@kernel
|
||||
def set_out_min(self, limit: TFloat):
|
||||
""".. seealso:: :meth:`set_duc_max`"""
|
||||
self.set_out_min_mu(int32(round(limit*self._out_scale)))
|
||||
|
||||
|
||||
class SAWG:
|
||||
"""Smart arbitrary waveform generator channel.
|
||||
The channel is parametrized as: ::
|
||||
|
||||
oscillators = exp(2j*pi*(frequency0*t + phase0))*(
|
||||
amplitude1*exp(2j*pi*(frequency1*t + phase1)) +
|
||||
amplitude2*exp(2j*pi*(frequency2*t + phase2)))
|
||||
|
||||
output = (offset +
|
||||
i_enable*Re(oscillators) +
|
||||
q_enable*Im(buddy_oscillators))
|
||||
|
||||
This parametrization can be viewed as two complex (quadrature) oscillators
|
||||
(``frequency1``/``phase1`` and ``frequency2``/``phase2``) that are
|
||||
executing and sampling at the coarse RTIO frequency. They can represent
|
||||
frequencies within the first Nyquist zone from ``-f_rtio_coarse/2`` to
|
||||
``f_rtio_coarse/2``.
|
||||
|
||||
.. note:: The coarse RTIO frequency ``f_rtio_coarse`` is the inverse of
|
||||
``ref_period*multiplier``. Both are arguments of the ``Core`` device,
|
||||
specified in the device database ``device_db.py``.
|
||||
|
||||
The sum of their outputs is then interpolated by a factor of
|
||||
:attr:`parallelism` (2, 4, 8 depending on the bitstream) using a
|
||||
finite-impulse-response (FIR) anti-aliasing filter (more accurately
|
||||
a half-band filter).
|
||||
|
||||
The filter is followed by a configurable saturating limiter.
|
||||
|
||||
After the limiter, the data is shifted in frequency using a complex
|
||||
digital up-converter (DUC, ``frequency0``/``phase0``) running at
|
||||
:attr:`parallelism` times the coarse RTIO frequency. The first Nyquist
|
||||
zone of the DUC extends from ``-f_rtio_coarse*parallelism/2`` to
|
||||
``f_rtio_coarse*parallelism/2``. Other Nyquist zones are usable depending
|
||||
on the interpolation/modulation options configured in the DAC.
|
||||
|
||||
The real/in-phase data after digital up-conversion can be offset using
|
||||
another spline interpolator ``offset``.
|
||||
|
||||
The ``i_enable``/``q_enable`` switches enable emission of quadrature
|
||||
signals for later analog quadrature mixing distinguishing upper and lower
|
||||
sidebands and thus doubling the bandwidth. They can also be used to emit
|
||||
four-tone signals.
|
||||
|
||||
.. note:: Quadrature data from the buddy channel is currently
|
||||
ignored in the SAWG gateware and not added to the DAC output.
|
||||
This is equivalent to the ``q_enable`` switch always being ``0``.
|
||||
|
||||
The configuration channel and the nine
|
||||
:class:`artiq.coredevice.spline.Spline` interpolators are accessible as
|
||||
attributes:
|
||||
|
||||
* :attr:`config`: :class:`Config`
|
||||
* :attr:`offset`, :attr:`amplitude1`, :attr:`amplitude2`: in units
|
||||
of full scale
|
||||
* :attr:`phase0`, :attr:`phase1`, :attr:`phase2`: in units of turns
|
||||
* :attr:`frequency0`, :attr:`frequency1`, :attr:`frequency2`: in units
|
||||
of Hz
|
||||
|
||||
.. note:: The latencies (pipeline depths) of the nine data channels (i.e.
|
||||
all except :attr:`config`) are matched. Equivalent channels (e.g.
|
||||
:attr:`phase1` and :attr:`phase2`) are exactly matched. Channels of
|
||||
different type or functionality (e.g. :attr:`offset` vs
|
||||
:attr:`amplitude1`, DDS vs DUC, :attr:`phase0` vs :attr:`phase1`) are
|
||||
only matched to within one coarse RTIO cycle.
|
||||
|
||||
:param channel_base: RTIO channel number of the first channel (amplitude).
|
||||
The configuration channel and frequency/phase/amplitude channels are
|
||||
then assumed to be successive channels.
|
||||
:param parallelism: Number of output samples per coarse RTIO clock cycle.
|
||||
:param core_device: Name of the core device that this SAWG is on.
|
||||
"""
|
||||
kernel_invariants = {"channel_base", "core", "parallelism",
|
||||
"amplitude1", "frequency1", "phase1",
|
||||
"amplitude2", "frequency2", "phase2",
|
||||
"frequency0", "phase0", "offset"}
|
||||
|
||||
def __init__(self, dmgr, channel_base, parallelism, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.channel_base = channel_base
|
||||
self.parallelism = parallelism
|
||||
width = 16
|
||||
time_width = 16
|
||||
cordic_gain = 1.646760258057163 # Cordic(width=16, guard=None).gain
|
||||
head_room = 1.001
|
||||
self.config = Config(channel_base, self.core, cordic_gain)
|
||||
self.offset = Spline(width, time_width, channel_base + 1,
|
||||
self.core, 2.*head_room)
|
||||
self.amplitude1 = Spline(width, time_width, channel_base + 2,
|
||||
self.core, 2*head_room*cordic_gain**2)
|
||||
self.frequency1 = Spline(3*width, time_width, channel_base + 3,
|
||||
self.core, 1/self.core.coarse_ref_period)
|
||||
self.phase1 = Spline(width, time_width, channel_base + 4,
|
||||
self.core, 1.)
|
||||
self.amplitude2 = Spline(width, time_width, channel_base + 5,
|
||||
self.core, 2*head_room*cordic_gain**2)
|
||||
self.frequency2 = Spline(3*width, time_width, channel_base + 6,
|
||||
self.core, 1/self.core.coarse_ref_period)
|
||||
self.phase2 = Spline(width, time_width, channel_base + 7,
|
||||
self.core, 1.)
|
||||
self.frequency0 = Spline(2*width, time_width, channel_base + 8,
|
||||
self.core,
|
||||
parallelism/self.core.coarse_ref_period)
|
||||
self.phase0 = Spline(width, time_width, channel_base + 9,
|
||||
self.core, 1.)
|
||||
|
||||
@kernel
|
||||
def reset(self):
|
||||
"""Re-establish initial conditions.
|
||||
|
||||
This clears all spline interpolators, accumulators and configuration
|
||||
settings.
|
||||
|
||||
This method advances the timeline by the time required to perform all
|
||||
7 writes to the configuration channel, plus 9 coarse RTIO cycles.
|
||||
"""
|
||||
self.config.set_div(0, 0)
|
||||
self.config.set_clr(1, 1, 1)
|
||||
self.config.set_iq_en(1, 0)
|
||||
self.config.set_duc_min(-1.)
|
||||
self.config.set_duc_max(1.)
|
||||
self.config.set_out_min(-1.)
|
||||
self.config.set_out_max(1.)
|
||||
self.frequency0.set_mu(0)
|
||||
coarse_cycle = int64(self.core.ref_multiplier)
|
||||
delay_mu(coarse_cycle)
|
||||
self.frequency1.set_mu(0)
|
||||
delay_mu(coarse_cycle)
|
||||
self.frequency2.set_mu(0)
|
||||
delay_mu(coarse_cycle)
|
||||
self.phase0.set_mu(0)
|
||||
delay_mu(coarse_cycle)
|
||||
self.phase1.set_mu(0)
|
||||
delay_mu(coarse_cycle)
|
||||
self.phase2.set_mu(0)
|
||||
delay_mu(coarse_cycle)
|
||||
self.amplitude1.set_mu(0)
|
||||
delay_mu(coarse_cycle)
|
||||
self.amplitude2.set_mu(0)
|
||||
delay_mu(coarse_cycle)
|
||||
self.offset.set_mu(0)
|
||||
delay_mu(coarse_cycle)
|
|
@ -1,54 +0,0 @@
|
|||
from artiq.language.core import kernel, delay
|
||||
from artiq.language.units import us
|
||||
|
||||
|
||||
class ShiftReg:
|
||||
"""Driver for shift registers/latch combos connected to TTLs"""
|
||||
kernel_invariants = {"dt", "n"}
|
||||
|
||||
def __init__(self, dmgr, clk, ser, latch, n=32, dt=10*us, ser_in=None):
|
||||
self.core = dmgr.get("core")
|
||||
self.clk = dmgr.get(clk)
|
||||
self.ser = dmgr.get(ser)
|
||||
self.latch = dmgr.get(latch)
|
||||
self.n = n
|
||||
self.dt = dt
|
||||
if ser_in is not None:
|
||||
self.ser_in = dmgr.get(ser_in)
|
||||
|
||||
@kernel
|
||||
def set(self, data):
|
||||
"""Sets the values of the latch outputs. This does not
|
||||
advance the timeline and the waveform is generated before
|
||||
`now`."""
|
||||
delay(-2*(self.n + 1)*self.dt)
|
||||
for i in range(self.n):
|
||||
if (data >> (self.n-i-1)) & 1 == 0:
|
||||
self.ser.off()
|
||||
else:
|
||||
self.ser.on()
|
||||
self.clk.off()
|
||||
delay(self.dt)
|
||||
self.clk.on()
|
||||
delay(self.dt)
|
||||
self.clk.off()
|
||||
self.latch.on()
|
||||
delay(self.dt)
|
||||
self.latch.off()
|
||||
delay(self.dt)
|
||||
|
||||
@kernel
|
||||
def get(self):
|
||||
delay(-2*(self.n + 1)*self.dt)
|
||||
data = 0
|
||||
for i in range(self.n):
|
||||
data <<= 1
|
||||
self.ser_in.sample_input()
|
||||
if self.ser_in.sample_get():
|
||||
data |= 1
|
||||
delay(self.dt)
|
||||
self.clk.on()
|
||||
delay(self.dt)
|
||||
self.clk.off()
|
||||
delay(self.dt)
|
||||
return data
|
|
@ -0,0 +1,623 @@
|
|||
from artiq.language.core import *
|
||||
from artiq.language.types import *
|
||||
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from artiq.language.units import us
|
||||
|
||||
|
||||
@portable
|
||||
def shuttler_volt_to_mu(volt):
|
||||
"""Return the equivalent DAC code. Valid input range is from -10 to
|
||||
10 - LSB.
|
||||
"""
|
||||
return round((1 << 16) * (volt / 20.0)) & 0xffff
|
||||
|
||||
|
||||
class Config:
|
||||
"""Shuttler configuration registers interface.
|
||||
|
||||
The configuration registers control waveform phase auto-clear, and pre-DAC
|
||||
gain & offset values for calibration with ADC on the Shuttler AFE card.
|
||||
|
||||
To find the calibrated DAC code, the Shuttler Core first multiplies the
|
||||
output data with pre-DAC gain, then adds the offset.
|
||||
|
||||
.. note::
|
||||
The DAC code is capped at 0x7fff and 0x8000.
|
||||
|
||||
:param channel: RTIO channel number of this interface.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariants = {
|
||||
"core", "channel", "target_base", "target_read",
|
||||
"target_gain", "target_offset", "target_clr"
|
||||
}
|
||||
|
||||
def __init__(self, dmgr, channel, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.channel = channel
|
||||
self.target_base = channel << 8
|
||||
self.target_read = 1 << 6
|
||||
self.target_gain = 0 * (1 << 4)
|
||||
self.target_offset = 1 * (1 << 4)
|
||||
self.target_clr = 1 * (1 << 5)
|
||||
|
||||
@kernel
|
||||
def set_clr(self, clr):
|
||||
"""Set/Unset waveform phase clear bits.
|
||||
|
||||
Each bit corresponds to a Shuttler waveform generator core. Setting a
|
||||
clear bit forces the Shuttler Core to clear the phase accumulator on
|
||||
waveform trigger (See :class:`Trigger` for the trigger method).
|
||||
Otherwise, the phase accumulator increments from its original value.
|
||||
|
||||
:param clr: Waveform phase clear bits. The MSB corresponds to Channel
|
||||
15, LSB corresponds to Channel 0.
|
||||
"""
|
||||
rtio_output(self.target_base | self.target_clr, clr)
|
||||
|
||||
@kernel
|
||||
def set_gain(self, channel, gain):
|
||||
"""Set the 16-bits pre-DAC gain register of a Shuttler Core channel.
|
||||
|
||||
The `gain` parameter represents the decimal portion of the gain
|
||||
factor. The MSB represents 0.5 and the sign bit. Hence, the valid
|
||||
total gain value (1 +/- 0.gain) ranges from 0.5 to 1.5 - LSB.
|
||||
|
||||
:param channel: Shuttler Core channel to be configured.
|
||||
:param gain: Shuttler Core channel gain.
|
||||
"""
|
||||
rtio_output(self.target_base | self.target_gain | channel, gain)
|
||||
|
||||
@kernel
|
||||
def get_gain(self, channel):
|
||||
"""Return the pre-DAC gain value of a Shuttler Core channel.
|
||||
|
||||
:param channel: The Shuttler Core channel.
|
||||
:return: Pre-DAC gain value. See :meth:`set_gain`.
|
||||
"""
|
||||
rtio_output(self.target_base | self.target_gain |
|
||||
self.target_read | channel, 0)
|
||||
return rtio_input_data(self.channel)
|
||||
|
||||
@kernel
|
||||
def set_offset(self, channel, offset):
|
||||
"""Set the 16-bits pre-DAC offset register of a Shuttler Core channel.
|
||||
|
||||
.. seealso::
|
||||
:meth:`shuttler_volt_to_mu`
|
||||
|
||||
:param channel: Shuttler Core channel to be configured.
|
||||
:param offset: Shuttler Core channel offset.
|
||||
"""
|
||||
rtio_output(self.target_base | self.target_offset | channel, offset)
|
||||
|
||||
@kernel
|
||||
def get_offset(self, channel):
|
||||
"""Return the pre-DAC offset value of a Shuttler Core channel.
|
||||
|
||||
:param channel: The Shuttler Core channel.
|
||||
:return: Pre-DAC offset value. See :meth:`set_offset`.
|
||||
"""
|
||||
rtio_output(self.target_base | self.target_offset |
|
||||
self.target_read | channel, 0)
|
||||
return rtio_input_data(self.channel)
|
||||
|
||||
|
||||
class DCBias:
|
||||
"""Shuttler Core cubic DC-bias spline.
|
||||
|
||||
A Shuttler channel can generate a waveform `w(t)` that is the sum of a
|
||||
cubic spline `a(t)` and a sinusoid modulated in amplitude by a cubic
|
||||
spline `b(t)` and in phase/frequency by a quadratic spline `c(t)`, where
|
||||
|
||||
.. math::
|
||||
w(t) = a(t) + b(t) * cos(c(t))
|
||||
|
||||
And `t` corresponds to time in seconds.
|
||||
This class controls the cubic spline `a(t)`, in which
|
||||
|
||||
.. math::
|
||||
a(t) = p_0 + p_1t + \\frac{p_2t^2}{2} + \\frac{p_3t^3}{6}
|
||||
|
||||
And `a(t)` is in Volt.
|
||||
|
||||
:param channel: RTIO channel number of this DC-bias spline interface.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariants = {"core", "channel", "target_o"}
|
||||
|
||||
def __init__(self, dmgr, channel, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.channel = channel
|
||||
self.target_o = channel << 8
|
||||
|
||||
@kernel
|
||||
def set_waveform(self, a0: TInt32, a1: TInt32, a2: TInt64, a3: TInt64):
|
||||
"""Set the DC-bias spline waveform.
|
||||
|
||||
Given `a(t)` as defined in :class:`DCBias`, the coefficients should be
|
||||
configured by the following formulae.
|
||||
|
||||
.. math::
|
||||
T &= 8*10^{-9}
|
||||
|
||||
a_0 &= p_0
|
||||
|
||||
a_1 &= p_1T + \\frac{p_2T^2}{2} + \\frac{p_3T^3}{6}
|
||||
|
||||
a_2 &= p_2T^2 + p_3T^3
|
||||
|
||||
a_3 &= p_3T^3
|
||||
|
||||
:math:`a_0`, :math:`a_1`, :math:`a_2` and :math:`a_3` are 16, 32, 48
|
||||
and 48 bits in width respectively. See :meth:`shuttler_volt_to_mu` for
|
||||
machine unit conversion.
|
||||
|
||||
Note: The waveform is not updated to the Shuttler Core until
|
||||
triggered. See :class:`Trigger` for the update triggering mechanism.
|
||||
|
||||
:param a0: The :math:`a_0` coefficient in machine unit.
|
||||
:param a1: The :math:`a_1` coefficient in machine unit.
|
||||
:param a2: The :math:`a_2` coefficient in machine unit.
|
||||
:param a3: The :math:`a_3` coefficient in machine unit.
|
||||
"""
|
||||
coef_words = [
|
||||
a0,
|
||||
a1,
|
||||
a1 >> 16,
|
||||
a2 & 0xFFFF,
|
||||
(a2 >> 16) & 0xFFFF,
|
||||
(a2 >> 32) & 0xFFFF,
|
||||
a3 & 0xFFFF,
|
||||
(a3 >> 16) & 0xFFFF,
|
||||
(a3 >> 32) & 0xFFFF,
|
||||
]
|
||||
|
||||
for i in range(len(coef_words)):
|
||||
rtio_output(self.target_o | i, coef_words[i])
|
||||
delay_mu(int64(self.core.ref_multiplier))
|
||||
|
||||
|
||||
class DDS:
|
||||
"""Shuttler Core DDS spline.
|
||||
|
||||
A Shuttler channel can generate a waveform `w(t)` that is the sum of a
|
||||
cubic spline `a(t)` and a sinusoid modulated in amplitude by a cubic
|
||||
spline `b(t)` and in phase/frequency by a quadratic spline `c(t)`, where
|
||||
|
||||
.. math::
|
||||
w(t) = a(t) + b(t) * cos(c(t))
|
||||
|
||||
And `t` corresponds to time in seconds.
|
||||
This class controls the cubic spline `b(t)` and quadratic spline `c(t)`,
|
||||
in which
|
||||
|
||||
.. math::
|
||||
b(t) &= g * (q_0 + q_1t + \\frac{q_2t^2}{2} + \\frac{q_3t^3}{6})
|
||||
|
||||
c(t) &= r_0 + r_1t + \\frac{r_2t^2}{2}
|
||||
|
||||
And `b(t)` is in Volt, `c(t)` is in number of turns. Note that `b(t)`
|
||||
contributes to a constant gain of :math:`g=1.64676`.
|
||||
|
||||
:param channel: RTIO channel number of this DC-bias spline interface.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariants = {"core", "channel", "target_o"}
|
||||
|
||||
def __init__(self, dmgr, channel, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.channel = channel
|
||||
self.target_o = channel << 8
|
||||
|
||||
@kernel
|
||||
def set_waveform(self, b0: TInt32, b1: TInt32, b2: TInt64, b3: TInt64,
|
||||
c0: TInt32, c1: TInt32, c2: TInt32):
|
||||
"""Set the DDS spline waveform.
|
||||
|
||||
Given `b(t)` and `c(t)` as defined in :class:`DDS`, the coefficients
|
||||
should be configured by the following formulae.
|
||||
|
||||
.. math::
|
||||
T &= 8*10^{-9}
|
||||
|
||||
b_0 &= q_0
|
||||
|
||||
b_1 &= q_1T + \\frac{q_2T^2}{2} + \\frac{q_3T^3}{6}
|
||||
|
||||
b_2 &= q_2T^2 + q_3T^3
|
||||
|
||||
b_3 &= q_3T^3
|
||||
|
||||
c_0 &= r_0
|
||||
|
||||
c_1 &= r_1T + \\frac{r_2T^2}{2}
|
||||
|
||||
c_2 &= r_2T^2
|
||||
|
||||
:math:`b_0`, :math:`b_1`, :math:`b_2` and :math:`b_3` are 16, 32, 48
|
||||
and 48 bits in width respectively. See :meth:`shuttler_volt_to_mu` for
|
||||
machine unit conversion. :math:`c_0`, :math:`c_1` and :math:`c_2` are
|
||||
16, 32 and 32 bits in width respectively.
|
||||
|
||||
Note: The waveform is not updated to the Shuttler Core until
|
||||
triggered. See :class:`Trigger` for the update triggering mechanism.
|
||||
|
||||
:param b0: The :math:`b_0` coefficient in machine unit.
|
||||
:param b1: The :math:`b_1` coefficient in machine unit.
|
||||
:param b2: The :math:`b_2` coefficient in machine unit.
|
||||
:param b3: The :math:`b_3` coefficient in machine unit.
|
||||
:param c0: The :math:`c_0` coefficient in machine unit.
|
||||
:param c1: The :math:`c_1` coefficient in machine unit.
|
||||
:param c2: The :math:`c_2` coefficient in machine unit.
|
||||
"""
|
||||
coef_words = [
|
||||
b0,
|
||||
b1,
|
||||
b1 >> 16,
|
||||
b2 & 0xFFFF,
|
||||
(b2 >> 16) & 0xFFFF,
|
||||
(b2 >> 32) & 0xFFFF,
|
||||
b3 & 0xFFFF,
|
||||
(b3 >> 16) & 0xFFFF,
|
||||
(b3 >> 32) & 0xFFFF,
|
||||
c0,
|
||||
c1,
|
||||
c1 >> 16,
|
||||
c2,
|
||||
c2 >> 16,
|
||||
]
|
||||
|
||||
for i in range(len(coef_words)):
|
||||
rtio_output(self.target_o | i, coef_words[i])
|
||||
delay_mu(int64(self.core.ref_multiplier))
|
||||
|
||||
|
||||
class Trigger:
|
||||
"""Shuttler Core spline coefficients update trigger.
|
||||
|
||||
:param channel: RTIO channel number of the trigger interface.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariants = {"core", "channel", "target_o"}
|
||||
|
||||
def __init__(self, dmgr, channel, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.channel = channel
|
||||
self.target_o = channel << 8
|
||||
|
||||
@kernel
|
||||
def trigger(self, trig_out):
|
||||
"""Triggers coefficient update of (a) Shuttler Core channel(s).
|
||||
|
||||
Each bit corresponds to a Shuttler waveform generator core. Setting
|
||||
`trig_out` bits commits the pending coefficient update (from
|
||||
`set_waveform` in :class:`DCBias` and :class:`DDS`) to the Shuttler Core
|
||||
synchronously.
|
||||
|
||||
:param trig_out: Coefficient update trigger bits. The MSB corresponds
|
||||
to Channel 15, LSB corresponds to Channel 0.
|
||||
"""
|
||||
rtio_output(self.target_o, trig_out)
|
||||
|
||||
|
||||
RELAY_SPI_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
|
||||
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
|
||||
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE |
|
||||
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
|
||||
|
||||
ADC_SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END |
|
||||
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
|
||||
1*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE |
|
||||
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
|
||||
|
||||
# SPI clock write and read dividers
|
||||
# CS should assert at least 9.5 ns after clk pulse
|
||||
SPIT_RELAY_WR = 4
|
||||
# 25 ns high/low pulse hold (limiting for write)
|
||||
SPIT_ADC_WR = 4
|
||||
SPIT_ADC_RD = 16
|
||||
|
||||
# SPI CS line
|
||||
CS_RELAY = 1 << 0
|
||||
CS_LED = 1 << 1
|
||||
CS_ADC = 1 << 0
|
||||
|
||||
# Referenced AD4115 registers
|
||||
_AD4115_REG_STATUS = 0x00
|
||||
_AD4115_REG_ADCMODE = 0x01
|
||||
_AD4115_REG_DATA = 0x04
|
||||
_AD4115_REG_ID = 0x07
|
||||
_AD4115_REG_CH0 = 0x10
|
||||
_AD4115_REG_SETUPCON0 = 0x20
|
||||
|
||||
|
||||
class Relay:
|
||||
"""Shuttler AFE relay switches.
|
||||
|
||||
It controls the AFE relay switches and the LEDs. Switch on the relay to
|
||||
enable AFE output; And off to disable the output. The LEDs indicates the
|
||||
relay status.
|
||||
|
||||
.. note::
|
||||
The relay does not disable ADC measurements. Voltage of any channels
|
||||
can still be read by the ADC even after switching off the relays.
|
||||
|
||||
:param spi_device: SPI bus device name.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariant = {"core", "bus"}
|
||||
|
||||
def __init__(self, dmgr, spi_device, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.bus = dmgr.get(spi_device)
|
||||
|
||||
@kernel
|
||||
def init(self):
|
||||
"""Initialize SPI device.
|
||||
|
||||
Configures the SPI bus to 16-bits, write-only, simultaneous relay
|
||||
switches and LED control.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED)
|
||||
|
||||
@kernel
|
||||
def enable(self, en: TInt32):
|
||||
"""Enable/Disable relay switches of corresponding channels.
|
||||
|
||||
Each bit corresponds to the relay switch of a channel. Asserting a bit
|
||||
turns on the corresponding relay switch; Deasserting the same bit
|
||||
turns off the switch instead.
|
||||
|
||||
:param en: Switch enable bits. The MSB corresponds to Channel 15, LSB
|
||||
corresponds to Channel 0.
|
||||
"""
|
||||
self.bus.write(en << 16)
|
||||
|
||||
|
||||
class ADC:
|
||||
"""Shuttler AFE ADC (AD4115) driver.
|
||||
|
||||
:param spi_device: SPI bus device name.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariant = {"core", "bus"}
|
||||
|
||||
def __init__(self, dmgr, spi_device, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.bus = dmgr.get(spi_device)
|
||||
|
||||
@kernel
|
||||
def read_id(self) -> TInt32:
|
||||
"""Read the product ID of the ADC.
|
||||
|
||||
The expected return value is 0x38DX, the 4 LSbs are don't cares.
|
||||
|
||||
:return: The read-back product ID.
|
||||
"""
|
||||
return self.read16(_AD4115_REG_ID)
|
||||
|
||||
@kernel
|
||||
def reset(self):
|
||||
"""AD4115 reset procedure.
|
||||
|
||||
This performs a write operation of 96 serial clock cycles with DIN
|
||||
held at high. It resets the entire device, including the register
|
||||
contents.
|
||||
|
||||
.. note::
|
||||
The datasheet only requires 64 cycles, but reasserting `CS_n` right
|
||||
after the transfer appears to interrupt the start-up sequence.
|
||||
"""
|
||||
self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC)
|
||||
self.bus.write(0xffffffff)
|
||||
self.bus.write(0xffffffff)
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC)
|
||||
self.bus.write(0xffffffff)
|
||||
|
||||
@kernel
|
||||
def read8(self, addr: TInt32) -> TInt32:
|
||||
"""Read from 8 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:return: Read-back register content.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||
16, SPIT_ADC_RD, CS_ADC)
|
||||
self.bus.write((addr | 0x40) << 24)
|
||||
return self.bus.read() & 0xff
|
||||
|
||||
@kernel
|
||||
def read16(self, addr: TInt32) -> TInt32:
|
||||
"""Read from 16 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:return: Read-back register content.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||
24, SPIT_ADC_RD, CS_ADC)
|
||||
self.bus.write((addr | 0x40) << 24)
|
||||
return self.bus.read() & 0xffff
|
||||
|
||||
@kernel
|
||||
def read24(self, addr: TInt32) -> TInt32:
|
||||
"""Read from 24 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:return: Read-back register content.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||
32, SPIT_ADC_RD, CS_ADC)
|
||||
self.bus.write((addr | 0x40) << 24)
|
||||
return self.bus.read() & 0xffffff
|
||||
|
||||
@kernel
|
||||
def write8(self, addr: TInt32, data: TInt32):
|
||||
"""Write to 8 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:param data: Data to be written.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END, 16, SPIT_ADC_WR, CS_ADC)
|
||||
self.bus.write(addr << 24 | (data & 0xff) << 16)
|
||||
|
||||
@kernel
|
||||
def write16(self, addr: TInt32, data: TInt32):
|
||||
"""Write to 16 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:param data: Data to be written.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END, 24, SPIT_ADC_WR, CS_ADC)
|
||||
self.bus.write(addr << 24 | (data & 0xffff) << 8)
|
||||
|
||||
@kernel
|
||||
def write24(self, addr: TInt32, data: TInt32):
|
||||
"""Write to 24 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:param data: Data to be written.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC)
|
||||
self.bus.write(addr << 24 | (data & 0xffffff))
|
||||
|
||||
@kernel
|
||||
def read_ch(self, channel: TInt32) -> TFloat:
|
||||
"""Sample a Shuttler channel on the AFE.
|
||||
|
||||
It performs a single conversion using profile 0 and setup 0, on the
|
||||
selected channel. The sample is then recovered and converted to volt.
|
||||
|
||||
:param channel: Shuttler channel to be sampled.
|
||||
:return: Voltage sample in volt.
|
||||
"""
|
||||
# Always configure Profile 0 for single conversion
|
||||
self.write16(_AD4115_REG_CH0, 0x8000 | ((channel * 2 + 1) << 4))
|
||||
self.write16(_AD4115_REG_SETUPCON0, 0x1300)
|
||||
self.single_conversion()
|
||||
|
||||
delay(100*us)
|
||||
adc_code = self.read24(_AD4115_REG_DATA)
|
||||
return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1
|
||||
|
||||
@kernel
|
||||
def single_conversion(self):
|
||||
"""Place the ADC in single conversion mode.
|
||||
|
||||
The ADC returns to standby mode after the conversion is complete.
|
||||
"""
|
||||
self.write16(_AD4115_REG_ADCMODE, 0x8010)
|
||||
|
||||
@kernel
|
||||
def standby(self):
|
||||
"""Place the ADC in standby mode and disables power down the clock.
|
||||
|
||||
The ADC can be returned to single conversion mode by calling
|
||||
:meth:`single_conversion`.
|
||||
"""
|
||||
# Selecting internal XO (0b00) also disables clock during standby
|
||||
self.write16(_AD4115_REG_ADCMODE, 0x8020)
|
||||
|
||||
@kernel
|
||||
def power_down(self):
|
||||
"""Place the ADC in power-down mode.
|
||||
|
||||
The ADC must be reset before returning to other modes.
|
||||
|
||||
.. note::
|
||||
The AD4115 datasheet suggests placing the ADC in standby mode
|
||||
before power-down. This is to prevent accidental entry into the
|
||||
power-down mode.
|
||||
|
||||
.. seealso::
|
||||
:meth:`standby`
|
||||
|
||||
:meth:`power_up`
|
||||
|
||||
"""
|
||||
self.write16(_AD4115_REG_ADCMODE, 0x8030)
|
||||
|
||||
@kernel
|
||||
def power_up(self):
|
||||
"""Exit the ADC power-down mode.
|
||||
|
||||
The ADC should be in power-down mode before calling this method.
|
||||
|
||||
.. seealso::
|
||||
:meth:`power_down`
|
||||
"""
|
||||
self.reset()
|
||||
# Although the datasheet claims 500 us reset wait time, only waiting
|
||||
# for ~500 us can result in DOUT pin stuck in high
|
||||
delay(2500*us)
|
||||
|
||||
@kernel
|
||||
def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]):
|
||||
"""Calibrate the Shuttler waveform generator using the ADC on the AFE.
|
||||
|
||||
It finds the average slope rate and average offset by samples, and
|
||||
compensate by writing the pre-DAC gain and offset registers in the
|
||||
configuration registers.
|
||||
|
||||
.. note::
|
||||
If the pre-calibration slope rate < 1, the calibration procedure
|
||||
will introduce a pre-DAC gain compensation. However, this may
|
||||
saturate the pre-DAC voltage code. (See :class:`Config` notes).
|
||||
Shuttler cannot cover the entire +/- 10 V range in this case.
|
||||
|
||||
.. seealso::
|
||||
:meth:`Config.set_gain`
|
||||
|
||||
:meth:`Config.set_offset`
|
||||
|
||||
:param volts: A list of all 16 cubic DC-bias spline.
|
||||
(See :class:`DCBias`)
|
||||
:param trigger: The Shuttler spline coefficient update trigger.
|
||||
:param config: The Shuttler Core configuration registers.
|
||||
:param samples: A list of sample voltages for calibration. There must
|
||||
be at least 2 samples to perform slope rate calculation.
|
||||
"""
|
||||
assert len(volts) == 16
|
||||
assert len(samples) > 1
|
||||
|
||||
measurements = [0.0] * len(samples)
|
||||
|
||||
for ch in range(16):
|
||||
# Find the average slope rate and offset
|
||||
for i in range(len(samples)):
|
||||
self.core.break_realtime()
|
||||
volts[ch].set_waveform(
|
||||
shuttler_volt_to_mu(samples[i]), 0, 0, 0)
|
||||
trigger.trigger(1 << ch)
|
||||
measurements[i] = self.read_ch(ch)
|
||||
|
||||
# Find the average output slope
|
||||
slope_sum = 0.0
|
||||
for i in range(len(samples) - 1):
|
||||
slope_sum += (measurements[i+1] - measurements[i])/(samples[i+1] - samples[i])
|
||||
slope_avg = slope_sum / (len(samples) - 1)
|
||||
|
||||
gain_code = int32(1 / slope_avg * (2 ** 16)) & 0xffff
|
||||
|
||||
# Scale the measurements by 1/slope, find average offset
|
||||
offset_sum = 0.0
|
||||
for i in range(len(samples)):
|
||||
offset_sum += (measurements[i] / slope_avg) - samples[i]
|
||||
offset_avg = offset_sum / len(samples)
|
||||
|
||||
offset_code = shuttler_volt_to_mu(-offset_avg)
|
||||
|
||||
self.core.break_realtime()
|
||||
config.set_gain(ch, gain_code)
|
||||
|
||||
delay_mu(int64(self.core.ref_multiplier))
|
||||
config.set_offset(ch, offset_code)
|
|
@ -72,6 +72,10 @@ class SPIMaster:
|
|||
self.channel = channel
|
||||
self.update_xfer_duration_mu(div, length)
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(channel, **kwargs):
|
||||
return [(channel, None)]
|
||||
|
||||
@portable
|
||||
def frequency_to_div(self, f):
|
||||
"""Convert a SPI clock frequency to the closest SPI clock divider."""
|
||||
|
@ -273,9 +277,8 @@ class NRTSPIMaster:
|
|||
def set_config_mu(self, flags=0, length=8, div=6, cs=1):
|
||||
"""Set the ``config`` register.
|
||||
|
||||
Note that the non-realtime SPI cores are usually clocked by the system
|
||||
clock and not the RTIO clock. In many cases, the SPI configuration is
|
||||
already set by the firmware and you do not need to call this method.
|
||||
In many cases, the SPI configuration is already set by the firmware
|
||||
and you do not need to call this method.
|
||||
"""
|
||||
spi_set_config(self.busno, flags, length, div, cs)
|
||||
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
from numpy import int32, int64
|
||||
from artiq.language.core import kernel, portable, delay
|
||||
from artiq.coredevice.rtio import rtio_output, rtio_output_wide
|
||||
from artiq.language.types import TInt32, TInt64, TFloat
|
||||
|
||||
|
||||
class Spline:
|
||||
r"""Spline interpolating RTIO channel.
|
||||
|
||||
One knot of a polynomial basis spline (B-spline) :math:`u(t)`
|
||||
is defined by the coefficients :math:`u_n` up to order :math:`n = k`.
|
||||
If the coefficients are evaluated starting at time :math:`t_0`,
|
||||
the output :math:`u(t)` for :math:`t > t_0, t_0` is:
|
||||
|
||||
.. math::
|
||||
u(t) &= \sum_{n=0}^k \frac{u_n}{n!} (t - t_0)^n \\
|
||||
&= u_0 + u_1 (t - t_0) + \frac{u_2}{2} (t - t_0)^2 + \dots
|
||||
|
||||
This class contains multiple methods to convert spline knot data from SI
|
||||
to machine units and multiple methods that set the current spline
|
||||
coefficient data. None of these advance the timeline. The :meth:`smooth`
|
||||
method is the only method that advances the timeline.
|
||||
|
||||
:param width: Width in bits of the quantity that this spline controls
|
||||
:param time_width: Width in bits of the time counter of this spline
|
||||
:param channel: RTIO channel number
|
||||
:param core_device: Core device that this spline is attached to
|
||||
:param scale: Scale for conversion between machine units and physical
|
||||
units; to be given as the "full scale physical value".
|
||||
"""
|
||||
|
||||
kernel_invariants = {"channel", "core", "scale", "width",
|
||||
"time_width", "time_scale"}
|
||||
|
||||
def __init__(self, width, time_width, channel, core_device, scale=1.):
|
||||
self.core = core_device
|
||||
self.channel = channel
|
||||
self.width = width
|
||||
self.scale = float((int64(1) << width) / scale)
|
||||
self.time_width = time_width
|
||||
self.time_scale = float((1 << time_width) *
|
||||
core_device.coarse_ref_period)
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def to_mu(self, value: TFloat) -> TInt32:
|
||||
"""Convert floating point ``value`` from physical units to 32 bit
|
||||
integer machine units."""
|
||||
return int32(round(value*self.scale))
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def from_mu(self, value: TInt32) -> TFloat:
|
||||
"""Convert 32 bit integer ``value`` from machine units to floating
|
||||
point physical units."""
|
||||
return value/self.scale
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def to_mu64(self, value: TFloat) -> TInt64:
|
||||
"""Convert floating point ``value`` from physical units to 64 bit
|
||||
integer machine units."""
|
||||
return int64(round(value*self.scale))
|
||||
|
||||
@kernel
|
||||
def set_mu(self, value: TInt32):
|
||||
"""Set spline value (machine units).
|
||||
|
||||
:param value: Spline value in integer machine units.
|
||||
"""
|
||||
rtio_output(self.channel << 8, value)
|
||||
|
||||
@kernel(flags={"fast-math"})
|
||||
def set(self, value: TFloat):
|
||||
"""Set spline value.
|
||||
|
||||
:param value: Spline value relative to full-scale.
|
||||
"""
|
||||
if self.width > 32:
|
||||
l = [int32(0)] * 2
|
||||
self.pack_coeff_mu([self.to_mu64(value)], l)
|
||||
rtio_output_wide(self.channel << 8, l)
|
||||
else:
|
||||
rtio_output(self.channel << 8, self.to_mu(value))
|
||||
|
||||
@kernel
|
||||
def set_coeff_mu(self, value): # TList(TInt32)
|
||||
"""Set spline raw values.
|
||||
|
||||
:param value: Spline packed raw values.
|
||||
"""
|
||||
rtio_output_wide(self.channel << 8, value)
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def pack_coeff_mu(self, coeff, packed): # TList(TInt64), TList(TInt32)
|
||||
"""Pack coefficients into RTIO data
|
||||
|
||||
:param coeff: TList(TInt64) list of machine units spline coefficients.
|
||||
Lowest (zeroth) order first. The coefficient list is zero-extended
|
||||
by the RTIO gateware.
|
||||
:param packed: TList(TInt32) list for packed RTIO data. Must be
|
||||
pre-allocated. Length in bits is
|
||||
``n*width + (n - 1)*n//2*time_width``
|
||||
"""
|
||||
pos = 0
|
||||
for i in range(len(coeff)):
|
||||
wi = self.width + i*self.time_width
|
||||
ci = coeff[i]
|
||||
while wi != 0:
|
||||
j = pos//32
|
||||
used = pos - 32*j
|
||||
avail = 32 - used
|
||||
if avail > wi:
|
||||
avail = wi
|
||||
cij = int32(ci)
|
||||
if avail != 32:
|
||||
cij &= (1 << avail) - 1
|
||||
packed[j] |= cij << used
|
||||
ci >>= avail
|
||||
wi -= avail
|
||||
pos += avail
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def coeff_to_mu(self, coeff, coeff64): # TList(TFloat), TList(TInt64)
|
||||
"""Convert a floating point list of coefficients into a 64 bit
|
||||
integer (preallocated).
|
||||
|
||||
:param coeff: TList(TFloat) list of coefficients in physical units.
|
||||
:param coeff64: TList(TInt64) preallocated list of coefficients in
|
||||
machine units.
|
||||
"""
|
||||
for i in range(len(coeff)):
|
||||
vi = coeff[i] * self.scale
|
||||
for j in range(i):
|
||||
vi *= self.time_scale
|
||||
ci = int64(round(vi))
|
||||
coeff64[i] = ci
|
||||
# artiq.wavesynth.coefficients.discrete_compensate:
|
||||
if i == 2:
|
||||
coeff64[1] += ci >> self.time_width + 1
|
||||
elif i == 3:
|
||||
coeff64[2] += ci >> self.time_width
|
||||
coeff64[1] += ci // 6 >> 2*self.time_width
|
||||
|
||||
def coeff_as_packed_mu(self, coeff64):
|
||||
"""Pack 64 bit integer machine units coefficients into 32 bit integer
|
||||
RTIO data list.
|
||||
|
||||
This is a host-only method that can be used to generate packed
|
||||
spline coefficient data to be frozen into kernels at compile time.
|
||||
"""
|
||||
n = len(coeff64)
|
||||
width = n*self.width + (n - 1)*n//2*self.time_width
|
||||
packed = [int32(0)] * ((width + 31)//32)
|
||||
self.pack_coeff_mu(coeff64, packed)
|
||||
return packed
|
||||
|
||||
def coeff_as_packed(self, coeff):
|
||||
"""Convert floating point spline coefficients into 32 bit integer
|
||||
packed data.
|
||||
|
||||
This is a host-only method that can be used to generate packed
|
||||
spline coefficient data to be frozen into kernels at compile time.
|
||||
"""
|
||||
coeff64 = [int64(0)] * len(coeff)
|
||||
self.coeff_to_mu(coeff, coeff64)
|
||||
return self.coeff_as_packed_mu(coeff64)
|
||||
|
||||
@kernel(flags={"fast-math"})
|
||||
def set_coeff(self, coeff): # TList(TFloat)
|
||||
"""Set spline coefficients.
|
||||
|
||||
Missing coefficients (high order) are zero-extended byt the RTIO
|
||||
gateware.
|
||||
|
||||
If more coefficients are supplied than the gateware supports the extra
|
||||
coefficients are ignored.
|
||||
|
||||
:param value: List of floating point spline coefficients,
|
||||
lowest order (constant) coefficient first. Units are the
|
||||
unit of this spline's value times increasing powers of 1/s.
|
||||
"""
|
||||
n = len(coeff)
|
||||
coeff64 = [int64(0)] * n
|
||||
self.coeff_to_mu(coeff, coeff64)
|
||||
width = n*self.width + (n - 1)*n//2*self.time_width
|
||||
packed = [int32(0)] * ((width + 31)//32)
|
||||
self.pack_coeff_mu(coeff64, packed)
|
||||
self.set_coeff_mu(packed)
|
||||
|
||||
@kernel(flags={"fast-math"})
|
||||
def smooth(self, start: TFloat, stop: TFloat, duration: TFloat,
|
||||
order: TInt32):
|
||||
"""Initiate an interpolated value change.
|
||||
|
||||
For zeroth order (step) interpolation, the step is at
|
||||
``start + duration/2``.
|
||||
|
||||
First order interpolation corresponds to a linear value ramp from
|
||||
``start`` to ``stop`` over ``duration``.
|
||||
|
||||
The third order interpolation is constrained to have zero first
|
||||
order derivative at both `start` and `stop`.
|
||||
|
||||
For first order and third order interpolation (linear and cubic)
|
||||
the interpolator needs to be stopped explicitly at the stop time
|
||||
(e.g. by setting spline coefficient data or starting a new
|
||||
:meth:`smooth` interpolation).
|
||||
|
||||
This method advances the timeline by ``duration``.
|
||||
|
||||
:param start: Initial value of the change. In physical units.
|
||||
:param stop: Final value of the change. In physical units.
|
||||
:param duration: Duration of the interpolation. In physical units.
|
||||
:param order: Order of the interpolation. Only 0, 1,
|
||||
and 3 are valid: step, linear, cubic.
|
||||
"""
|
||||
if order == 0:
|
||||
delay(duration/2.)
|
||||
self.set_coeff([stop])
|
||||
delay(duration/2.)
|
||||
elif order == 1:
|
||||
self.set_coeff([start, (stop - start)/duration])
|
||||
delay(duration)
|
||||
elif order == 3:
|
||||
v2 = 6.*(stop - start)/(duration*duration)
|
||||
self.set_coeff([start, 0., v2, -2.*v2/duration])
|
||||
delay(duration)
|
||||
else:
|
||||
raise ValueError("Invalid interpolation order. "
|
||||
"Supported orders are: 0, 1, 3.")
|
|
@ -23,12 +23,12 @@ def y_mu_to_full_scale(y):
|
|||
|
||||
|
||||
@portable
|
||||
def adc_mu_to_volts(x, gain):
|
||||
def adc_mu_to_volts(x, gain, corrected_fs=True):
|
||||
"""Convert servo ADC data from machine units to Volt."""
|
||||
val = (x >> 1) & 0xffff
|
||||
mask = 1 << 15
|
||||
val = -(val & mask) + (val & ~mask)
|
||||
return sampler.adc_mu_to_volt(val, gain)
|
||||
return sampler.adc_mu_to_volt(val, gain, corrected_fs)
|
||||
|
||||
|
||||
class SUServo:
|
||||
|
@ -62,14 +62,15 @@ class SUServo:
|
|||
:param gains: Initial value for PGIA gains shift register
|
||||
(default: 0x0000). Knowledge of this state is not transferred
|
||||
between experiments.
|
||||
:param sampler_hw_rev: Sampler's revision string
|
||||
:param core_device: Core device name
|
||||
"""
|
||||
kernel_invariants = {"channel", "core", "pgia", "cplds", "ddses",
|
||||
"ref_period_mu"}
|
||||
"ref_period_mu", "corrected_fs"}
|
||||
|
||||
def __init__(self, dmgr, channel, pgia_device,
|
||||
cpld_devices, dds_devices,
|
||||
gains=0x0000, core_device="core"):
|
||||
gains=0x0000, sampler_hw_rev="v2.2", core_device="core"):
|
||||
|
||||
self.core = dmgr.get(core_device)
|
||||
self.pgia = dmgr.get(pgia_device)
|
||||
|
@ -81,8 +82,13 @@ class SUServo:
|
|||
self.gains = gains
|
||||
self.ref_period_mu = self.core.seconds_to_mu(
|
||||
self.core.coarse_ref_period)
|
||||
self.corrected_fs = sampler.Sampler.use_corrected_fs(sampler_hw_rev)
|
||||
assert self.ref_period_mu == self.core.ref_multiplier
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(channel, **kwargs):
|
||||
return [(channel, None)]
|
||||
|
||||
@kernel
|
||||
def init(self):
|
||||
"""Initialize the servo, Sampler and both Urukuls.
|
||||
|
@ -234,7 +240,7 @@ class SUServo:
|
|||
"""
|
||||
val = self.get_adc_mu(channel)
|
||||
gain = (self.gains >> (channel*2)) & 0b11
|
||||
return adc_mu_to_volts(val, gain)
|
||||
return adc_mu_to_volts(val, gain, self.corrected_fs)
|
||||
|
||||
|
||||
class Channel:
|
||||
|
@ -255,6 +261,10 @@ class Channel:
|
|||
self.servo.channel)
|
||||
self.dds = self.servo.ddses[self.servo_channel // 4]
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(channel, **kwargs):
|
||||
return [(channel, None)]
|
||||
|
||||
@kernel
|
||||
def set(self, en_out, en_iir=0, profile=0):
|
||||
"""Operate channel.
|
||||
|
|
|
@ -36,6 +36,10 @@ class TTLOut:
|
|||
self.channel = channel
|
||||
self.target_o = channel << 8
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(channel, **kwargs):
|
||||
return [(channel, None)]
|
||||
|
||||
@kernel
|
||||
def output(self):
|
||||
pass
|
||||
|
@ -128,6 +132,10 @@ class TTLInOut:
|
|||
self.target_sens = (channel << 8) + 2
|
||||
self.target_sample = (channel << 8) + 3
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(channel, **kwargs):
|
||||
return [(channel, None)]
|
||||
|
||||
@kernel
|
||||
def set_oe(self, oe):
|
||||
rtio_output(self.target_oe, 1 if oe else 0)
|
||||
|
@ -465,6 +473,10 @@ class TTLClockGen:
|
|||
|
||||
self.acc_width = numpy.int64(acc_width)
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(channel, **kwargs):
|
||||
return [(channel, None)]
|
||||
|
||||
@portable
|
||||
def frequency_to_ftw(self, frequency):
|
||||
"""Returns the frequency tuning word corresponding to the given
|
||||
|
|
|
@ -36,7 +36,7 @@ class AppletsCCBDock(applets.AppletsDock):
|
|||
ccbp_group_menu.addAction(self.ccbp_group_create)
|
||||
actiongroup.addAction(self.ccbp_group_create)
|
||||
self.ccbp_group_enable = QtWidgets.QAction("Create and enable/disable applets",
|
||||
self.table)
|
||||
self.table)
|
||||
self.ccbp_group_enable.setCheckable(True)
|
||||
self.ccbp_group_enable.triggered.connect(lambda: self.set_ccbp("enable"))
|
||||
ccbp_group_menu.addAction(self.ccbp_group_enable)
|
||||
|
|
|
@ -5,101 +5,26 @@ import numpy as np
|
|||
from PyQt5 import QtCore, QtWidgets
|
||||
from sipyco import pyon
|
||||
|
||||
from artiq.tools import short_format, exc_to_warning
|
||||
from artiq.tools import scale_from_metadata, short_format, exc_to_warning
|
||||
from artiq.gui.tools import LayoutWidget, QRecursiveFilterProxyModel
|
||||
from artiq.gui.models import DictSyncTreeSepModel
|
||||
from artiq.gui.scientific_spinbox import ScientificSpinBox
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def rename(key, newkey, value, dataset_ctl):
|
||||
if key != newkey:
|
||||
async def rename(key, new_key, value, metadata, persist, dataset_ctl):
|
||||
if key != new_key:
|
||||
await dataset_ctl.delete(key)
|
||||
await dataset_ctl.set(newkey, value)
|
||||
await dataset_ctl.set(new_key, value, metadata=metadata, persist=persist)
|
||||
|
||||
|
||||
class Editor(QtWidgets.QDialog):
|
||||
def __init__(self, parent, dataset_ctl, key, value):
|
||||
QtWidgets.QDialog.__init__(self, parent=parent)
|
||||
self.dataset_ctl = dataset_ctl
|
||||
self.key = key
|
||||
self.initial_type = type(value)
|
||||
|
||||
self.setWindowTitle("Edit dataset")
|
||||
grid = QtWidgets.QGridLayout()
|
||||
self.setLayout(grid)
|
||||
|
||||
grid.addWidget(QtWidgets.QLabel("Name:"), 0, 0)
|
||||
|
||||
self.name_widget = QtWidgets.QLineEdit()
|
||||
self.name_widget.setText(key)
|
||||
|
||||
grid.addWidget(self.name_widget, 0, 1)
|
||||
|
||||
grid.addWidget(QtWidgets.QLabel("Value:"), 1, 0)
|
||||
grid.addWidget(self.get_edit_widget(value), 1, 1)
|
||||
|
||||
buttons = QtWidgets.QDialogButtonBox(
|
||||
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
|
||||
grid.setRowStretch(2, 1)
|
||||
grid.addWidget(buttons, 3, 0, 1, 2)
|
||||
buttons.accepted.connect(self.accept)
|
||||
buttons.rejected.connect(self.reject)
|
||||
|
||||
def accept(self):
|
||||
newkey = self.name_widget.text()
|
||||
value = self.initial_type(self.get_edit_widget_value())
|
||||
asyncio.ensure_future(rename(self.key, newkey, value, self.dataset_ctl))
|
||||
QtWidgets.QDialog.accept(self)
|
||||
|
||||
def get_edit_widget(self, initial_value):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_edit_widget_value(self):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class NumberEditor(Editor):
|
||||
def get_edit_widget(self, initial_value):
|
||||
self.edit_widget = ScientificSpinBox()
|
||||
self.edit_widget.setDecimals(13)
|
||||
self.edit_widget.setPrecision()
|
||||
self.edit_widget.setRelativeStep()
|
||||
self.edit_widget.setValue(float(initial_value))
|
||||
return self.edit_widget
|
||||
|
||||
def get_edit_widget_value(self):
|
||||
return self.edit_widget.value()
|
||||
|
||||
|
||||
class BoolEditor(Editor):
|
||||
def get_edit_widget(self, initial_value):
|
||||
self.edit_widget = QtWidgets.QCheckBox()
|
||||
self.edit_widget.setChecked(bool(initial_value))
|
||||
return self.edit_widget
|
||||
|
||||
def get_edit_widget_value(self):
|
||||
return self.edit_widget.isChecked()
|
||||
|
||||
|
||||
class StringEditor(Editor):
|
||||
def get_edit_widget(self, initial_value):
|
||||
self.edit_widget = QtWidgets.QLineEdit()
|
||||
self.edit_widget.setText(initial_value)
|
||||
return self.edit_widget
|
||||
|
||||
def get_edit_widget_value(self):
|
||||
return self.edit_widget.text()
|
||||
|
||||
|
||||
class Creator(QtWidgets.QDialog):
|
||||
def __init__(self, parent, dataset_ctl):
|
||||
class CreateEditDialog(QtWidgets.QDialog):
|
||||
def __init__(self, parent, dataset_ctl, key=None, value=None, metadata=None, persist=False):
|
||||
QtWidgets.QDialog.__init__(self, parent=parent)
|
||||
self.dataset_ctl = dataset_ctl
|
||||
|
||||
self.setWindowTitle("Create dataset")
|
||||
self.setWindowTitle("Create dataset" if key is None else "Edit dataset")
|
||||
grid = QtWidgets.QGridLayout()
|
||||
grid.setRowMinimumHeight(1, 40)
|
||||
grid.setColumnMinimumWidth(2, 60)
|
||||
|
@ -117,9 +42,21 @@ class Creator(QtWidgets.QDialog):
|
|||
grid.addWidget(self.data_type, 1, 2)
|
||||
self.value_widget.textChanged.connect(self.dtype)
|
||||
|
||||
grid.addWidget(QtWidgets.QLabel("Persist:"), 2, 0)
|
||||
grid.addWidget(QtWidgets.QLabel("Unit:"), 2, 0)
|
||||
self.unit_widget = QtWidgets.QLineEdit()
|
||||
grid.addWidget(self.unit_widget, 2, 1)
|
||||
|
||||
grid.addWidget(QtWidgets.QLabel("Scale:"), 3, 0)
|
||||
self.scale_widget = QtWidgets.QLineEdit()
|
||||
grid.addWidget(self.scale_widget, 3, 1)
|
||||
|
||||
grid.addWidget(QtWidgets.QLabel("Precision:"), 4, 0)
|
||||
self.precision_widget = QtWidgets.QLineEdit()
|
||||
grid.addWidget(self.precision_widget, 4, 1)
|
||||
|
||||
grid.addWidget(QtWidgets.QLabel("Persist:"), 5, 0)
|
||||
self.box_widget = QtWidgets.QCheckBox()
|
||||
grid.addWidget(self.box_widget, 2, 1)
|
||||
grid.addWidget(self.box_widget, 5, 1)
|
||||
|
||||
self.ok = QtWidgets.QPushButton('&Ok')
|
||||
self.ok.setEnabled(False)
|
||||
|
@ -129,23 +66,63 @@ class Creator(QtWidgets.QDialog):
|
|||
self.ok, QtWidgets.QDialogButtonBox.AcceptRole)
|
||||
self.buttons.addButton(
|
||||
self.cancel, QtWidgets.QDialogButtonBox.RejectRole)
|
||||
grid.setRowStretch(3, 1)
|
||||
grid.addWidget(self.buttons, 4, 0, 1, 3)
|
||||
grid.setRowStretch(6, 1)
|
||||
grid.addWidget(self.buttons, 7, 0, 1, 3, alignment=QtCore.Qt.AlignHCenter)
|
||||
self.buttons.accepted.connect(self.accept)
|
||||
self.buttons.rejected.connect(self.reject)
|
||||
|
||||
self.key = key
|
||||
self.name_widget.setText(key)
|
||||
|
||||
value_edit_string = self.value_to_edit_string(value)
|
||||
if metadata is not None:
|
||||
scale = scale_from_metadata(metadata)
|
||||
t = value.dtype if value is np.ndarray else type(value)
|
||||
if scale != 1 and np.issubdtype(t, np.number):
|
||||
# degenerates to float type
|
||||
value_edit_string = self.value_to_edit_string(value / scale)
|
||||
self.unit_widget.setText(metadata.get('unit', ''))
|
||||
self.scale_widget.setText(str(metadata.get('scale', '')))
|
||||
self.precision_widget.setText(str(metadata.get('precision', '')))
|
||||
|
||||
self.value_widget.setText(value_edit_string)
|
||||
self.box_widget.setChecked(persist)
|
||||
|
||||
def accept(self):
|
||||
key = self.name_widget.text()
|
||||
value = self.value_widget.text()
|
||||
persist = self.box_widget.isChecked()
|
||||
asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set(
|
||||
key, pyon.decode(value), persist)))
|
||||
unit = self.unit_widget.text()
|
||||
scale = self.scale_widget.text()
|
||||
precision = self.precision_widget.text()
|
||||
metadata = {}
|
||||
if unit != "":
|
||||
metadata['unit'] = unit
|
||||
if scale != "":
|
||||
metadata['scale'] = float(scale)
|
||||
if precision != "":
|
||||
metadata['precision'] = int(precision)
|
||||
scale = scale_from_metadata(metadata)
|
||||
value = self.parse_edit_string(value)
|
||||
t = value.dtype if value is np.ndarray else type(value)
|
||||
if scale != 1 and np.issubdtype(t, np.number):
|
||||
# degenerates to float type
|
||||
value = float(value * scale)
|
||||
if self.key and self.key != key:
|
||||
asyncio.ensure_future(exc_to_warning(rename(self.key, key, value, metadata, persist,
|
||||
self.dataset_ctl)))
|
||||
else:
|
||||
asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set(key, value, metadata=metadata,
|
||||
persist=persist)))
|
||||
self.key = key
|
||||
QtWidgets.QDialog.accept(self)
|
||||
|
||||
def dtype(self):
|
||||
txt = self.value_widget.text()
|
||||
try:
|
||||
result = pyon.decode(txt)
|
||||
result = self.parse_edit_string(txt)
|
||||
# ensure only pyon compatible types are permissable
|
||||
pyon.encode(result)
|
||||
except:
|
||||
pixmap = self.style().standardPixmap(
|
||||
QtWidgets.QStyle.SP_MessageBoxWarning)
|
||||
|
@ -155,9 +132,38 @@ class Creator(QtWidgets.QDialog):
|
|||
self.data_type.setText(type(result).__name__)
|
||||
self.ok.setEnabled(True)
|
||||
|
||||
@staticmethod
|
||||
def parse_edit_string(s):
|
||||
if s == "":
|
||||
raise TypeError
|
||||
_eval_dict = {
|
||||
"__builtins__": {},
|
||||
"array": np.array,
|
||||
"null": np.nan,
|
||||
"inf": np.inf
|
||||
}
|
||||
for t_ in pyon._numpy_scalar:
|
||||
_eval_dict[t_] = eval("np.{}".format(t_), {"np": np})
|
||||
return eval(s, _eval_dict, {})
|
||||
|
||||
@staticmethod
|
||||
def value_to_edit_string(v):
|
||||
t = type(v)
|
||||
r = ""
|
||||
if isinstance(v, np.generic):
|
||||
r += t.__name__
|
||||
r += "("
|
||||
r += repr(v)
|
||||
r += ")"
|
||||
elif v is None:
|
||||
return r
|
||||
else:
|
||||
r += repr(v)
|
||||
return r
|
||||
|
||||
|
||||
class Model(DictSyncTreeSepModel):
|
||||
def __init__(self, init):
|
||||
def __init__(self, init):
|
||||
DictSyncTreeSepModel.__init__(self, ".",
|
||||
["Dataset", "Persistent", "Value"],
|
||||
init)
|
||||
|
@ -166,13 +172,13 @@ class Model(DictSyncTreeSepModel):
|
|||
if column == 1:
|
||||
return "Y" if v[0] else "N"
|
||||
elif column == 2:
|
||||
return short_format(v[1])
|
||||
return short_format(v[1], v[2])
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
|
||||
class DatasetsDock(QtWidgets.QDockWidget):
|
||||
def __init__(self, datasets_sub, dataset_ctl):
|
||||
def __init__(self, dataset_sub, dataset_ctl):
|
||||
QtWidgets.QDockWidget.__init__(self, "Datasets")
|
||||
self.setObjectName("Datasets")
|
||||
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
|
||||
|
@ -212,7 +218,7 @@ class DatasetsDock(QtWidgets.QDockWidget):
|
|||
self.table.addAction(delete_action)
|
||||
|
||||
self.table_model = Model(dict())
|
||||
datasets_sub.add_setmodel_callback(self.set_model)
|
||||
dataset_sub.add_setmodel_callback(self.set_model)
|
||||
|
||||
def _search_datasets(self):
|
||||
if hasattr(self, "table_model_filter"):
|
||||
|
@ -226,7 +232,7 @@ class DatasetsDock(QtWidgets.QDockWidget):
|
|||
self.table.setModel(self.table_model_filter)
|
||||
|
||||
def create_clicked(self):
|
||||
Creator(self, self.dataset_ctl).open()
|
||||
CreateEditDialog(self, self.dataset_ctl).open()
|
||||
|
||||
def edit_clicked(self):
|
||||
idx = self.table.selectedIndexes()
|
||||
|
@ -234,19 +240,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
|
|||
idx = self.table_model_filter.mapToSource(idx[0])
|
||||
key = self.table_model.index_to_key(idx)
|
||||
if key is not None:
|
||||
persist, value = self.table_model.backing_store[key]
|
||||
t = type(value)
|
||||
if np.issubdtype(t, np.number):
|
||||
dialog_cls = NumberEditor
|
||||
elif np.issubdtype(t, np.bool_):
|
||||
dialog_cls = BoolEditor
|
||||
elif np.issubdtype(t, np.unicode_):
|
||||
dialog_cls = StringEditor
|
||||
else:
|
||||
logger.error("Cannot edit dataset %s: "
|
||||
"type %s is not supported", key, t)
|
||||
return
|
||||
dialog_cls(self, self.dataset_ctl, key, value).open()
|
||||
persist, value, metadata = self.table_model.backing_store[key]
|
||||
CreateEditDialog(self, self.dataset_ctl, key, value, metadata, persist).open()
|
||||
|
||||
def delete_clicked(self):
|
||||
idx = self.table.selectedIndexes()
|
||||
|
|
|
@ -9,9 +9,10 @@ import h5py
|
|||
|
||||
from sipyco import pyon
|
||||
|
||||
from artiq.gui.entries import procdesc_to_entry, ScanEntry
|
||||
from artiq.gui.entries import procdesc_to_entry, EntryTreeWidget
|
||||
from artiq.gui.fuzzy_select import FuzzySelectWidget
|
||||
from artiq.gui.tools import LayoutWidget, log_level_to_name, get_open_file_name
|
||||
from artiq.gui.tools import (LayoutWidget, log_level_to_name, get_open_file_name)
|
||||
from artiq.tools import parse_devarg_override, unparse_devarg_override
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -23,108 +24,23 @@ logger = logging.getLogger(__name__)
|
|||
# 2. file:<class name>@<file name>
|
||||
|
||||
|
||||
class _WheelFilter(QtCore.QObject):
|
||||
def eventFilter(self, obj, event):
|
||||
if (event.type() == QtCore.QEvent.Wheel and
|
||||
event.modifiers() != QtCore.Qt.NoModifier):
|
||||
event.ignore()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class _ArgumentEditor(QtWidgets.QTreeWidget):
|
||||
class _ArgumentEditor(EntryTreeWidget):
|
||||
def __init__(self, manager, dock, expurl):
|
||||
self.manager = manager
|
||||
self.expurl = expurl
|
||||
|
||||
QtWidgets.QTreeWidget.__init__(self)
|
||||
self.setColumnCount(3)
|
||||
self.header().setStretchLastSection(False)
|
||||
if hasattr(self.header(), "setSectionResizeMode"):
|
||||
set_resize_mode = self.header().setSectionResizeMode
|
||||
else:
|
||||
set_resize_mode = self.header().setResizeMode
|
||||
set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents)
|
||||
set_resize_mode(1, QtWidgets.QHeaderView.Stretch)
|
||||
set_resize_mode(2, QtWidgets.QHeaderView.ResizeToContents)
|
||||
self.header().setVisible(False)
|
||||
self.setSelectionMode(self.NoSelection)
|
||||
self.setHorizontalScrollMode(self.ScrollPerPixel)
|
||||
self.setVerticalScrollMode(self.ScrollPerPixel)
|
||||
|
||||
self.setStyleSheet("QTreeWidget {background: " +
|
||||
self.palette().midlight().color().name() + " ;}")
|
||||
|
||||
self.viewport().installEventFilter(_WheelFilter(self.viewport()))
|
||||
|
||||
self._groups = dict()
|
||||
self._arg_to_widgets = dict()
|
||||
EntryTreeWidget.__init__(self)
|
||||
|
||||
arguments = self.manager.get_submission_arguments(self.expurl)
|
||||
|
||||
if not arguments:
|
||||
self.addTopLevelItem(QtWidgets.QTreeWidgetItem(["No arguments"]))
|
||||
self.insertTopLevelItem(0, QtWidgets.QTreeWidgetItem(["No arguments"]))
|
||||
|
||||
gradient = QtGui.QLinearGradient(
|
||||
0, 0, 0, QtGui.QFontMetrics(self.font()).lineSpacing()*2.5)
|
||||
gradient.setColorAt(0, self.palette().base().color())
|
||||
gradient.setColorAt(1, self.palette().midlight().color())
|
||||
for name, argument in arguments.items():
|
||||
widgets = dict()
|
||||
self._arg_to_widgets[name] = widgets
|
||||
self.set_argument(name, argument)
|
||||
|
||||
entry = procdesc_to_entry(argument["desc"])(argument)
|
||||
widget_item = QtWidgets.QTreeWidgetItem([name])
|
||||
if argument["tooltip"]:
|
||||
widget_item.setToolTip(0, argument["tooltip"])
|
||||
widgets["entry"] = entry
|
||||
widgets["widget_item"] = widget_item
|
||||
self.quickStyleClicked.connect(dock.submit_clicked)
|
||||
|
||||
for col in range(3):
|
||||
widget_item.setBackground(col, gradient)
|
||||
font = widget_item.font(0)
|
||||
font.setBold(True)
|
||||
widget_item.setFont(0, font)
|
||||
|
||||
if argument["group"] is None:
|
||||
self.addTopLevelItem(widget_item)
|
||||
else:
|
||||
self._get_group(argument["group"]).addChild(widget_item)
|
||||
fix_layout = LayoutWidget()
|
||||
widgets["fix_layout"] = fix_layout
|
||||
fix_layout.addWidget(entry)
|
||||
self.setItemWidget(widget_item, 1, fix_layout)
|
||||
recompute_argument = QtWidgets.QToolButton()
|
||||
recompute_argument.setToolTip("Re-run the experiment's build "
|
||||
"method and take the default value")
|
||||
recompute_argument.setIcon(
|
||||
QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_BrowserReload))
|
||||
recompute_argument.clicked.connect(
|
||||
partial(self._recompute_argument_clicked, name))
|
||||
|
||||
tool_buttons = LayoutWidget()
|
||||
tool_buttons.addWidget(recompute_argument, 1)
|
||||
|
||||
disable_other_scans = QtWidgets.QToolButton()
|
||||
widgets["disable_other_scans"] = disable_other_scans
|
||||
disable_other_scans.setIcon(
|
||||
QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_DialogResetButton))
|
||||
disable_other_scans.setToolTip("Disable all other scans in "
|
||||
"this experiment")
|
||||
disable_other_scans.clicked.connect(
|
||||
partial(self._disable_other_scans, name))
|
||||
tool_buttons.layout.setRowStretch(0, 1)
|
||||
tool_buttons.layout.setRowStretch(3, 1)
|
||||
tool_buttons.addWidget(disable_other_scans, 2)
|
||||
if not isinstance(entry, ScanEntry):
|
||||
disable_other_scans.setVisible(False)
|
||||
|
||||
self.setItemWidget(widget_item, 2, tool_buttons)
|
||||
|
||||
widget_item = QtWidgets.QTreeWidgetItem()
|
||||
self.addTopLevelItem(widget_item)
|
||||
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
|
||||
recompute_arguments.setIcon(
|
||||
QtWidgets.QApplication.style().standardIcon(
|
||||
|
@ -143,24 +59,10 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
|||
buttons.layout.setColumnStretch(1, 0)
|
||||
buttons.layout.setColumnStretch(2, 0)
|
||||
buttons.layout.setColumnStretch(3, 1)
|
||||
self.setItemWidget(widget_item, 1, buttons)
|
||||
self.setItemWidget(self.bottom_item, 1, buttons)
|
||||
|
||||
def _get_group(self, name):
|
||||
if name in self._groups:
|
||||
return self._groups[name]
|
||||
group = QtWidgets.QTreeWidgetItem([name])
|
||||
for col in range(3):
|
||||
group.setBackground(col, self.palette().mid())
|
||||
group.setForeground(col, self.palette().brightText())
|
||||
font = group.font(col)
|
||||
font.setBold(True)
|
||||
group.setFont(col, font)
|
||||
self.addTopLevelItem(group)
|
||||
self._groups[name] = group
|
||||
return group
|
||||
|
||||
def _recompute_argument_clicked(self, name):
|
||||
asyncio.ensure_future(self._recompute_argument(name))
|
||||
def reset_entry(self, key):
|
||||
asyncio.ensure_future(self._recompute_argument(key))
|
||||
|
||||
async def _recompute_argument(self, name):
|
||||
try:
|
||||
|
@ -175,46 +77,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
|
|||
state = procdesc_to_entry(procdesc).default_state(procdesc)
|
||||
argument["desc"] = procdesc
|
||||
argument["state"] = state
|
||||
|
||||
# Qt needs a setItemWidget() to handle layout correctly,
|
||||
# simply replacing the entry inside the LayoutWidget
|
||||
# results in a bug.
|
||||
|
||||
widgets = self._arg_to_widgets[name]
|
||||
|
||||
widgets["entry"].deleteLater()
|
||||
widgets["entry"] = procdesc_to_entry(procdesc)(argument)
|
||||
widgets["disable_other_scans"].setVisible(
|
||||
isinstance(widgets["entry"], ScanEntry))
|
||||
widgets["fix_layout"].deleteLater()
|
||||
widgets["fix_layout"] = LayoutWidget()
|
||||
widgets["fix_layout"].addWidget(widgets["entry"])
|
||||
self.setItemWidget(widgets["widget_item"], 1, widgets["fix_layout"])
|
||||
self.updateGeometries()
|
||||
|
||||
def _disable_other_scans(self, current_name):
|
||||
for name, widgets in self._arg_to_widgets.items():
|
||||
if (name != current_name
|
||||
and isinstance(widgets["entry"], ScanEntry)):
|
||||
widgets["entry"].disable()
|
||||
|
||||
def save_state(self):
|
||||
expanded = []
|
||||
for k, v in self._groups.items():
|
||||
if v.isExpanded():
|
||||
expanded.append(k)
|
||||
return {
|
||||
"expanded": expanded,
|
||||
"scroll": self.verticalScrollBar().value()
|
||||
}
|
||||
|
||||
def restore_state(self, state):
|
||||
for e in state["expanded"]:
|
||||
try:
|
||||
self._groups[e].setExpanded(True)
|
||||
except KeyError:
|
||||
pass
|
||||
self.verticalScrollBar().setValue(state["scroll"])
|
||||
self.update_argument(name, argument)
|
||||
|
||||
# Hooks that allow user-supplied argument editors to react to imminent user
|
||||
# actions. Here, we always keep the manager-stored submission arguments
|
||||
|
@ -235,7 +98,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
def __init__(self, manager, expurl):
|
||||
QtWidgets.QMdiSubWindow.__init__(self)
|
||||
qfm = QtGui.QFontMetrics(self.font())
|
||||
self.resize(100*qfm.averageCharWidth(), 30*qfm.lineSpacing())
|
||||
self.resize(100 * qfm.averageCharWidth(), 30 * qfm.lineSpacing())
|
||||
self.setWindowTitle(expurl)
|
||||
self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_FileDialogContentsView))
|
||||
|
@ -268,17 +131,17 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
datetime.setDate(QtCore.QDate.currentDate())
|
||||
else:
|
||||
datetime.setDateTime(QtCore.QDateTime.fromMSecsSinceEpoch(
|
||||
scheduling["due_date"]*1000))
|
||||
int(scheduling["due_date"] * 1000)))
|
||||
datetime_en.setChecked(scheduling["due_date"] is not None)
|
||||
|
||||
def update_datetime(dt):
|
||||
scheduling["due_date"] = dt.toMSecsSinceEpoch()/1000
|
||||
scheduling["due_date"] = dt.toMSecsSinceEpoch() / 1000
|
||||
datetime_en.setChecked(True)
|
||||
datetime.dateTimeChanged.connect(update_datetime)
|
||||
|
||||
def update_datetime_en(checked):
|
||||
if checked:
|
||||
due_date = datetime.dateTime().toMSecsSinceEpoch()/1000
|
||||
due_date = datetime.dateTime().toMSecsSinceEpoch() / 1000
|
||||
else:
|
||||
due_date = None
|
||||
scheduling["due_date"] = due_date
|
||||
|
@ -311,7 +174,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
flush = self.flush
|
||||
flush.setToolTip("Flush the pipeline (of current- and higher-priority "
|
||||
"experiments) before starting the experiment")
|
||||
self.layout.addWidget(flush, 2, 2, 1, 2)
|
||||
self.layout.addWidget(flush, 2, 2)
|
||||
|
||||
flush.setChecked(scheduling["flush"])
|
||||
|
||||
|
@ -319,6 +182,20 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
scheduling["flush"] = bool(checked)
|
||||
flush.stateChanged.connect(update_flush)
|
||||
|
||||
devarg_override = QtWidgets.QComboBox()
|
||||
devarg_override.setEditable(True)
|
||||
devarg_override.lineEdit().setPlaceholderText("Override device arguments")
|
||||
devarg_override.lineEdit().setClearButtonEnabled(True)
|
||||
devarg_override.insertItem(0, "core:analyze_at_run_end=True")
|
||||
self.layout.addWidget(devarg_override, 2, 3)
|
||||
|
||||
devarg_override.setCurrentText(options["devarg_override"])
|
||||
|
||||
def update_devarg_override(text):
|
||||
options["devarg_override"] = text
|
||||
devarg_override.editTextChanged.connect(update_devarg_override)
|
||||
self.devarg_override = devarg_override
|
||||
|
||||
log_level = QtWidgets.QComboBox()
|
||||
log_level.addItems(log_levels)
|
||||
log_level.setCurrentIndex(1)
|
||||
|
@ -339,9 +216,11 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
if "repo_rev" in options:
|
||||
repo_rev = QtWidgets.QLineEdit()
|
||||
repo_rev.setPlaceholderText("current")
|
||||
repo_rev_label = QtWidgets.QLabel("Revision:")
|
||||
repo_rev.setClearButtonEnabled(True)
|
||||
repo_rev_label = QtWidgets.QLabel("Rev / ref:")
|
||||
repo_rev_label.setToolTip("Experiment repository revision "
|
||||
"(commit ID) to use")
|
||||
"(commit ID) or reference (branch "
|
||||
"or tag) to use")
|
||||
self.layout.addWidget(repo_rev_label, 3, 2)
|
||||
self.layout.addWidget(repo_rev, 3, 3)
|
||||
|
||||
|
@ -358,7 +237,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
|
||||
submit = QtWidgets.QPushButton("Submit")
|
||||
submit.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_DialogOkButton))
|
||||
QtWidgets.QStyle.SP_DialogOkButton))
|
||||
submit.setToolTip("Schedule the experiment (Ctrl+Return)")
|
||||
submit.setShortcut("CTRL+RETURN")
|
||||
submit.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
||||
|
@ -368,7 +247,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
|
||||
reqterm = QtWidgets.QPushButton("Terminate instances")
|
||||
reqterm.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_DialogCancelButton))
|
||||
QtWidgets.QStyle.SP_DialogCancelButton))
|
||||
reqterm.setToolTip("Request termination of instances (Ctrl+Backspace)")
|
||||
reqterm.setShortcut("CTRL+BACKSPACE")
|
||||
reqterm.setSizePolicy(QtWidgets.QSizePolicy.Expanding,
|
||||
|
@ -410,8 +289,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
arginfo = expdesc["arginfo"]
|
||||
for k, v in overrides.items():
|
||||
# Some values (e.g. scans) may have multiple defaults in a list
|
||||
if ("default" in arginfo[k][0]
|
||||
and isinstance(arginfo[k][0]["default"], list)):
|
||||
if ("default" in arginfo[k][0] and isinstance(arginfo[k][0]["default"], list)):
|
||||
arginfo[k][0]["default"].insert(0, v)
|
||||
else:
|
||||
arginfo[k][0]["default"] = v
|
||||
|
@ -422,8 +300,8 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
|
||||
editor_class = self.manager.get_argument_editor_class(self.expurl)
|
||||
self.argeditor = editor_class(self.manager, self, self.expurl)
|
||||
self.argeditor.restore_state(argeditor_state)
|
||||
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
|
||||
self.argeditor.restore_state(argeditor_state)
|
||||
|
||||
def contextMenuEvent(self, event):
|
||||
menu = QtWidgets.QMenu(self)
|
||||
|
@ -471,11 +349,14 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
return
|
||||
|
||||
try:
|
||||
if "devarg_override" in expid:
|
||||
self.devarg_override.setCurrentText(
|
||||
unparse_devarg_override(expid["devarg_override"]))
|
||||
self.log_level.setCurrentIndex(log_levels.index(
|
||||
log_level_to_name(expid["log_level"])))
|
||||
if ("repo_rev" in expid and
|
||||
expid["repo_rev"] != "N/A" and
|
||||
hasattr(self, "repo_rev")):
|
||||
if "repo_rev" in expid and \
|
||||
expid["repo_rev"] != "N/A" and \
|
||||
hasattr(self, "repo_rev"):
|
||||
self.repo_rev.setText(expid["repo_rev"])
|
||||
except:
|
||||
logger.error("Could not set submission options from HDF5 expid",
|
||||
|
@ -644,7 +525,8 @@ class ExperimentManager:
|
|||
else:
|
||||
# mutated by _ExperimentDock
|
||||
options = {
|
||||
"log_level": logging.WARNING
|
||||
"log_level": logging.WARNING,
|
||||
"devarg_override": ""
|
||||
}
|
||||
if expurl[:5] == "repo:":
|
||||
options["repo_rev"] = None
|
||||
|
@ -665,6 +547,21 @@ class ExperimentManager:
|
|||
self.argument_ui_names[expurl] = ui_name
|
||||
return arguments
|
||||
|
||||
def set_argument_value(self, expurl, name, value):
|
||||
try:
|
||||
argument = self.submission_arguments[expurl][name]
|
||||
if argument["desc"]["ty"] == "Scannable":
|
||||
ty = value["ty"]
|
||||
argument["state"]["selected"] = ty
|
||||
argument["state"][ty] = value
|
||||
else:
|
||||
argument["state"] = value
|
||||
if expurl in self.open_experiments.keys():
|
||||
self.open_experiments[expurl].argeditor.update_argument(name, argument)
|
||||
except:
|
||||
logger.warn("Failed to set value for argument \"{}\" in experiment: {}."
|
||||
.format(name, expurl), exc_info=1)
|
||||
|
||||
def get_submission_arguments(self, expurl):
|
||||
if expurl in self.submission_arguments:
|
||||
return self.submission_arguments[expurl]
|
||||
|
@ -673,8 +570,8 @@ class ExperimentManager:
|
|||
raise ValueError("Submission arguments must be preinitialized "
|
||||
"when not using repository")
|
||||
class_desc = self.explist[expurl[5:]]
|
||||
return self.initialize_submission_arguments(expurl,
|
||||
class_desc["arginfo"], class_desc.get("argument_ui", None))
|
||||
return self.initialize_submission_arguments(expurl, class_desc["arginfo"],
|
||||
class_desc.get("argument_ui", None))
|
||||
|
||||
def open_experiment(self, expurl):
|
||||
if expurl in self.open_experiments:
|
||||
|
@ -711,8 +608,13 @@ class ExperimentManager:
|
|||
del self.open_experiments[expurl]
|
||||
|
||||
async def _submit_task(self, expurl, *args):
|
||||
rid = await self.schedule_ctl.submit(*args)
|
||||
logger.info("Submitted '%s', RID is %d", expurl, rid)
|
||||
try:
|
||||
rid = await self.schedule_ctl.submit(*args)
|
||||
except KeyError:
|
||||
expid = args[1]
|
||||
logger.error("Submission failed - revision \"%s\" was not found", expid["repo_rev"])
|
||||
else:
|
||||
logger.info("Submitted '%s', RID is %d", expurl, rid)
|
||||
|
||||
def submit(self, expurl):
|
||||
file, class_name, _ = self.resolve_expurl(expurl)
|
||||
|
@ -725,7 +627,14 @@ class ExperimentManager:
|
|||
entry_cls = procdesc_to_entry(argument["desc"])
|
||||
argument_values[name] = entry_cls.state_to_value(argument["state"])
|
||||
|
||||
try:
|
||||
devarg_override = parse_devarg_override(options["devarg_override"])
|
||||
except:
|
||||
logger.error("Failed to parse device argument overrides for %s", expurl)
|
||||
return
|
||||
|
||||
expid = {
|
||||
"devarg_override": devarg_override,
|
||||
"log_level": options["log_level"],
|
||||
"file": file,
|
||||
"class_name": class_name,
|
||||
|
@ -762,9 +671,9 @@ class ExperimentManager:
|
|||
repo_match = "repo_rev" in expid
|
||||
else:
|
||||
repo_match = "repo_rev" not in expid
|
||||
if (repo_match and
|
||||
("file" in expid and expid["file"] == file) and
|
||||
expid["class_name"] == class_name):
|
||||
if repo_match and \
|
||||
("file" in expid and expid["file"] == file) and \
|
||||
expid["class_name"] == class_name:
|
||||
rids.append(rid)
|
||||
asyncio.ensure_future(self._request_term_multiple(rids))
|
||||
|
||||
|
@ -784,7 +693,7 @@ class ExperimentManager:
|
|||
for class_name, class_desc in description.items():
|
||||
expurl = "file:{}@{}".format(class_name, file)
|
||||
self.initialize_submission_arguments(expurl, class_desc["arginfo"],
|
||||
class_desc.get("argument_ui", None))
|
||||
class_desc.get("argument_ui", None))
|
||||
if expurl in self.open_experiments:
|
||||
self.open_experiments[expurl].close()
|
||||
self.open_experiment(expurl)
|
||||
|
@ -818,6 +727,7 @@ class ExperimentManager:
|
|||
|
||||
self.is_quick_open_shown = True
|
||||
dialog = _QuickOpenDialog(self)
|
||||
|
||||
def closed():
|
||||
self.is_quick_open_shown = False
|
||||
dialog.closed.connect(closed)
|
||||
|
|
|
@ -94,7 +94,7 @@ class _OpenFileDialog(QtWidgets.QDialog):
|
|||
else:
|
||||
break
|
||||
self.explorer.current_directory = \
|
||||
self.explorer.current_directory[:idx+1]
|
||||
self.explorer.current_directory[:idx + 1]
|
||||
if self.explorer.current_directory == "/":
|
||||
self.explorer.current_directory = ""
|
||||
asyncio.ensure_future(self.refresh_view())
|
||||
|
@ -103,6 +103,7 @@ class _OpenFileDialog(QtWidgets.QDialog):
|
|||
asyncio.ensure_future(self.refresh_view())
|
||||
else:
|
||||
file = self.explorer.current_directory + selected
|
||||
|
||||
async def open_task():
|
||||
try:
|
||||
await self.exp_manager.open_file(file)
|
||||
|
@ -232,7 +233,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
|||
|
||||
set_shortcut_menu = QtWidgets.QMenu()
|
||||
for i in range(12):
|
||||
action = QtWidgets.QAction("F" + str(i+1), self.el)
|
||||
action = QtWidgets.QAction("F" + str(i + 1), self.el)
|
||||
action.triggered.connect(partial(self.set_shortcut, i))
|
||||
set_shortcut_menu.addAction(action)
|
||||
|
||||
|
@ -246,12 +247,14 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
|||
|
||||
scan_repository_action = QtWidgets.QAction("Scan repository HEAD",
|
||||
self.el)
|
||||
|
||||
def scan_repository():
|
||||
asyncio.ensure_future(experiment_db_ctl.scan_repository_async())
|
||||
scan_repository_action.triggered.connect(scan_repository)
|
||||
self.el.addAction(scan_repository_action)
|
||||
|
||||
scan_ddb_action = QtWidgets.QAction("Scan device database", self.el)
|
||||
|
||||
def scan_ddb():
|
||||
asyncio.ensure_future(device_db_ctl.scan())
|
||||
scan_ddb_action.triggered.connect(scan_ddb)
|
||||
|
@ -292,7 +295,7 @@ class ExplorerDock(QtWidgets.QDockWidget):
|
|||
if expname is not None:
|
||||
expurl = "repo:" + expname
|
||||
self.d_shortcuts.set_shortcut(nr, expurl)
|
||||
logger.info("Set shortcut F%d to '%s'", nr+1, expurl)
|
||||
logger.info("Set shortcut F%d to '%s'", nr + 1, expurl)
|
||||
|
||||
def update_scanning(self, scanning):
|
||||
if scanning:
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
import logging
|
||||
import asyncio
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
from artiq.gui.models import DictSyncModel
|
||||
from artiq.gui.entries import EntryTreeWidget, procdesc_to_entry
|
||||
from artiq.gui.tools import LayoutWidget
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Model(DictSyncModel):
|
||||
def __init__(self, init):
|
||||
DictSyncModel.__init__(self, ["RID", "Title", "Args"], init)
|
||||
|
||||
def convert(self, k, v, column):
|
||||
if column == 0:
|
||||
return k
|
||||
elif column == 1:
|
||||
txt = ": " + v["title"] if v["title"] != "" else ""
|
||||
return str(k) + txt
|
||||
elif column == 2:
|
||||
return v["arglist_desc"]
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
def sort_key(self, k, v):
|
||||
return k
|
||||
|
||||
|
||||
class _InteractiveArgsRequest(EntryTreeWidget):
|
||||
supplied = QtCore.pyqtSignal(int, dict)
|
||||
cancelled = QtCore.pyqtSignal(int)
|
||||
|
||||
def __init__(self, rid, arglist_desc):
|
||||
EntryTreeWidget.__init__(self)
|
||||
self.rid = rid
|
||||
self.arguments = dict()
|
||||
for key, procdesc, group, tooltip in arglist_desc:
|
||||
self.arguments[key] = {"desc": procdesc, "group": group, "tooltip": tooltip}
|
||||
self.set_argument(key, self.arguments[key])
|
||||
self.quickStyleClicked.connect(self.supply)
|
||||
cancel_btn = QtWidgets.QPushButton("Cancel")
|
||||
cancel_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_DialogCancelButton))
|
||||
cancel_btn.clicked.connect(self.cancel)
|
||||
supply_btn = QtWidgets.QPushButton("Supply")
|
||||
supply_btn.setIcon(QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_DialogOkButton))
|
||||
supply_btn.clicked.connect(self.supply)
|
||||
buttons = LayoutWidget()
|
||||
buttons.addWidget(cancel_btn, 1, 1)
|
||||
buttons.addWidget(supply_btn, 1, 2)
|
||||
buttons.layout.setColumnStretch(0, 1)
|
||||
buttons.layout.setColumnStretch(1, 0)
|
||||
buttons.layout.setColumnStretch(2, 0)
|
||||
buttons.layout.setColumnStretch(3, 1)
|
||||
self.setItemWidget(self.bottom_item, 1, buttons)
|
||||
|
||||
def supply(self):
|
||||
argument_values = dict()
|
||||
for key, argument in self.arguments.items():
|
||||
entry_cls = procdesc_to_entry(argument["desc"])
|
||||
argument_values[key] = entry_cls.state_to_value(argument["state"])
|
||||
self.supplied.emit(self.rid, argument_values)
|
||||
|
||||
def cancel(self):
|
||||
self.cancelled.emit(self.rid)
|
||||
|
||||
|
||||
class _InteractiveArgsView(QtWidgets.QStackedWidget):
|
||||
supplied = QtCore.pyqtSignal(int, dict)
|
||||
cancelled = QtCore.pyqtSignal(int)
|
||||
|
||||
def __init__(self):
|
||||
QtWidgets.QStackedWidget.__init__(self)
|
||||
self.tabs = QtWidgets.QTabWidget()
|
||||
self.default_label = QtWidgets.QLabel("No pending interactive arguments requests.")
|
||||
self.default_label.setAlignment(QtCore.Qt.AlignCenter)
|
||||
font = QtGui.QFont(self.default_label.font())
|
||||
font.setItalic(True)
|
||||
self.default_label.setFont(font)
|
||||
self.addWidget(self.tabs)
|
||||
self.addWidget(self.default_label)
|
||||
self.model = Model({})
|
||||
|
||||
def setModel(self, model):
|
||||
self.setCurrentIndex(1)
|
||||
for i in range(self.tabs.count()):
|
||||
widget = self.tabs.widget(i)
|
||||
self.tabs.removeTab(i)
|
||||
widget.deleteLater()
|
||||
self.model = model
|
||||
self.model.rowsInserted.connect(self.rowsInserted)
|
||||
self.model.rowsRemoved.connect(self.rowsRemoved)
|
||||
for i in range(self.model.rowCount(QtCore.QModelIndex())):
|
||||
self._insert_widget(i)
|
||||
|
||||
def _insert_widget(self, row):
|
||||
rid = self.model.data(self.model.index(row, 0), QtCore.Qt.DisplayRole)
|
||||
title = self.model.data(self.model.index(row, 1), QtCore.Qt.DisplayRole)
|
||||
arglist_desc = self.model.data(self.model.index(row, 2), QtCore.Qt.DisplayRole)
|
||||
inter_args_request = _InteractiveArgsRequest(rid, arglist_desc)
|
||||
inter_args_request.supplied.connect(self.supplied)
|
||||
inter_args_request.cancelled.connect(self.cancelled)
|
||||
self.tabs.insertTab(row, inter_args_request, title)
|
||||
|
||||
def rowsInserted(self, parent, first, last):
|
||||
assert first == last
|
||||
self.setCurrentIndex(0)
|
||||
self._insert_widget(first)
|
||||
|
||||
def rowsRemoved(self, parent, first, last):
|
||||
assert first == last
|
||||
widget = self.tabs.widget(first)
|
||||
self.tabs.removeTab(first)
|
||||
widget.deleteLater()
|
||||
if self.tabs.count() == 0:
|
||||
self.setCurrentIndex(1)
|
||||
|
||||
|
||||
class InteractiveArgsDock(QtWidgets.QDockWidget):
|
||||
def __init__(self, interactive_args_sub, interactive_args_rpc):
|
||||
QtWidgets.QDockWidget.__init__(self, "Interactive Args")
|
||||
self.setObjectName("Interactive Args")
|
||||
self.setFeatures(
|
||||
QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable)
|
||||
self.interactive_args_rpc = interactive_args_rpc
|
||||
self.request_view = _InteractiveArgsView()
|
||||
self.request_view.supplied.connect(self.supply)
|
||||
self.request_view.cancelled.connect(self.cancel)
|
||||
self.setWidget(self.request_view)
|
||||
interactive_args_sub.add_setmodel_callback(self.request_view.setModel)
|
||||
|
||||
def supply(self, rid, values):
|
||||
asyncio.ensure_future(self._supply_task(rid, values))
|
||||
|
||||
async def _supply_task(self, rid, values):
|
||||
try:
|
||||
await self.interactive_args_rpc.supply(rid, values)
|
||||
except Exception:
|
||||
logger.error("failed to supply interactive arguments for experiment: %d",
|
||||
rid, exc_info=True)
|
||||
|
||||
def cancel(self, rid):
|
||||
asyncio.ensure_future(self._cancel_task(rid))
|
||||
|
||||
async def _cancel_task(self, rid):
|
||||
try:
|
||||
await self.interactive_args_rpc.cancel(rid)
|
||||
except Exception:
|
||||
logger.error("failed to cancel interactive args request for experiment: %d",
|
||||
rid, exc_info=True)
|
|
@ -3,19 +3,17 @@ import logging
|
|||
import textwrap
|
||||
from collections import namedtuple
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from sipyco.sync_struct import Subscriber
|
||||
|
||||
from artiq.coredevice.comm_moninj import *
|
||||
from artiq.coredevice.ad9910 import _AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7, _AD9910_REG_FTW
|
||||
from artiq.coredevice.ad9912_reg import AD9912_POW1
|
||||
from artiq.coredevice.comm_moninj import CommMonInj, TTLOverride, TTLProbe
|
||||
from artiq.coredevice.ad9912_reg import AD9912_SER_CONF
|
||||
from artiq.gui.tools import LayoutWidget
|
||||
from artiq.gui.flowlayout import FlowLayout
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class _CancellableLineEdit(QtWidgets.QLineEdit):
|
||||
def escapePressedConnect(self, cb):
|
||||
self.esc_cb = cb
|
||||
|
@ -130,7 +128,7 @@ class _TTLWidget(QtWidgets.QFrame):
|
|||
else:
|
||||
color = ""
|
||||
self.value.setText("<font size=\"5\"{}>{}</font>".format(
|
||||
color, value_s))
|
||||
color, value_s))
|
||||
oe = self.cur_oe or self.force_out
|
||||
direction = "OUT" if oe else "IN"
|
||||
self.direction.setText("<font size=\"2\">" + direction + "</font>")
|
||||
|
@ -188,7 +186,7 @@ class _DDSModel:
|
|||
self.cur_reg = 0
|
||||
self.dds_type = dds_type
|
||||
self.is_urukul = dds_type in ["AD9910", "AD9912"]
|
||||
|
||||
|
||||
if dds_type == "AD9914":
|
||||
self.ftw_per_hz = 2**32 / ref_clk
|
||||
else:
|
||||
|
@ -213,13 +211,14 @@ class _DDSModel:
|
|||
|
||||
|
||||
class _DDSWidget(QtWidgets.QFrame):
|
||||
def __init__(self, dm, title, bus_channel=0, channel=0, dds_model=None):
|
||||
def __init__(self, dm, title, bus_channel, channel,
|
||||
dds_type, ref_clk, cpld=None, pll=1, clk_div=0):
|
||||
self.dm = dm
|
||||
self.bus_channel = bus_channel
|
||||
self.channel = channel
|
||||
self.dds_name = title
|
||||
self.cur_frequency = 0
|
||||
self.dds_model = dds_model
|
||||
self.dds_model = _DDSModel(dds_type, ref_clk, cpld, pll, clk_div)
|
||||
|
||||
QtWidgets.QFrame.__init__(self)
|
||||
|
||||
|
@ -280,7 +279,7 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||
set_btn.setText("Set")
|
||||
set_btn.setToolTip("Set frequency")
|
||||
set_grid.addWidget(set_btn, 0, 1, 1, 1)
|
||||
|
||||
|
||||
# for urukuls also allow switching off RF
|
||||
if self.dds_model.is_urukul:
|
||||
off_btn = QtWidgets.QToolButton()
|
||||
|
@ -311,6 +310,10 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||
apply.clicked.connect(self.apply_changes)
|
||||
if self.dds_model.is_urukul:
|
||||
off_btn.clicked.connect(self.off_clicked)
|
||||
off_btn.setToolTip(textwrap.dedent(
|
||||
"""Note: If TTL RTIO sw for the channel is switched high,
|
||||
this button will not disable the channel.
|
||||
Use the TTL override instead."""))
|
||||
self.value_edit.returnPressed.connect(lambda: self.apply_changes(None))
|
||||
self.value_edit.escapePressedConnect(self.cancel_changes)
|
||||
cancel.clicked.connect(self.cancel_changes)
|
||||
|
@ -320,18 +323,17 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||
def set_clicked(self, set):
|
||||
self.data_stack.setCurrentIndex(1)
|
||||
self.button_stack.setCurrentIndex(1)
|
||||
self.value_edit.setText("{:.7f}"
|
||||
.format(self.cur_frequency/1e6))
|
||||
self.value_edit.setText("{:.7f}".format(self.cur_frequency / 1e6))
|
||||
self.value_edit.setFocus()
|
||||
self.value_edit.selectAll()
|
||||
|
||||
def off_clicked(self, set):
|
||||
self.dm.dds_channel_toggle(self.dds_name, self.dds_model, sw=False)
|
||||
|
||||
|
||||
def apply_changes(self, apply):
|
||||
self.data_stack.setCurrentIndex(0)
|
||||
self.button_stack.setCurrentIndex(0)
|
||||
frequency = float(self.value_edit.text())*1e6
|
||||
frequency = float(self.value_edit.text()) * 1e6
|
||||
self.dm.dds_set_frequency(self.dds_name, self.dds_model, frequency)
|
||||
|
||||
def cancel_changes(self, cancel):
|
||||
|
@ -340,10 +342,8 @@ class _DDSWidget(QtWidgets.QFrame):
|
|||
|
||||
def refresh_display(self):
|
||||
self.cur_frequency = self.dds_model.cur_frequency
|
||||
self.value_label.setText("<font size=\"4\">{:.7f}</font>"
|
||||
.format(self.cur_frequency/1e6))
|
||||
self.value_edit.setText("{:.7f}"
|
||||
.format(self.cur_frequency/1e6))
|
||||
self.value_label.setText("<font size=\"4\">{:.7f}</font>".format(self.cur_frequency / 1e6))
|
||||
self.value_edit.setText("{:.7f}".format(self.cur_frequency / 1e6))
|
||||
|
||||
def sort_key(self):
|
||||
return (self.bus_channel, self.channel)
|
||||
|
@ -358,7 +358,7 @@ class _DACWidget(_SimpleDisplayWidget):
|
|||
|
||||
def refresh_display(self):
|
||||
self.value.setText("<font size=\"4\">{:.3f}</font><font size=\"2\"> %</font>"
|
||||
.format(self.cur_value*100/2**16))
|
||||
.format(self.cur_value * 100 / 2**16))
|
||||
|
||||
def sort_key(self):
|
||||
return (self.spi_channel, self.channel)
|
||||
|
@ -385,18 +385,15 @@ def setup_from_ddb(ddb):
|
|||
force_out = v["class"] == "TTLOut"
|
||||
widget = _WidgetDesc(k, comment, _TTLWidget, (channel, force_out, k))
|
||||
description.add(widget)
|
||||
elif (v["module"] == "artiq.coredevice.ad9914"
|
||||
and v["class"] == "AD9914"):
|
||||
elif (v["module"] == "artiq.coredevice.ad9914" and v["class"] == "AD9914"):
|
||||
bus_channel = v["arguments"]["bus_channel"]
|
||||
channel = v["arguments"]["channel"]
|
||||
dds_sysclk = v["arguments"]["sysclk"]
|
||||
model = _DDSModel(v["class"], dds_sysclk)
|
||||
widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model))
|
||||
widget = _WidgetDesc(k, comment, _DDSWidget,
|
||||
(k, bus_channel, channel, v["class"], dds_sysclk))
|
||||
description.add(widget)
|
||||
elif (v["module"] == "artiq.coredevice.ad9910"
|
||||
and v["class"] == "AD9910") or \
|
||||
(v["module"] == "artiq.coredevice.ad9912"
|
||||
and v["class"] == "AD9912"):
|
||||
elif (v["module"] == "artiq.coredevice.ad9910" and v["class"] == "AD9910") or \
|
||||
(v["module"] == "artiq.coredevice.ad9912" and v["class"] == "AD9912"):
|
||||
channel = v["arguments"]["chip_select"] - 4
|
||||
if channel < 0:
|
||||
continue
|
||||
|
@ -406,18 +403,20 @@ def setup_from_ddb(ddb):
|
|||
pll = v["arguments"]["pll_n"]
|
||||
refclk = ddb[dds_cpld]["arguments"]["refclk"]
|
||||
clk_div = v["arguments"].get("clk_div", 0)
|
||||
model = _DDSModel( v["class"], refclk, dds_cpld, pll, clk_div)
|
||||
widget = _WidgetDesc(k, comment, _DDSWidget, (k, bus_channel, channel, model))
|
||||
description.add(widget)
|
||||
elif ( (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx")
|
||||
or (v["module"] == "artiq.coredevice.zotino" and v["class"] == "Zotino")):
|
||||
widget = _WidgetDesc(k, comment, _DDSWidget,
|
||||
(k, bus_channel, channel, v["class"],
|
||||
refclk, dds_cpld, pll, clk_div))
|
||||
description.add(widget)
|
||||
elif (v["module"] == "artiq.coredevice.ad53xx" and v["class"] == "AD53xx") or \
|
||||
(v["module"] == "artiq.coredevice.zotino" and v["class"] == "Zotino"):
|
||||
spi_device = v["arguments"]["spi_device"]
|
||||
spi_device = ddb[spi_device]
|
||||
while isinstance(spi_device, str):
|
||||
spi_device = ddb[spi_device]
|
||||
spi_channel = spi_device["arguments"]["channel"]
|
||||
for channel in range(32):
|
||||
widget = _WidgetDesc((k, channel), comment, _DACWidget, (spi_channel, channel, k))
|
||||
widget = _WidgetDesc((k, channel), comment, _DACWidget,
|
||||
(spi_channel, channel, k))
|
||||
description.add(widget)
|
||||
elif v["type"] == "controller" and k == "core_moninj":
|
||||
mi_addr = v["host"]
|
||||
|
@ -451,9 +450,8 @@ class _DeviceManager:
|
|||
|
||||
def init_ddb(self, ddb):
|
||||
self.ddb = ddb
|
||||
return ddb
|
||||
|
||||
def notify(self, mod):
|
||||
def notify_ddb(self, mod):
|
||||
mi_addr, mi_port, description = setup_from_ddb(self.ddb)
|
||||
|
||||
if (mi_addr, mi_port) != (self.mi_addr, self.mi_port):
|
||||
|
@ -479,7 +477,7 @@ class _DeviceManager:
|
|||
self.setup_dac_monitoring(False, widget.spi_channel, widget.channel)
|
||||
widget.deleteLater()
|
||||
del self.dac_widgets[(widget.spi_channel, widget.channel)]
|
||||
self.dac_cb()
|
||||
self.dac_cb()
|
||||
else:
|
||||
raise ValueError
|
||||
|
||||
|
@ -534,7 +532,7 @@ class _DeviceManager:
|
|||
"log_level": logging.WARNING,
|
||||
"content": content,
|
||||
"class_name": class_name,
|
||||
"arguments": []
|
||||
"arguments": {}
|
||||
}
|
||||
scheduling = {
|
||||
"pipeline_name": "main",
|
||||
|
@ -549,33 +547,55 @@ class _DeviceManager:
|
|||
scheduling["flush"])
|
||||
logger.info("Submitted '%s', RID is %d", title, rid)
|
||||
|
||||
def dds_set_frequency(self, dds_channel, dds_model, freq):
|
||||
def _dds_faux_injection(self, dds_channel, dds_model, action, title, log_msg):
|
||||
# create kernel and fill it in and send-by-content
|
||||
|
||||
# initialize CPLD (if applicable)
|
||||
if dds_model.is_urukul:
|
||||
# urukuls need CPLD init and switch to on
|
||||
# keep previous config if it was set already
|
||||
cpld_dev = """self.setattr_device("core_cache")
|
||||
self.setattr_device("{}")""".format(dds_model.cpld)
|
||||
cpld_init = """cfg = self.core_cache.get("_{cpld}_cfg")
|
||||
if len(cfg) > 0:
|
||||
self.{cpld}.cfg_reg = cfg[0]
|
||||
else:
|
||||
|
||||
# `sta`/`rf_sw`` variables are guaranteed for urukuls
|
||||
# so {action} can use it
|
||||
# if there's no RF enabled, CPLD may have not been initialized
|
||||
# but if there is, it has been initialised - no need to do again
|
||||
cpld_init = """delay(15*ms)
|
||||
was_init = self.core_cache.get("_{cpld}_init")
|
||||
sta = self.{cpld}.sta_read()
|
||||
rf_sw = urukul_sta_rf_sw(sta)
|
||||
if rf_sw == 0 and len(was_init) == 0:
|
||||
delay(15*ms)
|
||||
self.{cpld}.init()
|
||||
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
|
||||
cfg = self.core_cache.get("_{cpld}_cfg")
|
||||
self.core_cache.put("_{cpld}_init", [1])
|
||||
""".format(cpld=dds_model.cpld)
|
||||
cfg_sw = """self.{}.cfg_sw(True)
|
||||
cfg[0] = self.{}.cfg_reg
|
||||
""".format(dds_channel, dds_model.cpld)
|
||||
else:
|
||||
cpld_dev = ""
|
||||
cpld_init = ""
|
||||
cfg_sw = ""
|
||||
|
||||
# AD9912/9910: init channel (if uninitialized)
|
||||
if dds_model.dds_type == "AD9912":
|
||||
# 0xFF before init, 0x99 after
|
||||
channel_init = """
|
||||
if self.{dds_channel}.read({cfgreg}, length=1) == 0xFF:
|
||||
delay(10*ms)
|
||||
self.{dds_channel}.init()
|
||||
""".format(dds_channel=dds_channel, cfgreg=AD9912_SER_CONF)
|
||||
elif dds_model.dds_type == "AD9910":
|
||||
# -1 before init, 2 after
|
||||
channel_init = """
|
||||
if self.{dds_channel}.read32({cfgreg}) == -1:
|
||||
delay(10*ms)
|
||||
self.{dds_channel}.init()
|
||||
""".format(dds_channel=dds_channel, cfgreg=AD9912_SER_CONF)
|
||||
else:
|
||||
channel_init = "self.{dds_channel}.init()".format(dds_channel=dds_channel)
|
||||
|
||||
dds_exp = textwrap.dedent("""
|
||||
from artiq.experiment import *
|
||||
from artiq.coredevice.urukul import *
|
||||
|
||||
class SetDDS(EnvExperiment):
|
||||
class {title}(EnvExperiment):
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("{dds_channel}")
|
||||
|
@ -583,55 +603,57 @@ class _DeviceManager:
|
|||
|
||||
@kernel
|
||||
def run(self):
|
||||
self.core.reset()
|
||||
self.core.break_realtime()
|
||||
{cpld_init}
|
||||
delay(5*ms)
|
||||
self.{dds_channel}.init()
|
||||
self.{dds_channel}.set({freq})
|
||||
{cfg_sw}
|
||||
""".format(dds_channel=dds_channel, freq=freq,
|
||||
delay(10*ms)
|
||||
{channel_init}
|
||||
delay(15*ms)
|
||||
{action}
|
||||
""".format(title=title, action=action,
|
||||
dds_channel=dds_channel,
|
||||
cpld_dev=cpld_dev, cpld_init=cpld_init,
|
||||
cfg_sw=cfg_sw))
|
||||
channel_init=channel_init))
|
||||
asyncio.ensure_future(
|
||||
self._submit_by_content(
|
||||
dds_exp,
|
||||
"SetDDS",
|
||||
"Set DDS {} {}MHz".format(dds_channel, freq/1e6)))
|
||||
dds_exp,
|
||||
title,
|
||||
log_msg))
|
||||
|
||||
def dds_set_frequency(self, dds_channel, dds_model, freq):
|
||||
action = "self.{ch}.set({freq})".format(
|
||||
freq=freq, ch=dds_channel)
|
||||
if dds_model.is_urukul:
|
||||
action += """
|
||||
ch_no = self.{ch}.chip_select - 4
|
||||
self.{cpld}.cfg_switches(rf_sw | 1 << ch_no)
|
||||
""".format(ch=dds_channel, cpld=dds_model.cpld)
|
||||
self._dds_faux_injection(
|
||||
dds_channel,
|
||||
dds_model,
|
||||
action,
|
||||
"SetDDS",
|
||||
"Set DDS {} {}MHz".format(dds_channel, freq / 1e6))
|
||||
|
||||
def dds_channel_toggle(self, dds_channel, dds_model, sw=True):
|
||||
# urukul only
|
||||
toggle_exp = textwrap.dedent("""
|
||||
from artiq.experiment import *
|
||||
|
||||
class ToggleDDS(EnvExperiment):
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("{ch}")
|
||||
self.setattr_device("core_cache")
|
||||
self.setattr_device("{cpld}")
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
self.core.reset()
|
||||
cfg = self.core_cache.get("_{cpld}_cfg")
|
||||
if len(cfg) > 0:
|
||||
self.{cpld}.cfg_reg = cfg[0]
|
||||
else:
|
||||
delay(15*ms)
|
||||
self.{cpld}.init()
|
||||
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
|
||||
cfg = self.core_cache.get("_{cpld}_cfg")
|
||||
delay(5*ms)
|
||||
self.{ch}.init()
|
||||
self.{ch}.cfg_sw({sw})
|
||||
cfg[0] = self.{cpld}.cfg_reg
|
||||
""".format(ch=dds_channel, cpld=dds_model.cpld, sw=sw))
|
||||
asyncio.ensure_future(
|
||||
self._submit_by_content(
|
||||
toggle_exp,
|
||||
"ToggleDDS",
|
||||
"Toggle DDS {} {}".format(dds_channel, "on" if sw else "off"))
|
||||
if sw:
|
||||
switch = "| 1 << ch_no"
|
||||
else:
|
||||
switch = "& ~(1 << ch_no)"
|
||||
action = """
|
||||
ch_no = self.{dds_channel}.chip_select - 4
|
||||
self.{cpld}.cfg_switches(rf_sw {switch})
|
||||
""".format(
|
||||
dds_channel=dds_channel,
|
||||
cpld=dds_model.cpld,
|
||||
switch=switch
|
||||
)
|
||||
self._dds_faux_injection(
|
||||
dds_channel,
|
||||
dds_model,
|
||||
action,
|
||||
"ToggleDDS",
|
||||
"Toggle DDS {} {}".format(dds_channel, "on" if sw else "off"))
|
||||
|
||||
def setup_ttl_monitoring(self, enable, channel):
|
||||
if self.mi_connection is not None:
|
||||
|
@ -688,18 +710,16 @@ class _DeviceManager:
|
|||
await self.mi_connection.close()
|
||||
self.mi_connection = None
|
||||
new_mi_connection = CommMonInj(self.monitor_cb, self.injection_status_cb,
|
||||
self.disconnect_cb)
|
||||
self.disconnect_cb)
|
||||
try:
|
||||
await new_mi_connection.connect(self.mi_addr, self.mi_port)
|
||||
except asyncio.CancelledError:
|
||||
logger.info("cancelled connection to moninj")
|
||||
break
|
||||
except:
|
||||
logger.error("failed to connect to moninj", exc_info=True)
|
||||
except Exception:
|
||||
logger.error("failed to connect to moninj. Is aqctl_moninj_proxy running?",
|
||||
exc_info=True)
|
||||
await asyncio.sleep(10.)
|
||||
self.reconnect_mi.set()
|
||||
else:
|
||||
logger.info("ARTIQ dashboard connected to moninj proxy (%s)",
|
||||
logger.info("ARTIQ dashboard connected to moninj (%s)",
|
||||
self.mi_addr)
|
||||
self.mi_connection = new_mi_connection
|
||||
for ttl_channel in self.ttl_widgets.keys():
|
||||
|
@ -748,19 +768,10 @@ class MonInj:
|
|||
self.dac_dock = _MonInjDock("DAC")
|
||||
|
||||
self.dm = _DeviceManager(schedule_ctl)
|
||||
self.dm.ttl_cb = lambda: self.ttl_dock.layout_widgets(
|
||||
self.dm.ttl_widgets.values())
|
||||
self.dm.dds_cb = lambda: self.dds_dock.layout_widgets(
|
||||
self.dm.dds_widgets.values())
|
||||
self.dm.dac_cb = lambda: self.dac_dock.layout_widgets(
|
||||
self.dm.dac_widgets.values())
|
||||
|
||||
self.subscriber = Subscriber("devices", self.dm.init_ddb, self.dm.notify)
|
||||
|
||||
async def start(self, server, port):
|
||||
await self.subscriber.connect(server, port)
|
||||
self.dm.ttl_cb = lambda: self.ttl_dock.layout_widgets(self.dm.ttl_widgets.values())
|
||||
self.dm.dds_cb = lambda: self.dds_dock.layout_widgets(self.dm.dds_widgets.values())
|
||||
self.dm.dac_cb = lambda: self.dac_dock.layout_widgets(self.dm.dac_widgets.values())
|
||||
|
||||
async def stop(self):
|
||||
await self.subscriber.close()
|
||||
if self.dm is not None:
|
||||
await self.dm.close()
|
||||
|
|
|
@ -15,9 +15,8 @@ logger = logging.getLogger(__name__)
|
|||
class Model(DictSyncModel):
|
||||
def __init__(self, init):
|
||||
DictSyncModel.__init__(self,
|
||||
["RID", "Pipeline", "Status", "Prio", "Due date",
|
||||
"Revision", "File", "Class name"],
|
||||
init)
|
||||
["RID", "Pipeline", "Status", "Prio", "Due date",
|
||||
"Revision", "File", "Class name"], init)
|
||||
|
||||
def sort_key(self, k, v):
|
||||
# order by priority, and then by due date and RID
|
||||
|
@ -96,14 +95,14 @@ class ScheduleDock(QtWidgets.QDockWidget):
|
|||
|
||||
cw = QtGui.QFontMetrics(self.font()).averageCharWidth()
|
||||
h = self.table.horizontalHeader()
|
||||
h.resizeSection(0, 7*cw)
|
||||
h.resizeSection(1, 12*cw)
|
||||
h.resizeSection(2, 16*cw)
|
||||
h.resizeSection(3, 6*cw)
|
||||
h.resizeSection(4, 16*cw)
|
||||
h.resizeSection(5, 30*cw)
|
||||
h.resizeSection(6, 20*cw)
|
||||
h.resizeSection(7, 20*cw)
|
||||
h.resizeSection(0, 7 * cw)
|
||||
h.resizeSection(1, 12 * cw)
|
||||
h.resizeSection(2, 16 * cw)
|
||||
h.resizeSection(3, 6 * cw)
|
||||
h.resizeSection(4, 16 * cw)
|
||||
h.resizeSection(5, 30 * cw)
|
||||
h.resizeSection(6, 20 * cw)
|
||||
h.resizeSection(7, 20 * cw)
|
||||
|
||||
def set_model(self, model):
|
||||
self.table_model = model
|
||||
|
@ -143,7 +142,7 @@ class ScheduleDock(QtWidgets.QDockWidget):
|
|||
selected_rid = self.table_model.row_to_key[row]
|
||||
pipeline = self.table_model.backing_store[selected_rid]["pipeline"]
|
||||
logger.info("Requesting termination of all "
|
||||
"experiments in pipeline '%s'", pipeline)
|
||||
"experiments in pipeline '%s'", pipeline)
|
||||
|
||||
rids = set()
|
||||
for rid, info in self.table_model.backing_store.items():
|
||||
|
@ -151,7 +150,6 @@ class ScheduleDock(QtWidgets.QDockWidget):
|
|||
rids.add(rid)
|
||||
asyncio.ensure_future(self.request_term_multiple(rids))
|
||||
|
||||
|
||||
def save_state(self):
|
||||
return bytes(self.table.horizontalHeader().saveState())
|
||||
|
||||
|
|
|
@ -3,8 +3,6 @@ from functools import partial
|
|||
|
||||
from PyQt5 import QtCore, QtWidgets
|
||||
|
||||
from artiq.gui.tools import LayoutWidget
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -35,7 +33,7 @@ class ShortcutsDock(QtWidgets.QDockWidget):
|
|||
for i in range(12):
|
||||
row = i + 1
|
||||
|
||||
layout.addWidget(QtWidgets.QLabel("F" + str(i+1)), row, 0)
|
||||
layout.addWidget(QtWidgets.QLabel("F" + str(i + 1)), row, 0)
|
||||
|
||||
label = QtWidgets.QLabel()
|
||||
label.setSizePolicy(QtWidgets.QSizePolicy.Ignored,
|
||||
|
@ -70,7 +68,7 @@ class ShortcutsDock(QtWidgets.QDockWidget):
|
|||
"open": open,
|
||||
"submit": submit
|
||||
}
|
||||
shortcut = QtWidgets.QShortcut("F" + str(i+1), main_window)
|
||||
shortcut = QtWidgets.QShortcut("F" + str(i + 1), main_window)
|
||||
shortcut.setContext(QtCore.Qt.ApplicationShortcut)
|
||||
shortcut.activated.connect(partial(self._activated, i))
|
||||
|
||||
|
|
|
@ -0,0 +1,914 @@
|
|||
import os
|
||||
import asyncio
|
||||
import logging
|
||||
import bisect
|
||||
import itertools
|
||||
import math
|
||||
|
||||
from PyQt5 import QtCore, QtWidgets, QtGui
|
||||
|
||||
import pyqtgraph as pg
|
||||
import numpy as np
|
||||
|
||||
from sipyco.pc_rpc import AsyncioClient
|
||||
from sipyco import pyon
|
||||
|
||||
from artiq.tools import exc_to_warning, short_format
|
||||
from artiq.coredevice import comm_analyzer
|
||||
from artiq.coredevice.comm_analyzer import WaveformType
|
||||
from artiq.gui.tools import LayoutWidget, get_open_file_name, get_save_file_name
|
||||
from artiq.gui.models import DictSyncTreeSepModel
|
||||
from artiq.gui.dndwidgets import VDragScrollArea, VDragDropSplitter
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
WAVEFORM_MIN_HEIGHT = 50
|
||||
WAVEFORM_MAX_HEIGHT = 200
|
||||
|
||||
|
||||
class ProxyClient():
|
||||
def __init__(self, receive_cb, timeout=5, timer=5, timer_backoff=1.1):
|
||||
self.receive_cb = receive_cb
|
||||
self.receiver = None
|
||||
self.addr = None
|
||||
self.port_proxy = None
|
||||
self.port = None
|
||||
self._reconnect_event = asyncio.Event()
|
||||
self.timeout = timeout
|
||||
self.timer = timer
|
||||
self.timer_cur = timer
|
||||
self.timer_backoff = timer_backoff
|
||||
self._reconnect_task = asyncio.ensure_future(self._reconnect())
|
||||
|
||||
def update_address(self, addr, port, port_proxy):
|
||||
self.addr = addr
|
||||
self.port = port
|
||||
self.port_proxy = port_proxy
|
||||
self._reconnect_event.set()
|
||||
|
||||
async def trigger_proxy_task(self):
|
||||
remote = AsyncioClient()
|
||||
try:
|
||||
try:
|
||||
if self.addr is None:
|
||||
logger.error("missing core_analyzer host in device db")
|
||||
return
|
||||
await remote.connect_rpc(self.addr, self.port, "coreanalyzer_proxy_control")
|
||||
except:
|
||||
logger.error("error connecting to analyzer proxy control", exc_info=True)
|
||||
return
|
||||
await remote.trigger()
|
||||
except:
|
||||
logger.error("analyzer proxy reported failure", exc_info=True)
|
||||
finally:
|
||||
remote.close_rpc()
|
||||
|
||||
async def _reconnect(self):
|
||||
while True:
|
||||
await self._reconnect_event.wait()
|
||||
self._reconnect_event.clear()
|
||||
if self.receiver is not None:
|
||||
await self.receiver.close()
|
||||
self.receiver = None
|
||||
new_receiver = comm_analyzer.AnalyzerProxyReceiver(
|
||||
self.receive_cb, self.disconnect_cb)
|
||||
try:
|
||||
if self.addr is not None:
|
||||
await asyncio.wait_for(new_receiver.connect(self.addr, self.port_proxy),
|
||||
self.timeout)
|
||||
logger.info("ARTIQ dashboard connected to analyzer proxy (%s)", self.addr)
|
||||
self.timer_cur = self.timer
|
||||
self.receiver = new_receiver
|
||||
continue
|
||||
except Exception:
|
||||
logger.error("error connecting to analyzer proxy", exc_info=True)
|
||||
try:
|
||||
await asyncio.wait_for(self._reconnect_event.wait(), self.timer_cur)
|
||||
except asyncio.TimeoutError:
|
||||
self.timer_cur *= self.timer_backoff
|
||||
self._reconnect_event.set()
|
||||
else:
|
||||
self.timer_cur = self.timer
|
||||
|
||||
async def close(self):
|
||||
self._reconnect_task.cancel()
|
||||
try:
|
||||
await asyncio.wait_for(self._reconnect_task, None)
|
||||
except asyncio.CancelledError:
|
||||
pass
|
||||
if self.receiver is not None:
|
||||
await self.receiver.close()
|
||||
|
||||
def disconnect_cb(self):
|
||||
logger.error("lost connection to analyzer proxy")
|
||||
self._reconnect_event.set()
|
||||
|
||||
|
||||
class _BackgroundItem(pg.GraphicsWidgetAnchor, pg.GraphicsWidget):
|
||||
def __init__(self, parent, rect):
|
||||
pg.GraphicsWidget.__init__(self, parent)
|
||||
pg.GraphicsWidgetAnchor.__init__(self)
|
||||
self.item = QtWidgets.QGraphicsRectItem(rect, self)
|
||||
brush = QtGui.QBrush(QtGui.QColor(10, 10, 10, 140))
|
||||
self.item.setBrush(brush)
|
||||
|
||||
|
||||
class _BaseWaveform(pg.PlotWidget):
|
||||
cursorMove = QtCore.pyqtSignal(float)
|
||||
|
||||
def __init__(self, name, width, precision, unit,
|
||||
parent=None, pen="r", stepMode="right", connect="finite"):
|
||||
pg.PlotWidget.__init__(self,
|
||||
parent=parent,
|
||||
x=None,
|
||||
y=None,
|
||||
pen=pen,
|
||||
stepMode=stepMode,
|
||||
connect=connect)
|
||||
|
||||
self.setMinimumHeight(WAVEFORM_MIN_HEIGHT)
|
||||
self.setMaximumHeight(WAVEFORM_MAX_HEIGHT)
|
||||
self.setMenuEnabled(False)
|
||||
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
||||
|
||||
self.name = name
|
||||
self.width = width
|
||||
self.precision = precision
|
||||
self.unit = unit
|
||||
|
||||
self.x_data = []
|
||||
self.y_data = []
|
||||
|
||||
self.plot_item = self.getPlotItem()
|
||||
self.plot_item.hideButtons()
|
||||
self.plot_item.hideAxis("top")
|
||||
self.plot_item.getAxis("bottom").setStyle(showValues=False, tickLength=0)
|
||||
self.plot_item.getAxis("left").setStyle(showValues=False, tickLength=0)
|
||||
self.plot_item.setRange(yRange=(0, 1), padding=0.1)
|
||||
self.plot_item.showGrid(x=True, y=True)
|
||||
|
||||
self.plot_data_item = self.plot_item.listDataItems()[0]
|
||||
self.plot_data_item.setClipToView(True)
|
||||
|
||||
self.view_box = self.plot_item.getViewBox()
|
||||
self.view_box.setMouseEnabled(x=True, y=False)
|
||||
self.view_box.disableAutoRange(axis=pg.ViewBox.YAxis)
|
||||
self.view_box.setLimits(xMin=0, minXRange=20)
|
||||
|
||||
self.title_label = pg.LabelItem(self.name, parent=self.plot_item)
|
||||
self.title_label.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, 0))
|
||||
self.title_label.setAttr('justify', 'left')
|
||||
self.title_label.setZValue(10)
|
||||
|
||||
rect = self.title_label.boundingRect()
|
||||
rect.setHeight(rect.height() * 2)
|
||||
rect.setWidth(225)
|
||||
self.label_bg = _BackgroundItem(parent=self.plot_item, rect=rect)
|
||||
self.label_bg.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, 0))
|
||||
|
||||
self.cursor = pg.InfiniteLine()
|
||||
self.cursor_y = None
|
||||
self.addItem(self.cursor)
|
||||
|
||||
self.cursor_label = pg.LabelItem('', parent=self.plot_item)
|
||||
self.cursor_label.anchor(itemPos=(0, 0), parentPos=(0, 0), offset=(0, 20))
|
||||
self.cursor_label.setAttr('justify', 'left')
|
||||
self.cursor_label.setZValue(10)
|
||||
|
||||
def setStoppedX(self, stopped_x):
|
||||
self.stopped_x = stopped_x
|
||||
self.view_box.setLimits(xMax=stopped_x)
|
||||
|
||||
def setData(self, data):
|
||||
if len(data) == 0:
|
||||
self.x_data, self.y_data = [], []
|
||||
else:
|
||||
self.x_data, self.y_data = zip(*data)
|
||||
|
||||
def onDataChange(self, data):
|
||||
raise NotImplementedError
|
||||
|
||||
def onCursorMove(self, x):
|
||||
self.cursor.setValue(x)
|
||||
if len(self.x_data) < 1:
|
||||
return
|
||||
ind = bisect.bisect_left(self.x_data, x) - 1
|
||||
dr = self.plot_data_item.dataRect()
|
||||
self.cursor_y = None
|
||||
if dr is not None and 0 <= ind < len(self.y_data):
|
||||
self.cursor_y = self.y_data[ind]
|
||||
|
||||
def mouseMoveEvent(self, e):
|
||||
if e.buttons() == QtCore.Qt.LeftButton \
|
||||
and e.modifiers() == QtCore.Qt.ShiftModifier:
|
||||
drag = QtGui.QDrag(self)
|
||||
mime = QtCore.QMimeData()
|
||||
drag.setMimeData(mime)
|
||||
pixmapi = QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_FileIcon)
|
||||
drag.setPixmap(pixmapi.pixmap(32))
|
||||
drag.exec_(QtCore.Qt.MoveAction)
|
||||
else:
|
||||
super().mouseMoveEvent(e)
|
||||
|
||||
def wheelEvent(self, e):
|
||||
if e.modifiers() & QtCore.Qt.ControlModifier:
|
||||
super().wheelEvent(e)
|
||||
|
||||
def mouseDoubleClickEvent(self, e):
|
||||
pos = self.view_box.mapSceneToView(e.pos())
|
||||
self.cursorMove.emit(pos.x())
|
||||
|
||||
|
||||
class BitWaveform(_BaseWaveform):
|
||||
def __init__(self, name, width, precision, unit, parent=None):
|
||||
_BaseWaveform.__init__(self, name, width, precision, unit, parent)
|
||||
self.plot_item.showGrid(x=True, y=False)
|
||||
self._arrows = []
|
||||
|
||||
def onDataChange(self, data):
|
||||
try:
|
||||
self.setData(data)
|
||||
for arw in self._arrows:
|
||||
self.removeItem(arw)
|
||||
self._arrows = []
|
||||
l = len(data)
|
||||
display_y = np.empty(l)
|
||||
display_x = np.empty(l)
|
||||
display_map = {
|
||||
"X": 0.5,
|
||||
"1": 1,
|
||||
"0": 0
|
||||
}
|
||||
previous_y = None
|
||||
for i, coord in enumerate(data):
|
||||
x, y = coord
|
||||
dis_y = display_map[y]
|
||||
if previous_y == y:
|
||||
arw = pg.ArrowItem(pxMode=True, angle=90)
|
||||
self.addItem(arw)
|
||||
self._arrows.append(arw)
|
||||
arw.setPos(x, dis_y)
|
||||
display_y[i] = dis_y
|
||||
display_x[i] = x
|
||||
previous_y = y
|
||||
self.plot_data_item.setData(x=display_x, y=display_y)
|
||||
except:
|
||||
logger.error("Error when displaying waveform: %s", self.name, exc_info=True)
|
||||
for arw in self._arrows:
|
||||
self.removeItem(arw)
|
||||
self.plot_data_item.setData(x=[], y=[])
|
||||
|
||||
def onCursorMove(self, x):
|
||||
_BaseWaveform.onCursorMove(self, x)
|
||||
if self.cursor_y is not None:
|
||||
self.cursor_label.setText(self.cursor_y)
|
||||
else:
|
||||
self.cursor_label.setText("")
|
||||
|
||||
|
||||
class AnalogWaveform(_BaseWaveform):
|
||||
def __init__(self, name, width, precision, unit, parent=None):
|
||||
_BaseWaveform.__init__(self, name, width, precision, unit, parent)
|
||||
|
||||
def onDataChange(self, data):
|
||||
try:
|
||||
self.setData(data)
|
||||
self.plot_data_item.setData(x=self.x_data, y=self.y_data)
|
||||
if len(data) > 0:
|
||||
max_y = max(self.y_data)
|
||||
min_y = min(self.y_data)
|
||||
self.plot_item.setRange(yRange=(min_y, max_y), padding=0.1)
|
||||
except:
|
||||
logger.error("Error when displaying waveform: %s", self.name, exc_info=True)
|
||||
self.plot_data_item.setData(x=[], y=[])
|
||||
|
||||
def onCursorMove(self, x):
|
||||
_BaseWaveform.onCursorMove(self, x)
|
||||
if self.cursor_y is not None:
|
||||
t = short_format(self.cursor_y, {"precision": self.precision, "unit": self.unit})
|
||||
else:
|
||||
t = ""
|
||||
self.cursor_label.setText(t)
|
||||
|
||||
|
||||
class BitVectorWaveform(_BaseWaveform):
|
||||
def __init__(self, name, width, precision, unit, parent=None):
|
||||
_BaseWaveform.__init__(self, name, width, precision, parent)
|
||||
self._labels = []
|
||||
self._format_string = "{:0=" + str(math.ceil(width / 4)) + "X}"
|
||||
self.view_box.sigTransformChanged.connect(self._update_labels)
|
||||
self.plot_item.showGrid(x=True, y=False)
|
||||
|
||||
def _update_labels(self):
|
||||
for label in self._labels:
|
||||
self.removeItem(label)
|
||||
xmin, xmax = self.view_box.viewRange()[0]
|
||||
left_label_i = bisect.bisect_left(self.x_data, xmin)
|
||||
right_label_i = bisect.bisect_right(self.x_data, xmax) + 1
|
||||
for i, j in itertools.pairwise(range(left_label_i, right_label_i)):
|
||||
x1 = self.x_data[i]
|
||||
x2 = self.x_data[j] if j < len(self.x_data) else self.stopped_x
|
||||
lbl = self._labels[i]
|
||||
bounds = lbl.boundingRect()
|
||||
bounds_view = self.view_box.mapSceneToView(bounds)
|
||||
if bounds_view.boundingRect().width() < x2 - x1:
|
||||
self.addItem(lbl)
|
||||
|
||||
def onDataChange(self, data):
|
||||
try:
|
||||
self.setData(data)
|
||||
for lbl in self._labels:
|
||||
self.plot_item.removeItem(lbl)
|
||||
self._labels = []
|
||||
l = len(data)
|
||||
display_x = np.empty(l * 2)
|
||||
display_y = np.empty(l * 2)
|
||||
for i, coord in enumerate(data):
|
||||
x, y = coord
|
||||
display_x[i * 2] = x
|
||||
display_x[i * 2 + 1] = x
|
||||
display_y[i * 2] = 0
|
||||
display_y[i * 2 + 1] = int(int(y) != 0)
|
||||
lbl = pg.TextItem(
|
||||
self._format_string.format(int(y, 2)), anchor=(0, 0.5))
|
||||
lbl.setPos(x, 0.5)
|
||||
lbl.setTextWidth(100)
|
||||
self._labels.append(lbl)
|
||||
self.plot_data_item.setData(x=display_x, y=display_y)
|
||||
except:
|
||||
logger.error("Error when displaying waveform: %s", self.name, exc_info=True)
|
||||
for lbl in self._labels:
|
||||
self.plot_item.removeItem(lbl)
|
||||
self.plot_data_item.setData(x=[], y=[])
|
||||
|
||||
def onCursorMove(self, x):
|
||||
_BaseWaveform.onCursorMove(self, x)
|
||||
if self.cursor_y is not None:
|
||||
t = self._format_string.format(int(self.cursor_y, 2))
|
||||
else:
|
||||
t = ""
|
||||
self.cursor_label.setText(t)
|
||||
|
||||
|
||||
class LogWaveform(_BaseWaveform):
|
||||
def __init__(self, name, width, precision, unit, parent=None):
|
||||
_BaseWaveform.__init__(self, name, width, precision, parent)
|
||||
self.plot_data_item.opts['pen'] = None
|
||||
self.plot_data_item.opts['symbol'] = 'x'
|
||||
self._labels = []
|
||||
self.plot_item.showGrid(x=True, y=False)
|
||||
|
||||
def onDataChange(self, data):
|
||||
try:
|
||||
self.setData(data)
|
||||
for lbl in self._labels:
|
||||
self.plot_item.removeItem(lbl)
|
||||
self._labels = []
|
||||
self.plot_data_item.setData(
|
||||
x=self.x_data, y=np.ones(len(self.x_data)))
|
||||
if len(data) == 0:
|
||||
return
|
||||
old_x = data[0][0]
|
||||
old_msg = data[0][1]
|
||||
for x, msg in data[1:]:
|
||||
if x == old_x:
|
||||
old_msg += "\n" + msg
|
||||
else:
|
||||
lbl = pg.TextItem(old_msg)
|
||||
self.addItem(lbl)
|
||||
self._labels.append(lbl)
|
||||
lbl.setPos(old_x, 1)
|
||||
old_msg = msg
|
||||
old_x = x
|
||||
lbl = pg.TextItem(old_msg)
|
||||
self.addItem(lbl)
|
||||
self._labels.append(lbl)
|
||||
lbl.setPos(old_x, 1)
|
||||
except:
|
||||
logger.error("Error when displaying waveform: %s", self.name, exc_info=True)
|
||||
for lbl in self._labels:
|
||||
self.plot_item.removeItem(lbl)
|
||||
self.plot_data_item.setData(x=[], y=[])
|
||||
|
||||
|
||||
class _WaveformView(QtWidgets.QWidget):
|
||||
cursorMove = QtCore.pyqtSignal(float)
|
||||
|
||||
def __init__(self, parent):
|
||||
QtWidgets.QWidget.__init__(self, parent=parent)
|
||||
|
||||
self._stopped_x = None
|
||||
self._timescale = 1
|
||||
self._cursor_x = 0
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
self.setLayout(layout)
|
||||
|
||||
self._ref_axis = pg.PlotWidget()
|
||||
self._ref_axis.hideAxis("bottom")
|
||||
self._ref_axis.hideAxis("left")
|
||||
self._ref_axis.hideButtons()
|
||||
self._ref_axis.setFixedHeight(45)
|
||||
self._ref_axis.setMenuEnabled(False)
|
||||
self._top = pg.AxisItem("top")
|
||||
self._top.setScale(1e-12)
|
||||
self._top.setLabel(units="s")
|
||||
self._ref_axis.setAxisItems({"top": self._top})
|
||||
layout.addWidget(self._ref_axis)
|
||||
|
||||
self._ref_vb = self._ref_axis.getPlotItem().getViewBox()
|
||||
self._ref_vb.setFixedHeight(0)
|
||||
self._ref_vb.setMouseEnabled(x=True, y=False)
|
||||
self._ref_vb.setLimits(xMin=0)
|
||||
|
||||
scroll_area = VDragScrollArea(self)
|
||||
scroll_area.setWidgetResizable(True)
|
||||
scroll_area.setContentsMargins(0, 0, 0, 0)
|
||||
scroll_area.setFrameShape(QtWidgets.QFrame.NoFrame)
|
||||
scroll_area.setVerticalScrollBarPolicy(QtCore.Qt.ScrollBarAlwaysOff)
|
||||
layout.addWidget(scroll_area)
|
||||
|
||||
self._splitter = VDragDropSplitter(parent=scroll_area)
|
||||
self._splitter.setHandleWidth(1)
|
||||
scroll_area.setWidget(self._splitter)
|
||||
|
||||
self.cursorMove.connect(self.onCursorMove)
|
||||
|
||||
self.confirm_delete_dialog = QtWidgets.QMessageBox(self)
|
||||
self.confirm_delete_dialog.setIcon(
|
||||
QtWidgets.QMessageBox.Icon.Warning
|
||||
)
|
||||
self.confirm_delete_dialog.setText("Delete all waveforms?")
|
||||
self.confirm_delete_dialog.setStandardButtons(
|
||||
QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel
|
||||
)
|
||||
self.confirm_delete_dialog.setDefaultButton(
|
||||
QtWidgets.QMessageBox.Ok
|
||||
)
|
||||
|
||||
def setModel(self, model):
|
||||
self._model = model
|
||||
self._model.dataChanged.connect(self.onDataChange)
|
||||
self._model.rowsInserted.connect(self.onInsert)
|
||||
self._model.rowsRemoved.connect(self.onRemove)
|
||||
self._model.rowsMoved.connect(self.onMove)
|
||||
self._splitter.dropped.connect(self._model.move)
|
||||
self.confirm_delete_dialog.accepted.connect(self._model.clear)
|
||||
|
||||
def setTimescale(self, timescale):
|
||||
self._timescale = timescale
|
||||
self._top.setScale(1e-12 * timescale)
|
||||
|
||||
def setStoppedX(self, stopped_x):
|
||||
self._stopped_x = stopped_x
|
||||
self._ref_vb.setLimits(xMax=stopped_x)
|
||||
self._ref_vb.setRange(xRange=(0, stopped_x))
|
||||
for i in range(self._model.rowCount()):
|
||||
self._splitter.widget(i).setStoppedX(stopped_x)
|
||||
|
||||
def resetZoom(self):
|
||||
if self._stopped_x is not None:
|
||||
self._ref_vb.setRange(xRange=(0, self._stopped_x))
|
||||
|
||||
def onDataChange(self, top, bottom, roles):
|
||||
self.cursorMove.emit(0)
|
||||
first = top.row()
|
||||
last = bottom.row()
|
||||
data_row = self._model.headers.index("data")
|
||||
for i in range(first, last + 1):
|
||||
data = self._model.data(self._model.index(i, data_row))
|
||||
self._splitter.widget(i).onDataChange(data)
|
||||
|
||||
def onInsert(self, parent, first, last):
|
||||
for i in range(first, last + 1):
|
||||
w = self._create_waveform(i)
|
||||
self._splitter.insertWidget(i, w)
|
||||
self._resize()
|
||||
|
||||
def onRemove(self, parent, first, last):
|
||||
for i in reversed(range(first, last + 1)):
|
||||
w = self._splitter.widget(i)
|
||||
w.deleteLater()
|
||||
self._splitter.refresh()
|
||||
self._resize()
|
||||
|
||||
def onMove(self, src_parent, src_start, src_end, dest_parent, dest_row):
|
||||
w = self._splitter.widget(src_start)
|
||||
self._splitter.insertWidget(dest_row, w)
|
||||
|
||||
def onCursorMove(self, x):
|
||||
self._cursor_x = x
|
||||
for i in range(self._model.rowCount()):
|
||||
self._splitter.widget(i).onCursorMove(x)
|
||||
|
||||
def _create_waveform(self, row):
|
||||
name, ty, width, precision, unit = (
|
||||
self._model.data(self._model.index(row, i)) for i in range(5))
|
||||
waveform_cls = {
|
||||
WaveformType.BIT: BitWaveform,
|
||||
WaveformType.VECTOR: BitVectorWaveform,
|
||||
WaveformType.ANALOG: AnalogWaveform,
|
||||
WaveformType.LOG: LogWaveform
|
||||
}[ty]
|
||||
w = waveform_cls(name, width, precision, unit, parent=self._splitter)
|
||||
w.setXLink(self._ref_vb)
|
||||
w.setStoppedX(self._stopped_x)
|
||||
w.cursorMove.connect(self.cursorMove)
|
||||
w.onCursorMove(self._cursor_x)
|
||||
action = QtWidgets.QAction("Delete waveform", w)
|
||||
action.triggered.connect(lambda: self._delete_waveform(w))
|
||||
w.addAction(action)
|
||||
action = QtWidgets.QAction("Delete all waveforms", w)
|
||||
action.triggered.connect(self.confirm_delete_dialog.open)
|
||||
w.addAction(action)
|
||||
return w
|
||||
|
||||
def _delete_waveform(self, waveform):
|
||||
row = self._splitter.indexOf(waveform)
|
||||
self._model.pop(row)
|
||||
|
||||
def _resize(self):
|
||||
self._splitter.setFixedHeight(
|
||||
int((WAVEFORM_MIN_HEIGHT + WAVEFORM_MAX_HEIGHT) * self._model.rowCount() / 2))
|
||||
|
||||
|
||||
class _WaveformModel(QtCore.QAbstractTableModel):
|
||||
def __init__(self):
|
||||
self.backing_struct = []
|
||||
self.headers = ["name", "type", "width", "precision", "unit", "data"]
|
||||
QtCore.QAbstractTableModel.__init__(self)
|
||||
|
||||
def rowCount(self, parent=QtCore.QModelIndex()):
|
||||
return len(self.backing_struct)
|
||||
|
||||
def columnCount(self, parent=QtCore.QModelIndex()):
|
||||
return len(self.headers)
|
||||
|
||||
def data(self, index, role=QtCore.Qt.DisplayRole):
|
||||
if index.isValid():
|
||||
return self.backing_struct[index.row()][index.column()]
|
||||
return None
|
||||
|
||||
def extend(self, data):
|
||||
length = len(self.backing_struct)
|
||||
len_data = len(data)
|
||||
self.beginInsertRows(QtCore.QModelIndex(), length, length + len_data - 1)
|
||||
self.backing_struct.extend(data)
|
||||
self.endInsertRows()
|
||||
|
||||
def pop(self, row):
|
||||
self.beginRemoveRows(QtCore.QModelIndex(), row, row)
|
||||
self.backing_struct.pop(row)
|
||||
self.endRemoveRows()
|
||||
|
||||
def move(self, src, dest):
|
||||
if src == dest:
|
||||
return
|
||||
if src < dest:
|
||||
dest, src = src, dest
|
||||
self.beginMoveRows(QtCore.QModelIndex(), src, src, QtCore.QModelIndex(), dest)
|
||||
self.backing_struct.insert(dest, self.backing_struct.pop(src))
|
||||
self.endMoveRows()
|
||||
|
||||
def clear(self):
|
||||
self.beginRemoveRows(QtCore.QModelIndex(), 0, len(self.backing_struct) - 1)
|
||||
self.backing_struct.clear()
|
||||
self.endRemoveRows()
|
||||
|
||||
def export_list(self):
|
||||
return [[row[0], row[1].value, *row[2:5]] for row in self.backing_struct]
|
||||
|
||||
def import_list(self, channel_list):
|
||||
self.clear()
|
||||
data = [[row[0], WaveformType(row[1]), *row[2:5], []] for row in channel_list]
|
||||
self.extend(data)
|
||||
|
||||
def update_data(self, waveform_data, top, bottom):
|
||||
name_col = self.headers.index("name")
|
||||
data_col = self.headers.index("data")
|
||||
for i in range(top, bottom):
|
||||
name = self.data(self.index(i, name_col))
|
||||
self.backing_struct[i][data_col] = waveform_data.get(name, [])
|
||||
self.dataChanged.emit(self.index(i, data_col),
|
||||
self.index(i, data_col))
|
||||
|
||||
def update_all(self, waveform_data):
|
||||
self.update_data(waveform_data, 0, self.rowCount())
|
||||
|
||||
|
||||
class _CursorTimeControl(QtWidgets.QLineEdit):
|
||||
submit = QtCore.pyqtSignal(float)
|
||||
|
||||
def __init__(self, parent):
|
||||
QtWidgets.QLineEdit.__init__(self, parent=parent)
|
||||
self._text = ""
|
||||
self._value = 0
|
||||
self._timescale = 1
|
||||
self.setDisplayValue(0)
|
||||
self.textChanged.connect(self._onTextChange)
|
||||
self.returnPressed.connect(self._onReturnPress)
|
||||
|
||||
def setTimescale(self, timescale):
|
||||
self._timescale = timescale
|
||||
|
||||
def _onTextChange(self, text):
|
||||
self._text = text
|
||||
|
||||
def setDisplayValue(self, value):
|
||||
self._value = value
|
||||
self._text = pg.siFormat(value * 1e-12 * self._timescale,
|
||||
suffix="s",
|
||||
allowUnicode=False,
|
||||
precision=15)
|
||||
self.setText(self._text)
|
||||
|
||||
def _setValueFromText(self, text):
|
||||
try:
|
||||
self._value = pg.siEval(text) * (1e12 / self._timescale)
|
||||
except:
|
||||
logger.error("Error when parsing cursor time input", exc_info=True)
|
||||
|
||||
def _onReturnPress(self):
|
||||
self._setValueFromText(self._text)
|
||||
self.setDisplayValue(self._value)
|
||||
self.submit.emit(self._value)
|
||||
self.clearFocus()
|
||||
|
||||
|
||||
class Model(DictSyncTreeSepModel):
|
||||
def __init__(self, init):
|
||||
DictSyncTreeSepModel.__init__(self, "/", ["Channels"], init)
|
||||
|
||||
def clear(self):
|
||||
for k in self.backing_store:
|
||||
self._del_item(self, k.split(self.separator))
|
||||
self.backing_store.clear()
|
||||
|
||||
def update(self, d):
|
||||
for k, v in d.items():
|
||||
self[k] = v
|
||||
|
||||
|
||||
class _AddChannelDialog(QtWidgets.QDialog):
|
||||
def __init__(self, parent, model):
|
||||
QtWidgets.QDialog.__init__(self, parent=parent)
|
||||
self.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu)
|
||||
self.setWindowTitle("Add channels")
|
||||
|
||||
layout = QtWidgets.QVBoxLayout()
|
||||
self.setLayout(layout)
|
||||
|
||||
self._model = model
|
||||
self._tree_view = QtWidgets.QTreeView()
|
||||
self._tree_view.setHeaderHidden(True)
|
||||
self._tree_view.setSelectionBehavior(
|
||||
QtWidgets.QAbstractItemView.SelectItems)
|
||||
self._tree_view.setSelectionMode(
|
||||
QtWidgets.QAbstractItemView.ExtendedSelection)
|
||||
self._tree_view.setModel(self._model)
|
||||
layout.addWidget(self._tree_view)
|
||||
|
||||
self._button_box = QtWidgets.QDialogButtonBox(
|
||||
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel
|
||||
)
|
||||
self._button_box.setCenterButtons(True)
|
||||
self._button_box.accepted.connect(self.add_channels)
|
||||
self._button_box.rejected.connect(self.reject)
|
||||
layout.addWidget(self._button_box)
|
||||
|
||||
def add_channels(self):
|
||||
selection = self._tree_view.selectedIndexes()
|
||||
channels = []
|
||||
for select in selection:
|
||||
key = self._model.index_to_key(select)
|
||||
if key is not None:
|
||||
channels.append([key, *self._model[key].ref, []])
|
||||
self.channels = channels
|
||||
self.accept()
|
||||
|
||||
|
||||
class WaveformDock(QtWidgets.QDockWidget):
|
||||
def __init__(self, timeout, timer, timer_backoff):
|
||||
QtWidgets.QDockWidget.__init__(self, "Waveform")
|
||||
self.setObjectName("Waveform")
|
||||
self.setFeatures(
|
||||
QtWidgets.QDockWidget.DockWidgetMovable | QtWidgets.QDockWidget.DockWidgetFloatable)
|
||||
|
||||
self._channel_model = Model({})
|
||||
self._waveform_model = _WaveformModel()
|
||||
|
||||
self._ddb = None
|
||||
self._dump = None
|
||||
|
||||
self._waveform_data = {
|
||||
"timescale": 1,
|
||||
"stopped_x": None,
|
||||
"logs": dict(),
|
||||
"data": dict(),
|
||||
}
|
||||
|
||||
self._current_dir = os.getcwd()
|
||||
|
||||
self.proxy_client = ProxyClient(self.on_dump_receive,
|
||||
timeout,
|
||||
timer,
|
||||
timer_backoff)
|
||||
|
||||
grid = LayoutWidget()
|
||||
self.setWidget(grid)
|
||||
|
||||
self._menu_btn = QtWidgets.QPushButton()
|
||||
self._menu_btn.setIcon(
|
||||
QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_FileDialogStart))
|
||||
grid.addWidget(self._menu_btn, 0, 0)
|
||||
|
||||
self._request_dump_btn = QtWidgets.QToolButton()
|
||||
self._request_dump_btn.setToolTip("Fetch analyzer data from device")
|
||||
self._request_dump_btn.setIcon(
|
||||
QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_BrowserReload))
|
||||
self._request_dump_btn.clicked.connect(
|
||||
lambda: asyncio.ensure_future(exc_to_warning(self.proxy_client.trigger_proxy_task())))
|
||||
grid.addWidget(self._request_dump_btn, 0, 1)
|
||||
|
||||
self._add_channel_dialog = _AddChannelDialog(self, self._channel_model)
|
||||
self._add_channel_dialog.accepted.connect(self._add_channels)
|
||||
|
||||
self._add_btn = QtWidgets.QToolButton()
|
||||
self._add_btn.setToolTip("Add channels...")
|
||||
self._add_btn.setIcon(
|
||||
QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_FileDialogListView))
|
||||
self._add_btn.clicked.connect(self._add_channel_dialog.open)
|
||||
grid.addWidget(self._add_btn, 0, 2)
|
||||
|
||||
self._file_menu = QtWidgets.QMenu()
|
||||
self._add_async_action("Open trace...", self.load_trace)
|
||||
self._add_async_action("Save trace...", self.save_trace)
|
||||
self._add_async_action("Save trace as VCD...", self.save_vcd)
|
||||
self._add_async_action("Open channel list...", self.load_channels)
|
||||
self._add_async_action("Save channel list...", self.save_channels)
|
||||
self._menu_btn.setMenu(self._file_menu)
|
||||
|
||||
self._waveform_view = _WaveformView(self)
|
||||
self._waveform_view.setModel(self._waveform_model)
|
||||
grid.addWidget(self._waveform_view, 1, 0, colspan=12)
|
||||
|
||||
self._reset_zoom_btn = QtWidgets.QToolButton()
|
||||
self._reset_zoom_btn.setToolTip("Reset zoom")
|
||||
self._reset_zoom_btn.setIcon(
|
||||
QtWidgets.QApplication.style().standardIcon(
|
||||
QtWidgets.QStyle.SP_TitleBarMaxButton))
|
||||
self._reset_zoom_btn.clicked.connect(self._waveform_view.resetZoom)
|
||||
grid.addWidget(self._reset_zoom_btn, 0, 3)
|
||||
|
||||
self._cursor_control = _CursorTimeControl(self)
|
||||
self._waveform_view.cursorMove.connect(self._cursor_control.setDisplayValue)
|
||||
self._cursor_control.submit.connect(self._waveform_view.onCursorMove)
|
||||
grid.addWidget(self._cursor_control, 0, 4, colspan=6)
|
||||
|
||||
def _add_async_action(self, label, coro):
|
||||
action = QtWidgets.QAction(label, self)
|
||||
action.triggered.connect(
|
||||
lambda: asyncio.ensure_future(exc_to_warning(coro())))
|
||||
self._file_menu.addAction(action)
|
||||
|
||||
def _add_channels(self):
|
||||
channels = self._add_channel_dialog.channels
|
||||
count = self._waveform_model.rowCount()
|
||||
self._waveform_model.extend(channels)
|
||||
self._waveform_model.update_data(self._waveform_data['data'],
|
||||
count,
|
||||
count + len(channels))
|
||||
|
||||
def on_dump_receive(self, dump):
|
||||
self._dump = dump
|
||||
decoded_dump = comm_analyzer.decode_dump(dump)
|
||||
waveform_data = comm_analyzer.decoded_dump_to_waveform_data(self._ddb, decoded_dump)
|
||||
self._waveform_data.update(waveform_data)
|
||||
self._channel_model.update(self._waveform_data['logs'])
|
||||
self._waveform_model.update_all(self._waveform_data['data'])
|
||||
self._waveform_view.setStoppedX(self._waveform_data['stopped_x'])
|
||||
self._waveform_view.setTimescale(self._waveform_data['timescale'])
|
||||
self._cursor_control.setTimescale(self._waveform_data['timescale'])
|
||||
|
||||
async def load_trace(self):
|
||||
try:
|
||||
filename = await get_open_file_name(
|
||||
self,
|
||||
"Load Analyzer Trace",
|
||||
self._current_dir,
|
||||
"All files (*.*)")
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
self._current_dir = os.path.dirname(filename)
|
||||
try:
|
||||
with open(filename, 'rb') as f:
|
||||
dump = f.read()
|
||||
self.on_dump_receive(dump)
|
||||
except:
|
||||
logger.error("Failed to open analyzer trace", exc_info=True)
|
||||
|
||||
async def save_trace(self):
|
||||
if self._dump is None:
|
||||
logger.error("No analyzer trace stored in dashboard, "
|
||||
"try loading from file or fetching from device")
|
||||
return
|
||||
try:
|
||||
filename = await get_save_file_name(
|
||||
self,
|
||||
"Save Analyzer Trace",
|
||||
self._current_dir,
|
||||
"All files (*.*)")
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
self._current_dir = os.path.dirname(filename)
|
||||
try:
|
||||
with open(filename, 'wb') as f:
|
||||
f.write(self._dump)
|
||||
except:
|
||||
logger.error("Failed to save analyzer trace", exc_info=True)
|
||||
|
||||
async def save_vcd(self):
|
||||
if self._dump is None:
|
||||
logger.error("No analyzer trace stored in dashboard, "
|
||||
"try loading from file or fetching from device")
|
||||
return
|
||||
try:
|
||||
filename = await get_save_file_name(
|
||||
self,
|
||||
"Save VCD",
|
||||
self._current_dir,
|
||||
"All files (*.*)")
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
self._current_dir = os.path.dirname(filename)
|
||||
try:
|
||||
decoded_dump = comm_analyzer.decode_dump(self._dump)
|
||||
with open(filename, 'w') as f:
|
||||
comm_analyzer.decoded_dump_to_vcd(f, self._ddb, decoded_dump)
|
||||
except:
|
||||
logger.error("Failed to save trace as VCD", exc_info=True)
|
||||
|
||||
async def load_channels(self):
|
||||
try:
|
||||
filename = await get_open_file_name(
|
||||
self,
|
||||
"Open channel list",
|
||||
self._current_dir,
|
||||
"PYON files (*.pyon);;All files (*.*)")
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
self._current_dir = os.path.dirname(filename)
|
||||
try:
|
||||
channel_list = pyon.load_file(filename)
|
||||
self._waveform_model.import_list(channel_list)
|
||||
self._waveform_model.update_all(self._waveform_data['data'])
|
||||
except:
|
||||
logger.error("Failed to open channel list", exc_info=True)
|
||||
|
||||
async def save_channels(self):
|
||||
try:
|
||||
filename = await get_save_file_name(
|
||||
self,
|
||||
"Save channel list",
|
||||
self._current_dir,
|
||||
"PYON files (*.pyon);;All files (*.*)")
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
self._current_dir = os.path.dirname(filename)
|
||||
try:
|
||||
channel_list = self._waveform_model.export_list()
|
||||
pyon.store_file(filename, channel_list)
|
||||
except:
|
||||
logger.error("Failed to save channel list", exc_info=True)
|
||||
|
||||
def _process_ddb(self):
|
||||
channel_list = comm_analyzer.get_channel_list(self._ddb)
|
||||
self._channel_model.clear()
|
||||
self._channel_model.update(channel_list)
|
||||
desc = self._ddb.get("core_analyzer")
|
||||
if desc is not None:
|
||||
addr = desc["host"]
|
||||
port_proxy = desc.get("port_proxy", 1385)
|
||||
port = desc.get("port", 1386)
|
||||
self.proxy_client.update_address(addr, port, port_proxy)
|
||||
else:
|
||||
self.proxy_client.update_address(None, None, None)
|
||||
|
||||
def init_ddb(self, ddb):
|
||||
self._ddb = ddb
|
||||
self._process_ddb()
|
||||
return ddb
|
||||
|
||||
def notify_ddb(self, mod):
|
||||
self._process_ddb()
|
||||
|
||||
async def stop(self):
|
||||
if self.proxy_client is not None:
|
||||
await self.proxy_client.close()
|
|
@ -127,7 +127,7 @@
|
|||
"# let's connect to the master\n",
|
||||
"\n",
|
||||
"schedule, exps, datasets = [\n",
|
||||
" Client(\"::1\", 3251, \"master_\" + i) for i in\n",
|
||||
" Client(\"::1\", 3251, i) for i in\n",
|
||||
" \"schedule experiment_db dataset_db\".split()]\n",
|
||||
"\n",
|
||||
"print(\"current schedule\")\n",
|
||||
|
|
|
@ -7,7 +7,11 @@ device_db = {
|
|||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {"host": core_addr, "ref_period": 1e-9}
|
||||
"arguments": {
|
||||
"host": core_addr,
|
||||
"ref_period": 1e-9,
|
||||
"analyzer_proxy": "core_analyzer"
|
||||
}
|
||||
},
|
||||
"core_log": {
|
||||
"type": "controller",
|
||||
|
@ -22,6 +26,13 @@ device_db = {
|
|||
"port": 1384,
|
||||
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_analyzer": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1385,
|
||||
"port": 1386,
|
||||
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_cache": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.cache",
|
||||
|
|
|
@ -5,7 +5,11 @@ device_db = {
|
|||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {"host": core_addr, "ref_period": 1/(8*150e6)}
|
||||
"arguments": {
|
||||
"host": core_addr,
|
||||
"ref_period": 1e-9,
|
||||
"analyzer_proxy": "core_analyzer"
|
||||
}
|
||||
},
|
||||
"core_log": {
|
||||
"type": "controller",
|
||||
|
@ -20,6 +24,13 @@ device_db = {
|
|||
"port": 1384,
|
||||
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_analyzer": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1385,
|
||||
"port": 1386,
|
||||
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_cache": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.cache",
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
core_addr = "192.168.1.70"
|
||||
|
||||
device_db = {
|
||||
"core": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {"host": core_addr, "ref_period": 1/(8*150e6)}
|
||||
},
|
||||
"core_log": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port": 1068,
|
||||
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_moninj": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1383,
|
||||
"port": 1384,
|
||||
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_cache": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.cache",
|
||||
"class": "CoreCache"
|
||||
},
|
||||
"core_dma": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.dma",
|
||||
"class": "CoreDMA"
|
||||
},
|
||||
}
|
||||
|
||||
device_db.update(
|
||||
spi_urukul0={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.spi2",
|
||||
"class": "SPIMaster",
|
||||
"arguments": {"channel": 0}
|
||||
},
|
||||
ttl_urukul0_io_update={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 1}
|
||||
},
|
||||
ttl_urukul0_sw0={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 2}
|
||||
},
|
||||
ttl_urukul0_sw1={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 3}
|
||||
},
|
||||
ttl_urukul0_sw2={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 4}
|
||||
},
|
||||
ttl_urukul0_sw3={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 5}
|
||||
},
|
||||
urukul0_cpld={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.urukul",
|
||||
"class": "CPLD",
|
||||
"arguments": {
|
||||
"spi_device": "spi_urukul0",
|
||||
"io_update_device": "ttl_urukul0_io_update",
|
||||
"refclk": 150e6,
|
||||
"clk_sel": 2
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
for i in range(4):
|
||||
device_db["urukul0_ch" + str(i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ad9910",
|
||||
"class": "AD9910",
|
||||
"arguments": {
|
||||
"pll_n": 16, # 600MHz sample rate
|
||||
"pll_vco": 2,
|
||||
"chip_select": 4 + i,
|
||||
"cpld_device": "urukul0_cpld",
|
||||
"sw_device": "ttl_urukul0_sw" + str(i)
|
||||
}
|
||||
}
|
||||
|
||||
"""
|
||||
artiq_route routing.bin init
|
||||
artiq_route routing.bin set 0 0
|
||||
artiq_route routing.bin set 1 1 0
|
||||
artiq_route routing.bin set 2 1 1 0
|
||||
artiq_route routing.bin set 3 2 0
|
||||
artiq_route routing.bin set 4 2 1 0
|
||||
artiq_coremgmt -D kasli config write -f routing_table routing.bin
|
||||
"""
|
||||
|
||||
for sayma in range(2):
|
||||
amc_base = 0x010000 + sayma*0x020000
|
||||
rtm_base = 0x020000 + sayma*0x020000
|
||||
for i in range(4):
|
||||
device_db["led" + str(4*sayma+i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": amc_base + i}
|
||||
}
|
||||
for i in range(2):
|
||||
device_db["ttl_mcx" + str(2*sayma+i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLInOut",
|
||||
"arguments": {"channel": amc_base + 4 + i}
|
||||
}
|
||||
for i in range(8):
|
||||
device_db["sawg" + str(8*sayma+i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.sawg",
|
||||
"class": "SAWG",
|
||||
"arguments": {"channel_base": amc_base + 6 + i*10, "parallelism": 4}
|
||||
}
|
||||
for basemod in range(2):
|
||||
for i in range(4):
|
||||
device_db["sawg_sw" + str(8*sayma+4*basemod+i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": rtm_base + basemod*9 + i}
|
||||
}
|
||||
att_idx = 2*sayma + basemod
|
||||
device_db["basemod_att_rst_n"+str(att_idx)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": rtm_base + basemod*9 + 4}
|
||||
}
|
||||
device_db["basemod_att_clk"+str(att_idx)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": rtm_base + basemod*9 + 5}
|
||||
}
|
||||
device_db["basemod_att_le"+str(att_idx)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": rtm_base + basemod*9 + 6}
|
||||
}
|
||||
device_db["basemod_att_mosi"+str(att_idx)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": rtm_base + basemod*9 + 7}
|
||||
}
|
||||
device_db["basemod_att_miso"+str(att_idx)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLInOut",
|
||||
"arguments": {"channel": rtm_base + basemod*9 + 8}
|
||||
}
|
||||
device_db["basemod_att"+str(att_idx)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.basemod_att",
|
||||
"class": "BaseModAtt",
|
||||
"arguments": {
|
||||
"rst_n": "basemod_att_rst_n"+str(att_idx),
|
||||
"clk": "basemod_att_clk"+str(att_idx),
|
||||
"le": "basemod_att_le"+str(att_idx),
|
||||
"mosi": "basemod_att_mosi"+str(att_idx),
|
||||
"miso": "basemod_att_miso"+str(att_idx),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
from artiq.experiment import *
|
||||
|
||||
|
||||
class BaseMod(EnvExperiment):
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.basemods = [self.get_device("basemod_att0"), self.get_device("basemod_att1")]
|
||||
self.rfsws = [self.get_device("sawg_sw"+str(i)) for i in range(8)]
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
self.core.reset()
|
||||
for basemod in self.basemods:
|
||||
self.core.break_realtime()
|
||||
delay(10*ms)
|
||||
basemod.reset()
|
||||
delay(10*ms)
|
||||
basemod.set(0.0, 0.0, 0.0, 0.0)
|
||||
delay(10*ms)
|
||||
print(basemod.get_mu())
|
||||
|
||||
self.core.break_realtime()
|
||||
for rfsw in self.rfsws:
|
||||
rfsw.on()
|
||||
delay(1*ms)
|
|
@ -1,37 +0,0 @@
|
|||
from artiq.experiment import *
|
||||
|
||||
|
||||
class Sines2Sayma(EnvExperiment):
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.sawgs = [self.get_device("sawg"+str(i)) for i in range(16)]
|
||||
|
||||
@kernel
|
||||
def drtio_is_up(self):
|
||||
for i in range(5):
|
||||
if not self.core.get_rtio_destination_status(i):
|
||||
return False
|
||||
return True
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
while True:
|
||||
print("waiting for DRTIO ready...")
|
||||
while not self.drtio_is_up():
|
||||
pass
|
||||
print("OK")
|
||||
|
||||
self.core.reset()
|
||||
|
||||
for sawg in self.sawgs:
|
||||
delay(1*ms)
|
||||
sawg.reset()
|
||||
|
||||
for sawg in self.sawgs:
|
||||
delay(1*ms)
|
||||
sawg.amplitude1.set(.4)
|
||||
# Do not use a sub-multiple of oscilloscope sample rates.
|
||||
sawg.frequency0.set(9*MHz)
|
||||
|
||||
while self.drtio_is_up():
|
||||
pass
|
|
@ -1,89 +0,0 @@
|
|||
from artiq.experiment import *
|
||||
|
||||
|
||||
class SinesUrukulSayma(EnvExperiment):
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("urukul0_cpld")
|
||||
|
||||
# Urukul clock output syntonized to the RTIO clock.
|
||||
# Can be used as HMC830 reference on Sayma RTM.
|
||||
# When using this reference, Sayma must be recalibrated every time Urukul
|
||||
# is rebooted, as Urukul is not synchronized to the Kasli.
|
||||
self.urukul_hmc_ref = self.get_device("urukul0_ch3")
|
||||
|
||||
# Urukul measurement channels - compare with SAWG outputs.
|
||||
# When testing sync, do not reboot Urukul, as it is not
|
||||
# synchronized to the Kasli.
|
||||
self.urukul_meas = [self.get_device("urukul0_ch" + str(i)) for i in range(3)]
|
||||
# The same waveform is output on all first 4 SAWG channels (first DAC).
|
||||
self.sawgs = [self.get_device("sawg"+str(i)) for i in range(4)]
|
||||
self.basemod = self.get_device("basemod_att0")
|
||||
self.rfsws = [self.get_device("sawg_sw"+str(i)) for i in range(4)]
|
||||
|
||||
|
||||
# DRTIO destinations:
|
||||
# 0: local
|
||||
# 1: Sayma AMC
|
||||
# 2: Sayma RTM
|
||||
@kernel
|
||||
def drtio_is_up(self):
|
||||
for i in range(3):
|
||||
if not self.core.get_rtio_destination_status(i):
|
||||
return False
|
||||
return True
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
f = 9*MHz
|
||||
dds_ftw = self.urukul_meas[0].frequency_to_ftw(f)
|
||||
sawg_ftw = self.sawgs[0].frequency0.to_mu(f)
|
||||
if dds_ftw != sawg_ftw:
|
||||
print("DDS and SAWG FTWs do not match:", dds_ftw, sawg_ftw)
|
||||
return
|
||||
|
||||
self.core.reset()
|
||||
self.urukul0_cpld.init()
|
||||
|
||||
delay(1*ms)
|
||||
self.urukul_hmc_ref.init()
|
||||
self.urukul_hmc_ref.set_mu(0x40000000, asf=self.urukul_hmc_ref.amplitude_to_asf(0.6))
|
||||
self.urukul_hmc_ref.set_att(6.)
|
||||
self.urukul_hmc_ref.sw.on()
|
||||
|
||||
for urukul_ch in self.urukul_meas:
|
||||
delay(1*ms)
|
||||
urukul_ch.init()
|
||||
urukul_ch.set_mu(dds_ftw, asf=urukul_ch.amplitude_to_asf(0.5))
|
||||
urukul_ch.set_att(6.)
|
||||
urukul_ch.sw.on()
|
||||
|
||||
while True:
|
||||
print("waiting for DRTIO ready...")
|
||||
while not self.drtio_is_up():
|
||||
pass
|
||||
print("OK")
|
||||
|
||||
self.core.reset()
|
||||
|
||||
delay(10*ms)
|
||||
self.basemod.reset()
|
||||
delay(10*ms)
|
||||
self.basemod.set(3.0, 3.0, 3.0, 3.0)
|
||||
delay(10*ms)
|
||||
for rfsw in self.rfsws:
|
||||
delay(1*ms)
|
||||
rfsw.on()
|
||||
|
||||
for sawg in self.sawgs:
|
||||
delay(1*ms)
|
||||
sawg.reset()
|
||||
|
||||
for sawg in self.sawgs:
|
||||
delay(1*ms)
|
||||
sawg.amplitude1.set(.4)
|
||||
sawg.frequency0.set_mu(sawg_ftw)
|
||||
sawg.phase0.set_mu(sawg_ftw*now_mu() >> 17)
|
||||
|
||||
while self.drtio_is_up():
|
||||
pass
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"target": "kasli",
|
||||
"variant": "shuttlerdemo",
|
||||
"hw_rev": "v2.0",
|
||||
"drtio_role": "master",
|
||||
"peripherals": [
|
||||
{
|
||||
"type": "shuttler",
|
||||
"ports": [0]
|
||||
},
|
||||
{
|
||||
"type": "dio",
|
||||
"ports": [1],
|
||||
"bank_direction_low": "input",
|
||||
"bank_direction_high": "output"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,330 @@
|
|||
from artiq.experiment import *
|
||||
from artiq.coredevice.shuttler import shuttler_volt_to_mu
|
||||
|
||||
DAC_Fs_MHZ = 125
|
||||
CORDIC_GAIN = 1.64676
|
||||
|
||||
@portable
|
||||
def shuttler_phase_offset(offset_degree):
|
||||
return round(offset_degree / 360 * (2 ** 16))
|
||||
|
||||
@portable
|
||||
def shuttler_freq_mu(freq_mhz):
|
||||
return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz)
|
||||
|
||||
@portable
|
||||
def shuttler_chirp_rate_mu(freq_mhz_per_us):
|
||||
return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2))
|
||||
|
||||
@portable
|
||||
def shuttler_freq_sweep(start_f_MHz, end_f_MHz, time_us):
|
||||
return shuttler_chirp_rate_mu((end_f_MHz - start_f_MHz)/(time_us))
|
||||
|
||||
@portable
|
||||
def shuttler_volt_amp_mu(volt):
|
||||
return shuttler_volt_to_mu(volt)
|
||||
|
||||
@portable
|
||||
def shuttler_volt_damp_mu(volt_per_us):
|
||||
return round(float(2) ** 32 * (volt_per_us / 20) / DAC_Fs_MHZ)
|
||||
|
||||
@portable
|
||||
def shuttler_volt_ddamp_mu(volt_per_us_square):
|
||||
return round(float(2) ** 48 * (volt_per_us_square / 20) * 2 / (DAC_Fs_MHZ ** 2))
|
||||
|
||||
@portable
|
||||
def shuttler_volt_dddamp_mu(volt_per_us_cube):
|
||||
return round(float(2) ** 48 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3))
|
||||
|
||||
@portable
|
||||
def shuttler_dds_amp_mu(volt):
|
||||
return shuttler_volt_amp_mu(volt / CORDIC_GAIN)
|
||||
|
||||
@portable
|
||||
def shuttler_dds_damp_mu(volt_per_us):
|
||||
return shuttler_volt_damp_mu(volt_per_us / CORDIC_GAIN)
|
||||
|
||||
@portable
|
||||
def shuttler_dds_ddamp_mu(volt_per_us_square):
|
||||
return shuttler_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN)
|
||||
|
||||
@portable
|
||||
def shuttler_dds_dddamp_mu(volt_per_us_cube):
|
||||
return shuttler_volt_dddamp_mu(volt_per_us_cube / CORDIC_GAIN)
|
||||
|
||||
class Shuttler(EnvExperiment):
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("core_dma")
|
||||
self.setattr_device("scheduler")
|
||||
self.shuttler0_leds = [ self.get_device("shuttler0_led{}".format(i)) for i in range(2) ]
|
||||
self.setattr_device("shuttler0_config")
|
||||
self.setattr_device("shuttler0_trigger")
|
||||
self.shuttler0_dcbias = [ self.get_device("shuttler0_dcbias{}".format(i)) for i in range(16) ]
|
||||
self.shuttler0_dds = [ self.get_device("shuttler0_dds{}".format(i)) for i in range(16) ]
|
||||
self.setattr_device("shuttler0_relay")
|
||||
self.setattr_device("shuttler0_adc")
|
||||
|
||||
|
||||
@kernel
|
||||
def record(self):
|
||||
with self.core_dma.record("example_waveform"):
|
||||
self.example_waveform()
|
||||
|
||||
@kernel
|
||||
def init(self):
|
||||
self.led()
|
||||
self.relay_init()
|
||||
self.adc_init()
|
||||
self.shuttler_reset()
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
self.core.reset()
|
||||
self.core.break_realtime()
|
||||
self.init()
|
||||
|
||||
self.record()
|
||||
example_waveform_handle = self.core_dma.get_handle("example_waveform")
|
||||
|
||||
print("Example Waveforms are on OUT0 and OUT1")
|
||||
self.core.break_realtime()
|
||||
while not(self.scheduler.check_termination()):
|
||||
delay(1*s)
|
||||
self.core_dma.playback_handle(example_waveform_handle)
|
||||
|
||||
@kernel
|
||||
def shuttler_reset(self):
|
||||
for i in range(16):
|
||||
self.shuttler_channel_reset(i)
|
||||
# To avoid RTIO Underflow
|
||||
delay(50*us)
|
||||
|
||||
@kernel
|
||||
def shuttler_channel_reset(self, ch):
|
||||
self.shuttler0_dcbias[ch].set_waveform(
|
||||
a0=0,
|
||||
a1=0,
|
||||
a2=0,
|
||||
a3=0,
|
||||
)
|
||||
self.shuttler0_dds[ch].set_waveform(
|
||||
b0=0,
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
c0=0,
|
||||
c1=0,
|
||||
c2=0,
|
||||
)
|
||||
self.shuttler0_trigger.trigger(1 << ch)
|
||||
|
||||
@kernel
|
||||
def example_waveform(self):
|
||||
# Equation of Output Waveform
|
||||
# w(t_us) = a(t_us) + b(t_us) * cos(c(t_us))
|
||||
# Step 1:
|
||||
# Enable the Output Relay of OUT0 and OUT1
|
||||
# Step 2: Cosine Wave Frequency Sweep from 10kHz to 50kHz in 500us
|
||||
# OUT0: b(t_us) = 1
|
||||
# c(t_us) = 2 * pi * (0.08 * t_us ^ 2 + 0.01 * t_us)
|
||||
# OUT1: b(t_us) = 1
|
||||
# c(t_us) = 2 * pi * (0.05 * t_us)
|
||||
# Step 3(after 500us): Cosine Wave with 180 Degree Phase Offset
|
||||
# OUT0: b(t_us) = 1
|
||||
# c(t_us) = 2 * pi * (0.05 * t_us) + pi
|
||||
# OUT1: b(t_us) = 1
|
||||
# c(t_us) = 2 * pi * (0.05 * t_us)
|
||||
# Step 4(after 500us): Cosine Wave with Amplitude Envelop
|
||||
# OUT0: b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us
|
||||
# c(t_us) = 2 * pi * (0.05 * t_us)
|
||||
# OUT1: b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us
|
||||
# c(t_us) = 0
|
||||
# Step 5(after 500us): Sawtooth Wave Modulated with 50kHz Cosine Wave
|
||||
# OUT0: a(t_us) = 0.01 * t_us - 5
|
||||
# b(t_us) = 1
|
||||
# c(t_us) = 2 * pi * (0.05 * t_us)
|
||||
# OUT1: a(t_us) = 0.01 * t_us - 5
|
||||
# Step 6(after 1000us): A Combination of Previous Waveforms
|
||||
# OUT0: a(t_us) = 0.01 * t_us - 5
|
||||
# b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us
|
||||
# c(t_us) = 2 * pi * (0.08 * t_us ^ 2 + 0.01 * t_us)
|
||||
# Step 7(after 500us): Mirrored Waveform in Step 6
|
||||
# OUT0: a(t_us) = 2.5 + -0.01 * (1000 ^ 2) * t_us
|
||||
# b(t_us) = 0.0001367187 * t_us ^ 2 - 0.06835937 * t_us
|
||||
# c(t_us) = 2 * pi * (-0.08 * t_us ^ 2 + 0.05 * t_us) + pi
|
||||
# Step 8(after 500us):
|
||||
# Disable Output Relay of OUT0 and OUT1
|
||||
# Reset OUT0 and OUT1
|
||||
|
||||
## Step 1 ##
|
||||
self.shuttler0_relay.enable(0b11)
|
||||
|
||||
## Step 2 ##
|
||||
start_f_MHz = 0.01
|
||||
end_f_MHz = 0.05
|
||||
duration_us = 500
|
||||
# OUT0 and OUT1 have their frequency and phase aligned at 500us
|
||||
self.shuttler0_dds[0].set_waveform(
|
||||
b0=shuttler_dds_amp_mu(1.0),
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
c0=0,
|
||||
c1=shuttler_freq_mu(start_f_MHz),
|
||||
c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us),
|
||||
)
|
||||
self.shuttler0_dds[1].set_waveform(
|
||||
b0=shuttler_dds_amp_mu(1.0),
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
c0=0,
|
||||
c1=shuttler_freq_mu(end_f_MHz),
|
||||
c2=0,
|
||||
)
|
||||
self.shuttler0_trigger.trigger(0b11)
|
||||
delay(500*us)
|
||||
|
||||
## Step 3 ##
|
||||
# OUT0 and OUT1 has 180 degree phase difference
|
||||
self.shuttler0_dds[0].set_waveform(
|
||||
b0=shuttler_dds_amp_mu(1.0),
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
c0=shuttler_phase_offset(180.0),
|
||||
c1=shuttler_freq_mu(end_f_MHz),
|
||||
c2=0,
|
||||
)
|
||||
# Phase and Output Setting of OUT1 is retained
|
||||
# if the channel is not triggered or config is not cleared
|
||||
self.shuttler0_trigger.trigger(0b1)
|
||||
delay(500*us)
|
||||
|
||||
## Step 4 ##
|
||||
# b(0) = 0, b(250) = 8.545, b(500) = 0
|
||||
self.shuttler0_dds[0].set_waveform(
|
||||
b0=0,
|
||||
b1=shuttler_dds_damp_mu(0.06835937),
|
||||
b2=shuttler_dds_ddamp_mu(-0.0001367187),
|
||||
b3=0,
|
||||
c0=0,
|
||||
c1=shuttler_freq_mu(end_f_MHz),
|
||||
c2=0,
|
||||
)
|
||||
self.shuttler0_dds[1].set_waveform(
|
||||
b0=0,
|
||||
b1=shuttler_dds_damp_mu(0.06835937),
|
||||
b2=shuttler_dds_ddamp_mu(-0.0001367187),
|
||||
b3=0,
|
||||
c0=0,
|
||||
c1=0,
|
||||
c2=0,
|
||||
)
|
||||
self.shuttler0_trigger.trigger(0b11)
|
||||
delay(500*us)
|
||||
|
||||
## Step 5 ##
|
||||
self.shuttler0_dcbias[0].set_waveform(
|
||||
a0=shuttler_volt_amp_mu(-5.0),
|
||||
a1=int32(shuttler_volt_damp_mu(0.01)),
|
||||
a2=0,
|
||||
a3=0,
|
||||
)
|
||||
self.shuttler0_dds[0].set_waveform(
|
||||
b0=shuttler_dds_amp_mu(1.0),
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
c0=0,
|
||||
c1=shuttler_freq_mu(end_f_MHz),
|
||||
c2=0,
|
||||
)
|
||||
self.shuttler0_dcbias[1].set_waveform(
|
||||
a0=shuttler_volt_amp_mu(-5.0),
|
||||
a1=int32(shuttler_volt_damp_mu(0.01)),
|
||||
a2=0,
|
||||
a3=0,
|
||||
)
|
||||
self.shuttler0_dds[1].set_waveform(
|
||||
b0=0,
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
c0=0,
|
||||
c1=0,
|
||||
c2=0,
|
||||
)
|
||||
self.shuttler0_trigger.trigger(0b11)
|
||||
delay(1000*us)
|
||||
|
||||
## Step 6 ##
|
||||
self.shuttler0_dcbias[0].set_waveform(
|
||||
a0=shuttler_volt_amp_mu(-2.5),
|
||||
a1=int32(shuttler_volt_damp_mu(0.01)),
|
||||
a2=0,
|
||||
a3=0,
|
||||
)
|
||||
self.shuttler0_dds[0].set_waveform(
|
||||
b0=0,
|
||||
b1=shuttler_dds_damp_mu(0.06835937),
|
||||
b2=shuttler_dds_ddamp_mu(-0.0001367187),
|
||||
b3=0,
|
||||
c0=0,
|
||||
c1=shuttler_freq_mu(start_f_MHz),
|
||||
c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us),
|
||||
)
|
||||
self.shuttler0_trigger.trigger(0b1)
|
||||
self.shuttler_channel_reset(1)
|
||||
delay(500*us)
|
||||
|
||||
## Step 7 ##
|
||||
self.shuttler0_dcbias[0].set_waveform(
|
||||
a0=shuttler_volt_amp_mu(2.5),
|
||||
a1=int32(shuttler_volt_damp_mu(-0.01)),
|
||||
a2=0,
|
||||
a3=0,
|
||||
)
|
||||
self.shuttler0_dds[0].set_waveform(
|
||||
b0=0,
|
||||
b1=shuttler_dds_damp_mu(-0.06835937),
|
||||
b2=shuttler_dds_ddamp_mu(0.0001367187),
|
||||
b3=0,
|
||||
c0=shuttler_phase_offset(180.0),
|
||||
c1=shuttler_freq_mu(end_f_MHz),
|
||||
c2=shuttler_freq_sweep(end_f_MHz, start_f_MHz, duration_us),
|
||||
)
|
||||
self.shuttler0_trigger.trigger(0b1)
|
||||
delay(500*us)
|
||||
|
||||
## Step 8 ##
|
||||
self.shuttler0_relay.enable(0)
|
||||
self.shuttler_channel_reset(0)
|
||||
self.shuttler_channel_reset(1)
|
||||
|
||||
@kernel
|
||||
def led(self):
|
||||
for i in range(2):
|
||||
for j in range(3):
|
||||
self.shuttler0_leds[i].pulse(.1*s)
|
||||
delay(.1*s)
|
||||
|
||||
@kernel
|
||||
def relay_init(self):
|
||||
self.shuttler0_relay.init()
|
||||
self.shuttler0_relay.enable(0x0000)
|
||||
|
||||
@kernel
|
||||
def adc_init(self):
|
||||
delay_mu(int64(self.core.ref_multiplier))
|
||||
self.shuttler0_adc.power_up()
|
||||
|
||||
delay_mu(int64(self.core.ref_multiplier))
|
||||
assert self.shuttler0_adc.read_id() >> 4 == 0x038d
|
||||
|
||||
delay_mu(int64(self.core.ref_multiplier))
|
||||
# The actual output voltage is limited by the hardware, the calculated calibration gain and offset.
|
||||
# For example, if the system has a calibration gain of 1.06, then the max output voltage = 10 / 1.06 = 9.43V.
|
||||
# Setting a value larger than 9.43V will result in overflow.
|
||||
self.shuttler0_adc.calibrate(self.shuttler0_dcbias, self.shuttler0_trigger, self.shuttler0_config)
|
|
@ -5,7 +5,11 @@ device_db = {
|
|||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {"host": core_addr, "ref_period": 1e-9}
|
||||
"arguments": {
|
||||
"host": core_addr,
|
||||
"ref_period": 1e-9,
|
||||
"analyzer_proxy": "core_analyzer"
|
||||
}
|
||||
},
|
||||
"core_log": {
|
||||
"type": "controller",
|
||||
|
@ -20,6 +24,13 @@ device_db = {
|
|||
"port": 1384,
|
||||
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_analyzer": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1385,
|
||||
"port": 1386,
|
||||
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_cache": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.cache",
|
||||
|
|
|
@ -9,7 +9,11 @@ device_db = {
|
|||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {"host": core_addr, "ref_period": 1e-9}
|
||||
"arguments": {
|
||||
"host": core_addr,
|
||||
"ref_period": 1e-9,
|
||||
"analyzer_proxy": "core_analyzer"
|
||||
}
|
||||
},
|
||||
"core_log": {
|
||||
"type": "controller",
|
||||
|
@ -24,6 +28,13 @@ device_db = {
|
|||
"port": 1384,
|
||||
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_analyzer": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1385,
|
||||
"port": 1386,
|
||||
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_cache": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.cache",
|
||||
|
|
|
@ -20,7 +20,7 @@ class DDSSetter(EnvExperiment):
|
|||
"driver": self.get_device(k),
|
||||
"frequency": self.get_argument(
|
||||
"{}_frequency".format(k),
|
||||
NumberValue(100e6, scale=1e6, unit="MHz", ndecimals=6))
|
||||
NumberValue(100e6, scale=1e6, unit="MHz", precision=6))
|
||||
}
|
||||
|
||||
@kernel
|
||||
|
|
|
@ -12,8 +12,8 @@ class PhotonHistogram(EnvExperiment):
|
|||
self.setattr_device("bdd_sw")
|
||||
self.setattr_device("pmt")
|
||||
|
||||
self.setattr_argument("nbins", NumberValue(100, ndecimals=0, step=1))
|
||||
self.setattr_argument("repeats", NumberValue(100, ndecimals=0, step=1))
|
||||
self.setattr_argument("nbins", NumberValue(100, precision=0, step=1))
|
||||
self.setattr_argument("repeats", NumberValue(100, precision=0, step=1))
|
||||
|
||||
self.setattr_dataset("cool_f", 230*MHz)
|
||||
self.setattr_dataset("detect_f", 220*MHz)
|
||||
|
|
|
@ -79,7 +79,7 @@ class SpeedBenchmark(EnvExperiment):
|
|||
"CoreSend1MB",
|
||||
"CorePrimes"]))
|
||||
self.setattr_argument("nruns", NumberValue(10, min=1, max=1000,
|
||||
ndecimals=0, step=1))
|
||||
precision=0, step=1))
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("scheduler")
|
||||
|
||||
|
|
|
@ -1,102 +0,0 @@
|
|||
core_addr = "192.168.1.65"
|
||||
|
||||
device_db = {
|
||||
"core": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {"host": core_addr, "ref_period": 1/(8*150e6)}
|
||||
},
|
||||
"core_log": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port": 1068,
|
||||
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_moninj": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1383,
|
||||
"port": 1384,
|
||||
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_cache": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.cache",
|
||||
"class": "CoreCache"
|
||||
},
|
||||
"core_dma": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.dma",
|
||||
"class": "CoreDMA"
|
||||
}
|
||||
}
|
||||
|
||||
# master peripherals
|
||||
for i in range(4):
|
||||
device_db["led" + str(i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": i},
|
||||
}
|
||||
|
||||
# DEST#1 peripherals
|
||||
amc_base = 0x070000
|
||||
rtm_base = 0x020000
|
||||
|
||||
for i in range(4):
|
||||
device_db["led" + str(4+i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": amc_base + i},
|
||||
}
|
||||
|
||||
#DIO (EEM0) starting at RTIO channel 0x000056
|
||||
for i in range(8):
|
||||
device_db["ttl" + str(i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": amc_base + 0x000056 + i},
|
||||
}
|
||||
|
||||
#DIO (EEM1) starting at RTIO channel 0x00005e
|
||||
for i in range(8):
|
||||
device_db["ttl" + str(8+i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": amc_base + 0x00005e + i},
|
||||
}
|
||||
|
||||
device_db["fmcdio_dirctl_clk"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": amc_base + 0x000066}
|
||||
}
|
||||
|
||||
device_db["fmcdio_dirctl_ser"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": amc_base + 0x000067}
|
||||
}
|
||||
|
||||
device_db["fmcdio_dirctl_latch"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": amc_base + 0x000068}
|
||||
}
|
||||
|
||||
device_db["fmcdio_dirctl"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.shiftreg",
|
||||
"class": "ShiftReg",
|
||||
"arguments": {"clk": "fmcdio_dirctl_clk",
|
||||
"ser": "fmcdio_dirctl_ser",
|
||||
"latch": "fmcdio_dirctl_latch"}
|
||||
}
|
|
@ -1,129 +0,0 @@
|
|||
import sys
|
||||
import os
|
||||
import select
|
||||
|
||||
from artiq.experiment import *
|
||||
from artiq.coredevice.fmcdio_vhdci_eem import *
|
||||
|
||||
|
||||
def chunker(seq, size):
|
||||
res = []
|
||||
for el in seq:
|
||||
res.append(el)
|
||||
if len(res) == size:
|
||||
yield res
|
||||
res = []
|
||||
if res:
|
||||
yield res
|
||||
|
||||
|
||||
def is_enter_pressed() -> TBool:
|
||||
if os.name == "nt":
|
||||
if msvcrt.kbhit() and msvcrt.getch() == b"\r":
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
else:
|
||||
if select.select([sys.stdin, ], [], [], 0.0)[0]:
|
||||
sys.stdin.read(1)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class Demo(EnvExperiment):
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("fmcdio_dirctl")
|
||||
|
||||
self.leds = dict()
|
||||
self.ttl_outs = dict()
|
||||
|
||||
ddb = self.get_device_db()
|
||||
for name, desc in ddb.items():
|
||||
if isinstance(desc, dict) and desc["type"] == "local":
|
||||
module, cls = desc["module"], desc["class"]
|
||||
if (module, cls) == ("artiq.coredevice.ttl", "TTLOut"):
|
||||
dev = self.get_device(name)
|
||||
if "led" in name: # guess
|
||||
self.leds[name] = dev
|
||||
elif "ttl" in name: # to exclude fmcdio_dirctl
|
||||
self.ttl_outs[name] = dev
|
||||
|
||||
self.leds = sorted(self.leds.items(), key=lambda x: x[1].channel)
|
||||
self.ttl_outs = sorted(self.ttl_outs.items(), key=lambda x: x[1].channel)
|
||||
|
||||
self.dirctl_word = (
|
||||
shiftreg_bits(0, dio_bank0_out_pins | dio_bank1_out_pins) |
|
||||
shiftreg_bits(1, dio_bank0_out_pins | dio_bank1_out_pins)
|
||||
)
|
||||
|
||||
@kernel
|
||||
def init(self):
|
||||
self.core.break_realtime()
|
||||
print("*** Waiting for DRTIO ready...")
|
||||
drtio_indices = [7]
|
||||
for i in drtio_indices:
|
||||
while not self.drtio_is_up(i):
|
||||
pass
|
||||
|
||||
self.fmcdio_dirctl.set(self.dirctl_word)
|
||||
|
||||
@kernel
|
||||
def drtio_is_up(self, drtio_index):
|
||||
if not self.core.get_rtio_destination_status(drtio_index):
|
||||
return False
|
||||
print("DRTIO #", drtio_index, "is ready\n")
|
||||
return True
|
||||
|
||||
@kernel
|
||||
def test_led(self, led):
|
||||
while not is_enter_pressed():
|
||||
self.core.break_realtime()
|
||||
# do not fill the FIFOs too much to avoid long response times
|
||||
t = now_mu() - self.core.seconds_to_mu(0.2)
|
||||
while self.core.get_rtio_counter_mu() < t:
|
||||
pass
|
||||
for i in range(3):
|
||||
led.pulse(100*ms)
|
||||
delay(100*ms)
|
||||
|
||||
def test_leds(self):
|
||||
print("*** Testing LEDs.")
|
||||
print("Check for blinking. Press ENTER when done.")
|
||||
|
||||
for led_name, led_dev in self.leds:
|
||||
print("Testing LED: {}".format(led_name))
|
||||
self.test_led(led_dev)
|
||||
|
||||
@kernel
|
||||
def test_ttl_out_chunk(self, ttl_chunk):
|
||||
while not is_enter_pressed():
|
||||
self.core.break_realtime()
|
||||
for _ in range(50000):
|
||||
i = 0
|
||||
for ttl in ttl_chunk:
|
||||
i += 1
|
||||
for _ in range(i):
|
||||
ttl.pulse(1*us)
|
||||
delay(1*us)
|
||||
delay(10*us)
|
||||
|
||||
def test_ttl_outs(self):
|
||||
print("*** Testing TTL outputs.")
|
||||
print("Outputs are tested in groups of 4. Touch each TTL connector")
|
||||
print("with the oscilloscope probe tip, and check that the number of")
|
||||
print("pulses corresponds to its number in the group.")
|
||||
print("Press ENTER when done.")
|
||||
|
||||
for ttl_chunk in chunker(self.ttl_outs, 4):
|
||||
print("Testing TTL outputs: {}.".format(", ".join(name for name, dev in ttl_chunk)))
|
||||
self.test_ttl_out_chunk([dev for name, dev in ttl_chunk])
|
||||
|
||||
def run(self):
|
||||
self.core.reset()
|
||||
|
||||
if self.leds:
|
||||
self.test_leds()
|
||||
if self.ttl_outs:
|
||||
self.test_ttl_outs()
|
|
@ -45,13 +45,13 @@ class ArgumentsDemo(EnvExperiment):
|
|||
PYONValue(self.get_dataset("foo", default=42)))
|
||||
self.setattr_argument("number", NumberValue(42e-6,
|
||||
unit="us",
|
||||
ndecimals=4))
|
||||
precision=4))
|
||||
self.setattr_argument("integer", NumberValue(42,
|
||||
step=1, ndecimals=0))
|
||||
step=1, precision=0))
|
||||
self.setattr_argument("string", StringValue("Hello World"))
|
||||
self.setattr_argument("scan", Scannable(global_max=400,
|
||||
default=NoScan(325),
|
||||
ndecimals=6))
|
||||
precision=6))
|
||||
self.setattr_argument("boolean", BooleanValue(True), "Group")
|
||||
self.setattr_argument("enum", EnumerationValue(
|
||||
["foo", "bar", "quux"], "foo"), "Group")
|
||||
|
|
|
@ -4,13 +4,13 @@ from artiq.applets.simple import SimpleApplet
|
|||
|
||||
|
||||
class DemoWidget(QtWidgets.QLabel):
|
||||
def __init__(self, args):
|
||||
def __init__(self, args, ctl):
|
||||
QtWidgets.QLabel.__init__(self)
|
||||
self.dataset_name = args.dataset
|
||||
|
||||
def data_changed(self, data, mods):
|
||||
def data_changed(self, value, metadata, persist, mods):
|
||||
try:
|
||||
n = str(data[self.dataset_name][1])
|
||||
n = str(value[self.dataset_name])
|
||||
except (KeyError, ValueError, TypeError):
|
||||
n = "---"
|
||||
n = "<font size=15>" + n + "</font>"
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
from artiq.experiment import *
|
||||
|
||||
|
||||
class InteractiveDemo(EnvExperiment):
|
||||
def build(self):
|
||||
pass
|
||||
|
||||
def run(self):
|
||||
print("Waiting for user input...")
|
||||
with self.interactive(title="Interactive Demo") as interactive:
|
||||
interactive.setattr_argument("pyon_value",
|
||||
PYONValue(self.get_dataset("foo", default=42)))
|
||||
interactive.setattr_argument("number", NumberValue(42e-6,
|
||||
unit="us",
|
||||
precision=4))
|
||||
interactive.setattr_argument("integer", NumberValue(42,
|
||||
step=1, precision=0))
|
||||
interactive.setattr_argument("string", StringValue("Hello World"))
|
||||
interactive.setattr_argument("scan", Scannable(global_max=400,
|
||||
default=NoScan(325),
|
||||
precision=6))
|
||||
interactive.setattr_argument("boolean", BooleanValue(True), "Group")
|
||||
interactive.setattr_argument("enum",
|
||||
EnumerationValue(["foo", "bar", "quux"], "foo"),
|
||||
"Group")
|
||||
print("Done! Values:")
|
||||
print(interactive.pyon_value)
|
||||
print(interactive.boolean)
|
||||
print(interactive.enum)
|
||||
print(interactive.number, type(interactive.number))
|
||||
print(interactive.integer, type(interactive.integer))
|
||||
print(interactive.string)
|
||||
for i in interactive.scan:
|
||||
print(i)
|
|
@ -1,173 +0,0 @@
|
|||
core_addr = "192.168.1.60"
|
||||
|
||||
device_db = {
|
||||
"core": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {"host": core_addr, "ref_period": 1/(8*150e6)}
|
||||
},
|
||||
"core_log": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port": 1068,
|
||||
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_moninj": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1383,
|
||||
"port": 1384,
|
||||
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_cache": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.cache",
|
||||
"class": "CoreCache"
|
||||
},
|
||||
"core_dma": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.dma",
|
||||
"class": "CoreDMA"
|
||||
},
|
||||
}
|
||||
|
||||
for i in range(4):
|
||||
device_db["led" + str(i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": i},
|
||||
}
|
||||
|
||||
|
||||
for i in range(2):
|
||||
device_db["ttl" + str(i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLInOut",
|
||||
"arguments": {"channel": 4 + i},
|
||||
}
|
||||
|
||||
|
||||
device_db.update(
|
||||
fmcdio_dirctl_clk={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 6}
|
||||
},
|
||||
fmcdio_dirctl_ser={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 7}
|
||||
},
|
||||
fmcdio_dirctl_latch={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 8}
|
||||
},
|
||||
fmcdio_dirctl={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.shiftreg",
|
||||
"class": "ShiftReg",
|
||||
"arguments": {"clk": "fmcdio_dirctl_clk",
|
||||
"ser": "fmcdio_dirctl_ser",
|
||||
"latch": "fmcdio_dirctl_latch"}
|
||||
}
|
||||
)
|
||||
|
||||
device_db.update(
|
||||
spi_urukul0={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.spi2",
|
||||
"class": "SPIMaster",
|
||||
"arguments": {"channel": 17}
|
||||
},
|
||||
ttl_urukul0_io_update={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 18}
|
||||
},
|
||||
ttl_urukul0_sw0={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 19}
|
||||
},
|
||||
ttl_urukul0_sw1={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 20}
|
||||
},
|
||||
ttl_urukul0_sw2={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 21}
|
||||
},
|
||||
ttl_urukul0_sw3={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 22}
|
||||
},
|
||||
urukul0_cpld={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.urukul",
|
||||
"class": "CPLD",
|
||||
"arguments": {
|
||||
"spi_device": "spi_urukul0",
|
||||
"io_update_device": "ttl_urukul0_io_update",
|
||||
"refclk": 125e6,
|
||||
"clk_sel": 0
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
device_db["spi_zotino0"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.spi2",
|
||||
"class": "SPIMaster",
|
||||
"arguments": {"channel": 23}
|
||||
}
|
||||
device_db["ttl_zotino0_ldac"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 24}
|
||||
}
|
||||
device_db["ttl_zotino0_clr"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 25}
|
||||
}
|
||||
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"
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
from artiq.experiment import *
|
||||
from artiq.coredevice.fmcdio_vhdci_eem import *
|
||||
|
||||
|
||||
class Demo(EnvExperiment):
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("fmcdio_dirctl")
|
||||
|
||||
self.ttls = [self.get_device("ttl" + str(i)) for i in range(8)]
|
||||
self.setattr_device("urukul0_cpld")
|
||||
self.urukul_chs = [self.get_device("urukul0_ch" + str(i)) for i in range(4)]
|
||||
self.setattr_device("zotino0")
|
||||
|
||||
self.dirctl_word = (
|
||||
shiftreg_bits(1, urukul_out_pins) |
|
||||
shiftreg_bits(0, urukul_aux_out_pins) |
|
||||
shiftreg_bits(2, dio_bank0_out_pins | dio_bank1_out_pins) |
|
||||
shiftreg_bits(3, zotino_out_pins))
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
self.core.reset()
|
||||
delay(10*ms)
|
||||
self.fmcdio_dirctl.set(self.dirctl_word)
|
||||
delay(10*ms)
|
||||
|
||||
self.urukul0_cpld.init()
|
||||
delay(10*ms)
|
||||
|
||||
self.zotino0.init()
|
||||
delay(1*ms)
|
||||
for i in range(32):
|
||||
self.zotino0.write_dac(i, i/4)
|
||||
delay(1*ms)
|
||||
|
||||
while True:
|
||||
for ttl in self.ttls:
|
||||
ttl.pulse(100*ms)
|
||||
for urukul_ch in self.urukul_chs:
|
||||
urukul_ch.sw.pulse(100*ms)
|
|
@ -1,10 +1,50 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
# And yet, manual edits have been made. Crate yanking should be illegal.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.16.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3e61f2b7f93d2c7d2b08263acaa4a363b3e276806c68af6134c44f523bf1aacd"
|
||||
dependencies = [
|
||||
"cpp_demangle",
|
||||
"fallible-iterator",
|
||||
"gimli",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
||||
|
||||
[[package]]
|
||||
name = "adler"
|
||||
version = "1.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
|
||||
|
||||
[[package]]
|
||||
name = "ahash"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efa60d2eadd8b12a996add391db32bd1153eac697ba4869660c0016353611426"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"once_cell",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
version = "0.7.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
@ -13,6 +53,12 @@ dependencies = [
|
|||
name = "alloc_list"
|
||||
version = "0.0.0"
|
||||
|
||||
[[package]]
|
||||
name = "arrayvec"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
|
||||
|
||||
[[package]]
|
||||
name = "bare-metal"
|
||||
version = "0.2.5"
|
||||
|
@ -24,9 +70,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "bit_field"
|
||||
version = "0.10.1"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4"
|
||||
checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
|
@ -66,12 +112,30 @@ dependencies = [
|
|||
name = "bootloader"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"addr2line",
|
||||
"ahash",
|
||||
"board_misoc",
|
||||
"build_misoc",
|
||||
"byteorder",
|
||||
"cc",
|
||||
"compiler_builtins",
|
||||
"crc",
|
||||
"dlmalloc",
|
||||
"fortanix-sgx-abi",
|
||||
"getopts",
|
||||
"getrandom",
|
||||
"hashbrown",
|
||||
"hermit-abi",
|
||||
"libc 0.2.99",
|
||||
"miniz_oxide 0.4.0",
|
||||
"object",
|
||||
"once_cell",
|
||||
"riscv",
|
||||
"rustc-demangle",
|
||||
"smoltcp",
|
||||
"unicode-width",
|
||||
"version_check",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -92,9 +156,9 @@ checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
|||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.0.70"
|
||||
version = "1.0.69"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0"
|
||||
checksum = "e70cc2f62c6ce1868963827bd677764c62d07c3d9a3e1fb1177ee1a9ab199eb2"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
|
@ -110,9 +174,18 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
|||
|
||||
[[package]]
|
||||
name = "compiler_builtins"
|
||||
version = "0.1.39"
|
||||
version = "0.1.49"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3748f82c7d366a0b4950257d19db685d4958d2fa27c6d164a3f069fec42b748b"
|
||||
checksum = "20b1438ef42c655665a8ab2c1c6d605a305f031d38d9be689ddfef41a20f3aa2"
|
||||
|
||||
[[package]]
|
||||
name = "cpp_demangle"
|
||||
version = "0.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
|
@ -123,12 +196,30 @@ dependencies = [
|
|||
"build_const",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cslice"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f8cb7306107e4b10e64994de6d3274bd08996a7c1322a27b86482392f96be0a"
|
||||
|
||||
[[package]]
|
||||
name = "dlmalloc"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "332570860c2edf2d57914987bf9e24835425f75825086b6ba7d1e6a3e4f1f254"
|
||||
dependencies = [
|
||||
"libc 0.2.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dyld"
|
||||
version = "0.0.0"
|
||||
|
@ -137,7 +228,6 @@ version = "0.0.0"
|
|||
name = "eh"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"compiler_builtins",
|
||||
"cslice",
|
||||
"libc 0.1.0",
|
||||
"unwind",
|
||||
|
@ -160,12 +250,82 @@ dependencies = [
|
|||
"synstructure",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fallible-iterator"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide 0.7.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fortanix-sgx-abi"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c56c422ef86062869b2d57ae87270608dc5929969dd130a6e248979cf4fb6ca6"
|
||||
|
||||
[[package]]
|
||||
name = "fringe"
|
||||
version = "1.2.1"
|
||||
source = "git+https://git.m-labs.hk/M-Labs/libfringe.git?rev=3ecbe5#3ecbe53f7644b18ee46ebd5b2ca12c9cbceec43a"
|
||||
dependencies = [
|
||||
"libc 0.2.101",
|
||||
"libc 0.2.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getopts"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee8025cf36f917e6a52cce185b7c7177689b838b7ec138364e50cc2277a56cf4"
|
||||
dependencies = [
|
||||
"cfg-if 0.1.10",
|
||||
"libc 0.2.99",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7"
|
||||
dependencies = [
|
||||
"fallible-iterator",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "362385356d610bd1e5a408ddf8d022041774b683f345a1d2cfcb4f60f8ae2db5"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||
dependencies = [
|
||||
"libc 0.2.99",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -206,9 +366,9 @@ version = "0.1.0"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.101"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21"
|
||||
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
|
@ -252,6 +412,40 @@ version = "2.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f"
|
||||
dependencies = [
|
||||
"adler 0.2.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
|
||||
dependencies = [
|
||||
"adler 1.0.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "object"
|
||||
version = "0.26.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "39f37e50073ccad23b6d09bcb5b263f4e76d3bb6038e4a3c08e52162ffa8abc2"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56"
|
||||
|
||||
[[package]]
|
||||
name = "proto_artiq"
|
||||
version = "0.0.0"
|
||||
|
@ -274,9 +468,9 @@ checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a"
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.5.4"
|
||||
version = "1.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -285,9 +479,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.6.25"
|
||||
version = "0.6.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
|
||||
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
|
||||
|
||||
[[package]]
|
||||
name = "riscv"
|
||||
|
@ -320,6 +514,7 @@ dependencies = [
|
|||
"build_misoc",
|
||||
"byteorder",
|
||||
"cslice",
|
||||
"dyld",
|
||||
"eh",
|
||||
"failure",
|
||||
"failure_derive",
|
||||
|
@ -331,9 +526,16 @@ dependencies = [
|
|||
"proto_artiq",
|
||||
"riscv",
|
||||
"smoltcp",
|
||||
"tar-no-std",
|
||||
"unwind_backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-demangle"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
|
@ -347,10 +549,15 @@ dependencies = [
|
|||
name = "satman"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"alloc_list",
|
||||
"board_artiq",
|
||||
"board_misoc",
|
||||
"build_misoc",
|
||||
"cslice",
|
||||
"eh",
|
||||
"io",
|
||||
"log",
|
||||
"proto_artiq",
|
||||
"riscv",
|
||||
]
|
||||
|
||||
|
@ -370,16 +577,28 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "smoltcp"
|
||||
version = "0.8.0"
|
||||
name = "smallvec"
|
||||
version = "1.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2308a1657c8db1f5b4993bab4e620bdbe5623bd81f254cf60326767bb243237"
|
||||
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
|
||||
|
||||
[[package]]
|
||||
name = "smoltcp"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee34c1e1bfc7e9206cc0fb8030a90129b4e319ab53856249bb27642cab914fb3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"managed 0.8.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "0.11.11"
|
||||
|
@ -410,6 +629,22 @@ dependencies = [
|
|||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tar-no-std"
|
||||
version = "0.1.8"
|
||||
source = "git+https://git.m-labs.hk/M-Labs/tar-no-std?rev=2ab6dc5#2ab6dc58e5249c59c4eb03eaf3a119bcdd678d32"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"bitflags",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.0.4"
|
||||
|
@ -431,3 +666,15 @@ dependencies = [
|
|||
"libc 0.1.0",
|
||||
"unwind",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
||||
|
|
|
@ -13,8 +13,30 @@ path = "main.rs"
|
|||
build_misoc = { path = "../libbuild_misoc" }
|
||||
|
||||
[dependencies]
|
||||
byteorder = { version = "1.0", default-features = false }
|
||||
byteorder = { version = "=1.4.3", default-features = false }
|
||||
crc = { version = "1.7", default-features = false }
|
||||
board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp"] }
|
||||
smoltcp = { version = "0.8.0", default-features = false, features = ["medium-ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp"] }
|
||||
smoltcp = { version = "0.8.2", default-features = false, features = ["medium-ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp"] }
|
||||
riscv = { version = "0.6.0", features = ["inline-asm"] }
|
||||
|
||||
# insanity required by using cargo build-std over xbuild with nix
|
||||
# cargo update does not work thanks to ahash 0.7 problems
|
||||
[dev-dependencies]
|
||||
getopts = "=0.2.21"
|
||||
libc = "=0.2.99"
|
||||
unicode-width = "=0.1.8"
|
||||
addr2line = "=0.16.0"
|
||||
hashbrown = "=0.11.0" # must be injected into lockfile manually
|
||||
ahash = "=0.7.0" # must be injected into lockfile manually
|
||||
miniz_oxide = "=0.4.0"
|
||||
rustc-demangle = "=0.1.21"
|
||||
hermit-abi = "=0.1.19"
|
||||
dlmalloc = "=0.2.1"
|
||||
fortanix-sgx-abi = "=0.3.3"
|
||||
cc = "=1.0.69"
|
||||
compiler_builtins = "=0.1.49"
|
||||
version_check = "=0.9.3"
|
||||
once_cell = "=1.8.0"
|
||||
wasi = "=0.9.0"
|
||||
getrandom = "=0.2.0"
|
||||
object = "=0.26.2"
|
|
@ -3,8 +3,6 @@ include $(MISOC_DIRECTORY)/software/common.mak
|
|||
|
||||
RUSTFLAGS += -Cpanic=abort
|
||||
|
||||
export XBUILD_SYSROOT_PATH=$(BUILDINC_DIRECTORY)/../sysroot
|
||||
|
||||
all:: bootloader.bin
|
||||
|
||||
.PHONY: $(RUSTOUT)/libbootloader.a
|
||||
|
|
|
@ -18,8 +18,10 @@ use board_misoc::slave_fpga;
|
|||
use board_misoc::{clock, ethmac, net_settings};
|
||||
use board_misoc::uart_console::Console;
|
||||
use riscv::register::{mcause, mepc, mtval};
|
||||
use smoltcp::iface::SocketStorage;
|
||||
use smoltcp::wire::{HardwareAddress, IpAddress, Ipv4Address};
|
||||
#[cfg(has_ethmac)]
|
||||
use smoltcp::iface::{Routes, SocketStorage};
|
||||
#[cfg(has_ethmac)]
|
||||
use smoltcp::wire::{HardwareAddress, IpAddress, Ipv4Address, Ipv6Address};
|
||||
|
||||
fn check_integrity() -> bool {
|
||||
extern {
|
||||
|
@ -411,28 +413,29 @@ fn network_boot() {
|
|||
println!("Network addresses: {}", net_addresses);
|
||||
let mut ip_addrs = [
|
||||
IpCidr::new(IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), 0),
|
||||
IpCidr::new(net_addresses.ipv6_ll_addr, 0),
|
||||
IpCidr::new(net_addresses.ipv6_ll_addr, 0)
|
||||
net_addresses.ipv6_ll_addr,
|
||||
IpCidr::new(IpAddress::Ipv6(Ipv6Address::UNSPECIFIED), 0)
|
||||
];
|
||||
if let net_settings::Ipv4AddrConfig::Static(ipv4) = net_addresses.ipv4_addr {
|
||||
ip_addrs[0] = IpCidr::new(IpAddress::Ipv4(ipv4), 0);
|
||||
ip_addrs[0] = IpCidr::Ipv4(ipv4);
|
||||
}
|
||||
let mut interface = match net_addresses.ipv6_addr {
|
||||
Some(addr) => {
|
||||
ip_addrs[2] = IpCidr::new(addr, 0);
|
||||
smoltcp::iface::InterfaceBuilder::new(net_device, &mut sockets[..])
|
||||
.hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr))
|
||||
.ip_addrs(&mut ip_addrs[..])
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.finalize()
|
||||
}
|
||||
None =>
|
||||
smoltcp::iface::InterfaceBuilder::new(net_device, &mut sockets[..])
|
||||
.hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr))
|
||||
.ip_addrs(&mut ip_addrs[..2])
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.finalize()
|
||||
if let Some(ipv6) = net_addresses.ipv6_addr {
|
||||
ip_addrs[2] = IpCidr::Ipv6(ipv6);
|
||||
};
|
||||
let mut routes = [None; 2];
|
||||
let mut interface = smoltcp::iface::InterfaceBuilder::new(net_device, &mut sockets[..])
|
||||
.hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr))
|
||||
.ip_addrs(&mut ip_addrs[..])
|
||||
.neighbor_cache(neighbor_cache)
|
||||
.routes(Routes::new(&mut routes[..]))
|
||||
.finalize();
|
||||
|
||||
if let Some(default_route) = net_addresses.ipv4_default_route {
|
||||
interface.routes_mut().add_default_ipv4_route(default_route).unwrap();
|
||||
}
|
||||
if let Some(default_route) = net_addresses.ipv6_default_route {
|
||||
interface.routes_mut().add_default_ipv6_route(default_route).unwrap();
|
||||
}
|
||||
|
||||
let mut rx_storage = [0; 4096];
|
||||
let mut tx_storage = [0; 128];
|
||||
|
@ -497,7 +500,7 @@ pub extern fn main() -> i32 {
|
|||
println!(r"|_| |_|_|____/ \___/ \____|");
|
||||
println!("");
|
||||
println!("MiSoC Bootloader");
|
||||
println!("Copyright (c) 2017-2022 M-Labs Limited");
|
||||
println!("Copyright (c) 2017-2024 M-Labs Limited");
|
||||
println!("");
|
||||
|
||||
#[cfg(has_ethmac)]
|
||||
|
|
|
@ -6,7 +6,7 @@ ENTRY(_reset_handler)
|
|||
* ld does not allow this expression here.
|
||||
*/
|
||||
MEMORY {
|
||||
runtime (RWX) : ORIGIN = 0x40000000, LENGTH = 0x4000000 /* 64M */
|
||||
firmware (RWX) : ORIGIN = 0x40000000, LENGTH = 0x4000000 /* 64M */
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
|
@ -14,24 +14,24 @@ SECTIONS
|
|||
.vectors :
|
||||
{
|
||||
*(.vectors)
|
||||
} > runtime
|
||||
} > firmware
|
||||
|
||||
.text :
|
||||
{
|
||||
*(.text .text.*)
|
||||
} > runtime
|
||||
} > firmware
|
||||
|
||||
.eh_frame :
|
||||
{
|
||||
__eh_frame_start = .;
|
||||
KEEP(*(.eh_frame))
|
||||
__eh_frame_end = .;
|
||||
} > runtime
|
||||
} > firmware
|
||||
|
||||
.eh_frame_hdr :
|
||||
{
|
||||
KEEP(*(.eh_frame_hdr))
|
||||
} > runtime
|
||||
} > firmware
|
||||
|
||||
__eh_frame_hdr_start = SIZEOF(.eh_frame_hdr) > 0 ? ADDR(.eh_frame_hdr) : 0;
|
||||
__eh_frame_hdr_end = SIZEOF(.eh_frame_hdr) > 0 ? . : 0;
|
||||
|
@ -39,35 +39,35 @@ SECTIONS
|
|||
.gcc_except_table :
|
||||
{
|
||||
*(.gcc_except_table)
|
||||
} > runtime
|
||||
} > firmware
|
||||
|
||||
/* https://sourceware.org/bugzilla/show_bug.cgi?id=20475 */
|
||||
.got :
|
||||
{
|
||||
*(.got)
|
||||
} > runtime
|
||||
} > firmware
|
||||
|
||||
.got.plt :
|
||||
{
|
||||
*(.got.plt)
|
||||
} > runtime
|
||||
} > firmware
|
||||
|
||||
.rodata :
|
||||
{
|
||||
*(.rodata .rodata.*)
|
||||
} > runtime
|
||||
} > firmware
|
||||
|
||||
.data :
|
||||
{
|
||||
*(.data .data.*)
|
||||
} > runtime
|
||||
} > firmware
|
||||
|
||||
.bss (NOLOAD) : ALIGN(4)
|
||||
{
|
||||
_fbss = .;
|
||||
*(.sbss .sbss.* .bss .bss.*);
|
||||
_ebss = .;
|
||||
} > runtime
|
||||
} > firmware
|
||||
|
||||
.stack (NOLOAD) : ALIGN(0x1000)
|
||||
{
|
||||
|
@ -76,12 +76,12 @@ SECTIONS
|
|||
_estack = .;
|
||||
. += 0x10000;
|
||||
_fstack = . - 16;
|
||||
} > runtime
|
||||
} > firmware
|
||||
|
||||
.heap (NOLOAD) : ALIGN(16)
|
||||
{
|
||||
_fheap = .;
|
||||
. = ORIGIN(runtime) + LENGTH(runtime);
|
||||
. = ORIGIN(firmware) + LENGTH(firmware);
|
||||
_eheap = .;
|
||||
} > runtime
|
||||
} > firmware
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue