forked from M-Labs/artiq
1
0
Fork 0

Compare commits

..

259 Commits

Author SHA1 Message Date
Robert Jördens 22ba2858d9 manual: deprecate old release
closes #863
2017-12-06 13:00:37 +01:00
Robert Jördens fd6c01f537 README: GPLv3+ 2016-09-15 09:52:52 -04:00
whitequark 3c8a7df913 coreanalyzer: fix rtio_log message extraction.
Fixes #550.
2016-09-14 18:37:03 +08:00
Sebastien Bourdeauducq 6150ab8509 doc: move VADJ, closes #554 2016-09-09 18:41:27 +08:00
Robert Jördens 2bdc5f33aa conda: fix nist_clock summary 2016-09-03 11:08:53 +02:00
whitequark 592993eddd conda: require misoc 0.2. 2016-08-21 10:44:03 +08:00
Robert Jördens 929ded30b8 RELEASE_NOTES: 1.3 2016-08-11 20:49:05 +02:00
Robert Jördens 92d5491cdc gui: cleanup compact_exponential, 15 digits 2016-08-11 14:18:41 +02:00
Robert Jördens 46cae14ab1 lda: fix windows path 2016-08-11 14:13:04 +02:00
Sebastien Bourdeauducq ff9497054c manual: explain how applets work. Closes #536 2016-08-11 19:10:04 +08:00
Robert Jördens 14addf74c1 gui.entries: avoid intermediate value feedback, closes #533 2016-08-04 11:35:57 +02:00
Kelly Stevens 0728be7378 doc/getting_started_mgmt: fix result folders 2016-08-03 14:10:06 +08:00
whitequark bdb85c8eab protocols.pc_rpc: exclude kernel_invariants from proxying.
Fixes #531.
2016-08-03 13:19:34 +08:00
whitequark ecc30979df transforms.llvm_ir_generator: skip RPC values for attribute writeback. 2016-08-03 13:19:29 +08:00
Sebastien Bourdeauducq ff1e35e03f analyzer: use picosecond resolution in VCD output. Closes #528 2016-08-03 10:57:47 +08:00
Kelly Stevens d5711133be doc: re-formatting a directory path in lda driver comments so it will display correctly in the sphinx documentation 2016-08-01 23:36:26 +02:00
Sebastien Bourdeauducq 3f224add2f artiq_flash: support using alternative OpenOCD config files 2016-07-19 15:36:18 +08:00
Robert Jördens 25a9d345d9 artiq_flash: fix openocd scripts path (#513) 2016-07-15 15:49:34 +02:00
Sebastien Bourdeauducq ce73e8eea7 target/pipistrello: shrink TTL FIFOs 2016-07-14 15:59:21 +08:00
Sebastien Bourdeauducq fe2b2496c1 RELEASE_NOTES: 1.2 2016-07-14 15:34:50 +08:00
Sebastien Bourdeauducq 2c749c77e7 targets/pipistrello: work around Xst placer idiocy 2016-07-14 15:14:11 +08:00
whitequark 1d58e3cf95 ir: `invoke` is a valid `delay` decomposition.
Fixes #510.
2016-07-14 01:10:52 +08:00
Sebastien Bourdeauducq c27f157a27 make sure monkey patches are applied when asyncio.open_connection() is used 2016-07-13 11:29:18 +08:00
Sebastien Bourdeauducq 972a74219d monkey-patch Python 3.5.2 to disable broken asyncio.base_events._ipaddr_info optimization (#506) 2016-07-13 11:26:35 +08:00
Sebastien Bourdeauducq 078a9abeb9 rtio: do not reset DDS and SPI PHYs on RTIO reset (#503) 2016-07-11 21:22:20 +02:00
whitequark eceafad7e3 compiler.testbench.perf_embedding: more fine grained reporting. 2016-07-11 17:55:18 +00:00
whitequark b5901db265 compiler.testbench.perf_embedding: update for core changes. 2016-07-11 16:31:35 +00:00
whitequark 3547b1d5ae runtime: update ppp code for lwip 2.0.0.
Fixes #499.
2016-07-09 09:01:00 +08:00
whitequark 2678bb060a embedding: reimplement 373578bc properly.
The core of the problem that 373578bc was attempting to solve is
that diagnostics sometimes should be chained; one way of chaining
is the loc.expanded_from feature, which handles macro-like expansion,
but another is providing context.

Before this commit, context was provided using an ad-hoc override
of a diagnostic engine, which did not work in cases where diagnostic
engine was not threaded through the call stack. This commit uses
the newly added pythonparser context feature to elegantly handle
the problem.
2016-07-07 16:01:22 +00:00
whitequark 6a1706b872 runtime: fix serialization of object lists.
Fixes #500.
2016-07-07 22:41:46 +08:00
whitequark a801cde953 embedding: fix location for diagnostics on quoted values.
Fixes #489.
2016-07-07 17:01:09 +08:00
Sebastien Bourdeauducq adcf53f1cb targets/kc705: redefine user SMAs as 3.3V IO. Closes #502 2016-07-07 14:56:36 +08:00
Sebastien Bourdeauducq d96d222814 targets/kc705: fix import 2016-07-07 13:41:18 +08:00
Robert Jördens 07b41763c2 spi: expose more documentation on chaining transfers 2016-07-04 12:44:34 +02:00
Robert Jördens cda20ab2ed spi: do not shift when starting a xfer, closes #495 2016-07-04 12:44:21 +02:00
Sebastien Bourdeauducq 1946e3c3cd language/environment: fix mutate_dataset with parent. Closes #498 2016-07-03 12:28:38 +08:00
Sebastien Bourdeauducq bf3c3cdfd8 worker: increase send_timeout (Windows can be really slow) 2016-07-03 12:24:43 +08:00
Sebastien Bourdeauducq e116d756b5 dashboard: kill the Qt built-in main window closing mechanism
When the main window is closed, Qt makes QApplication.exec() return, which conflicts with Quamash's implementation of loop.run_until_complete(). The conflict causes Quamash's run_forever() to return earlier than it should, and cause "RuntimeError('Event loop stopped before Future completed.')".

Closes #475
2016-07-01 18:47:27 +08:00
whitequark 6dc510a976 compiler.embedding: use the builtin print as RPC.
Fixes #206.
2016-06-29 11:58:05 +08:00
whitequark d22eefc13e compiler: remove now()/at().
Fixes #490.
2016-06-29 11:55:58 +08:00
Sebastien Bourdeauducq 1bb09f9ca6 RELEASE_NOTES: 1.1 2016-06-24 14:48:31 +08:00
Sebastien Bourdeauducq 9dd88f8b3b runtime: save now on RPC 2016-06-24 14:15:40 +08:00
Sebastien Bourdeauducq 1bee5bb460 manual: split source install instructions to a separate page 2016-06-22 10:00:52 +08:00
whitequark 43d0bddc9f Upgrade lwip to 2.0.0 to fix the keepalive bug #456. 2016-06-22 09:23:08 +08:00
Sebastien Bourdeauducq f0ac0b78c1 runtime: minor cleanup 2016-06-20 08:34:13 +08:00
Sebastien Bourdeauducq 5baba5fd1e test: add test for seamless handover on exception termination 2016-06-20 08:34:02 +08:00
Sebastien Bourdeauducq 5f8b02a1d2 runtime: save now when terminating with exception 2016-06-20 08:33:55 +08:00
Sebastien Bourdeauducq e069ce9dd8 runtime: cleanup now_init/now_save 2016-06-20 08:33:43 +08:00
Sebastien Bourdeauducq 6a81b16230 doc: scan objects not supported on core device 2016-06-18 18:56:28 +08:00
whitequark cc28b596b3 compiler.embedding: always do one final inference pass.
Fixes #477.
2016-06-17 08:49:38 +00:00
whitequark 770dda6fd7 transforms.inferencer: allow variable as type of `n` in `[]*n`.
Fixes #473.
2016-06-17 16:46:37 +08:00
Robert Jördens 3589528362 spi: cross-reference bit ordering and alignment, closes #482 2016-06-15 15:05:16 +02:00
Sebastien Bourdeauducq e56c50a8a0 pyon: support slices 2016-06-15 19:19:07 +08:00
Sebastien Bourdeauducq 46b75dba8d gui: save/restore last folder outside repository. Closes #476 2016-06-12 13:19:07 +08:00
Sebastien Bourdeauducq 2b936429da doc: precisions about time cursor interaction 2016-06-12 13:05:54 +08:00
Sebastien Bourdeauducq 77280a75d9 language: support setting slices of data in mutate_dataset 2016-06-12 12:56:12 +08:00
Sebastien Bourdeauducq 9dcd43fb0d gui/scanwidget: use -inf/inf to represent absence of boundaries (consistently with QDoubleSpinbox) 2016-06-11 16:52:48 -06:00
Sebastien Bourdeauducq ddd1c12852 gui/entries/_RangeScan: set range before setting value. Fixes clamping to 99.99 2016-06-11 16:50:16 -06:00
Sebastien Bourdeauducq 1faac1018b scanwidget: value may be None 2016-06-11 16:48:58 -06:00
Sebastien Bourdeauducq d3f092ce98 doc: add warning about pipistrello current draw 2016-06-11 10:27:22 -06:00
Sebastien Bourdeauducq 7e9fa3a81a doc: add core device comms details 2016-06-11 10:27:15 -06:00
Sebastien Bourdeauducq 47e3106c4e gateware/nist_clock: increase DDS bus drive strength. Closes #468 2016-06-07 11:08:05 -04:00
whitequark a80103576d analyzer: explicitly delimit messages (with \x1D).
Fixes #461.
2016-06-07 10:57:58 -04:00
whitequark 284d726d5e artiq_flash: explicitly pass path within conda env to openocd datarootdir.
By default, openocd searches for scripts in DATAROOTDIR/openocd/scripts.
This of course makes it not relocatable. Conda has a flag to try to
detect and fix such hardcoded paths, but it does not work on openocd
(likely because the .rodata contains an already concatenated path,
which cannot be padded with zeroes from the right).

So, we pass the path explicitly instead.
2016-06-06 15:07:03 -04:00
Sebastien Bourdeauducq 67b3afd3e7 gui/moninj: reduce logging level of UDP failure 2016-06-04 16:34:07 -04:00
Sebastien Bourdeauducq c5c7c269f7 gui/moninj: do not crash when there is no network 2016-06-04 16:30:30 -04:00
whitequark 188d53ac05 doc: update installing.rst to reflect openocd packaged in conda. 2016-06-04 16:26:47 -04:00
Sebastien Bourdeauducq 8d9c483cd0 explorer: fix directory listing error handling 2016-06-04 10:18:45 -04:00
Sebastien Bourdeauducq f571b3e1a1 dds: use fast math for asf computations 2016-06-03 23:34:20 -04:00
Sebastien Bourdeauducq 36f8aa8d9e gui/entries: remove ScientificSpinbox. Closes #457 2016-06-03 23:33:20 -04:00
Sebastien Bourdeauducq 2803b8574e doc: document common KC705 problems. Closes #450 2016-06-03 23:26:57 -04:00
Sebastien Bourdeauducq 97ec3de6f3 doc/getting_started_mgmt: adapt to changes. Closes #462 2016-06-03 23:26:57 -04:00
dhslichter 3c817be213 dds: fix asf_to_amplitude 2016-06-03 23:26:57 -04:00
Sebastien Bourdeauducq 404cf3524c gui/entries: remove unneeded parent 2016-06-03 23:26:57 -04:00
Sebastien Bourdeauducq 3ddb1e4201 doc: fix bool input 2016-06-03 23:26:57 -04:00
Sebastien Bourdeauducq ba98ac1dcc examples: run should not return a value 2016-06-03 23:26:57 -04:00
Sebastien Bourdeauducq f7eb0a6f22 examples/histograms: convert to mutate_dataset API. Closes #459 2016-05-31 20:24:46 -05:00
Sebastien Bourdeauducq eeab7db3d4 applets/plot_xy: use numpy array for default X axis. Closes #458 2016-05-30 22:48:44 -05:00
Sebastien Bourdeauducq f537562768 gui: fix explicit scan input validation 2016-05-30 15:45:38 -05:00
Sebastien Bourdeauducq b57b1ddc57 language/environment: be more verbose in NumberValue unit/scale documentation (#448) 2016-05-28 13:38:24 -05:00
Sebastien Bourdeauducq 6c39d939d8 do not attempt to use broken conda features 2016-05-27 23:09:52 -05:00
Robert Jördens 109aa73a6b gui: fix new() being called with arguments by qt (closes #444) 2016-05-25 23:13:23 +02:00
Sebastien Bourdeauducq 8d0034e11d rpctool: make readline optional, add to conda dependencies. Closes #442 2016-05-25 11:12:15 -05:00
Sebastien Bourdeauducq aa26b13816 language/RandomScan: automatic seed by default 2016-05-23 14:33:32 -07:00
Sebastien Bourdeauducq 42c84e0c72 coredevice/TCA6424A: convert 'outputs' value to little endian. Closes #437 2016-05-22 06:54:08 -07:00
Sebastien Bourdeauducq 9db9f4e624 bit2bin: close input file explicitly 2016-05-21 21:50:30 +08:00
dhslichter dfeff967ba qc2: swap SPI/TTL, all TTL lines are now In+Out compatible 2016-05-19 10:42:19 +08:00
Robert Jördens e9a3e5642e flash: tcl-quote paths (c.f. #256) 2016-05-17 09:20:06 +08:00
Robert Jördens b81b40d553 flash: use the handle 2016-05-17 09:20:06 +08:00
Robert Jördens 7bdf373b95 flash: close files (c.f. #256) 2016-05-17 09:20:06 +08:00
Robert Jördens f37bfef275 RELEASE_NOTES: 1.0 2016-05-10 12:50:43 +02:00
whitequark 7de77cfc8f Commit missing parts of 4e5d75295. 2016-05-09 23:57:52 +08:00
whitequark 9196cdc554 compiler: fix quoting of methods (fixes #423). 2016-05-09 23:57:52 +08:00
Sebastien Bourdeauducq ef3465a181 RELEASE_NOTES: 1.0rc4 2016-05-08 11:15:47 +08:00
Sebastien Bourdeauducq 85ecb900df examples/photon_histogram: delay after count() 2016-05-07 18:33:48 +08:00
Sebastien Bourdeauducq 18b6718d0c lwip/liteethif: cleanup, drop frames above MTU (#398) 2016-05-07 18:33:48 +08:00
Sebastien Bourdeauducq d365ce8de8 examples/photon_histogram: integers 2016-05-07 18:33:48 +08:00
Sebastien Bourdeauducq 7466a4d9a9 gui/applets: catch duplicate applet UIDs (#430) 2016-05-07 11:47:30 +08:00
Sebastien Bourdeauducq 2eb67902e8 gui: do not crash if 'recompute all arguments' fails 2016-05-05 00:53:29 +08:00
Sebastien Bourdeauducq fa609283d5 worker: use unix time for HDF5 start_time 2016-05-03 21:40:48 +02:00
Robert Jördens 53cef7e695 worker: run experiment in output directory 2016-05-03 21:02:51 +02:00
Robert Jördens 7803b68c4d worker_impl: save expid, rid, start_time 2016-05-03 20:53:57 +02:00
Robert Jördens 5b955e8ce8 worker_db: factor get_output_prefix() 2016-05-03 20:53:54 +02:00
Sebastien Bourdeauducq 16fdebad8e gateware/nist_qc2: increase DDS bus drive strength. Closes #421 2016-05-03 16:44:14 +08:00
Sebastien Bourdeauducq adeb88619c language/environment: update kernel_invariants in setattr_argument and setattr_device 2016-05-03 16:44:11 +08:00
Sebastien Bourdeauducq 9e14419abc style 2016-05-03 16:43:09 +08:00
Robert Jördens f7ba61b47b examples/transport: add slack between experiments/after count() 2016-05-03 10:41:43 +02:00
Robert Jördens dcf082e427 Revert "lwip: set MTU to 9000 to support jumbo frames"
This reverts commit dbbd11d798.

Breaks more than it fixes.
2016-04-30 08:23:03 +02:00
Sebastien Bourdeauducq 06268d182f gui/moninj: sort by channel. Closes #413 2016-04-30 12:31:06 +08:00
Sebastien Bourdeauducq b05e3f42e9 lwip: set MTU to 9000 to support jumbo frames 2016-04-30 00:31:17 +08:00
whitequark 88fd5431b5 compiler: make kernel_invariant an instance, not class, property.
Fixes #409.
2016-04-30 00:31:17 +08:00
whitequark e835ae2a2a doc: explain RPC return type annotations.
Fixes #410.
2016-04-30 00:31:17 +08:00
Sebastien Bourdeauducq 7844e98b1d doc/environment: datasets readonly in build 2016-04-27 01:43:01 +08:00
Sebastien Bourdeauducq 0a9b2aecbc doc: move compiler details to separate chapter 2016-04-27 01:37:39 +08:00
whitequark 9b04778f66 embedding: ignore empty lines, like annotations, before kernel functions.
Fixes #363.
2016-04-27 01:37:28 +08:00
whitequark 24e24ddca4 doc: Document fast-math flag and kernel invariants.
Fixes #351, #359.
2016-04-27 01:37:28 +08:00
whitequark 0f6f684670 compiler: allow RPCing builtin functions.
Fixes #366.
2016-04-27 01:37:28 +08:00
whitequark 8d9a22f8da compiler: don't typecheck RPCs except for return type.
Fixes #260.
2016-04-27 01:36:51 +08:00
whitequark 8a28039b74 coredevice: deserialize int64(width=64) as int(width=64), not host_int.
Fixes #402.
2016-04-22 23:28:01 +08:00
Sebastien Bourdeauducq 53f0477c3b RELEASE_NOTES: 1.0rc3 2016-04-18 14:39:18 +08:00
Sebastien Bourdeauducq ed17972104 master/experiments: log more details about experiment name conflicts 2016-04-16 21:36:33 +08:00
Sebastien Bourdeauducq f962092b38 environment: cleanup docs 2016-04-16 19:38:52 +08:00
Sebastien Bourdeauducq 9b4a04b307 environment/get_device_db: raise ValueError when device manager not present 2016-04-16 19:38:46 +08:00
Sebastien Bourdeauducq 398468410f Revert "environment,worker: remove enable_processors"
This reverts commit 08f903b8f4.
2016-04-16 16:49:15 +08:00
Sebastien Bourdeauducq 08f903b8f4 environment,worker: remove enable_processors 2016-04-16 14:21:23 +08:00
Sebastien Bourdeauducq 0a259418fb environment: make NumberValue return integers when appropriate. Closes #397 2016-04-16 14:21:22 +08:00
Sebastien Bourdeauducq 91645ffc24 test/analyzer: clear analyzer buffer after IO init 2016-04-15 01:18:58 +08:00
Robert Jördens a2e4f95a00 test_analyzer: loop_out.off() 2016-04-14 22:57:47 +08:00
Sebastien Bourdeauducq 385e8d98fc test/PulseRateDDS: run more iterations 2016-04-14 19:15:31 +08:00
Robert Jördens 2086e46598 test_rtio: integer division 2016-04-14 19:14:53 +08:00
Sebastien Bourdeauducq a27aa9680e protocols/pyon: minor cleanup 2016-04-14 19:14:38 +08:00
Sebastien Bourdeauducq c97cb1d3b9 master/worker_db: style 2016-04-14 19:14:20 +08:00
Robert Jördens 69f534cc20 gui.models: style 2016-04-14 19:12:07 +08:00
Robert Jördens 9ceca44dbe applets: style 2016-04-14 18:38:57 +08:00
Robert Jördens 001f6d6fab test_rtio: scale speed test results to 'event' intervals 2016-04-14 18:19:05 +08:00
Robert Jördens 3d487d98b7 test_rtio: comments and correction
* add comments what is actually being measured in the two rate tests
* remove spurious factor of two
2016-04-14 18:18:54 +08:00
whitequark d6510083b7 Commit missing parts of bb064c67a. 2016-04-14 18:14:47 +08:00
whitequark 904379db7e runtime: add kernel-accessible sqrt.
Fixes #382.
2016-04-14 18:14:47 +08:00
whitequark 2248a2eb9e embedding: s/kernel_constant_attributes/kernel_invariants/g
Requested in #359.
2016-04-14 18:14:44 +08:00
whitequark e6666ce6a9 test_pulse_rate_dds: adjust bounds. 2016-04-14 18:10:41 +08:00
whitequark 34454621fa conda: update llvmlite-artiq dependency.
Build 24 includes addc optimizations.
2016-04-14 18:10:30 +08:00
whitequark c6f946a816 llvm_ir_generator: add fast-math flags to fcmp.
This is allowed in 3.8.
2016-04-14 18:10:30 +08:00
whitequark d4f1614a23 llvm_ir_generator: change !{→unconditionally_}dereferenceable.
Since LLVM 3.8, !dereferenceable is weaker, so we introduce
!unconditionally_dereferenceable (http://reviews.llvm.org/D18738)
to regain its functionality.
2016-04-14 18:10:17 +08:00
whitequark 75252ca5a4 llvm_ir_generator: fix DICompileUnit.language. 2016-04-14 18:10:17 +08:00
whitequark 31b5154222 conda: update llvmlite-artiq dependency.
Build 22 includes debug information support.
2016-04-14 18:10:17 +08:00
whitequark 89326fb189 compiler: purge generated functions from backtraces. 2016-04-14 18:09:59 +08:00
whitequark a2f6e81c50 ttl: mark constant attributes for TTL{In,InOut,ClockGen}. 2016-04-14 18:09:59 +08:00
whitequark 702e959033 llvm_ir_generator: add TBAA metadata for @now. 2016-04-14 18:09:59 +08:00
whitequark f958cba4ed llvm_ir_generator: update debug info emission for LLVM 3.8. 2016-04-14 18:09:36 +08:00
whitequark 7c520aa0c4 coredevice: format backtrace RA as +0xN, not 0xN.
The absolute address is somewhere in the 0x4000000 range; the one
that is displayed is an offset from the shared object base.
2016-04-14 18:09:36 +08:00
whitequark 66bbee51d8 conda: require llvmlite-artiq built for LLVM 3.8. 2016-04-14 18:09:09 +08:00
whitequark f26990aa57 compiler: emit verbose assembly via ARTIQ_DUMP_ASM. 2016-04-14 18:09:02 +08:00
whitequark c89c27e389 compiler: add analysis passes from TargetMachine.
This doesn't have any effect right now, but is the right thing to do.
2016-04-14 18:08:47 +08:00
whitequark 1120c264b1 compiler: mark loaded pointers as !dereferenceable.
Also, lower the bound for test_pulse_rate_dds, since we generate
better code for it now.
2016-04-14 18:08:47 +08:00
whitequark 03b6555d9d compiler: update for LLVM 3.7. 2016-04-14 18:08:28 +08:00
whitequark 932e680f3e compiler: use correct data layout. 2016-04-14 18:07:56 +08:00
whitequark f59fd8faec llvm_ir_generator: do not use 'coldcc' calling convention.
First, this calling convention doesn't actually exist in OR1K
and trying to use it in Asserts build causes an UNREACHABLE.

Second, I tried to introduce it and it does not appear to produce
any measurable benefit: not only OR1K has a ton of CSRs but also
it is quite hard, if not realistically impossible, to produce
the kind of register pressure that would be relieved by sparing
a few more CSRs for our exception raising function calls, since
temporaries don't have to be preserved before a noreturn call
and spilling over ten registers across an exceptional edge
is not something that the code we care about would do.

Third, it produces measurable drawbacks: it inflates code size
of check:* functions by adding spills. Of course, this could be
alleviated by making __artiq_raise coldcc as well, but what's
the point anyway?
2016-04-14 18:07:35 +08:00
whitequark e416246e78 llvm_ir_generator: mark loads as non-null where applicable. 2016-04-14 18:07:35 +08:00
whitequark 50ae17649d test: relax lit/embedding/syscall_flags.
We currently have broken debug info. In either case, debug info
is irrelevant to this test.
2016-04-14 18:07:35 +08:00
whitequark f7603dcb6f compiler: fix ARTIQ_DUMP_ELF. 2016-04-14 18:07:17 +08:00
whitequark 812e79b63d llvm_ir_generator: don't mark non-constant attribute loads as invariant.
Oops.
2016-04-14 18:07:13 +08:00
whitequark dcb0ffdd03 Commit missing parts of 1d8b0d46. 2016-04-14 18:07:04 +08:00
whitequark ee7e648cb0 compiler: allow specifying per-function "fast-math" flags.
Fixes #351.
2016-04-14 18:07:04 +08:00
whitequark 5fafcc1341 Commit missing parts of 6f5332f8. 2016-04-14 18:07:04 +08:00
whitequark f7d4a37df9 compiler: allow flagging syscalls, providing information to optimizer.
This also fixes a crash in test_cache introduced in 1d8b0d46.
2016-04-14 18:06:47 +08:00
whitequark c6b21652ba compiler: mark FFI functions as ModRef=Ref using TBAA metadata.
Fascinatingly, the fact that you can mark call instructions with
!tbaa metadata is completely undocumented. Regardless, it is true:
a !tbaa metadata for an "immutable" type will cause
AliasAnalysis::getModRefBehavior to return OnlyReadsMemory for that
call site.

Don't bother marking loads with TBAA yet since we already place
!load.invariant on them (which is as good as the TBAA "immutable"
flag) and after that we're limited by lack of !nonnull anyway.

Also, add TBAA analysis passes in our pipeline to actually engage it.
2016-04-14 18:06:47 +08:00
whitequark 0e0f81b509 compiler: mark loads of kernel constant attributes as load invariant.
Also, enable LICM, since it can take advantage of this.
2016-04-14 18:06:47 +08:00
whitequark 081edb27d7 coredevice: add some kernel_constant_attributes specifications. 2016-04-14 18:06:47 +08:00
whitequark b5fd257a33 compiler: do not write back kernel constant attributes.
Fixes #322.
2016-04-14 18:06:21 +08:00
whitequark 665e59e064 compiler: implement kernel constant attributes.
Part of #322.
2016-04-14 18:06:21 +08:00
whitequark 348e058c6f test_pulse_rate_dds: tighten upper bound to 400us. 2016-04-14 18:06:06 +08:00
whitequark 718d411dd5 compiler: run IPSCCP.
This doesn't do much, only frees some registers.
2016-04-14 18:05:57 +08:00
whitequark 019f528ea6 compiler: raise inliner threshold to the equivalent of -O3. 2016-04-14 18:05:57 +08:00
whitequark 3fa5762c10 compiler: extract runtime checks into separate cold functions.
This reduces register pressure as well as function size, which
favorably affects the inliner.
2016-04-14 18:05:57 +08:00
whitequark fcf2a73f82 test_pulse_rate: tighten upper bound to 1500ns. 2016-04-14 18:05:31 +08:00
whitequark 92f3dc705f llvm_ir_generator: generate code more amenable to LLVM's GlobalOpt.
This exposes almost all embedded methods to inlining, with massive
gains.
2016-04-14 18:05:10 +08:00
whitequark f2c92fffea compiler: make quoted functions independent of outer environment. 2016-04-14 18:04:42 +08:00
whitequark ccb1d54beb compiler: tune the LLVM optimizer pipeline (fixes #315). 2016-04-14 18:04:42 +08:00
whitequark 8fa4281470 compiler: significantly increase readability of LLVM and ARTIQ IRs. 2016-04-14 18:04:42 +08:00
whitequark e534941383 compiler: quote functions directly instead of going through a local. 2016-04-14 18:04:22 +08:00
whitequark f72e050af5 transforms.llvm_ir_generator: extract class function attributes.
This should give LLVM more visibility.
2016-04-14 18:04:22 +08:00
whitequark 00facbbc78 compiler: get rid of the GetConstructor opcode. 2016-04-14 18:04:22 +08:00
Sebastien Bourdeauducq 321ba57e84 manual/installing: --toolchain vivado 2016-04-14 01:25:48 +08:00
Sebastien Bourdeauducq 582efe5b91 typo 2016-04-14 01:17:47 +08:00
Sebastien Bourdeauducq 349ccfb633 gateware/nist_qc2: substitute FMC 2016-04-14 01:04:19 +08:00
Sebastien Bourdeauducq 71b9ba6ab7 manual/faq: list HITL TTL connections 2016-04-14 01:04:19 +08:00
Sebastien Bourdeauducq 317e6ea38d manual/git: commit needed before starting master, refresh. Closes #387 2016-04-14 01:04:19 +08:00
dhslichter 08e742ce68 Updated qc2 pinouts for SPI and 2x DDS bus, update docs 2016-04-13 18:39:50 +08:00
Robert Jördens 90876e0143 examples: move pdq2 frame selects away from TTLInOut ttl3 2016-04-12 19:41:16 +08:00
Robert Jördens 6552aa4c28 test: set inputs to input(), should close #383 2016-04-12 18:17:39 +08:00
Sebastien Bourdeauducq 936190033e gui/models: handle Qt calling DictSyncTreeSepModel.index with garbage inputs. Closes #388 2016-04-11 20:12:07 +08:00
Sebastien Bourdeauducq e7d448efd3 dashboard/moninj: use ephemeral UDP port 2016-04-11 18:58:22 +08:00
Sebastien Bourdeauducq a6c17d3e40 dashboard/moninj: fix windows problems 2016-04-11 18:58:13 +08:00
Sebastien Bourdeauducq e4833a33fc dashboard/moninj: use thread instead of asyncio UDP (#39) 2016-04-11 18:57:21 +08:00
Robert Jördens 2617b9db82 installing.rst: typo 2016-04-10 21:01:15 +08:00
Robert Jördens a82f042337 installing.rst: triple colons 2016-04-10 11:21:32 +08:00
Robert Jördens 64d1bca6c1 installing.rst: update, clarify 2016-04-10 00:03:36 +08:00
Robert Jördens c858d44c73 README: rewrite, summarizing more aspects of ARTIQ 2016-04-10 00:00:20 +08:00
Robert Jördens a48f44eb39 introduction.rst: update license 2016-04-10 00:00:12 +08:00
Robert Jördens dee574084e README: note about doc/manual/introduction.rst 2016-04-09 23:59:50 +08:00
Robert Jördens e2def34ede ipython notebook example: datasets subgroup 2016-04-08 12:21:43 +08:00
Robert Jördens f3d8ac301c applets/simple: fix error msg, style 2016-04-08 01:27:22 +08:00
Robert Jördens 787ed65d00 plot_xy: fix errorbar plot 2016-04-08 01:26:42 +08:00
Robert Jördens ca24e00400 plot_xy: un-randomize the fit plot 2016-04-08 01:26:23 +08:00
Robert Jördens 1d3c0166da dashboard: allow more than 99 scan points 2016-04-08 01:26:05 +08:00
whitequark 5fef95b073 doc: use proper CMAKE_BUILD_TYPE for LLVM.
Fixes #380.
2016-04-06 22:08:10 +00:00
Sebastien Bourdeauducq 2b516d21ae manual/developing_a_ndsp: update to new bind interface 2016-04-06 19:14:13 +08:00
Robert Jördens d6339e49ca RELEASE_NOTES: spelling 2016-04-05 18:10:10 +08:00
Robert Jördens eea7cdcf89 worker_impl: style 2016-04-05 18:03:55 +08:00
Robert Jördens 7dab0433be RELEASE_NOTES: HDF5 encoding 2016-04-05 18:03:55 +08:00
Robert Jördens 0b32d9946a worker: trust that h5py encodes strings 2016-04-05 18:03:55 +08:00
Robert Jördens 690eb8c304 worker: trust that h5py maps all types as we want 2016-04-05 18:03:55 +08:00
Robert Jördens 4ba07e01d1 test_h5types: also test ndarrays 2016-04-05 18:03:55 +08:00
Robert Jördens a52b8fa3da test_h5types: use in-memory files 2016-04-05 18:03:55 +08:00
Robert Jördens 138271e936 RELEASE_NOTES: style 2016-04-05 18:03:46 +08:00
Robert Jördens e1f3968a4a worker, hdf5: move datasets to subgroup 2016-04-05 18:03:04 +08:00
Sebastien Bourdeauducq 4f589a7277 artiq_flash: clear error message when bin directory is absent 2016-04-05 16:09:53 +08:00
Sebastien Bourdeauducq 7081a11db6 pyqtgraph dock patch is not needed anymore 2016-04-05 15:24:24 +08:00
Sebastien Bourdeauducq cff01274e9 ship examples with package 2016-04-05 14:36:31 +08:00
Sebastien Bourdeauducq f4c5403803 manual/faq: cleanup/update 2016-04-05 14:17:25 +08:00
Sebastien Bourdeauducq eba90c8782 client: add --async option to scan-repository, recommend usage in git post-receive 2016-04-04 22:18:29 +08:00
Sebastien Bourdeauducq f9db7e472b examples/arguments_demo: demonstrate arguments from datasets (#368) 2016-04-02 23:08:14 +08:00
Sebastien Bourdeauducq b095c94919 master/worker_impl: use ParentDatasetDB in examine mode. Closes #368 2016-04-02 23:08:14 +08:00
Sebastien Bourdeauducq 2f404bae41 master: always expose full set of worker handlers (#368) 2016-04-02 23:08:14 +08:00
Sebastien Bourdeauducq 08549bc3c5 gui/experiment: fix recompute argument error handling 2016-04-02 23:08:14 +08:00
Sebastien Bourdeauducq 0808db6b40 Merge branch 'release-1' of github.com:m-labs/artiq into release-1 2016-04-02 22:32:27 +08:00
Sebastien Bourdeauducq ee5eb57645 RELEASE_NOTES: 1.0rc2 2016-04-02 22:31:58 +08:00
whitequark 3e6e8c6b51 Revert "test: XFAIL lit/devirtualization/*."
This reverts commit 63121e4faf.
2016-04-02 09:54:35 +00:00
whitequark 0eae25cae7 Revert "conda: simplify llvmlite-artiq version requirement"
This reverts commit 4d54695863.
2016-04-02 09:53:56 +00:00
Sebastien Bourdeauducq 4d54695863 conda: simplify llvmlite-artiq version requirement
Later 0.5.1 packages deleted.
2016-04-02 16:48:30 +08:00
whitequark 63121e4faf test: XFAIL lit/devirtualization/*. 2016-03-31 09:17:34 +00:00
whitequark aee3def228 again 2016-03-31 09:09:53 +00:00
Sebastien Bourdeauducq be61c7fa98 again 2016-03-31 16:49:20 +08:00
Sebastien Bourdeauducq 4467016de8 conda: try another way of specifying build number 2016-03-31 16:44:58 +08:00
Sebastien Bourdeauducq 56952aeb74 conda: restrict llvmlite-artiq 2016-03-31 16:36:14 +08:00
Robert Jördens 19e259e5ee doc: conda channel -> label 2016-03-31 10:30:06 +02:00
Robert Jördens 822c6545b5 doc: add note about xcb (closes #361) 2016-03-31 10:29:58 +02:00
Sebastien Bourdeauducq f4dd37924e conda: restrict to llvm-or1k 3.5 2016-03-31 16:17:35 +08:00
Sebastien Bourdeauducq eeba804095 manual: QC2 FMC voltage 2016-03-31 10:47:05 +08:00
Sebastien Bourdeauducq b761a824ee runtime: fix ddstest help (#365) 2016-03-31 10:27:41 +08:00
Sebastien Bourdeauducq 06e626024b gui: setParent(None) before deleteLater() to remove dock appears unnecessary and causes memory corruption on Windows. Closes #362 2016-03-30 11:39:36 +08:00
Sebastien Bourdeauducq 72da5cc0de gui: do 60114447 properly 2016-03-30 01:48:25 +08:00
Sebastien Bourdeauducq b64cea0a79 gui: log error and bail out on artiq_gui.pyon write failure (#360) 2016-03-30 01:45:37 +08:00
Sebastien Bourdeauducq 7cff4977b4 gui/applets: use a better default size, make minimum size proportional to font 2016-03-29 17:11:04 +08:00
Sebastien Bourdeauducq ddf6ec433e gui: better default layout 2016-03-29 17:11:04 +08:00
Sebastien Bourdeauducq ac0f62628d environment,worker_db: mutate datasets from experiments via dedicated method instead of Notifier. Closes #345 2016-03-29 17:11:04 +08:00
Sebastien Bourdeauducq 1884b22528 doc/tutorial: add missing type annotation in LED example. Closes #356 2016-03-29 14:54:05 +08:00
Sebastien Bourdeauducq e6da8f778e doc/dds: fix init timing margin 2016-03-29 12:01:22 +08:00
Sebastien Bourdeauducq 74b71e5f64 doc: fix comment about when and how DDS init should be done. Closes #353 2016-03-29 11:11:13 +08:00
Robert Jördens b04b5c8239 scanwidget: handle min, max, suffix (closes #352) 2016-03-29 00:55:27 +08:00
Robert Jördens 9de11dd958 RELEASE_NOTES: pipistrello speed change 2016-03-25 13:25:04 +01:00
Sebastien Bourdeauducq 027aa5d66a doc: update flterm instructions. Closes #346 2016-03-25 20:11:09 +08:00
Sebastien Bourdeauducq 4e0e8341ca gui/log: split lines correctly 2016-03-25 20:05:13 +08:00
Sebastien Bourdeauducq 69a531edf4 gui/log: send Qt model notifications correctly 2016-03-25 20:05:08 +08:00
Sebastien Bourdeauducq 74b3c47614 protocols/pc_rpc: short_exc_info 2016-03-25 20:04:53 +08:00
Sebastien Bourdeauducq 7bdec1b93b master/worker: use only first line in short_exc_info 2016-03-25 20:03:27 +08:00
Sebastien Bourdeauducq 5d5a4433a7 gui: redesign table/trees to avoid slow and buggy qt/pyqt autosize. Closes #182. Closes #187. 2016-03-25 20:03:22 +08:00
Robert Jördens 358d2a6ba3 i2c: fix variable name (closes #347) 2016-03-25 12:52:18 +01:00
Sebastien Bourdeauducq bbef353057 protocols/pipe_ipc: raise line length limit 2016-03-23 15:14:24 +08:00
780 changed files with 28170 additions and 91308 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
artiq/_version.py export-subst

View File

@ -1,49 +0,0 @@
---
name: Bug report
about: Report a bug in ARTIQ
---
<!-- Above are non-Markdown tags for Github auto-prompting issue type. Template based on pylint: https://raw.githubusercontent.com/PyCQA/pylint/master/.github/ISSUE_TEMPLATE/ -->
# Bug Report
<!-- Thanks for reporting a bug report to ARTIQ! You can also discuss issues and ask questions on IRC (the [#m-labs channel on freenode](https://webchat.freenode.net/?channels=m-labs) or on the [forum](https://forum.m-labs.hk). Please check Github/those forums to avoid posting a repeat issue.
Context helps us fix issues faster, so please include the following when relevant:
-->
## One-Line Summary
Short summary.
## Issue Details
### Steps to Reproduce
1. Step 1.
2. Step 2.
3. Step 3.
### Expected Behavior
Behavior
### Actual (undesired) Behavior
* Text description
* Log message, tracebacks, screen shots where relevant
### Your System (omit irrelevant parts)
* Operating System:
* ARTIQ version: (with recent versions of ARTIQ, run ``artiq_client --version``)
* Version of the gateware and runtime loaded in the core device: (in the output of ``artiq_coremgmt -D .... log``)
* If using Conda, output of `conda list` (please submit as a file attachment, as this tends to be long)
* Hardware involved:
<!--
For in-depth information on bug reporting, see:
http://www.chiark.greenend.org.uk/~sgtatham/bugs.html https://developer.mozilla.org/en-US/docs/Mozilla/QA/Bug_writing_guidelines
-->

View File

@ -1,28 +0,0 @@
---
name: Feature request
about: Suggest an idea for ARTIQ
---
<!--
Hi there! Thank you for wanting to make ARTIQ better.
Before you submit this, make sure that this feature wasn't
already requested or if it is not already implemented in the master branch.
Based on pylint: https://raw.githubusercontent.com/PyCQA/pylint/master/.github/ISSUE_TEMPLATE/2_Feature_request.md
-->
# ARTIQ Feature Request
## Problem this request addresses
A clear and concise description of what the problem is.
## Describe the solution you'd like
A clear and concise description of what you want to happen.
## Additional context
Add any other context about the feature request here.

View File

@ -1,29 +0,0 @@
---
name: Support question
about: Questions about ARTIQ that are not covered in the documentation
---
# Question
<!--
Make sure you check the ARTIQ documentation before posting a question.
Don't forget you can search it!
Beta version: https://m-labs.hk/artiq/manual-beta/
Stable version: https://m-labs.hk/artiq/manual/
The forum is also a very good place for questions: https://forum.m-labs.hk/
Can also ask on IRC: https://webchat.freenode.net/?channels=m-labs or
check mailing list archives: https://ssl.serverraum.org/lists-archive/artiq/
Remember: if you have this question then others probably do too! The best way of thanking the people who help you with this issue is to contribute to ARTIQ by submitting a pull request to update the documentation.
-->
## Category: FILL_IN
<!-- One-word category this question falls into: GUI, installation/setup, devices, development, documentation, etc. -->
## Description
Question text

View File

@ -1,69 +0,0 @@
<!--
Thank you for submitting a PR to ARTIQ!
To ease the process of reviewing your PR, do make sure to complete the following boxes.
You can also read more about contributing to ARTIQ in this document:
https://github.com/m-labs/artiq/blob/master/CONTRIBUTING.rst#contributing-code
Based on https://raw.githubusercontent.com/PyCQA/pylint/master/.github/PULL_REQUEST_TEMPLATE.md
-->
# ARTIQ Pull Request
## Description of Changes
### Related Issue
<!--
If this PR fixes a particular issue, use the following to automatically close that issue
once this PR gets merged:
Closes #XXX
-->
## Type of Changes
<!-- Leave ONLY the corresponding lines for the applicable type of change: -->
| | Type |
| ------------- | ------------- |
| ✓ | :bug: Bug fix |
| ✓ | :sparkles: New feature |
| ✓ | :hammer: Refactoring |
| ✓ | :scroll: Docs |
## Steps (Choose relevant, delete irrelevant before submitting)
### All Pull Requests
- [x] Use correct spelling and grammar.
- [ ] Update [RELEASE_NOTES.rst](../RELEASE_NOTES.rst) if there are noteworthy changes, especially if there are changes to existing APIs.
- [ ] Close/update issues.
- [ ] Check the copyright situation of your changes and sign off your patches (`git commit --signoff`, see [copyright](../CONTRIBUTING.rst#copyright-and-sign-off)).
### Code Changes
- [ ] Run `flake8` to check code style (follow PEP-8 style). `flake8` has issues with parsing Migen/gateware code, ignore as necessary.
- [ ] Test your changes or have someone test them. Mention what was tested and how.
- [ ] Add and check docstrings and comments
- [ ] Check, test, and update the [unittests in /artiq/test/](../artiq/test/) or [gateware simulations in /artiq/gateware/test](../artiq/gateware/test)
### Documentation Changes
- [ ] Check, test, and update the documentation in [doc/](../doc/). Build documentation (`nix build .#artiq-manual-html; nix build .#artiq-manual-pdf`) to ensure no errors.
### Git Logistics
- [ ] Split your contribution into logically separate changes (`git rebase --interactive`). Merge/squash/fixup commits that just fix or amend previous commits. Remove unintended changes & cleanup. See [tutorial](https://www.atlassian.com/git/tutorials/rewriting-history/git-rebase).
- [ ] Write short & meaningful commit messages. Review each commit for messages (`git show`). Format:
```
topic: description. < 50 characters total.
Longer description. < 70 characters per line
```
### Licensing
See [copyright & licensing for more info](https://github.com/m-labs/artiq/blob/master/CONTRIBUTING.rst#copyright-and-sign-off).
ARTIQ files that do not contain a license header are copyrighted by M-Labs Limited and are licensed under LGPLv3+.

26
.gitignore vendored
View File

@ -6,32 +6,28 @@ __pycache__/
*.bin *.bin
*.elf *.elf
*.fbi *.fbi
*.pcap
*.prof
.ipynb_checkpoints .ipynb_checkpoints
/doc/manual/_build /doc/manual/_build
/build /build
/result
/dist /dist
/*.egg-info /*.egg-info
/.coverage /.coverage
/artiq/test/lit/*/Output/ /artiq/test/lit/*/Output/
/artiq/binaries /artiq/binaries
/artiq/firmware/target/
/misoc_*/ /misoc_*/
/artiq_*/
/artiq/test/results /artiq/test/results
/artiq/examples/*/results /artiq/examples/master/results
/artiq/examples/*/last_rid.pyon /artiq/examples/master/last_rid.pyon
/artiq/examples/*/dataset_db.mdb /artiq/examples/master/dataset_db.pyon
/artiq/examples/*/dataset_db.mdb-lock /artiq/examples/sim/results
/artiq/examples/sim/dataset_db.pyon
# when testing ad-hoc experiments at the root: # recommended location for testbed
/repository/ /run
# alternatively, when testing ad-hoc experiments at the root:
/results /results
/last_rid.pyon /last_rid.pyon
/dataset_db.mdb /dataset_db.pyon
/dataset_db.mdb-lock /device_db.pyon
/device_db*.py /test*.py
/test*

4
.gitmodules vendored Normal file
View File

@ -0,0 +1,4 @@
[submodule "artiq/runtime/lwip"]
path = artiq/runtime/lwip
url = https://github.com/m-labs/lwip
ignore = untracked

View File

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

View File

@ -1,20 +0,0 @@
Sharing development boards
==========================
To avoid conflicts for development boards on the server, while using a board you must hold the corresponding lock file present in the ``/tmp`` folder of the machine to which the board is connected. Holding the lock file grants you exclusive access to the board.
For example, to lock the KC705 until ENTER is pressed:
::
ssh rpi-1.m-labs.hk "flock /tmp/board_lock-kc705-1 -c 'echo locked; read; echo unlocked'"
If the board is already locked by another user, the ``flock`` commands above will wait for the lock to be released.
To determine which user is locking a board, use a command such as:
::
ssh rpi-1.m-labs.hk "fuser -v /tmp/board_lock-kc705-1"
Deleting git branches
=====================
Never use ``git push origin :branch`` nor ``git push origin --delete branch``, as this can delete code that others have pushed without warning. Instead, always delete branches using the GitHub web interface that lets you check better if the branch you are deleting has been fully merged.

757
LICENSE
View File

@ -1,165 +1,674 @@
GNU LESSER GENERAL PUBLIC LICENSE GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007 Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed. of this license document, but changing it is not allowed.
Preamble
This version of the GNU Lesser General Public License incorporates The GNU General Public License is a free, copyleft license for
the terms and conditions of version 3 of the GNU General Public software and other kinds of works.
License, supplemented by the additional permissions listed below.
0. Additional Definitions. The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
As used herein, "this License" refers to version 3 of the GNU Lesser When we speak of free software, we are referring to freedom, not
General Public License, and the "GNU GPL" refers to version 3 of the GNU price. Our General Public Licenses are designed to make sure that you
General Public License. have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
"The Library" refers to a covered work governed by this License, To protect your rights, we need to prevent others from denying you
other than an Application or a Combined Work as defined below. these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
An "Application" is any work that makes use of an interface provided For example, if you distribute copies of such a program, whether
by the Library, but which is not otherwise based on the Library. gratis or for a fee, you must pass on to the recipients the same
Defining a subclass of a class defined by the Library is deemed a mode freedoms that you received. You must make sure that they, too, receive
of using an interface provided by the Library. or can get the source code. And you must show them these terms so they
know their rights.
A "Combined Work" is a work produced by combining or linking an Developers that use the GNU GPL protect your rights with two steps:
Application with the Library. The particular version of the Library (1) assert copyright on the software, and (2) offer you this License
with which the Combined Work was made is also called the "Linked giving you legal permission to copy, distribute and/or modify it.
Version".
The "Minimal Corresponding Source" for a Combined Work means the For the developers' and authors' protection, the GPL clearly explains
Corresponding Source for the Combined Work, excluding any source code that there is no warranty for this free software. For both users' and
for portions of the Combined Work that, considered in isolation, are authors' sake, the GPL requires that modified versions be marked as
based on the Application, and not on the Linked Version. changed, so that their problems will not be attributed erroneously to
authors of previous versions.
The "Corresponding Application Code" for a Combined Work means the Some devices are designed to deny users access to install or run
object code and/or source code for the Application, including any data modified versions of the software inside them, although the manufacturer
and utility programs needed for reproducing the Combined Work from the can do so. This is fundamentally incompatible with the aim of
Application, but excluding the System Libraries of the Combined Work. protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
1. Exception to Section 3 of the GNU GPL. Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
You may convey a covered work under sections 3 and 4 of this License The precise terms and conditions for copying, distribution and
without being bound by section 3 of the GNU GPL. modification follow.
2. Conveying Modified Versions. TERMS AND CONDITIONS
If you modify a copy of the Library, and, in your modifications, a 0. Definitions.
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to "This License" refers to version 3 of the GNU General Public License.
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of "Copyright" also means copyright-like laws that apply to other kinds of
this License applicable to that copy. works, such as semiconductor masks.
3. Object Code Incorporating Material from Library Header Files. "The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
The object code form of an Application may incorporate material from To "modify" a work means to copy from or adapt all or part of the work
a header file that is part of the Library. You may convey such object in a fashion requiring copyright permission, other than the making of an
code under terms of your choice, provided that, if the incorporated exact copy. The resulting work is called a "modified version" of the
material is not limited to numerical parameters, data structure earlier work or a work "based on" the earlier work.
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the A "covered work" means either the unmodified Program or a work based
Library is used in it and that the Library and its use are on the Program.
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license To "propagate" a work means to do anything with it that, without
document. permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
4. Combined Works. To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
You may convey a Combined Work under terms of your choice that, An interactive user interface displays "Appropriate Legal Notices"
taken together, effectively do not restrict modification of the to the extent that it includes a convenient and prominently visible
portions of the Library contained in the Combined Work and reverse feature that (1) displays an appropriate copyright notice, and (2)
engineering for debugging such modifications, if you also do each of tells the user that there is no warranty for the work (except to the
the following: extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
a) Give prominent notice with each copy of the Combined Work that 1. Source Code.
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license The "source code" for a work means the preferred form of the work
document. for making modifications to it. "Object code" means any non-source
form of a work.
c) For a Combined Work that displays copyright notices during A "Standard Interface" means an interface that either is an official
execution, include the copyright notice for the Library among standard defined by a recognized standards body, or, in the case of
these notices, as well as a reference directing the user to the interfaces specified for a particular programming language, one that
copies of the GNU GPL and this license document. is widely used among developers working in that language.
d) Do one of the following: The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
0) Convey the Minimal Corresponding Source under the terms of this The "Corresponding Source" for a work in object code form means all
License, and the Corresponding Application Code in a form the source code needed to generate, install, and (for an executable
suitable for, and under terms that permit, the user to work) run the object code and to modify the work, including scripts to
recombine or relink the Application with a modified version of control those activities. However, it does not include the work's
the Linked Version to produce a modified Combined Work, in the System Libraries, or general-purpose tools or generally available free
manner specified by section 6 of the GNU GPL for conveying programs which are used unmodified in performing those activities but
Corresponding Source. which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
1) Use a suitable shared library mechanism for linking with the The Corresponding Source need not include anything that users
Library. A suitable mechanism is one that (a) uses at run time can regenerate automatically from other parts of the Corresponding
a copy of the Library already present on the user's computer Source.
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise The Corresponding Source for a work in source code form is that
be required to provide such information under section 6 of the same work.
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries. 2. Basic Permissions.
You may place library facilities that are a work based on the All rights granted under this License are granted for the term of
Library side by side in a single library together with other library copyright on the Program, and are irrevocable provided the stated
facilities that are not Applications and are not covered by this conditions are met. This License explicitly affirms your unlimited
License, and convey such a combined library under terms of your permission to run the unmodified Program. The output from running a
choice, if you do both of the following: covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
a) Accompany the combined library with a copy of the same work based You may make, run and propagate covered works that you do not
on the Library, uncombined with any other library facilities, convey, without conditions so long as your license otherwise remains
conveyed under the terms of this License. in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
b) Give prominent notice with the combined library that part of it Conveying under any other circumstances is permitted solely under
is a work based on the Library, and explaining where to find the the conditions stated below. Sublicensing is not allowed; section 10
accompanying uncombined form of the same work. makes it unnecessary.
6. Revised Versions of the GNU Lesser General Public License. 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
The Free Software Foundation may publish revised and/or new versions No covered work shall be deemed part of an effective technological
of the GNU Lesser General Public License from time to time. Such new measure under any applicable law fulfilling obligations under article
versions will be similar in spirit to the present version, but may 11 of the WIPO copyright treaty adopted on 20 December 1996, or
differ in detail to address new problems or concerns. similar laws prohibiting or restricting circumvention of such
measures.
Each version is given a distinguishing version number. If the When you convey a covered work, you waive any legal power to forbid
Library as you received it specifies that a certain numbered version circumvention of technological measures to the extent such circumvention
of the GNU Lesser General Public License "or any later version" is effected by exercising rights under this License with respect to
applies to it, you have the option of following the terms and the covered work, and you disclaim any intention to limit operation or
conditions either of that published version or of any later version modification of the work as a means of enforcing, against the work's
published by the Free Software Foundation. If the Library as you users, your or third parties' legal rights to forbid circumvention of
received it does not specify a version number of the GNU Lesser technological measures.
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide 4. Conveying Verbatim Copies.
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is You may convey verbatim copies of the Program's source code as you
permanent authorization for you to choose that version for the receive it, in any medium, provided that you conspicuously and
Library. appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@ -1,674 +0,0 @@
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.

View File

@ -1,8 +1,5 @@
graft artiq/firmware graft artiq/runtime
graft artiq/examples graft artiq/examples
include artiq/gui/logo*.svg include artiq/gui/logo.svg
include versioneer.py include versioneer.py
include artiq/_version.py include artiq/_version.py
include artiq/coredevice/coredevice_generic.schema.json
include artiq/compiler/kernel.ld
include artiq/afws.pem

View File

@ -1,59 +1,32 @@
.. Always keep doc/manual/introduction.rst synchronized with this file, with the exception of the logo. .. Always keep doc/manual/introduction.rst synchronized with this file, with the exception of the logo.
.. Absolute so that it works on github and on pypi .. image:: doc/logo/artiq.png
.. image:: https://raw.githubusercontent.com/m-labs/artiq/master/doc/logo/artiq.png
:target: https://m-labs.hk/artiq
ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is a leading-edge control and data acquisition system for quantum information experiments. ARTIQ (Advanced Real-Time Infrastructure for Quantum physics) is the next-generation control system for quantum information experiments.
It is maintained and developed by `M-Labs <https://m-labs.hk>`_ and the initial development was for and in partnership with the `Ion Storage Group at NIST <https://www.nist.gov/pml/time-and-frequency-division/ion-storage>`_. ARTIQ is free software and offered to the entire research community as a solution equally applicable to other challenging control tasks, including outside the field of ion trapping. Many laboratories around the world have adopted ARTIQ as their control system and some have `contributed <https://m-labs.hk/experiment-control/funding/>`_ to it. It is developed by `M-Labs <https://m-labs.hk>`_ for and in partnership with the `Ion Storage Group at NIST <http://www.nist.gov/pml/div688/grp10/index.cfm>`_ as free software.
It is offered to the entire research community as a solution equally applicable to other challenging control tasks outside the field of ion trapping.
The system features a high-level programming language, capable of describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results. The system features a high-level programming language that helps describing complex experiments, which is compiled and executed on dedicated hardware with nanosecond timing resolution and sub-microsecond latency. It includes graphical user interfaces to parametrize and schedule experiments and to visualize and explore the results.
ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware <https://github.com/sinara-hw>`_, and in particular the Kasli FPGA carrier, are designed to work with ARTIQ. ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers. Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort. ARTIQ uses FPGA hardware to perform its time-critical tasks.
It is designed to be portable to hardware platforms from different vendors and FPGA manufacturers.
Currently, one configuration of a `low-cost open hardware FPGA board <http://pipistrello.saanlima.com/>`_ and several different configurations of a `high-end FPGA evaluation kit <http://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ are used and supported.
Any of these FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort.
ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions. Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration. Like any open-source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_. Custom hardware components with widely extended capabilities and advanced support for scalable and fully distributed real-time control of experiments `are being designed <https://github.com/m-labs/artiq-hardware>`_.
ARTIQ is supported by M-Labs and developed openly. Components, features, fixes, improvements, and extensions are often `funded <https://m-labs.hk/experiment-control/funding/>`_ by and developed for the partnering research groups. ARTIQ and its dependencies are available in the form of `conda packages <https://conda.anaconda.org/m-labs/label/main>`_ for both Linux and Windows.
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>`_.
Core technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `Migen-AXI <https://github.com/peteut/migen-axi>`_, `Rust <https://www.rust-lang.org/>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`VexRiscv <https://github.com/SpinalHDL/VexRiscv>`_, `LLVM <https://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt6 <https://www.qt.io/>`_. ARTIQ is supported by M-Labs and developed openly.
Components, features, fixes, improvements, and extensions are funded by and developed for the partnering research groups.
| Website: https://m-labs.hk/experiment-control/artiq Technologies employed include `Python <https://www.python.org/>`_, `Migen <https://github.com/m-labs/migen>`_, `MiSoC <https://github.com/m-labs/misoc>`_/`mor1kx <https://github.com/openrisc/mor1kx>`_, `LLVM <http://llvm.org/>`_/`llvmlite <https://github.com/numba/llvmlite>`_, and `Qt5 <http://www.qt.io/>`_.
| (US-hosted mirror: https://m-labs-intl.com/experiment-control/artiq)
Website: https://m-labs.hk/artiq
`Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``. `Cite ARTIQ <http://dx.doi.org/10.5281/zenodo.51303>`_ as ``Bourdeauducq, Sébastien et al. (2016). ARTIQ 1.0. Zenodo. 10.5281/zenodo.51303``.
License Copyright (C) 2014-2016 M-Labs Limited.
======= Licensed under GNU GPL version 3 or any later version.
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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
ARTIQ is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with ARTIQ. If not, see <http://www.gnu.org/licenses/>.
The ARTIQ manifesto
===================
The free and open dissemination of methods and results is central to scientific progress.
The ARTIQ and Sinara authors, contributors, and supporters consider the free and open exchange of scientific tools to be equally important and have chosen the licensing terms of ARTIQ and Sinara accordingly. ARTIQ, including its gateware, the firmware, and the ARTIQ tools and libraries are licensed as LGPLv3+. The Sinara hardware designs are licensed under CERN OHL.
This ensures that a user of ARTIQ or Sinara hardware designs obtains broad rights to use, redistribute, study, and modify them.
The following statements are intended to clarify the interpretation and application of the licensing terms:
* There is no requirement to distribute any unmodified, modified, or extended versions of ARTIQ. Only when distributing ARTIQ the source needs to be made available.
* Unmodified, modified, or extended versions of ARTIQ can be distributed freely under the terms of the LGPLv3+.
* Your ``Experiments``, ``Applets``, and ARTIQ device drivers are considered "Applications" (see the LGPLv3+) and can be (but don't have to be) distributed under the terms of your choice. The public distribution under a free and open license is encouraged, however.
* Similarly, distribution and licensing of your experiment data, calibrations, or measurement results is entirely at your discretion.
* Other features, changes, and additions are considered modifications of ARTIQ and inherit the LGPLv3+. Those include for example adding new RTIO ``Phys``, compiler optimizations, compiler features, ports of the compiler to other target CPUs, dashboard or browser features that are not in applets, as well as scheduler, master and runtime modifications.
* Analogously to the established practice in the Linux kernel, we do not consider components that are developed and used independently to be modifications of ARTIQ.
* For example, when developing a ``Phy`` for an independently developed and used gateware component, that gateware component is not considered a modification of ARTIQ (but the ``Phy`` is).

View File

@ -3,643 +3,39 @@
Release notes Release notes
============= =============
ARTIQ-9 (Unreleased)
--------------------
* GUI state files are now automatically backed up upon successful loading.
* Zotino monitoring in the dashboard now displays the values in volts.
* afws_client now uses the "happy eyeballs" algorithm (RFC 6555) for a faster and more
reliable connection to the server.
* The Zadig driver installer was added to the MSYS2 offline installer.
* Fastino monitoring with Moninj is now supported.
* Qt6 support.
* Python 3.12 support.
ARTIQ-8
-------
Highlights:
* New hardware support:
- Support for Shuttler, a 16-channel 125MSPS DAC card intended for ion transport.
Waveform generator and user API are similar to the NIST PDQ.
- Implemented Phaser-servo. This requires recent gateware on Phaser.
- Almazny v1.2 with finer RF switch control.
- Metlino and Sayma support has been dropped due to complications with synchronous RTIO clocking.
- More user LEDs are exposed to RTIO on Kasli.
- Implemented Phaser-MIQRO support. This requires the proprietary Phaser MIQRO gateware
variant from QUARTIQ.
- Sampler: fixed ADC MU to Volt conversion factor for Sampler v2.2+.
For earlier hardware versions, specify the hardware version in the device
database file (e.g. ``"hw_rev": "v2.1"``) to use the correct conversion factor.
* Support for distributed DMA, where DMA is run directly on satellites for corresponding
RTIO events, increasing bandwidth in scenarios with heavy satellite usage.
* Support for subkernels, where kernels are run on satellite device CPUs to offload some
of the processing and RTIO operations.
* CPU (on softcore platforms) and AXI bus (on Zynq) are now clocked synchronously with the RTIO
clock, to facilitate implementation of local processing on DRTIO satellites, and to slightly
reduce RTIO latency.
* Support for DRTIO-over-EEM, used with Shuttler.
* Support for WRPLL low-noise clock recovery.
* Enabled event spreading on DRTIO satellites, using high watermark for lane switching.
* Added channel names to RTIO error messages.
* The RTIO analyzer is now proxied by ``aqctl_coreanalyzer_proxy`` typically running on the master
machine, similarly to ``aqctl_moninj_proxy``.
* GUI:
- Integrated waveform analyzer, removing the need for external VCD viewers such as GtkWave.
- Implemented Applet Request Interfaces which allow applets to modify datasets and set the
current values of widgets in the dashboard's experiment windows.
- Implemented a new ``EntryArea`` widget which allows argument entry widgets to be used in applets.
- The "Close all applets" command (shortcut: Ctrl-Alt-W) now ignores docked applets,
making it a convenient way to clean up after exploratory work without destroying a
carefully arranged default workspace.
- Hotkeys now organize experiment windows in the order they were last interacted with:
+ CTRL+SHIFT+T tiles experiment windows
+ CTRL+SHIFT+C cascades experiment windows
- By enabling the ``quickstyle`` option, ``EnumerationValue`` entry widgets can now alternatively display
its choices as buttons that submit the experiment on click.
* Datasets can now be associated with units and scale factors, and displayed accordingly in the dashboard
including applets, like widgets such as ``NumberValue`` already did in earlier ARTIQ versions.
* Experiments can now request arguments interactively from the user at any time.
* Persistent datasets are now stored in a LMDB database for improved performance.
* Python's built-in types (such as ``float``, or ``List[...]``) can now be used in type annotations on
kernel functions.
* MSYS2 packaging for Windows, which replaces Conda. Conda packages are still available to
support legacy installations, but may be removed in a future release.
* Experiments can now be submitted with revisions set to a branch / tag name instead of only git hashes.
* Grabber image input now has an optional timeout.
* On NAR3-supported devices (Kasli-SoC, ZC706), when a Rust panic occurs, a minimal environment is started
where the network and ``artiq_coremgmt`` can be used. This allows the user to inspect logs, change
configuration options, update the firmware, and reboot the device.
* Full Python 3.11 support.
Breaking changes:
* ``SimpleApplet`` now calls widget constructors with an additional ``ctl`` parameter for control
operations, which includes dataset operations. It can be ignored if not needed. For an example usage,
refer to the ``big_number.py`` applet.
* ``SimpleApplet`` and ``TitleApplet`` now call ``data_changed`` with additional parameters. Derived applets
should change the function signature as below:
::
# SimpleApplet
def data_changed(self, value, metadata, persist, mods)
# SimpleApplet (old version)
def data_changed(self, data, mods)
# TitleApplet
def data_changed(self, value, metadata, persist, mods, title)
# TitleApplet (old version)
def data_changed(self, data, mods, title)
Accesses to the data argument should be replaced as below:
::
data[key][0] ==> persist[key]
data[key][1] ==> value[key]
* The ``ndecimals`` parameter in ``NumberValue`` and ``Scannable`` has been renamed to ``precision``.
Parameters after and including ``scale`` in both constructors are now keyword-only.
Refer to the updated ``no_hardware/arguments_demo.py`` example for current usage.
* Almazny v1.2 is incompatible with the legacy versions and is the default.
To use legacy versions, specify ``almazny_hw_rev`` in the JSON description.
* kasli_generic.py has been merged into kasli.py, and the demonstration designs without JSON descriptions
have been removed. The base classes remain present in kasli.py to support third-party flows without
JSON descriptions.
* Legacy PYON databases should be converted to LMDB with the script below:
::
from sipyco import pyon
import lmdb
old = pyon.load_file("dataset_db.pyon")
new = lmdb.open("dataset_db.mdb", subdir=False, map_size=2**30)
with new.begin(write=True) as txn:
for key, value in old.items():
txn.put(key.encode(), pyon.encode((value, {})).encode())
new.close()
* ``artiq.wavesynth`` has been removed.
ARTIQ-7
-------
Highlights:
* New hardware support:
- Kasli-SoC, a new EEM carrier based on a Zynq SoC, enabling much faster kernel execution
(see: https://arxiv.org/abs/2111.15290).
- DRTIO support on Zynq-based devices (Kasli-SoC and ZC706).
- DRTIO support on KC705.
- HVAMP_8CH 8 channel HV amplifier for Fastino / Zotinos
- 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()``).
- 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.
* Faster compilation for large arrays/lists.
* Faster exception handling.
* Several exception handling bugs fixed.
* Support for a simpler shared library system with faster calls into the runtime. This is only used by the NAC3
compiler (nac3ld) and improves RTIO output performance (test_pulse_rate) by 9-10%.
* Moninj improvements:
- Urukul monitoring and frequency setting (through dashboard) is now supported.
- Core device moninj is now proxied via the ``aqctl_moninj_proxy`` controller.
* The configuration entry ``rtio_clock`` supports multiple clocking settings, deprecating the usage
of compile-time options.
* Added support for 100MHz RTIO clock in DRTIO.
* Previously detected RTIO async errors are reported to the host after each kernel terminates and a
warning is logged. The warning is additional to the one already printed in the core device log
immediately upon detection of the error.
* Extended Kasli gateware JSON description with configuration for SPI over DIO.
* TTL outputs can be now configured to work as a clock generator from the JSON.
* On Kasli, the number of FIFO lanes in the scalable events dispatcher (SED) can now be configured in
the JSON.
* ``artiq_ddb_template`` generates edge-counter keys that start with the key of the corresponding
TTL device (e.g. ``ttl_0_counter`` for the edge counter on TTL device ``ttl_0``).
* ``artiq_master`` now has an ``--experiment-subdir`` option to scan only a subdirectory of the
repository when building the list of experiments.
* Experiments can now be submitted by-content.
* The master can now optionally log all experiments submitted into a CSV file.
* Removed worker DB warning for writing a dataset that is also in the archive.
* Experiments can now call ``scheduler.check_termination()`` to test if the user
has requested graceful termination.
* ARTIQ command-line programs and controllers now exit cleanly on Ctrl-C.
* ``artiq_coremgmt reboot`` now reloads gateware as well, providing a more thorough and reliable
device reset (7-series FPGAs only).
* Firmware and gateware can now be built on-demand on the M-Labs server using ``afws_client``
(subscribers only). Self-compilation remains possible.
* Easier-to-use packaging via Nix Flakes.
* Python 3.10 support (experimental).
Breaking changes:
* Due to the new RISC-V CPU, the device database entry for the core device needs to be updated.
The ``target`` parameter needs to be set to ``rv32ima`` for Kasli 1.x and to ``rv32g`` for all
other boards. Freshly generated device database templates already contain this update.
* Updated Phaser-Upconverter default frequency 2.875 GHz. The new default uses the target PFD
frequency of the hardware design.
* ``Phaser.init()`` now disables all Kasli-oscillators. This avoids full power RF output being
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()``.
* The deprecated ``set_dataset(..., save=...)`` is no longer supported.
* 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.
ARTIQ-6
-------
Highlights:
* New hardware support:
- Phaser, a quad channel 1GS/s RF generator card with dual IQ upconverter and dual 5MS/s
ADC and FPGA.
- Zynq SoC core device (ZC706), enabling kernels to run on 1 GHz CPU core with a floating-point
unit for faster computations. This currently requires an external
repository (https://git.m-labs.hk/m-labs/artiq-zynq).
- Mirny 4-channel wide-band PLL/VCO-based microwave frequency synthesiser
- Fastino 32-channel, 3MS/s per channel, 16-bit DAC EEM
- Kasli 2.0, an improved core device with 12 built-in EEM slots, faster FPGA, 4 SFPs, and
high-precision clock recovery circuitry for DRTIO (to be supported in ARTIQ-7).
* ARTIQ Python (core device kernels):
- Multidimensional arrays are now available on the core device, using NumPy syntax.
Elementwise operations (e.g. ``+``, ``/``), matrix multiplication (``@``) and
multidimensional indexing are supported; slices and views are not yet.
- Trigonometric and other common math functions from NumPy are now available on the
core device (e.g. ``numpy.sin``), both for scalar arguments and implicitly
broadcast across multidimensional arrays.
- Failed assertions now raise ``AssertionError``\ s instead of aborting kernel
execution.
* Performance improvements:
- SERDES TTL inputs can now detect edges on pulses that are shorter
than the RTIO period (https://github.com/m-labs/artiq/issues/1432)
- Improved performance for kernel RPC involving list and array.
* Coredevice SI to mu conversions now always return valid codes, or raise a ``ValueError``.
* Zotino now exposes ``voltage_to_mu()``
* ``ad9910``:
- The maximum amplitude scale factor is now ``0x3fff`` (was ``0x3ffe`` before).
- The default single-tone profile is now 7 (was 0).
- Added option to ``set_mu()`` that affects the ASF, FTW and POW registers
instead of the single-tone profile register.
* Mirny now supports HW revision independent, human readable ``clk_sel`` parameters:
"XO", "SMA", and "MMCX". Passing an integer is backwards compatible.
* Dashboard:
- Applets now restart if they are running and a ccb call changes their spec
- A "Quick Open" dialog to open experiments by typing part of their name can
be brought up Ctrl-P (Ctrl+Return to immediately submit the selected entry
with the default arguments).
- The Applets dock now has a context menu command to quickly close all open
applets (shortcut: Ctrl-Alt-W).
* Experiment results are now always saved to HDF5, even if ``run()`` fails.
* Core device: ``panic_reset 1`` now correctly resets the kernel CPU as well if
communication CPU panic occurs.
* NumberValue accepts a ``type`` parameter specifying the output as ``int`` or ``float``
* A parameter ``--identifier-str`` has been added to many targets to aid
with reproducible builds.
* Python 3.7 support in Conda packages.
* `kasli_generic` JSON descriptions are now validated against a
schema. Description defaults have moved from Python to the
schema. Warns if ARTIQ version is too old.
Breaking changes:
* ``artiq_netboot`` has been moved to its own repository at
https://git.m-labs.hk/m-labs/artiq-netboot
* Core device watchdogs have been removed.
* The ARTIQ compiler now implements arrays following NumPy semantics, rather than as a
thin veneer around lists. Most prior use cases of NumPy arrays in kernels should work
unchanged with the new implementation, but the behavior might differ slightly in some
cases (for instance, non-rectangular arrays are not currently supported).
* ``quamash`` has been replaced with ``qasync``.
* Protocols are updated to use device endian.
* Analyzer dump format includes a byte for device endianness.
* To support variable numbers of Urukul cards in the future, the
``artiq.coredevice.suservo.SUServo`` constructor now accepts two device name lists,
``cpld_devices`` and ``dds_devices``, rather than four individual arguments.
* Experiment classes with underscore-prefixed names are now ignored when ``artiq_client``
determines which experiment to submit (consistent with ``artiq_run``).
ARTIQ-5
-------
Highlights:
* Performance improvements:
- Faster RTIO event submission (1.5x improvement in pulse rate test)
See: https://github.com/m-labs/artiq/issues/636
- Faster compilation times (3 seconds saved on kernel compilation time on a typical
medium-size experiment)
See: https://github.com/m-labs/artiq/commit/611bcc4db4ed604a32d9678623617cd50e968cbf
* Improved packaging and build system:
- new continuous integration/delivery infrastructure based on Nix and Hydra,
providing reproducibility, speed and independence.
- rolling release process (https://github.com/m-labs/artiq/issues/1326).
- firmware, gateware and device database templates are automatically built for all
supported Kasli variants.
- new JSON description format for generic Kasli systems.
- Nix packages are now supported.
- many Conda problems worked around.
- controllers are now out-of-tree.
- split packages that enable lightweight applications that communicate with ARTIQ,
e.g. controllers running on non-x86 single-board computers.
* Improved Urukul support:
- AD9910 RAM mode.
- Configurable refclk divider and PLL bypass.
- More reliable phase synchronization at high sample rates.
- Synchronization calibration data can be read from EEPROM.
* A gateware-level input edge counter has been added, which offers higher
throughput and increased flexibility over the usual TTL input PHYs where
edge timestamps are not required. See ``artiq.coredevice.edge_counter`` for
the core device driver and ``artiq.gateware.rtio.phy.edge_counter``/
``artiq.gateware.eem.DIO.add_std`` for the gateware components.
* With DRTIO, Siphaser uses a better calibration mechanism.
See: https://github.com/m-labs/artiq/commit/cc58318500ecfa537abf24127f2c22e8fe66e0f8
* Schedule updates can be sent to influxdb (artiq_influxdb_schedule).
* Experiments can now programatically set their default pipeline, priority, and flush flag.
* List datasets can now be efficiently appended to from experiments using
``artiq.language.environment.HasEnvironment.append_to_dataset``.
* The core device now supports IPv6.
* To make development easier, the bootloader can receive firmware and secondary FPGA
gateware from the network.
* Python 3.7 compatibility (Nix and source builds only, no Conda).
* Various other bugs from 4.0 fixed.
* Preliminary Sayma v2 and Metlino hardware support.
Breaking changes:
* The ``artiq.coredevice.ad9910.AD9910`` and
``artiq.coredevice.ad9914.AD9914`` phase reference timestamp parameters
have been renamed to ``ref_time_mu`` for consistency, as they are in machine
units.
* The controller manager now ignores device database entries without the
``command`` key set to facilitate sharing of devices between multiple
masters.
* The meaning of the ``-d/--dir`` and ``--srcbuild`` options of ``artiq_flash``
has changed.
* Controllers for third-party devices are now out-of-tree.
* ``aqctl_corelog`` now filters log messages below the ``WARNING`` level by default.
This behavior can be changed using the ``-v`` and ``-q`` options like the other
programs.
* On Kasli the firmware now starts with a unique default MAC address
from EEPROM if `mac` is absent from the flash config.
* The ``-e/--experiment`` switch of ``artiq_run`` and ``artiq_compile``
has been renamed ``-c/--class-name``.
* ``artiq_devtool`` has been removed.
* Much of ``artiq.protocols`` has been moved to a separate package ``sipyco``.
``artiq_rpctool`` has been renamed to ``sipyco_rpctool``.
ARTIQ-4
-------
4.0
***
* The ``artiq.coredevice.ttl`` drivers no longer track the timestamps of
submitted events in software, requiring the user to explicitly specify the
timeout for ``count()``/``timestamp_mu()``. Support for ``sync()`` has been dropped.
Now that RTIO has gained DMA support, there is no longer a reliable way for
the kernel CPU to track the individual events submitted on any one channel.
Requiring the timeouts to be specified explicitly ensures consistent API
behavior. To make this more convenient, the ``TTLInOut.gate_*()`` functions
now return the cursor position at the end of the gate, e.g.::
ttl_input.count(ttl_input.gate_rising(100 * us))
In most situations that is, unless the timeline cursor is rewound after the
respective ``gate_*()`` call simply passing ``now_mu()`` is also a valid
upgrade path::
ttl_input.count(now_mu())
The latter might use up more timeline slack than necessary, though.
In place of ``TTL(In)Out.sync``, the new ``Core.wait_until_mu()`` method can
be used, which blocks execution until the hardware RTIO cursor reaches the
given timestamp::
ttl_output.pulse(10 * us)
self.core.wait_until_mu(now_mu())
* RTIO outputs use a new architecture called Scalable Event Dispatcher (SED),
which allows building systems with large number of RTIO channels more
efficiently.
From the user perspective, collision errors become asynchronous, and non-
monotonic timestamps on any combination of channels are generally allowed
(instead of producing sequence errors).
RTIO inputs are not affected.
* The DDS channel number for the NIST CLOCK target has changed.
* The dashboard configuration files are now stored one-per-master, keyed by the
server address argument and the notify port.
* The master now has a ``--name`` argument. If given, the dashboard is labelled
with this name rather than the server address.
* ``artiq_flash`` targets Kasli by default. Use ``-t kc705`` to flash a KC705
instead.
* ``artiq_flash -m/--adapter`` has been changed to ``artiq_flash -V/--variant``.
* The ``proxy`` action of ``artiq_flash`` is determined automatically and should
not be specified manually anymore.
* ``kc705_dds`` has been renamed ``kc705``.
* The ``-H/--hw-adapter`` option of ``kc705`` has been renamed ``-V/--variant``.
* SPI masters have been switched from misoc-spi to misoc-spi2. This affects
all out-of-tree RTIO core device drivers using those buses. See the various
commits on e.g. the ``ad53xx`` driver for an example how to port from the old
to the new bus.
* The ``ad5360`` coredevice driver has been renamed to ``ad53xx`` and the API
has changed to better support Zotino.
* ``artiq.coredevice.dds`` has been renamed to ``artiq.coredevice.ad9914`` and
simplified. DDS batch mode is no longer supported. The ``core_dds`` device
is no longer necessary.
* The configuration entry ``startup_clock`` is renamed ``rtio_clock``. Switching
clocks dynamically (i.e. without device restart) is no longer supported.
* ``set_dataset(..., save=True)`` has been renamed
``set_dataset(..., archive=True)``.
* On the AD9914 DDS, when switching to ``PHASE_MODE_CONTINUOUS`` from another mode,
use the returned value of the last ``set_mu`` call as the phase offset for
``PHASE_MODE_CONTINUOUS`` to avoid a phase discontinuity. This is no longer done
automatically. If one phase glitch when entering ``PHASE_MODE_CONTINUOUS`` is not
an issue, this recommendation can be ignored.
ARTIQ-3
-------
3.7
***
No further notes.
3.6
***
No further notes.
3.5
***
No further notes.
3.4
***
No further notes.
3.3
***
No further notes.
3.2
***
* To accommodate larger runtimes, the flash layout as changed. As a result, the
contents of the flash storage will be lost when upgrading. Set the values back
(IP, MAC address, startup kernel, etc.) after the upgrade.
3.1
***
No further notes.
3.0
***
* The ``--embed`` option of applets is replaced with the environment variable
``ARTIQ_APPLET_EMBED``. The GUI sets this enviroment variable itself and the
user simply needs to remove the ``--embed`` argument.
* ``EnvExperiment``'s ``prepare`` calls ``prepare`` for all its children.
* Dynamic ``__getattr__``'s returning RPC target methods are not supported anymore.
Controller driver classes must define all their methods intended for RPC as
members.
* Datasets requested by experiments are by default archived into their HDF5
output. If this behavior is undesirable, turn it off by passing
``archive=False`` to ``get_dataset``.
* ``seconds_to_mu`` and ``mu_to_seconds`` have become methods of the core
device driver (use e.g. ``self.core.seconds_to_mu()``).
* AD9858 DDSes and NIST QC1 hardware are no longer supported.
* The DDS class names and setup options have changed, this requires an update of
the device database.
* ``int(a, width=b)`` has been removed. Use ``int32(a)`` and ``int64(a)``.
* The KC705 gateware target has been renamed ``kc705_dds``.
* ``artiq.coredevice.comm_tcp`` has been renamed ``artiq.coredevice.comm_kernel``,
and ``Comm`` has been renamed ``CommKernel``.
* The "collision" and "busy" RTIO errors are reported through the log instead of
raising exceptions.
* Results are still saved when ``analyze`` raises an exception.
* ``LinearScan`` and ``RandomScan`` have been consolidated into RangeScan.
* The Pipistrello is no longer supported. For a low-cost ARTIQ setup, use either
ARTIQ 2.x with Pipistrello, or the future ARTIQ 4.x with Kasli. Note that the
Pipistrello board has also been discontinued by the manufacturer but its design
files are freely available.
* The device database is now generated by an executable Python script. To migrate
an existing database, add ``device_db = `` at the beginning, and replace any PYON
identifiers (``true``, ``null``, ...) with their Python equivalents
(``True``, ``None`` ...).
* Controllers are now named ``aqctl_XXX`` instead of ``XXX_controller``.
* In the device database, the ``comm`` device has been folded into the ``core`` device.
Move the "host" argument into the ``core`` device, and remove the ``comm`` device.
* The core device log now contains important information about events such as
RTIO collisions. A new controller ``aqctl_corelog`` must be running to forward
those logs to the master. See the example device databases to see how to
instantiate this controller. Using ``artiq_session`` ensures that a controller
manager is running simultaneously with the master.
* Experiments scheduled with the "flush pipeline" option now proceed when there
are lower-priority experiments in the pipeline. Only experiments at the current
(or higher) priority level are flushed.
* The PDQ(2/3) driver has been removed and is now being maintained out-of tree
at https://github.com/m-labs/pdq. All SPI/USB driver layers, Mediator,
CompoundPDQ and examples/documentation has been moved.
* The master now rotates log files at midnight, rather than based on log size.
* The results keys ``start_time`` and ``run_time`` are now stored as doubles of UNIX time,
rather than ints. The file names are still based on local time.
* Packages are no longer available for 32-bit Windows.
ARTIQ-2
-------
2.5
***
No further notes.
2.4
***
No further notes.
2.3
***
* When using conda, add the conda-forge channel before installing ARTIQ.
2.2
***
No further notes.
2.1
***
No further notes.
2.0
***
No further notes.
2.0rc2
******
No further notes.
2.0rc1
******
* The format of the influxdb pattern file is simplified. The procedure to
edit patterns is also changed to modifying the pattern file and calling:
``artiq_rpctool.py ::1 3248 call scan_patterns`` (or restarting the bridge)
The patterns can be converted to the new format using this code snippet::
from artiq.protocols import pyon
patterns = pyon.load_file("influxdb_patterns.pyon")
for p in patterns:
print(p)
* The "GUI" has been renamed the "dashboard".
* When flashing NIST boards, use "-m nist_qcX" or "-m nist_clock" instead of
just "-m qcX" or "-m clock" (#290).
* Applet command lines now use templates (e.g. $python) instead of formats
(e.g. {python}).
* On Windows, GUI applications no longer open a console. For debugging
purposes, the console messages can still be displayed by running the GUI
applications this way::
python3.5 -m artiq.frontend.artiq_browser
python3.5 -m artiq.frontend.artiq_dashboard
(you may need to replace python3.5 with python)
Please always include the console output when reporting a GUI crash.
* The result folders are formatted "%Y-%m-%d/%H instead of "%Y-%m-%d/%H-%M".
(i.e. grouping by day and then by hour, instead of by day and then by minute)
* The ``parent`` keyword argument of ``HasEnvironment`` (and ``EnvExperiment``)
has been replaced. Pass the parent as first argument instead.
* During experiment examination (and a fortiori repository scan), the values of
all arguments are set to ``None`` regardless of any default values supplied.
* In the dashboard's experiment windows, partial or full argument recomputation
takes into account the repository revision field.
* By default, ``NumberValue`` and ``Scannable`` infer the scale from the unit
for common units.
* By default, artiq_client keeps the current persist flag on the master.
* GUI state files for the browser and the dashboard are stores in "standard"
locations for each operating system. Those are
``~/.config/artiq/2/artiq_*.pyon`` on Linux and
``C:\Users\<username>\AppData\Local\m-labs\artiq\2\artiq_*.pyon`` on
Windows 7.
* The position of the time cursor is kept across experiments and RTIO resets
are manual and explicit (inter-experiment seamless handover).
* All integers manipulated by kernels are numpy integers (numpy.int32,
numpy.int64). If you pass an integer as a RPC argument, the target function
receives a numpy type.
ARTIQ-1
-------
1.3 1.3
*** ---
No further notes. No further notes.
1.2 1.2
*** ---
No further notes. No further notes.
1.1 1.1
*** ---
* TCA6424A.set converts the "outputs" value to little-endian before programming * TCA6424A.set converts the "outputs" value to little-endian before programming
it into the registers. it into the registers.
1.0 1.0
*** ---
No further notes. No further notes.
1.0rc4 1.0rc4
****** ------
* setattr_argument and setattr_device add their key to kernel_invariants. * setattr_argument and setattr_device add their key to kernel_invariants.
1.0rc3 1.0rc3
****** ------
* The HDF5 format has changed. * The HDF5 format has changed.
@ -653,7 +49,7 @@ No further notes.
1.0rc2 1.0rc2
****** ------
* The CPU speed in the pipistrello gateware has been reduced from 83 1/3 MHz to * The CPU speed in the pipistrello gateware has been reduced from 83 1/3 MHz to
75 MHz. This will reduce the achievable sustained pulse rate and latency 75 MHz. This will reduce the achievable sustained pulse rate and latency
@ -663,7 +59,7 @@ No further notes.
1.0rc1 1.0rc1
****** ------
* Experiments (your code) should use ``from artiq.experiment import *`` * Experiments (your code) should use ``from artiq.experiment import *``
(and not ``from artiq import *`` as previously) (and not ``from artiq import *`` as previously)

21
RELEASING.rst Normal file
View File

@ -0,0 +1,21 @@
Release process
===============
Maintain ``RELEASE_NOTES.rst`` with a list of new features and API changes in each major release.
Major releases
--------------
1. Create branch release-X from master.
2. Ensure that release versions of all packages required are available under the ``main`` label in conda. Ensure that new packages in ``main`` do not break older ARTIQ releases.
3. Remove any unfinished features.
4. Test and fix any problems found.
5. If you have willing testers for release candidates, tag X.0rc0, have it build, and point testers there. Iterate over the previous points with new release candidates if necessary.
6. Tag X.0, have it build, and copy its packages to ``main`` channel.
Minor (bugfix) releases
-----------------------
1. Backport bugfixes from the master branch or fix bugs specific to old releases into the currently maintained release-X branch(es).
2. When significant bugs have been fixed, tag X.Y+1.
3. To help dealing with regressions, no new features or refactorings should be implemented in release-X branches. Those happen in the master branch, and then a new release-X+1 branch is created.

View File

@ -1,6 +1,6 @@
from ._version import get_version from ._version import get_versions
__version__ = get_version() __version__ = get_versions()['version']
del get_version del get_versions
import os import os
__artiq_dir__ = os.path.dirname(os.path.abspath(__file__)) __artiq_dir__ = os.path.dirname(os.path.abspath(__file__))

View File

@ -1,7 +1,486 @@
# This file helps to compute a version number in source trees obtained from
# git-archive tarball (such as those provided by githubs download-from-tag
# feature). Distribution tarballs (built by setup.py sdist) and build
# directories (produced by setup.py build) will contain a much shorter file
# that just contains the computed version number.
# This file is released into the public domain. Generated by
# versioneer-0.15+dev (https://github.com/warner/python-versioneer)
"""Git implementation of _version.py."""
import errno
import os import os
import re
import subprocess
import sys
def get_rev():
return os.getenv("VERSIONEER_REV", default="unknown")
def get_version(): def get_keywords():
return os.getenv("VERSIONEER_OVERRIDE", default="9.0+unknown.beta") """Get the keywords needed to look up the version information."""
# these strings will be replaced by git during git-archive.
# setup.py/versioneer.py will grep for the variable names, so they must
# each be defined on a line of their own. _version.py will just call
# get_keywords().
git_refnames = "$Format:%d$"
git_full = "$Format:%H$"
keywords = {"refnames": git_refnames, "full": git_full}
return keywords
class VersioneerConfig:
"""Container for Versioneer configuration parameters."""
def get_config():
"""Create, populate and return the VersioneerConfig() object."""
# these strings are filled in when 'setup.py versioneer' creates
# _version.py
cfg = VersioneerConfig()
cfg.VCS = "git"
cfg.style = "pep440"
cfg.tag_prefix = ""
cfg.parentdir_prefix = "artiq-"
cfg.versionfile_source = "artiq/_version.py"
cfg.verbose = False
return cfg
class NotThisMethod(Exception):
"""Exception raised if a method is not valid for the current scenario."""
LONG_VERSION_PY = {}
HANDLERS = {}
def register_vcs_handler(vcs, method): # decorator
"""Decorator to mark a method as the handler for a particular VCS."""
def decorate(f):
"""Store f in HANDLERS[vcs][method]."""
if vcs not in HANDLERS:
HANDLERS[vcs] = {}
HANDLERS[vcs][method] = f
return f
return decorate
def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False):
"""Call the given command(s)."""
assert isinstance(commands, list)
p = None
for c in commands:
try:
dispcmd = str([c] + args)
# remember shell=False, so use git.cmd on windows, not just git
p = subprocess.Popen([c] + args, cwd=cwd, stdout=subprocess.PIPE,
stderr=(subprocess.PIPE if hide_stderr
else None))
break
except EnvironmentError:
e = sys.exc_info()[1]
if e.errno == errno.ENOENT:
continue
if verbose:
print("unable to run %s" % dispcmd)
print(e)
return None
else:
if verbose:
print("unable to find command, tried %s" % (commands,))
return None
stdout = p.communicate()[0].strip()
if sys.version_info[0] >= 3:
stdout = stdout.decode()
if p.returncode != 0:
if verbose:
print("unable to run %s (error)" % dispcmd)
return None
return stdout
def versions_from_parentdir(parentdir_prefix, root, verbose):
"""Try to determine the version from the parent directory name.
Source tarballs conventionally unpack into a directory that includes
both the project name and a version string.
"""
dirname = os.path.basename(root)
if not dirname.startswith(parentdir_prefix):
if verbose:
print("guessing rootdir is '%s', but '%s' doesn't start with "
"prefix '%s'" % (root, dirname, parentdir_prefix))
raise NotThisMethod("rootdir doesn't start with parentdir_prefix")
return {"version": dirname[len(parentdir_prefix):],
"full-revisionid": None,
"dirty": False, "error": None}
@register_vcs_handler("git", "get_keywords")
def git_get_keywords(versionfile_abs):
"""Extract version information from the given file."""
# the code embedded in _version.py can just fetch the value of these
# keywords. When used from setup.py, we don't want to import _version.py,
# so we do it with a regexp instead. This function is not used from
# _version.py.
keywords = {}
try:
f = open(versionfile_abs, "r")
for line in f.readlines():
if line.strip().startswith("git_refnames ="):
mo = re.search(r'=\s*"(.*)"', line)
if mo:
keywords["refnames"] = mo.group(1)
if line.strip().startswith("git_full ="):
mo = re.search(r'=\s*"(.*)"', line)
if mo:
keywords["full"] = mo.group(1)
f.close()
except EnvironmentError:
pass
return keywords
@register_vcs_handler("git", "keywords")
def git_versions_from_keywords(keywords, tag_prefix, verbose):
"""Get version information from git keywords."""
if not keywords:
raise NotThisMethod("no keywords at all, weird")
refnames = keywords["refnames"].strip()
if refnames.startswith("$Format"):
if verbose:
print("keywords are unexpanded, not using")
raise NotThisMethod("unexpanded keywords, not a git-archive tarball")
refs = set([r.strip() for r in refnames.strip("()").split(",")])
# starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of
# just "foo-1.0". If we see a "tag: " prefix, prefer those.
TAG = "tag: "
tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)])
if not tags:
# Either we're using git < 1.8.3, or there really are no tags. We use
# a heuristic: assume all version tags have a digit. The old git %d
# expansion behaves like git log --decorate=short and strips out the
# refs/heads/ and refs/tags/ prefixes that would let us distinguish
# between branches and tags. By ignoring refnames without digits, we
# filter out many common branch names like "release" and
# "stabilization", as well as "HEAD" and "master".
tags = set([r for r in refs if re.search(r'\d', r)])
if verbose:
print("discarding '%s', no digits" % ",".join(refs-tags))
if verbose:
print("likely tags: %s" % ",".join(sorted(tags)))
for ref in sorted(tags):
# sorting will prefer e.g. "2.0" over "2.0rc1"
if ref.startswith(tag_prefix):
r = ref[len(tag_prefix):]
if verbose:
print("picking %s" % r)
return {"version": r,
"full-revisionid": keywords["full"].strip(),
"dirty": False, "error": None
}
# no suitable tags, so version is "0+unknown", but full hex is still there
if verbose:
print("no suitable tags, using unknown + full revision id")
return {"version": "0+unknown",
"full-revisionid": keywords["full"].strip(),
"dirty": False, "error": "no suitable tags"}
@register_vcs_handler("git", "pieces_from_vcs")
def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command):
"""Get version from 'git describe' in the root of the source tree.
This only gets called if the git-archive 'subst' keywords were *not*
expanded, and _version.py hasn't already been rewritten with a short
version string, meaning we're inside a checked out source tree.
"""
if not os.path.exists(os.path.join(root, ".git")):
if verbose:
print("no .git in %s" % root)
raise NotThisMethod("no .git directory")
GITS = ["git"]
if sys.platform == "win32":
GITS = ["git.cmd", "git.exe"]
# if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty]
# if there isn't one, this yields HEX[-dirty] (no NUM)
describe_out = run_command(GITS, ["describe", "--tags", "--dirty",
"--always", "--long",
"--match", "%s*" % tag_prefix],
cwd=root)
# --long was added in git-1.5.5
if describe_out is None:
raise NotThisMethod("'git describe' failed")
describe_out = describe_out.strip()
full_out = run_command(GITS, ["rev-parse", "HEAD"], cwd=root)
if full_out is None:
raise NotThisMethod("'git rev-parse' failed")
full_out = full_out.strip()
pieces = {}
pieces["long"] = full_out
pieces["short"] = full_out[:7] # maybe improved later
pieces["error"] = None
# parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty]
# TAG might have hyphens.
git_describe = describe_out
# look for -dirty suffix
dirty = git_describe.endswith("-dirty")
pieces["dirty"] = dirty
if dirty:
git_describe = git_describe[:git_describe.rindex("-dirty")]
# now we have TAG-NUM-gHEX or HEX
if "-" in git_describe:
# TAG-NUM-gHEX
mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe)
if not mo:
# unparseable. Maybe git-describe is misbehaving?
pieces["error"] = ("unable to parse git-describe output: '%s'"
% describe_out)
return pieces
# tag
full_tag = mo.group(1)
if not full_tag.startswith(tag_prefix):
if verbose:
fmt = "tag '%s' doesn't start with prefix '%s'"
print(fmt % (full_tag, tag_prefix))
pieces["error"] = ("tag '%s' doesn't start with prefix '%s'"
% (full_tag, tag_prefix))
return pieces
pieces["closest-tag"] = full_tag[len(tag_prefix):]
# distance: number of commits since tag
pieces["distance"] = int(mo.group(2))
# commit: short hex revision ID
pieces["short"] = mo.group(3)
else:
# HEX: no tags
pieces["closest-tag"] = None
count_out = run_command(GITS, ["rev-list", "HEAD", "--count"],
cwd=root)
pieces["distance"] = int(count_out) # total number of commits
return pieces
def plus_or_dot(pieces):
"""Return a + if we don't already have one, else return a ."""
if "+" in pieces.get("closest-tag", ""):
return "."
return "+"
def render_pep440(pieces):
"""Build up version string, with post-release "local version identifier".
Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you
get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty
Exceptions:
1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
rendered += plus_or_dot(pieces)
rendered += "%d.g%s" % (pieces["distance"], pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
else:
# exception #1
rendered = "0+untagged.%d.g%s" % (pieces["distance"],
pieces["short"])
if pieces["dirty"]:
rendered += ".dirty"
return rendered
def render_pep440_pre(pieces):
"""TAG[.post.devDISTANCE] -- No -dirty.
Exceptions:
1: no tags. 0.post.devDISTANCE
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"]:
rendered += ".post.dev%d" % pieces["distance"]
else:
# exception #1
rendered = "0.post.dev%d" % pieces["distance"]
return rendered
def render_pep440_post(pieces):
"""TAG[.postDISTANCE[.dev0]+gHEX] .
The ".dev0" means dirty. Note that .dev0 sorts backwards
(a dirty tree will appear "older" than the corresponding clean one),
but you shouldn't be releasing software with -dirty anyways.
Exceptions:
1: no tags. 0.postDISTANCE[.dev0]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
rendered += ".post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
rendered += plus_or_dot(pieces)
rendered += "g%s" % pieces["short"]
else:
# exception #1
rendered = "0.post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
rendered += "+g%s" % pieces["short"]
return rendered
def render_pep440_old(pieces):
"""TAG[.postDISTANCE[.dev0]] .
The ".dev0" means dirty.
Eexceptions:
1: no tags. 0.postDISTANCE[.dev0]
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"] or pieces["dirty"]:
rendered += ".post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
else:
# exception #1
rendered = "0.post%d" % pieces["distance"]
if pieces["dirty"]:
rendered += ".dev0"
return rendered
def render_git_describe(pieces):
"""TAG[-DISTANCE-gHEX][-dirty].
Like 'git describe --tags --dirty --always'.
Exceptions:
1: no tags. HEX[-dirty] (note: no 'g' prefix)
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
if pieces["distance"]:
rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
else:
# exception #1
rendered = pieces["short"]
if pieces["dirty"]:
rendered += "-dirty"
return rendered
def render_git_describe_long(pieces):
"""TAG-DISTANCE-gHEX[-dirty].
Like 'git describe --tags --dirty --always -long'.
The distance/hash is unconditional.
Exceptions:
1: no tags. HEX[-dirty] (note: no 'g' prefix)
"""
if pieces["closest-tag"]:
rendered = pieces["closest-tag"]
rendered += "-%d-g%s" % (pieces["distance"], pieces["short"])
else:
# exception #1
rendered = pieces["short"]
if pieces["dirty"]:
rendered += "-dirty"
return rendered
def render(pieces, style):
"""Render the given version pieces into the requested style."""
if pieces["error"]:
return {"version": "unknown",
"full-revisionid": pieces.get("long"),
"dirty": None,
"error": pieces["error"]}
if not style or style == "default":
style = "pep440" # the default
if style == "pep440":
rendered = render_pep440(pieces)
elif style == "pep440-pre":
rendered = render_pep440_pre(pieces)
elif style == "pep440-post":
rendered = render_pep440_post(pieces)
elif style == "pep440-old":
rendered = render_pep440_old(pieces)
elif style == "git-describe":
rendered = render_git_describe(pieces)
elif style == "git-describe-long":
rendered = render_git_describe_long(pieces)
else:
raise ValueError("unknown style '%s'" % style)
return {"version": rendered, "full-revisionid": pieces["long"],
"dirty": pieces["dirty"], "error": None}
def get_versions():
"""Get version information or return default if unable to do so."""
# I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have
# __file__, we can work backwards from there to the root. Some
# py2exe/bbfreeze/non-CPython implementations don't do __file__, in which
# case we can only use expanded keywords.
cfg = get_config()
verbose = cfg.verbose
try:
return git_versions_from_keywords(get_keywords(), cfg.tag_prefix,
verbose)
except NotThisMethod:
pass
try:
root = os.path.realpath(__file__)
# versionfile_source is the relative path from the top of the source
# tree (where the .git directory might live) to this file. Invert
# this to find the root from __file__.
for i in cfg.versionfile_source.split('/'):
root = os.path.dirname(root)
except NameError:
return {"version": "0+unknown", "full-revisionid": None,
"dirty": None,
"error": "unable to find root of source tree"}
try:
pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose)
return render(pieces, cfg.style)
except NotThisMethod:
pass
try:
if cfg.parentdir_prefix:
return versions_from_parentdir(cfg.parentdir_prefix, root, verbose)
except NotThisMethod:
pass
return {"version": "0+unknown", "full-revisionid": None,
"dirty": None,
"error": "unable to compute version"}

View File

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

View File

@ -1,28 +0,0 @@
#!/usr/bin/env python3
import PyQt6 # make sure pyqtgraph imports Qt6
import pyqtgraph
from artiq.applets.simple import SimpleApplet
class Image(pyqtgraph.ImageView):
def __init__(self, args, req):
pyqtgraph.ImageView.__init__(self)
self.args = args
def data_changed(self, value, metadata, persist, mods):
try:
img = value[self.args.img]
except KeyError:
return
self.setImage(img)
def main():
applet = SimpleApplet(Image)
applet.add_dataset("img", "image data (2D numpy array)")
applet.run()
if __name__ == "__main__":
main()

View File

@ -1,51 +1,36 @@
#!/usr/bin/env python3 #!/usr/bin/env python3.5
import PyQt6 # make sure pyqtgraph imports Qt6 import PyQt5 # make sure pyqtgraph imports Qt5
from PyQt6.QtCore import QTimer
import pyqtgraph import pyqtgraph
from artiq.applets.simple import TitleApplet from artiq.applets.simple import SimpleApplet
class HistogramPlot(pyqtgraph.PlotWidget): class HistogramPlot(pyqtgraph.PlotWidget):
def __init__(self, args, req): def __init__(self, args):
pyqtgraph.PlotWidget.__init__(self) pyqtgraph.PlotWidget.__init__(self)
self.args = args self.args = args
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.length_warning)
def data_changed(self, value, metadata, persist, mods, title): def data_changed(self, data, mods):
try: try:
y = value[self.args.y] y = data[self.args.y][1]
if self.args.x is None: if self.args.x is None:
x = None x = None
else: else:
x = value[self.args.x] x = data[self.args.x][1]
except KeyError: except KeyError:
return return
if x is None: if x is None:
x = list(range(len(y)+1)) x = list(range(len(y)+1))
if len(y) and len(x) == len(y) + 1: if len(y) and len(x) == len(y) + 1:
self.timer.stop()
self.clear() self.clear()
self.plot(x, y, stepMode=True, fillLevel=0, self.plot(x, y, stepMode=True, fillLevel=0,
brush=(0, 0, 255, 150)) brush=(0, 0, 255, 150))
self.setTitle(title)
else:
if not self.timer.isActive():
self.timer.start(1000)
def length_warning(self):
self.clear()
text = "⚠️ dataset lengths mismatch:\n"\
"There should be one more bin boundaries than there are Y values"
self.addItem(pyqtgraph.TextItem(text))
def main(): def main():
applet = TitleApplet(HistogramPlot) applet = SimpleApplet(HistogramPlot)
applet.add_dataset("y", "Y values") applet.add_dataset("y", "Y values")
applet.add_dataset("x", "Bin boundaries", required=False) applet.add_dataset("x", "Bin boundaries", required=False)
applet.run() applet.run()

View File

@ -1,63 +1,43 @@
#!/usr/bin/env python3 #!/usr/bin/env python3.5
import numpy as np import numpy as np
import PyQt6 # make sure pyqtgraph imports Qt6 import PyQt5 # make sure pyqtgraph imports Qt5
from PyQt6.QtCore import QTimer
import pyqtgraph import pyqtgraph
from artiq.applets.simple import TitleApplet from artiq.applets.simple import SimpleApplet
class XYPlot(pyqtgraph.PlotWidget): class XYPlot(pyqtgraph.PlotWidget):
def __init__(self, args, req): def __init__(self, args):
pyqtgraph.PlotWidget.__init__(self) pyqtgraph.PlotWidget.__init__(self)
self.args = args self.args = args
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.length_warning)
self.mismatch = {'X values': False,
'Error bars': False,
'Fit values': False}
def data_changed(self, value, metadata, persist, mods, title): def data_changed(self, data, mods):
try: try:
y = value[self.args.y] y = data[self.args.y][1]
except KeyError: except KeyError:
return return
x = value.get(self.args.x) x = data.get(self.args.x, (False, None))[1]
if x is None: if x is None:
x = np.arange(len(y)) x = np.arange(len(y))
error = value.get(self.args.error) error = data.get(self.args.error, (False, None))[1]
fit = value.get(self.args.fit) fit = data.get(self.args.fit, (False, None))[1]
if not len(y) or len(y) != len(x): if not len(y) or len(y) != len(x):
self.mismatch['X values'] = True return
else:
self.mismatch['X values'] = False
if error is not None and hasattr(error, "__len__"): if error is not None and hasattr(error, "__len__"):
if not len(error): if not len(error):
error = None error = None
elif len(error) != len(y): elif len(error) != len(y):
self.mismatch['Error bars'] = True return
else:
self.mismatch['Error bars'] = False
if fit is not None: if fit is not None:
if not len(fit): if not len(fit):
fit = None fit = None
elif len(fit) != len(y): elif len(fit) != len(y):
self.mismatch['Fit values'] = True return
else:
self.mismatch['Fit values'] = False
if not any(self.mismatch.values()):
self.timer.stop()
else:
if not self.timer.isActive():
self.timer.start(1000)
return
self.clear() self.clear()
self.plot(x, y, pen=None, symbol="x") self.plot(x, y, pen=None, symbol="x")
self.setTitle(title)
if error is not None: if error is not None:
# See https://github.com/pyqtgraph/pyqtgraph/issues/211 # See https://github.com/pyqtgraph/pyqtgraph/issues/211
if hasattr(error, "__len__") and not isinstance(error, np.ndarray): if hasattr(error, "__len__") and not isinstance(error, np.ndarray):
@ -69,16 +49,9 @@ class XYPlot(pyqtgraph.PlotWidget):
xi = np.argsort(x) xi = np.argsort(x)
self.plot(x[xi], fit[xi]) self.plot(x[xi], fit[xi])
def length_warning(self):
self.clear()
text = "⚠️ dataset lengths mismatch:\n"
errors = ', '.join([k for k, v in self.mismatch.items() if v])
text = ' '.join([errors, "should have the same length as Y values"])
self.addItem(pyqtgraph.TextItem(text))
def main(): def main():
applet = TitleApplet(XYPlot) applet = SimpleApplet(XYPlot)
applet.add_dataset("y", "Y values") applet.add_dataset("y", "Y values")
applet.add_dataset("x", "X values", required=False) applet.add_dataset("x", "X values", required=False)
applet.add_dataset("error", "Error bars for each X value", required=False) applet.add_dataset("error", "Error bars for each X value", required=False)

View File

@ -1,8 +1,7 @@
#!/usr/bin/env python3 #!/usr/bin/env python3.5
import numpy as np import numpy as np
from PyQt6 import QtWidgets from PyQt5 import QtWidgets
from PyQt6.QtCore import QTimer
import pyqtgraph import pyqtgraph
from artiq.applets.simple import SimpleApplet from artiq.applets.simple import SimpleApplet
@ -22,7 +21,7 @@ def _compute_ys(histogram_bins, histograms_counts):
# pyqtgraph.GraphicsWindow fails to behave like a regular Qt widget # pyqtgraph.GraphicsWindow fails to behave like a regular Qt widget
# and breaks embedding. Do not use as top widget. # and breaks embedding. Do not use as top widget.
class XYHistPlot(QtWidgets.QSplitter): class XYHistPlot(QtWidgets.QSplitter):
def __init__(self, args, req): def __init__(self, args):
QtWidgets.QSplitter.__init__(self) QtWidgets.QSplitter.__init__(self)
self.resize(1000, 600) self.resize(1000, 600)
self.setWindowTitle("XY/Histogram") self.setWindowTitle("XY/Histogram")
@ -38,10 +37,6 @@ class XYHistPlot(QtWidgets.QSplitter):
self.hist_plot_data = None self.hist_plot_data = None
self.args = args self.args = args
self.timer = QTimer()
self.timer.setSingleShot(True)
self.timer.timeout.connect(self.length_warning)
self.mismatch = {'bins': False, 'xs': False}
def _set_full_data(self, xs, histogram_bins, histograms_counts): def _set_full_data(self, xs, histogram_bins, histograms_counts):
self.xy_plot.clear() self.xy_plot.clear()
@ -64,9 +59,9 @@ class XYHistPlot(QtWidgets.QSplitter):
point.histogram_index = index point.histogram_index = index
point.histogram_counts = counts point.histogram_counts = counts
text = "click on a data point at the left\n"\ self.hist_plot_data = self.hist_plot.plot(
"to see the corresponding histogram" stepMode=True, fillLevel=0,
self.hist_plot.addItem(pyqtgraph.TextItem(text)) brush=(0, 0, 255, 150))
def _set_partial_data(self, xs, histograms_counts): def _set_partial_data(self, xs, histograms_counts):
ys = _compute_ys(self.histogram_bins, histograms_counts) ys = _compute_ys(self.histogram_bins, histograms_counts)
@ -92,17 +87,8 @@ class XYHistPlot(QtWidgets.QSplitter):
else: else:
self.arrow.setPos(position) self.arrow.setPos(position)
self.selected_index = spot_item.histogram_index self.selected_index = spot_item.histogram_index
self.hist_plot_data.setData(x=self.histogram_bins,
if self.hist_plot_data is None: y=spot_item.histogram_counts)
self.hist_plot.clear()
self.hist_plot_data = self.hist_plot.plot(
x=self.histogram_bins,
y=spot_item.histogram_counts,
stepMode=True, fillLevel=0,
brush=(0, 0, 255, 150))
else:
self.hist_plot_data.setData(x=self.histogram_bins,
y=spot_item.histogram_counts)
def _can_use_partial(self, mods): def _can_use_partial(self, mods):
if self.hist_plot_data is None: if self.hist_plot_data is None:
@ -124,48 +110,18 @@ class XYHistPlot(QtWidgets.QSplitter):
return False return False
return True return True
def data_changed(self, value, metadata, persist, mods): def data_changed(self, data, mods):
try: try:
xs = value[self.args.xs] xs = data[self.args.xs][1]
histogram_bins = value[self.args.histogram_bins] histogram_bins = data[self.args.histogram_bins][1]
histograms_counts = value[self.args.histograms_counts] histograms_counts = data[self.args.histograms_counts][1]
except KeyError: except KeyError:
return return
if len(xs) != histograms_counts.shape[0]:
self.mismatch['xs'] = True
else:
self.mismatch['xs'] = False
if histograms_counts.shape[1] != len(histogram_bins) - 1:
self.mismatch['bins'] = True
else:
self.mismatch['bins'] = False
if any(self.mismatch.values()):
if not self.timer.isActive():
self.timer.start(1000)
return
else:
self.timer.stop()
if self._can_use_partial(mods): if self._can_use_partial(mods):
self._set_partial_data(xs, histograms_counts) self._set_partial_data(xs, histograms_counts)
else: else:
self._set_full_data(xs, histogram_bins, histograms_counts) self._set_full_data(xs, histogram_bins, histograms_counts)
def length_warning(self):
self.xy_plot.clear()
self.hist_plot.clear()
text = "⚠️ dataset lengths mismatch:\n\n"
if self.mismatch['bins']:
text = ''.join([text,
"bin boundaries should have the same length\n"
"as the first dimension of histogram counts."])
if self.mismatch['bins'] and self.mismatch['xs']:
text = ''.join([text, '\n\n'])
if self.mismatch['xs']:
text = ''.join([text,
"point abscissas should have the same length\n"
"as the second dimension of histogram counts."])
self.xy_plot.addItem(pyqtgraph.TextItem(text))
def main(): def main():
applet = SimpleApplet(XYHistPlot) applet = SimpleApplet(XYHistPlot)

View File

@ -1,34 +0,0 @@
#!/usr/bin/env python3
from PyQt6 import QtWidgets
from artiq.applets.simple import SimpleApplet
class ProgressWidget(QtWidgets.QProgressBar):
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, value, metadata, persist, mods):
try:
val = round(value[self.dataset_value])
except (KeyError, ValueError, TypeError):
val = 0
self.setValue(val)
def main():
applet = SimpleApplet(ProgressWidget)
applet.add_dataset("value", "counter")
applet.argparser.add_argument("--min", type=int, default=0,
help="minimum (left) value of the bar")
applet.argparser.add_argument("--max", type=int, default=100,
help="maximum (right) value of the bar")
applet.run()
if __name__ == "__main__":
main()

View File

@ -2,118 +2,17 @@ import logging
import argparse import argparse
import asyncio import asyncio
import os import os
import string
from qasync import QEventLoop, QtWidgets, QtCore from quamash import QEventLoop, QtWidgets, QtCore
from sipyco.sync_struct import Subscriber, process_mod from artiq.protocols.sync_struct import Subscriber, process_mod
from sipyco.pc_rpc import AsyncioClient as RPCClient from artiq.protocols import pyon
from sipyco import pyon from artiq.protocols.pipe_ipc import AsyncioChildComm
from sipyco.pipe_ipc import AsyncioChildComm
from artiq.language.scan import ScanObject
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class _AppletRequestInterface:
def __init__(self):
raise NotImplementedError
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
"""
Set a dataset.
See documentation of :meth:`~artiq.language.environment.HasEnvironment.set_dataset`.
"""
raise NotImplementedError
def mutate_dataset(self, key, index, value):
"""
Mutate a dataset.
See documentation of :meth:`~artiq.language.environment.HasEnvironment.mutate_dataset`.
"""
raise NotImplementedError
def append_to_dataset(self, key, value):
"""
Append to a dataset.
See documentation of :meth:`~artiq.language.environment.HasEnvironment.append_to_dataset`.
"""
raise NotImplementedError
def set_argument_value(self, expurl, key, value):
"""
Temporarily set the value of an argument in a experiment in the dashboard.
The value resets to default value when recomputing the argument.
:param expurl: Experiment URL identifying the experiment in the dashboard. Example: 'repo:ArgumentsDemo'.
:param key: Name of the argument in the experiment.
:param value: Object representing the new temporary value of the argument. For :class:`~artiq.language.scan.Scannable` arguments,
this parameter should be a :class:`~artiq.language.scan.ScanObject`. The type of the :class:`~artiq.language.scan.ScanObject`
will be set as the selected type when this function is called.
"""
raise NotImplementedError
class AppletRequestIPC(_AppletRequestInterface):
def __init__(self, ipc):
self.ipc = ipc
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
metadata = {}
if unit is not None:
metadata["unit"] = unit
if scale is not None:
metadata["scale"] = scale
if precision is not None:
metadata["precision"] = precision
self.ipc.set_dataset(key, value, metadata, persist)
def mutate_dataset(self, key, index, value):
mod = {"action": "setitem", "path": [key, 1], "key": index, "value": value}
self.ipc.update_dataset(mod)
def append_to_dataset(self, key, value):
mod = {"action": "append", "path": [key, 1], "x": value}
self.ipc.update_dataset(mod)
def set_argument_value(self, expurl, key, value):
if isinstance(value, ScanObject):
value = value.describe()
self.ipc.set_argument_value(expurl, key, value)
class AppletRequestRPC(_AppletRequestInterface):
def __init__(self, loop, dataset_ctl):
self.loop = loop
self.dataset_ctl = dataset_ctl
self.background_tasks = set()
def _background(self, coro, *args, **kwargs):
task = self.loop.create_task(coro(*args, **kwargs))
self.background_tasks.add(task)
task.add_done_callback(self.background_tasks.discard)
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
metadata = {}
if unit is not None:
metadata["unit"] = unit
if scale is not None:
metadata["scale"] = scale
if precision is not None:
metadata["precision"] = precision
self._background(self.dataset_ctl.set, key, value, metadata=metadata, persist=persist)
def mutate_dataset(self, key, index, value):
mod = {"action": "setitem", "path": [key, 1], "key": index, "value": value}
self._background(self.dataset_ctl.update, mod)
def append_to_dataset(self, key, value):
mod = {"action": "append", "path": [key, 1], "x": value}
self._background(self.dataset_ctl.update, mod)
class AppletIPCClient(AsyncioChildComm): class AppletIPCClient(AsyncioChildComm):
def set_close_cb(self, close_cb): def set_close_cb(self, close_cb):
self.close_cb = close_cb self.close_cb = close_cb
@ -137,8 +36,9 @@ class AppletIPCClient(AsyncioChildComm):
logger.error("unexpected action reply to embed request: %s", logger.error("unexpected action reply to embed request: %s",
reply["action"]) reply["action"])
self.close_cb() self.close_cb()
else:
return reply["size_w"], reply["size_h"] def fix_initial_size(self):
self.write_pyon({"action": "fix_initial_size"})
async def listen(self): async def listen(self):
data = None data = None
@ -163,30 +63,12 @@ class AppletIPCClient(AsyncioChildComm):
exc_info=True) exc_info=True)
self.close_cb() self.close_cb()
def subscribe(self, datasets, init_cb, mod_cb, dataset_prefixes=[], *, loop): def subscribe(self, datasets, init_cb, mod_cb):
self.write_pyon({"action": "subscribe", self.write_pyon({"action": "subscribe",
"datasets": datasets, "datasets": datasets})
"dataset_prefixes": dataset_prefixes})
self.init_cb = init_cb self.init_cb = init_cb
self.mod_cb = mod_cb self.mod_cb = mod_cb
self.listen_task = loop.create_task(self.listen()) asyncio.ensure_future(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: class SimpleApplet:
@ -208,11 +90,12 @@ class SimpleApplet:
"for dataset notifications " "for dataset notifications "
"(ignored in embedded mode)") "(ignored in embedded mode)")
group.add_argument( group.add_argument(
"--port-notify", default=3250, type=int, "--port", default=3250, type=int,
help="TCP port to connect to for notifications (ignored in embedded mode)") help="TCP port to connect to")
group.add_argument(
"--port-control", default=3251, type=int, self.argparser.add_argument(
help="TCP port to connect to for control (ignored in embedded mode)") "--embed", default=None, help="embed into GUI",
metavar="IPC_ADDRESS")
self._arggroup_datasets = self.argparser.add_argument_group("datasets") self._arggroup_datasets = self.argparser.add_argument_group("datasets")
@ -230,49 +113,32 @@ class SimpleApplet:
def args_init(self): def args_init(self):
self.args = self.argparser.parse_args() self.args = self.argparser.parse_args()
self.embed = os.getenv("ARTIQ_APPLET_EMBED")
self.datasets = {getattr(self.args, arg.replace("-", "_")) self.datasets = {getattr(self.args, arg.replace("-", "_"))
for arg in self.dataset_args} for arg in self.dataset_args}
# Optional prefixes (dataset sub-trees) to match subscriptions against;
# currently only used by out-of-tree subclasses (ndscan).
self.dataset_prefixes = []
def qasync_init(self): def quamash_init(self):
app = QtWidgets.QApplication([]) app = QtWidgets.QApplication([])
self.loop = QEventLoop(app) self.loop = QEventLoop(app)
asyncio.set_event_loop(self.loop) asyncio.set_event_loop(self.loop)
def ipc_init(self): def ipc_init(self):
if self.embed is not None: if self.args.embed is not None:
self.ipc = AppletIPCClient(self.embed) self.ipc = AppletIPCClient(self.args.embed)
self.loop.run_until_complete(self.ipc.connect()) self.loop.run_until_complete(self.ipc.connect())
def ipc_close(self): def ipc_close(self):
if self.embed is not None: if self.args.embed is not None:
self.ipc.close() 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): def create_main_widget(self):
self.main_widget = self.main_widget_class(self.args, self.req) self.main_widget = self.main_widget_class(self.args)
if self.embed is not None: if self.args.embed is not None:
self.ipc.set_close_cb(self.main_widget.close) self.ipc.set_close_cb(self.main_widget.close)
if os.name == "nt": if os.name == "nt":
# HACK: if the window has a frame, there will be garbage # HACK: if the window has a frame, there will be garbage
# (usually white) displayed at its right and bottom borders # (usually white) displayed at its right and bottom borders
# after it is embedded. # after it is embedded.
self.main_widget.setWindowFlags(QtCore.Qt.WindowType.FramelessWindowHint) self.main_widget.setWindowFlags(QtCore.Qt.FramelessWindowHint)
self.main_widget.show() self.main_widget.show()
win_id = int(self.main_widget.winId()) win_id = int(self.main_widget.winId())
self.loop.run_until_complete(self.ipc.embed(win_id)) self.loop.run_until_complete(self.ipc.embed(win_id))
@ -285,13 +151,12 @@ class SimpleApplet:
# 2. applet creates native window without showing it, and # 2. applet creates native window without showing it, and
# gets its ID # gets its ID
# 3. applet sends the ID to host, host embeds the widget # 3. applet sends the ID to host, host embeds the widget
# and returns embedded size # 4. applet shows the widget
# 4. applet is resized to that given size # 5. parent resizes the widget
# 5. applet shows the widget
win_id = int(self.main_widget.winId()) win_id = int(self.main_widget.winId())
size_w, size_h = self.loop.run_until_complete(self.ipc.embed(win_id)) self.loop.run_until_complete(self.ipc.embed(win_id))
self.main_widget.resize(size_w, size_h)
self.main_widget.show() self.main_widget.show()
self.ipc.fix_initial_size()
else: else:
self.main_widget.show() self.main_widget.show()
@ -299,38 +164,22 @@ class SimpleApplet:
self.data = data self.data = data
return data return data
def is_dataset_subscribed(self, key):
if key in self.datasets:
return True
for prefix in self.dataset_prefixes:
if key.startswith(prefix):
return True
return False
def filter_mod(self, mod): def filter_mod(self, mod):
if self.embed is not None: if self.args.embed is not None:
# the parent already filters for us # the parent already filters for us
return True return True
if mod["action"] == "init": if mod["action"] == "init":
return True return True
if mod["path"]: if mod["path"]:
return self.is_dataset_subscribed(mod["path"][0]) return mod["path"][0] in self.datasets
elif mod["action"] in {"setitem", "delitem"}: elif mod["action"] in {"setitem", "delitem"}:
return self.is_dataset_subscribed(mod["key"]) return mod["key"] in self.datasets
else: else:
return False return False
def emit_data_changed(self, 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): def flush_mod_buffer(self):
self.emit_data_changed(self.data, self.mod_buffer) self.main_widget.data_changed(self.data, self.mod_buffer)
del self.mod_buffer del self.mod_buffer
def sub_mod(self, mod): def sub_mod(self, mod):
@ -342,83 +191,37 @@ class SimpleApplet:
self.mod_buffer.append(mod) self.mod_buffer.append(mod)
else: else:
self.mod_buffer = [mod] self.mod_buffer = [mod]
self.loop.call_later(self.args.update_delay, asyncio.get_event_loop().call_later(self.args.update_delay,
self.flush_mod_buffer) self.flush_mod_buffer)
else: else:
self.emit_data_changed(self.data, [mod]) self.main_widget.data_changed(self.data, [mod])
def subscribe(self): def subscribe(self):
if self.embed is None: if self.args.embed is None:
self.subscriber = Subscriber("datasets", self.subscriber = Subscriber("datasets",
self.sub_init, self.sub_mod) self.sub_init, self.sub_mod)
self.loop.run_until_complete(self.subscriber.connect( self.loop.run_until_complete(self.subscriber.connect(
self.args.server, self.args.port_notify)) self.args.server, self.args.port))
else: else:
self.ipc.subscribe(self.datasets, self.sub_init, self.sub_mod, self.ipc.subscribe(self.datasets, self.sub_init, self.sub_mod)
dataset_prefixes=self.dataset_prefixes,
loop=self.loop)
def unsubscribe(self): def unsubscribe(self):
if self.embed is None: if self.args.embed is None:
self.loop.run_until_complete(self.subscriber.close()) self.loop.run_until_complete(self.subscriber.close())
def run(self): def run(self):
self.args_init() self.args_init()
self.qasync_init() self.quamash_init()
try: try:
self.ipc_init() self.ipc_init()
try: try:
self.req_init() self.create_main_widget()
self.subscribe()
try: try:
self.create_main_widget() self.loop.run_forever()
self.subscribe()
try:
self.loop.run_forever()
finally:
self.unsubscribe()
finally: finally:
self.req_close() self.unsubscribe()
finally: finally:
self.ipc_close() self.ipc_close()
finally: finally:
self.loop.close() self.loop.close()
class TitleApplet(SimpleApplet):
def __init__(self, *args, **kwargs):
SimpleApplet.__init__(self, *args, **kwargs)
self.argparser.add_argument("--title", default=None,
help="set title (can be a Python format "
"string where field names are dataset "
"names, replace '.' with '/')")
def args_init(self):
SimpleApplet.args_init(self)
if self.args.title is not None:
self.dataset_title = set()
parsed = string.Formatter().parse(self.args.title)
for _, format_field, _, _ in parsed:
if format_field is None:
break
if not format_field:
raise ValueError("Invalid title format string")
self.dataset_title.add(format_field.replace("/", "."))
self.datasets |= self.dataset_title
def emit_data_changed(self, data, mod_buffer):
if self.args.title is not None:
title_values = {k.replace(".", "/"): data.get(k, (False, None))[1]
for k in self.dataset_title}
try:
title = self.args.title.format(**title_values)
except:
logger.warning("failed to format title", exc_info=True)
title = self.args.title
else:
title = None
persist = dict()
value = dict()
metadata = dict()
for k, d in data.items():
persist[k], value[k], metadata[k] = d
self.main_widget.data_changed(value, metadata, persist, mod_buffer, title)

View File

@ -1,133 +0,0 @@
import logging
import asyncio
from PyQt6 import QtCore, QtGui, QtWidgets
from sipyco.pc_rpc import AsyncioClient as RPCClient
from artiq.tools import short_format
from artiq.gui.tools import LayoutWidget
from artiq.gui.models import DictSyncTreeSepModel
# reduced read-only version of artiq.dashboard.datasets
logger = logging.getLogger(__name__)
class Model(DictSyncTreeSepModel):
def __init__(self, init):
DictSyncTreeSepModel.__init__(self, ".", ["Dataset", "Value"], init)
def convert(self, k, v, column):
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, dataset_sub, dataset_ctl):
QtWidgets.QDockWidget.__init__(self, "Datasets")
self.setObjectName("Datasets")
self.setFeatures(self.DockWidgetFeature.DockWidgetMovable |
self.DockWidgetFeature.DockWidgetFloatable)
grid = LayoutWidget()
self.setWidget(grid)
self.search = QtWidgets.QLineEdit()
self.search.setPlaceholderText("search...")
self.search.editingFinished.connect(self._search_datasets)
grid.addWidget(self.search, 0, 0)
self.table = QtWidgets.QTreeView()
self.table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows)
self.table.setSelectionMode(
QtWidgets.QAbstractItemView.SelectionMode.SingleSelection)
grid.addWidget(self.table, 1, 0)
metadata_grid = LayoutWidget()
self.metadata = {}
for i, label in enumerate("artiq_version repo_rev file class_name "
"rid start_time".split()):
metadata_grid.addWidget(QtWidgets.QLabel(label), i, 0)
v = QtWidgets.QLabel()
v.setTextInteractionFlags(QtCore.Qt.TextInteractionFlag.TextSelectableByMouse)
metadata_grid.addWidget(v, i, 1)
self.metadata[label] = v
grid.addWidget(metadata_grid, 2, 0)
self.table.setContextMenuPolicy(QtCore.Qt.ContextMenuPolicy.ActionsContextMenu)
upload_action = QtGui.QAction("Upload dataset to master",
self.table)
upload_action.triggered.connect(self.upload_clicked)
self.table.addAction(upload_action)
self.set_model(Model(dict()))
dataset_sub.add_setmodel_callback(self.set_model)
self.dataset_ctl = dataset_ctl
def _search_datasets(self):
if hasattr(self, "table_model_filter"):
self.table_model_filter.setFilterFixedString(
self.search.displayText())
def metadata_changed(self, new):
for k, v in new.items():
self.metadata[k].setText("{}".format(v))
def set_model(self, model):
self.table_model = model
self.table_model_filter = QtCore.QSortFilterProxyModel()
self.table_model_filter.setRecursiveFilteringEnabled(True)
self.table_model_filter.setSourceModel(self.table_model)
self.table.setModel(self.table_model_filter)
def 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, 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())
def restore_state(self, state):
self.table.header().restoreState(QtCore.QByteArray(state))

View File

@ -1,421 +0,0 @@
import asyncio
import logging
import os
from functools import partial
from collections import OrderedDict
from PyQt6 import QtCore, QtGui, QtWidgets
import h5py
from sipyco import pyon
from artiq import __artiq_dir__ as artiq_dir
from artiq.gui.tools import (LayoutWidget, log_level_to_name, get_open_file_name)
from artiq.gui.entries import procdesc_to_entry, EntryTreeWidget
from artiq.master.worker import Worker, log_worker_exception
logger = logging.getLogger(__name__)
class _ArgumentEditor(EntryTreeWidget):
def __init__(self, dock):
EntryTreeWidget.__init__(self)
self._dock = dock
if not self._dock.arguments:
self.insertTopLevelItem(0, QtWidgets.QTreeWidgetItem(["No arguments"]))
for name, argument in self._dock.arguments.items():
self.set_argument(name, argument)
self.quickStyleClicked.connect(self._dock._run_clicked)
recompute_arguments = QtWidgets.QPushButton("Recompute all arguments")
recompute_arguments.setIcon(
QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_BrowserReload))
recompute_arguments.clicked.connect(self._recompute_arguments_clicked)
load = QtWidgets.QPushButton("Set arguments from HDF5")
load.setToolTip("Set arguments from currently selected HDF5 file")
load.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogApplyButton))
load.clicked.connect(self._load_clicked)
buttons = LayoutWidget()
buttons.addWidget(recompute_arguments, 1, 1)
buttons.addWidget(load, 1, 2)
for i, s in enumerate((1, 0, 0, 1)):
buttons.layout.setColumnStretch(i, s)
self.setItemWidget(self.bottom_item, 1, buttons)
def _load_clicked(self):
asyncio.ensure_future(self._dock.load_hdf5_task())
def _recompute_arguments_clicked(self):
asyncio.ensure_future(self._dock._recompute_arguments())
def reset_entry(self, key):
asyncio.ensure_future(self._recompute_argument(key))
async def _recompute_argument(self, name):
try:
arginfo = await self._dock.compute_arginfo()
except:
logger.error("Could not recompute argument '%s' of '%s'",
name, self._dock.expurl, exc_info=True)
return
argument = self._dock.arguments[name]
procdesc = arginfo[name][0]
state = procdesc_to_entry(procdesc).default_state(procdesc)
argument["desc"] = procdesc
argument["state"] = state
self.update_argument(name, argument)
log_levels = ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
class _ExperimentDock(QtWidgets.QMdiSubWindow):
sigClosed = QtCore.pyqtSignal()
def __init__(self, area, expurl, arguments):
QtWidgets.QMdiSubWindow.__init__(self)
qfm = QtGui.QFontMetrics(self.font())
self.resize(100*qfm.averageCharWidth(), 30*qfm.lineSpacing())
self.setWindowTitle(expurl)
self.setWindowIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_FileDialogContentsView))
self.setAcceptDrops(True)
self.layout = QtWidgets.QGridLayout()
top_widget = QtWidgets.QWidget()
top_widget.setLayout(self.layout)
self.setWidget(top_widget)
self.layout.setSpacing(5)
self.layout.setContentsMargins(5, 5, 5, 5)
self._area = area
self._run_task = None
self.expurl = expurl
self.arguments = arguments
self.options = {"log_level": logging.WARNING}
self.argeditor = _ArgumentEditor(self)
self.layout.addWidget(self.argeditor, 0, 0, 1, 5)
self.layout.setRowStretch(0, 1)
log_level = QtWidgets.QComboBox()
log_level.addItems(log_levels)
log_level.setCurrentIndex(1)
log_level.setToolTip("Minimum level for log entry production")
log_level_label = QtWidgets.QLabel("Logging level:")
log_level_label.setToolTip("Minimum level for log message production")
self.layout.addWidget(log_level_label, 3, 0)
self.layout.addWidget(log_level, 3, 1)
log_level.setCurrentIndex(log_levels.index(
log_level_to_name(self.options["log_level"])))
def update_log_level(index):
self.options["log_level"] = getattr(logging,
log_level.currentText())
log_level.currentIndexChanged.connect(update_log_level)
self.log_level = log_level
run = QtWidgets.QPushButton("Analyze")
run.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogOkButton))
run.setToolTip("Run analysis stage (Ctrl+Return)")
run.setShortcut("CTRL+RETURN")
run.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(run, 2, 4)
run.clicked.connect(self._run_clicked)
self._run = run
terminate = QtWidgets.QPushButton("Terminate")
terminate.setIcon(QtWidgets.QApplication.style().standardIcon(
QtWidgets.QStyle.StandardPixmap.SP_DialogCancelButton))
terminate.setToolTip("Terminate analysis (Ctrl+Backspace)")
terminate.setShortcut("CTRL+BACKSPACE")
terminate.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding,
QtWidgets.QSizePolicy.Policy.Expanding)
self.layout.addWidget(terminate, 3, 4)
terminate.clicked.connect(self._terminate_clicked)
terminate.setEnabled(False)
self._terminate = terminate
def dragEnterEvent(self, ev):
if ev.mimeData().hasFormat("text/uri-list"):
ev.acceptProposedAction()
def dropEvent(self, ev):
for uri in ev.mimeData().urls():
if uri.scheme() == "file":
filename = QtCore.QDir.toNativeSeparators(uri.toLocalFile())
logger.debug("Loading HDF5 arguments from %s", filename)
asyncio.ensure_future(self.load_hdf5_task(filename))
break
async def compute_arginfo(self):
return await self._area.compute_arginfo(self.expurl)
async def _recompute_arguments(self, overrides={}):
try:
arginfo = await self.compute_arginfo()
except:
logger.error("Could not recompute arguments of '%s'",
self.expurl, exc_info=True)
return
for k, v in overrides.items():
# Some values (e.g. scans) may have multiple defaults in a list
if isinstance(arginfo[k][0].get("default"), list):
arginfo[k][0]["default"].insert(0, v)
else:
arginfo[k][0]["default"] = v
self.arguments = self._area.initialize_submission_arguments(arginfo)
state = self.argeditor.save_state()
self.argeditor.deleteLater()
self.argeditor = _ArgumentEditor(self)
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:
if self._area.dataset is None:
return
filename = self._area.dataset
try:
with h5py.File(filename, "r") as f:
expid = f["expid"][()]
expid = pyon.decode(expid)
arguments = expid["arguments"]
except:
logger.error("Could not retrieve expid from HDF5 file",
exc_info=True)
return
try:
self.log_level.setCurrentIndex(log_levels.index(
log_level_to_name(expid["log_level"])))
except:
logger.error("Could not set submission options from HDF5 expid",
exc_info=True)
return
await self._recompute_arguments(arguments)
def _run_clicked(self):
class_name, file = self.expurl.split("@", maxsplit=1)
expid = {
"repo_rev": "N/A",
"file": file,
"class_name": class_name,
"log_level": self.options["log_level"],
"arguments": {
name: procdesc_to_entry(argument["desc"]).state_to_value(
argument["state"])
for name, argument in self.arguments.items()},
}
self._run_task = asyncio.ensure_future(self._get_run_task(expid))
self._run.setEnabled(False)
self._terminate.setEnabled(True)
def done(fut):
logger.debug("Analysis done")
self._run_task = None
self._run.setEnabled(True)
self._terminate.setEnabled(False)
self._run_task.add_done_callback(done)
async def _get_run_task(self, expid):
logger.info("Running '%s'...", self.expurl)
worker = Worker(self._area.worker_handlers)
try:
await worker.build(rid=None, pipeline_name="browser",
wd=os.path.abspath("."),
expid=expid, priority=0)
await worker.analyze()
except:
logger.error("Failed to run '%s'", self.expurl)
log_worker_exception()
else:
logger.info("Finished running '%s'", self.expurl)
finally:
await worker.close()
def _terminate_clicked(self):
try:
self._run_task.cancel()
except:
logger.error("Unexpected failure terminating '%s'",
self.expurl, exc_info=True)
def closeEvent(self, event):
self.sigClosed.emit()
QtWidgets.QMdiSubWindow.closeEvent(self, event)
def save_state(self):
return {
"argeditor": self.argeditor.save_state(),
"geometry": bytes(self.saveGeometry()),
"options": self.options,
}
def restore_state(self, state):
self.argeditor.restore_state(state["argeditor"])
self.restoreGeometry(QtCore.QByteArray(state["geometry"]))
self.options = state["options"]
class LocalDatasetDB:
def __init__(self, dataset_sub):
self.dataset_sub = dataset_sub
dataset_sub.add_setmodel_callback(self.init)
def init(self, data):
self._data = data
def get(self, key):
return self._data.backing_store[key][1]
def update(self, mod):
self.dataset_sub.update(mod)
class ExperimentsArea(QtWidgets.QMdiArea):
def __init__(self, root, dataset_sub):
QtWidgets.QMdiArea.__init__(self)
self.pixmap = QtGui.QPixmap(os.path.join(
artiq_dir, "gui", "logo_ver.svg"))
self.current_dir = root
self.dataset = None
self.open_experiments = []
self._ddb = LocalDatasetDB(dataset_sub)
self.worker_handlers = {
"get_device_db": lambda: {},
"get_device": lambda key, resolve_alias=False: {"type": "dummy"},
"get_dataset": self._ddb.get,
"update_dataset": self._ddb.update,
}
def dataset_changed(self, path):
self.dataset = path
def dataset_activated(self, path):
sub = self.currentSubWindow()
if sub is None:
return
asyncio.ensure_future(sub.load_hdf5_task(path))
def mousePressEvent(self, ev):
if ev.button() == QtCore.Qt.MouseButton.LeftButton:
self.select_experiment()
def paintEvent(self, event):
QtWidgets.QMdiArea.paintEvent(self, event)
painter = QtGui.QPainter(self.viewport())
x = (self.width() - self.pixmap.width())//2
y = (self.height() - self.pixmap.height())//2
painter.setOpacity(0.5)
painter.drawPixmap(x, y, self.pixmap)
def save_state(self):
return {"experiments": [{
"expurl": dock.expurl,
"arguments": dock.arguments,
"dock": dock.save_state(),
} for dock in self.open_experiments]}
def restore_state(self, state):
if self.open_experiments:
raise NotImplementedError
for ex_state in state["experiments"]:
dock = self.open_experiment(ex_state["expurl"],
ex_state["arguments"])
dock.restore_state(ex_state["dock"])
def select_experiment(self):
asyncio.ensure_future(self._select_experiment_task())
async def _select_experiment_task(self):
try:
file = await get_open_file_name(
self, "Open experiment", self.current_dir,
"Experiments (*.py);;All files (*.*)")
except asyncio.CancelledError:
return
self.current_dir = os.path.dirname(file)
logger.debug("Opening experiment %s", file)
try:
description = await self.examine(file)
except:
logger.error("Could not examine experiment '%s'",
file, exc_info=True)
return
for class_name, class_desc in description.items():
expurl = "{}@{}".format(class_name, file)
arguments = self.initialize_submission_arguments(
class_desc["arginfo"])
self.open_experiment(expurl, arguments)
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,
"group": group,
"tooltip": tooltip,
"state": state # mutated by entries
}
return arguments
async def examine(self, file):
worker = Worker(self.worker_handlers)
try:
return await worker.examine("examine", file)
finally:
await worker.close()
async def compute_arginfo(self, expurl):
class_name, file = expurl.split("@", maxsplit=1)
try:
desc = await self.examine(file)
except:
logger.error("Could not examine experiment '%s'",
file, exc_info=True)
return
return desc[class_name]["arginfo"]
def open_experiment(self, expurl, arguments):
try:
dock = _ExperimentDock(self, expurl, arguments)
except:
logger.warning("Failed to create experiment dock for %s, "
"retrying with arguments reset", expurl,
exc_info=True)
dock = _ExperimentDock(self, expurl, {})
asyncio.ensure_future(dock._recompute_arguments())
dock.setAttribute(QtCore.Qt.WidgetAttribute.WA_DeleteOnClose)
self.addSubWindow(dock)
dock.show()
dock.sigClosed.connect(partial(self.on_dock_closed, dock))
self.open_experiments.append(dock)
return dock
def set_argument_value(self, expurl, name, value):
logger.warning("Unable to set argument '%s', dropping change. "
"'set_argument_value' not supported in browser.", name)
def on_dock_closed(self, dock):
self.open_experiments.remove(dock)

View File

@ -1,289 +0,0 @@
import logging
import os
from datetime import datetime
import h5py
from PyQt6 import QtCore, QtWidgets, QtGui
from sipyco import pyon
logger = logging.getLogger(__name__)
def open_h5(info):
if not (info.isFile() and info.isReadable() and
info.suffix() == "h5"):
return
try:
return h5py.File(info.filePath(), "r")
except OSError: # e.g. file being written (see #470)
logger.debug("OSError when opening HDF5 file %s", info.filePath(),
exc_info=True)
except:
logger.warning("unable to read HDF5 file %s", info.filePath(),
exc_info=True)
class ThumbnailIconProvider(QtWidgets.QFileIconProvider):
def icon(self, info):
icon = self.hdf5_thumbnail(info)
if icon is None:
icon = QtWidgets.QFileIconProvider.icon(self, info)
return icon
def hdf5_thumbnail(self, info):
f = open_h5(info)
if not f:
return
with f:
try:
t = f["datasets/thumbnail"]
except KeyError:
return
try:
img = QtGui.QImage.fromData(t[()])
except:
logger.warning("unable to read thumbnail from %s",
info.filePath(), exc_info=True)
return
pix = QtGui.QPixmap.fromImage(img)
return QtGui.QIcon(pix)
class DirsOnlyProxy(QtCore.QSortFilterProxyModel):
def filterAcceptsRow(self, row, parent):
idx = self.sourceModel().index(row, 0, parent)
if not self.sourceModel().fileInfo(idx).isDir():
return False
return QtCore.QSortFilterProxyModel.filterAcceptsRow(self, row, parent)
class ZoomIconView(QtWidgets.QListView):
zoom_step = 2**.25
aspect = 2/3
default_size = 25
min_size = 10
max_size = 1000
def __init__(self):
QtWidgets.QListView.__init__(self)
self._char_width = QtGui.QFontMetrics(self.font()).averageCharWidth()
self.setViewMode(self.ViewMode.IconMode)
w = self._char_width*self.default_size
self.setIconSize(QtCore.QSize(w, int(w*self.aspect)))
self.setFlow(self.Flow.LeftToRight)
self.setResizeMode(self.ResizeMode.Adjust)
self.setWrapping(True)
def wheelEvent(self, ev):
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
a = self._char_width*self.min_size
b = self._char_width*self.max_size
w = self.iconSize().width()*self.zoom_step**(
ev.angleDelta().y()/120.)
if a <= w <= b:
self.setIconSize(QtCore.QSize(int(w), int(w*self.aspect)))
else:
QtWidgets.QListView.wheelEvent(self, ev)
class Hdf5FileSystemModel(QtGui.QFileSystemModel):
def __init__(self):
QtGui.QFileSystemModel.__init__(self)
self.setFilter(QtCore.QDir.Filter.Drives | QtCore.QDir.Filter.NoDotAndDotDot |
QtCore.QDir.Filter.AllDirs | QtCore.QDir.Filter.Files)
self.setNameFilterDisables(False)
self.setIconProvider(ThumbnailIconProvider())
def data(self, idx, role):
if role == QtCore.Qt.ItemDataRole.ToolTipRole:
info = self.fileInfo(idx)
h5 = open_h5(info)
if h5 is not None:
try:
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"].asstr()[()] if "artiq_version" in h5 else "<none>",
expid.get("repo_rev", "<none>"),
expid.get("file", "<none>"), expid.get("class_name", "<none>"),
h5["rid"][()] if "rid" in h5 else "<none>", start_time)
return v
except:
logger.warning("unable to read metadata from %s",
info.filePath(), exc_info=True)
return QtGui.QFileSystemModel.data(self, idx, role)
class FilesDock(QtWidgets.QDockWidget):
dataset_activated = QtCore.pyqtSignal(str)
dataset_changed = QtCore.pyqtSignal(str)
metadata_changed = QtCore.pyqtSignal(dict)
def __init__(self, datasets, browse_root=""):
QtWidgets.QDockWidget.__init__(self, "Files")
self.setObjectName("Files")
self.setFeatures(self.DockWidgetFeature.DockWidgetMovable | self.DockWidgetFeature.DockWidgetFloatable)
self.splitter = QtWidgets.QSplitter()
self.setWidget(self.splitter)
self.datasets = datasets
self.model = Hdf5FileSystemModel()
self.rt = QtWidgets.QTreeView()
rt_model = DirsOnlyProxy()
rt_model.setDynamicSortFilter(True)
rt_model.setSourceModel(self.model)
self.rt.setModel(rt_model)
self.model.directoryLoaded.connect(
lambda: self.rt.resizeColumnToContents(0))
self.rt.setAnimated(False)
if browse_root != "":
browse_root = os.path.abspath(browse_root)
self.rt.setRootIndex(rt_model.mapFromSource(
self.model.setRootPath(browse_root)))
self.rt.setHeaderHidden(True)
self.rt.setSelectionBehavior(self.rt.SelectionBehavior.SelectRows)
self.rt.setSelectionMode(self.rt.SelectionMode.SingleSelection)
self.rt.selectionModel().currentChanged.connect(
self.tree_current_changed)
self.rt.setRootIsDecorated(False)
for i in range(1, 4):
self.rt.hideColumn(i)
self.splitter.addWidget(self.rt)
self.rl = ZoomIconView()
self.rl.setModel(self.model)
self.rl.selectionModel().currentChanged.connect(
self.list_current_changed)
self.rl.activated.connect(self.list_activated)
self.splitter.addWidget(self.rl)
def tree_current_changed(self, current, previous):
idx = self.rt.model().mapToSource(current)
self.rl.setRootIndex(idx)
def list_current_changed(self, current, previous):
info = self.model.fileInfo(current)
f = open_h5(info)
if not f:
return
logger.debug("loading datasets from %s", info.filePath())
with f:
try:
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"].asstr()[()] if "artiq_version" in f else "<none>",
"repo_rev": expid.get("repo_rev", "<none>"),
"file": expid.get("file", "<none>"),
"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)
except:
logger.warning("unable to read metadata from %s",
info.filePath(), exc_info=True)
rd = {}
if "archive" in f:
def visitor(k, v):
if isinstance(v, h5py.Dataset):
# 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)
if "datasets" in f:
def visitor(k, v):
if isinstance(v, h5py.Dataset):
if k in rd:
logger.warning("dataset '%s' is both in archive "
"and outputs", k)
# 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)
self.datasets.init(rd)
self.dataset_changed.emit(info.filePath())
def list_activated(self, idx):
info = self.model.fileInfo(idx)
if not info.isDir():
self.dataset_activated.emit(info.filePath())
return
self.rl.setRootIndex(idx)
idx = self.rt.model().mapFromSource(idx)
self.rt.expand(idx)
self.rt.setCurrentIndex(idx)
def select(self, path):
f = os.path.abspath(path)
if os.path.isdir(f):
self.select_dir(f)
else:
self.select_file(f)
def select_dir(self, path):
if not os.path.exists(path):
logger.warning("directory does not exist %s", path)
return
idx = self.model.index(path)
if not idx.isValid():
logger.warning("directory invalid %s", path)
return
self.rl.setRootIndex(idx)
# ugly, see Spyder: late indexing, late scroll
def scroll_when_loaded(p):
if p != path:
return
self.model.directoryLoaded.disconnect(scroll_when_loaded)
QtCore.QTimer.singleShot(
100,
lambda: self.rt.scrollTo(
self.rt.model().mapFromSource(self.model.index(path)),
self.rt.ScrollHint.PositionAtCenter)
)
self.model.directoryLoaded.connect(scroll_when_loaded)
idx = self.rt.model().mapFromSource(idx)
self.rt.expand(idx)
self.rt.setCurrentIndex(idx)
def select_file(self, path):
if not os.path.exists(path):
logger.warning("file does not exist %s", path)
return
self.select_dir(os.path.dirname(path))
idx = self.model.index(path)
if not idx.isValid():
logger.warning("file invalid %s", path)
return
self.rl.setCurrentIndex(idx)
def save_state(self):
state = {
"dir": self.model.filePath(self.rl.rootIndex()),
"splitter": bytes(self.splitter.saveState()),
}
idx = self.rl.currentIndex()
if idx.isValid():
state["file"] = self.model.filePath(idx)
else:
state["file"] = None
return state
def restore_state(self, state):
self.splitter.restoreState(QtCore.QByteArray(state["splitter"]))
self.select_dir(state["dir"])
if state["file"] is not None:
self.select_file(state["file"])

View File

@ -1,78 +0,0 @@
import os
import subprocess
from migen import *
from migen.build.platforms.sinara import kasli
from misoc.interconnect.csr import *
from misoc.integration.builder import *
from artiq.gateware.amp import AMPSoC
from artiq import __version__ as artiq_version
from artiq import __artiq_dir__ as artiq_dir
__all__ = ["add_identifier", "build_artiq_soc"]
def get_identifier_string(soc, suffix="", add_class_name=True):
r = artiq_version
if suffix or add_class_name:
r += ";"
if add_class_name:
r += getattr(soc, "class_name_override", soc.__class__.__name__.lower())
r += suffix
return r
class ReprogrammableIdentifier(Module, AutoCSR):
def __init__(self, ident):
self.address = CSRStorage(8)
self.data = CSRStatus(8)
contents = list(ident.encode())
l = len(contents)
if l > 255:
raise ValueError("Identifier string must be 255 characters or less")
contents.insert(0, l)
for i in range(8):
self.specials += Instance("ROM256X1", name="identifier_str"+str(i),
i_A0=self.address.storage[0], i_A1=self.address.storage[1],
i_A2=self.address.storage[2], i_A3=self.address.storage[3],
i_A4=self.address.storage[4], i_A5=self.address.storage[5],
i_A6=self.address.storage[6], i_A7=self.address.storage[7],
o_O=self.data.status[i],
p_INIT=sum(1 << j if c & (1 << i) else 0 for j, c in enumerate(contents)))
def add_identifier(soc, *args, gateware_identifier_str=None, **kwargs):
if hasattr(soc, "identifier"):
raise ValueError
identifier_str = get_identifier_string(soc, *args, **kwargs)
soc.submodules.identifier = ReprogrammableIdentifier(gateware_identifier_str or identifier_str)
soc.config["IDENTIFIER_STR"] = identifier_str
def build_artiq_soc(soc, argdict):
firmware_dir = os.path.join(artiq_dir, "firmware")
builder = Builder(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")
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:
builder.add_software_package("satman", os.path.join(firmware_dir, "satman"))
try:
builder.build()
except subprocess.CalledProcessError as e:
raise SystemExit("Command {} failed".format(" ".join(e.cmd)))

View File

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

View File

@ -0,0 +1,30 @@
"""
:class:`Constness` checks that no attribute marked
as constant is ever set.
"""
from pythonparser import algorithm, diagnostic
from .. import types
class Constness(algorithm.Visitor):
def __init__(self, engine):
self.engine = engine
self.in_assign = False
def visit_Assign(self, node):
self.visit(node.value)
self.in_assign = True
self.visit(node.targets)
self.in_assign = False
def visit_AttributeT(self, node):
self.generic_visit(node)
if self.in_assign:
typ = node.value.type.find()
if types.is_instance(typ) and node.attr in typ.constant_attributes:
diag = diagnostic.Diagnostic("error",
"cannot assign to constant attribute '{attr}' of class '{class}'",
{"attr": node.attr, "class": typ.name},
node.loc)
self.engine.process(diag)
return

View File

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

View File

@ -21,19 +21,13 @@ class scoped(object):
set of variables resolved as globals 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 # Typed versions of untyped nodes
class argT(ast.arg, commontyped): class argT(ast.arg, commontyped):
pass pass
class ClassDefT(ast.ClassDef): class ClassDefT(ast.ClassDef):
_types = ("constructor_type",) _types = ("constructor_type",)
class FunctionDefT(ast.FunctionDef, scoped, remote): class FunctionDefT(ast.FunctionDef, scoped):
_types = ("signature_type",) _types = ("signature_type",)
class QuotedFunctionDefT(FunctionDefT): class QuotedFunctionDefT(FunctionDefT):
""" """
@ -64,7 +58,7 @@ class BinOpT(ast.BinOp, commontyped):
pass pass
class BoolOpT(ast.BoolOp, commontyped): class BoolOpT(ast.BoolOp, commontyped):
pass pass
class CallT(ast.Call, commontyped, remote): class CallT(ast.Call, commontyped):
""" """
:ivar iodelay: (:class:`iodelay.Expr`) :ivar iodelay: (:class:`iodelay.Expr`)
:ivar arg_exprs: (dict of str to :class:`iodelay.Expr`) :ivar arg_exprs: (dict of str to :class:`iodelay.Expr`)

View File

@ -38,22 +38,12 @@ class TInt(types.TMono):
def one(): def one():
return 1 return 1
def TInt8():
return TInt(types.TValue(8))
def TInt32(): def TInt32():
return TInt(types.TValue(32)) return TInt(types.TValue(32))
def TInt64(): def TInt64():
return TInt(types.TValue(64)) return TInt(types.TValue(64))
def _int_printer(typ, printer, depth, max_depth):
if types.is_var(typ["width"]):
return "numpy.int?"
else:
return "numpy.int{}".format(types.get_value(typ.find()["width"]))
types.TypePrinter.custom_printers["int"] = _int_printer
class TFloat(types.TMono): class TFloat(types.TMono):
def __init__(self): def __init__(self):
super().__init__("float") super().__init__("float")
@ -70,44 +60,12 @@ class TStr(types.TMono):
def __init__(self): def __init__(self):
super().__init__("str") super().__init__("str")
class TBytes(types.TMono):
def __init__(self):
super().__init__("bytes")
class TByteArray(types.TMono):
def __init__(self):
super().__init__("bytearray")
class TList(types.TMono): class TList(types.TMono):
def __init__(self, elt=None): def __init__(self, elt=None):
if elt is None: if elt is None:
elt = types.TVar() elt = types.TVar()
super().__init__("list", {"elt": elt}) super().__init__("list", {"elt": elt})
class TArray(types.TMono):
def __init__(self, elt=None, num_dims=1):
if elt is None:
elt = types.TVar()
if isinstance(num_dims, int):
# Make TArray more convenient to instantiate from (ARTIQ) user code.
num_dims = types.TValue(num_dims)
# For now, enforce number of dimensions to be known, as we'd otherwise
# need to implement custom unification logic for the type of `shape`.
# Default to 1 to keep compatibility with old user code from before
# multidimensional array support.
assert isinstance(num_dims.value, int), "Number of dimensions must be resolved"
super().__init__("array", {"elt": elt, "num_dims": num_dims})
self.attributes = OrderedDict([
("buffer", types._TPointer(elt)),
("shape", types.TTuple([TInt32()] * num_dims.value)),
])
def _array_printer(typ, printer, depth, max_depth):
return "numpy.array(elt={}, num_dims={})".format(
printer.name(typ["elt"], depth, max_depth), typ["num_dims"].value)
types.TypePrinter.custom_printers["array"] = _array_printer
class TRange(types.TMono): class TRange(types.TMono):
def __init__(self, elt=None): def __init__(self, elt=None):
if elt is None: if elt is None:
@ -125,24 +83,19 @@ class TException(types.TMono):
# (which also serves as the EHABI type_info). # (which also serves as the EHABI type_info).
# * File, line and column where it was raised (str, int, int). # * File, line and column where it was raised (str, int, int).
# * Message, which can contain substitutions {0}, {1} and {2} (str). # * Message, which can contain substitutions {0}, {1} and {2} (str).
# * Three 64-bit integers, parameterizing the message (numpy.int64). # * Three 64-bit integers, parameterizing the message (int(width=64)).
# These attributes are prefixed with `#` so that users cannot access them,
# and we don't have to do string allocation in the runtime.
# #__name__ is now a string key in the host. TStr may not be an actual
# CSlice in the runtime, they might be a CSlice with length = i32::MAX and
# ptr = string key in the host.
# Keep this in sync with the function ARTIQIRGenerator.alloc_exn. # Keep this in sync with the function ARTIQIRGenerator.alloc_exn.
attributes = OrderedDict([ attributes = OrderedDict([
("#__name__", TInt32()), ("__name__", TStr()),
("#__file__", TStr()), ("__file__", TStr()),
("#__line__", TInt32()), ("__line__", TInt32()),
("#__col__", TInt32()), ("__col__", TInt32()),
("#__func__", TStr()), ("__func__", TStr()),
("#__message__", TStr()), ("__message__", TStr()),
("#__param0__", TInt64()), ("__param0__", TInt64()),
("#__param1__", TInt64()), ("__param1__", TInt64()),
("#__param2__", TInt64()), ("__param2__", TInt64()),
]) ])
def __init__(self, name="Exception", id=0): def __init__(self, name="Exception", id=0):
@ -155,32 +108,15 @@ def fn_bool():
def fn_int(): def fn_int():
return types.TConstructor(TInt()) return types.TConstructor(TInt())
def fn_int32():
return types.TBuiltinFunction("int32")
def fn_int64():
return types.TBuiltinFunction("int64")
def fn_float(): def fn_float():
return types.TConstructor(TFloat()) return types.TConstructor(TFloat())
def fn_str(): def fn_str():
return types.TConstructor(TStr()) return types.TConstructor(TStr())
def fn_bytes():
return types.TConstructor(TBytes())
def fn_bytearray():
return types.TConstructor(TByteArray())
def fn_list(): def fn_list():
return types.TConstructor(TList()) return types.TConstructor(TList())
def fn_array():
# numpy.array() is actually a "magic" macro that is expanded in-place, but
# just as for builtin functions, we do not want to quote it, etc.
return types.TBuiltinFunction("array")
def fn_Exception(): def fn_Exception():
return types.TExceptionConstructor(TException("Exception")) return types.TExceptionConstructor(TException("Exception"))
@ -193,9 +129,6 @@ def fn_ValueError():
def fn_ZeroDivisionError(): def fn_ZeroDivisionError():
return types.TExceptionConstructor(TException("ZeroDivisionError")) return types.TExceptionConstructor(TException("ZeroDivisionError"))
def fn_RuntimeError():
return types.TExceptionConstructor(TException("RuntimeError"))
def fn_range(): def fn_range():
return types.TBuiltinFunction("range") return types.TBuiltinFunction("range")
@ -205,18 +138,6 @@ def fn_len():
def fn_round(): def fn_round():
return types.TBuiltinFunction("round") return types.TBuiltinFunction("round")
def fn_abs():
return types.TBuiltinFunction("abs")
def fn_min():
return types.TBuiltinFunction("min")
def fn_max():
return types.TBuiltinFunction("max")
def fn_make_array():
return types.TBuiltinFunction("make_array")
def fn_print(): def fn_print():
return types.TBuiltinFunction("print") return types.TBuiltinFunction("print")
@ -232,6 +153,9 @@ def obj_interleave():
def obj_sequential(): def obj_sequential():
return types.TBuiltin("sequential") return types.TBuiltin("sequential")
def fn_watchdog():
return types.TBuiltinFunction("watchdog")
def fn_delay(): def fn_delay():
return types.TBuiltinFunction("delay") return types.TBuiltinFunction("delay")
@ -244,21 +168,15 @@ def fn_delay_mu():
def fn_at_mu(): def fn_at_mu():
return types.TBuiltinFunction("at_mu") return types.TBuiltinFunction("at_mu")
def fn_mu_to_seconds():
return types.TBuiltinFunction("mu_to_seconds")
def fn_seconds_to_mu():
return types.TBuiltinFunction("seconds_to_mu")
def fn_rtio_log(): def fn_rtio_log():
return types.TBuiltinFunction("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 # Accessors
def is_none(typ): def is_none(typ):
@ -289,12 +207,6 @@ def is_float(typ):
def is_str(typ): def is_str(typ):
return types.is_mono(typ, "str") return types.is_mono(typ, "str")
def is_bytes(typ):
return types.is_mono(typ, "bytes")
def is_bytearray(typ):
return types.is_mono(typ, "bytearray")
def is_numeric(typ): def is_numeric(typ):
typ = typ.find() typ = typ.find()
return isinstance(typ, types.TMono) and \ return isinstance(typ, types.TMono) and \
@ -306,20 +218,6 @@ def is_list(typ, elt=None):
else: else:
return types.is_mono(typ, "list") return types.is_mono(typ, "list")
def is_array(typ, elt=None):
if elt is not None:
return types.is_mono(typ, "array", elt=elt)
else:
return types.is_mono(typ, "array")
def is_listish(typ, elt=None):
if is_list(typ, elt) or is_array(typ, elt):
return True
elif elt is None:
return is_str(typ) or is_bytes(typ) or is_bytearray(typ)
else:
return False
def is_range(typ, elt=None): def is_range(typ, elt=None):
if elt is not None: if elt is not None:
return types.is_mono(typ, "range", {"elt": elt}) return types.is_mono(typ, "range", {"elt": elt})
@ -334,18 +232,13 @@ def is_exception(typ, name=None):
typ.name == name typ.name == name
def is_iterable(typ): def is_iterable(typ):
return is_listish(typ) or is_range(typ) typ = typ.find()
return isinstance(typ, types.TMono) and \
typ.name in ('list', 'range')
def get_iterable_elt(typ): def get_iterable_elt(typ):
# TODO: Arrays count as listish, but this returns the innermost element type for if is_iterable(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 TInt8()
elif types._is_pointer(typ) or is_iterable(typ):
return typ.find()["elt"].find() return typ.find()["elt"].find()
else:
assert False
def is_collection(typ): def is_collection(typ):
typ = typ.find() typ = typ.find()
@ -356,6 +249,6 @@ def is_allocated(typ):
return not (is_none(typ) or is_bool(typ) or is_int(typ) or return not (is_none(typ) or is_bool(typ) or is_int(typ) or
is_float(typ) or is_range(typ) or is_float(typ) or is_range(typ) or
types._is_pointer(typ) or types.is_function(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_c_function(typ) or types.is_rpc(typ) or
types.is_subkernel(typ) or types.is_method(typ) or types.is_method(typ) or types.is_tuple(typ) or
types.is_tuple(typ) or types.is_value(typ)) types.is_value(typ))

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -36,47 +36,12 @@ class TKeyword(types.TMono):
def is_keyword(typ): def is_keyword(typ):
return isinstance(typ, TKeyword) return isinstance(typ, TKeyword)
class TExceptionTypeInfo(types.TMono):
def __init__(self):
super().__init__("exntypeinfo")
# See rpc_proto.rs and comm_kernel.py:_{send,receive}_rpc_value. def is_exn_typeinfo(typ):
def rpc_tag(typ, error_handler): return isinstance(typ, TExceptionTypeInfo)
typ = typ.find()
if types.is_tuple(typ):
assert len(typ.elts) < 256
return b"t" + bytes([len(typ.elts)]) + \
b"".join([rpc_tag(elt_type, error_handler)
for elt_type in typ.elts])
elif builtins.is_none(typ):
return b"n"
elif builtins.is_bool(typ):
return b"b"
elif builtins.is_int(typ, types.TValue(32)):
return b"i"
elif builtins.is_int(typ, types.TValue(64)):
return b"I"
elif builtins.is_float(typ):
return b"f"
elif builtins.is_str(typ):
return b"s"
elif builtins.is_bytes(typ):
return b"B"
elif builtins.is_bytearray(typ):
return b"A"
elif builtins.is_list(typ):
return b"l" + rpc_tag(builtins.get_iterable_elt(typ), error_handler)
elif builtins.is_array(typ):
num_dims = typ["num_dims"].value
return b"a" + bytes([num_dims]) + rpc_tag(typ["elt"], error_handler)
elif builtins.is_range(typ):
return b"r" + rpc_tag(builtins.get_iterable_elt(typ), error_handler)
elif is_keyword(typ):
return b"k" + rpc_tag(typ.params["value"], error_handler)
elif types.is_function(typ) or types.is_method(typ) or types.is_rpc(typ):
raise ValueError("RPC tag for functional value")
elif '__objectid__' in typ.attributes:
return b"O"
else:
error_handler(typ)
class Value: class Value:
""" """
@ -135,7 +100,6 @@ class NamedValue(Value):
def __init__(self, typ, name): def __init__(self, typ, name):
super().__init__(typ) super().__init__(typ)
self.name, self.function = name, None self.name, self.function = name, None
self.is_removed = False
def set_name(self, new_name): def set_name(self, new_name):
if self.function is not None: if self.function is not None:
@ -236,7 +200,7 @@ class Instruction(User):
self.drop_references() self.drop_references()
# Check this after drop_references in case this # Check this after drop_references in case this
# is a self-referencing phi. # is a self-referencing phi.
assert all(use.is_removed for use in self.uses) assert not any(self.uses)
def replace_with(self, value): def replace_with(self, value):
self.replace_all_uses_with(value) self.replace_all_uses_with(value)
@ -346,7 +310,6 @@ class BasicBlock(NamedValue):
:ivar instructions: (list of :class:`Instruction`) :ivar instructions: (list of :class:`Instruction`)
""" """
_dump_loc = True
def __init__(self, instructions, name=""): def __init__(self, instructions, name=""):
super().__init__(TBasicBlock(), name) super().__init__(TBasicBlock(), name)
@ -371,7 +334,7 @@ class BasicBlock(NamedValue):
self.remove_from_parent() self.remove_from_parent()
# Check this after erasing instructions in case the block # Check this after erasing instructions in case the block
# loops into itself. # loops into itself.
assert all(use.is_removed for use in self.uses) assert not any(self.uses)
def prepend(self, insn): def prepend(self, insn):
assert isinstance(insn, Instruction) assert isinstance(insn, Instruction)
@ -422,12 +385,12 @@ class BasicBlock(NamedValue):
lines = ["{}:".format(escape_name(self.name))] lines = ["{}:".format(escape_name(self.name))]
if self.function is not None: if self.function is not None:
lines[0] += " ; predecessors: {}".format( lines[0] += " ; predecessors: {}".format(
", ".join(sorted([escape_name(pred.name) for pred in self.predecessors()]))) ", ".join([escape_name(pred.name) for pred in self.predecessors()]))
# Annotated instructions # Annotated instructions
loc = None loc = None
for insn in self.instructions: for insn in self.instructions:
if self._dump_loc and loc != insn.loc: if loc != insn.loc:
loc = insn.loc loc = insn.loc
if loc is None: if loc is None:
@ -453,13 +416,7 @@ class BasicBlock(NamedValue):
class Argument(NamedValue): class Argument(NamedValue):
""" """
A function argument. A function argument.
:ivar loc: (:class:`pythonparser.source.Range` or None)
source location
""" """
def __init__(self, typ, name):
super().__init__(typ, name)
self.loc = None
def as_entity(self, type_printer): def as_entity(self, type_printer):
return self.as_operand(type_printer) return self.as_operand(type_printer)
@ -706,81 +663,6 @@ class SetLocal(Instruction):
def value(self): def value(self):
return self.operands[1] 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): class GetAttr(Instruction):
""" """
An intruction that loads an attribute from an object, An intruction that loads an attribute from an object,
@ -803,7 +685,7 @@ class GetAttr(Instruction):
typ = obj.type.attributes[attr] typ = obj.type.attributes[attr]
else: else:
typ = obj.type.constructor.attributes[attr] typ = obj.type.constructor.attributes[attr]
if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(typ): if types.is_function(typ) or types.is_rpc(typ):
typ = types.TMethod(obj.type, typ) typ = types.TMethod(obj.type, typ)
super().__init__([obj], typ, name) super().__init__([obj], typ, name)
self.attr = attr self.attr = attr
@ -856,33 +738,6 @@ class SetAttr(Instruction):
def value(self): def value(self):
return self.operands[1] return self.operands[1]
class Offset(Instruction):
"""
An intruction that adds an offset to a pointer (indexes into a list).
This is used to represent internally generated pointer arithmetic, and must
remain inside the same object (see :class:`GetElem` and LLVM's GetElementPtr).
"""
"""
:param lst: (:class:`Value`) list
:param index: (:class:`Value`) index
"""
def __init__(self, base, offset, name=""):
assert isinstance(base, Value)
assert isinstance(offset, Value)
typ = types._TPointer(builtins.get_iterable_elt(base.type))
super().__init__([base, offset], typ, name)
def opcode(self):
return "offset"
def base(self):
return self.operands[0]
def index(self):
return self.operands[1]
class GetElem(Instruction): class GetElem(Instruction):
""" """
An intruction that loads an element from a list. An intruction that loads an element from a list.
@ -900,7 +755,7 @@ class GetElem(Instruction):
def opcode(self): def opcode(self):
return "getelem" return "getelem"
def base(self): def list(self):
return self.operands[0] return self.operands[0]
def index(self): def index(self):
@ -926,7 +781,7 @@ class SetElem(Instruction):
def opcode(self): def opcode(self):
return "setelem" return "setelem"
def base(self): def list(self):
return self.operands[0] return self.operands[0]
def index(self): def index(self):
@ -985,7 +840,6 @@ class Arith(Instruction):
def rhs(self): def rhs(self):
return self.operands[1] return self.operands[1]
class Compare(Instruction): class Compare(Instruction):
""" """
A comparison operation on numbers. A comparison operation on numbers.
@ -1047,42 +901,6 @@ class Builtin(Instruction):
def opcode(self): def opcode(self):
return "builtin({})".format(self.op) return "builtin({})".format(self.op)
class BuiltinInvoke(Terminator):
"""
A builtin operation which can raise exceptions.
:ivar op: (string) operation name
"""
"""
:param op: (string) operation name
:param normal: (:class:`BasicBlock`) normal target
:param exn: (:class:`BasicBlock`) exceptional target
"""
def __init__(self, op, operands, typ, normal, exn, name=None):
assert isinstance(op, str)
for operand in operands: assert isinstance(operand, Value)
assert isinstance(normal, BasicBlock)
assert isinstance(exn, BasicBlock)
if name is None:
name = "BLTINV.{}".format(op)
super().__init__(operands + [normal, exn], typ, name)
self.op = op
def copy(self, mapper):
self_copy = super().copy(mapper)
self_copy.op = self.op
return self_copy
def normal_target(self):
return self.operands[-2]
def exception_target(self):
return self.operands[-1]
def opcode(self):
return "builtinInvokable({})".format(self.op)
class Closure(Instruction): class Closure(Instruction):
""" """
A closure creation operation. A closure creation operation.
@ -1301,18 +1119,14 @@ class IndirectBranch(Terminator):
class Return(Terminator): class Return(Terminator):
""" """
A return instruction. 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 :param value: (:class:`Value`) return value
""" """
def __init__(self, value, remote_return=False, name=""): def __init__(self, value, name=""):
assert isinstance(value, Value) assert isinstance(value, Value)
super().__init__([value], builtins.TNone(), name) super().__init__([value], builtins.TNone(), name)
self.remote_return = remote_return
def opcode(self): def opcode(self):
return "return" return "return"
@ -1361,9 +1175,9 @@ class Raise(Terminator):
if len(self.operands) > 1: if len(self.operands) > 1:
return self.operands[1] return self.operands[1]
class Resume(Terminator): class Reraise(Terminator):
""" """
A resume instruction. A reraise instruction.
""" """
""" """
@ -1377,7 +1191,7 @@ class Resume(Terminator):
super().__init__(operands, builtins.TNone(), name) super().__init__(operands, builtins.TNone(), name)
def opcode(self): def opcode(self):
return "resume" return "reraise"
def exception_target(self): def exception_target(self):
if len(self.operands) > 0: if len(self.operands) > 0:
@ -1463,7 +1277,6 @@ class LandingPad(Terminator):
def __init__(self, cleanup, name=""): def __init__(self, cleanup, name=""):
super().__init__([cleanup], builtins.TException(), name) super().__init__([cleanup], builtins.TException(), name)
self.types = [] self.types = []
self.has_cleanup = True
def copy(self, mapper): def copy(self, mapper):
self_copy = super().copy(mapper) self_copy = super().copy(mapper)

View File

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

View File

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

View File

@ -10,7 +10,7 @@ string and infers types for it using a trivial :module:`prelude`.
import os import os
from pythonparser import source, diagnostic, parse_buffer from pythonparser import source, diagnostic, parse_buffer
from . import prelude, types, transforms, analyses, validators, embedding from . import prelude, types, transforms, analyses, validators
class Source: class Source:
def __init__(self, source_buffer, engine=None): def __init__(self, source_buffer, engine=None):
@ -18,7 +18,11 @@ class Source:
self.engine = diagnostic.Engine(all_errors_are_fatal=True) self.engine = diagnostic.Engine(all_errors_are_fatal=True)
else: else:
self.engine = engine self.engine = engine
self.embedding_map = embedding.EmbeddingMap()
self.function_map = {}
self.object_map = None
self.type_map = {}
self.name, _ = os.path.splitext(os.path.basename(source_buffer.name)) self.name, _ = os.path.splitext(os.path.basename(source_buffer.name))
asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine, asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine,
@ -40,60 +44,55 @@ class Source:
return cls(source.Buffer(f.read(), filename, 1), engine=engine) return cls(source.Buffer(f.read(), filename, 1), engine=engine)
class Module: class Module:
def __init__(self, src, ref_period=1e-6, attribute_writeback=True, remarks=False): def __init__(self, src, ref_period=1e-6):
self.attribute_writeback = attribute_writeback
self.engine = src.engine self.engine = src.engine
self.embedding_map = src.embedding_map self.function_map = src.function_map
self.name = src.name self.object_map = src.object_map
self.globals = src.globals self.type_map = src.type_map
int_monomorphizer = transforms.IntMonomorphizer(engine=self.engine) int_monomorphizer = transforms.IntMonomorphizer(engine=self.engine)
cast_monomorphizer = transforms.CastMonomorphizer(engine=self.engine)
inferencer = transforms.Inferencer(engine=self.engine) inferencer = transforms.Inferencer(engine=self.engine)
monomorphism_validator = validators.MonomorphismValidator(engine=self.engine) monomorphism_validator = validators.MonomorphismValidator(engine=self.engine)
escape_validator = validators.EscapeValidator(engine=self.engine) escape_validator = validators.EscapeValidator(engine=self.engine)
iodelay_estimator = transforms.IODelayEstimator(engine=self.engine, iodelay_estimator = transforms.IODelayEstimator(engine=self.engine,
ref_period=ref_period) ref_period=ref_period)
constness_validator = validators.ConstnessValidator(engine=self.engine) constness = analyses.Constness(engine=self.engine)
artiq_ir_generator = transforms.ARTIQIRGenerator(engine=self.engine, artiq_ir_generator = transforms.ARTIQIRGenerator(engine=self.engine,
module_name=src.name, module_name=src.name,
ref_period=ref_period, ref_period=ref_period)
embedding_map=self.embedding_map)
dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine) dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine)
local_access_validator = validators.LocalAccessValidator(engine=self.engine) local_access_validator = validators.LocalAccessValidator(engine=self.engine)
local_demoter = transforms.LocalDemoter()
constant_hoister = transforms.ConstantHoister()
devirtualization = analyses.Devirtualization() devirtualization = analyses.Devirtualization()
interleaver = transforms.Interleaver(engine=self.engine) interleaver = transforms.Interleaver(engine=self.engine)
invariant_detection = analyses.InvariantDetection(engine=self.engine)
self.name = src.name
self.globals = src.globals
int_monomorphizer.visit(src.typedtree) int_monomorphizer.visit(src.typedtree)
cast_monomorphizer.visit(src.typedtree)
inferencer.visit(src.typedtree) inferencer.visit(src.typedtree)
monomorphism_validator.visit(src.typedtree) monomorphism_validator.visit(src.typedtree)
escape_validator.visit(src.typedtree) escape_validator.visit(src.typedtree)
iodelay_estimator.visit_fixpoint(src.typedtree) iodelay_estimator.visit_fixpoint(src.typedtree)
constness_validator.visit(src.typedtree) constness.visit(src.typedtree)
devirtualization.visit(src.typedtree) devirtualization.visit(src.typedtree)
self.artiq_ir = artiq_ir_generator.visit(src.typedtree) self.artiq_ir = artiq_ir_generator.visit(src.typedtree)
artiq_ir_generator.annotate_calls(devirtualization) artiq_ir_generator.annotate_calls(devirtualization)
dead_code_eliminator.process(self.artiq_ir) dead_code_eliminator.process(self.artiq_ir)
interleaver.process(self.artiq_ir) interleaver.process(self.artiq_ir)
local_access_validator.process(self.artiq_ir) local_access_validator.process(self.artiq_ir)
local_demoter.process(self.artiq_ir)
constant_hoister.process(self.artiq_ir)
if remarks:
invariant_detection.process(self.artiq_ir)
# for subkernels: main kernel inferencer output, to be passed to further compilations
self.subkernel_arg_types = inferencer.subkernel_arg_types
def build_llvm_ir(self, target): def build_llvm_ir(self, target):
"""Compile the module to LLVM IR for the specified target.""" """Compile the module to LLVM IR for the specified target."""
llvm_ir_generator = transforms.LLVMIRGenerator( llvm_ir_generator = transforms.LLVMIRGenerator(
engine=self.engine, module_name=self.name, target=target, engine=self.engine, module_name=self.name, target=target,
embedding_map=self.embedding_map) function_map=self.function_map, object_map=self.object_map, type_map=self.type_map)
return llvm_ir_generator.process(self.artiq_ir, return llvm_ir_generator.process(self.artiq_ir, attribute_writeback=True)
attribute_writeback=self.attribute_writeback)
def entry_point(self):
"""Return the name of the function that is the entry point of this module."""
if self.name != "":
return self.name + ".__modinit__"
else:
return "__modinit__"
def __repr__(self): def __repr__(self):
printer = types.TypePrinter() printer = types.TypePrinter()

View File

@ -11,54 +11,39 @@ def globals():
"bool": builtins.fn_bool(), "bool": builtins.fn_bool(),
"int": builtins.fn_int(), "int": builtins.fn_int(),
"float": builtins.fn_float(), "float": builtins.fn_float(),
"str": builtins.fn_str(),
"bytes": builtins.fn_bytes(),
"bytearray": builtins.fn_bytearray(),
"list": builtins.fn_list(), "list": builtins.fn_list(),
"array": builtins.fn_array(),
"range": builtins.fn_range(), "range": builtins.fn_range(),
"int32": builtins.fn_int32(),
"int64": builtins.fn_int64(),
# Exception constructors # Exception constructors
"Exception": builtins.fn_Exception(), "Exception": builtins.fn_Exception(),
"IndexError": builtins.fn_IndexError(), "IndexError": builtins.fn_IndexError(),
"ValueError": builtins.fn_ValueError(), "ValueError": builtins.fn_ValueError(),
"ZeroDivisionError": builtins.fn_ZeroDivisionError(), "ZeroDivisionError": builtins.fn_ZeroDivisionError(),
"RuntimeError": builtins.fn_RuntimeError(),
# Built-in Python functions # Built-in Python functions
"len": builtins.fn_len(), "len": builtins.fn_len(),
"round": builtins.fn_round(), "round": builtins.fn_round(),
"abs": builtins.fn_abs(),
"min": builtins.fn_min(),
"max": builtins.fn_max(),
"print": builtins.fn_print(), "print": builtins.fn_print(),
# ARTIQ decorators # ARTIQ decorators
"kernel": builtins.fn_kernel(), "kernel": builtins.fn_kernel(),
"subkernel": builtins.fn_kernel(),
"portable": builtins.fn_kernel(), "portable": builtins.fn_kernel(),
"rpc": builtins.fn_kernel(),
# ARTIQ context managers # ARTIQ context managers
"parallel": builtins.obj_parallel(), "parallel": builtins.obj_parallel(),
"interleave": builtins.obj_interleave(), "interleave": builtins.obj_interleave(),
"sequential": builtins.obj_sequential(), "sequential": builtins.obj_sequential(),
"watchdog": builtins.fn_watchdog(),
# ARTIQ time management functions # ARTIQ time management functions
"delay": builtins.fn_delay(), "delay": builtins.fn_delay(),
"now_mu": builtins.fn_now_mu(), "now_mu": builtins.fn_now_mu(),
"delay_mu": builtins.fn_delay_mu(), "delay_mu": builtins.fn_delay_mu(),
"at_mu": builtins.fn_at_mu(), "at_mu": builtins.fn_at_mu(),
"mu_to_seconds": builtins.fn_mu_to_seconds(),
"seconds_to_mu": builtins.fn_seconds_to_mu(),
# ARTIQ utility functions # ARTIQ utility functions
"rtio_log": builtins.fn_rtio_log(), "rtio_log": builtins.fn_rtio_log(),
"core_log": builtins.fn_print(), "core_log": builtins.fn_print(),
# ARTIQ subkernel utility functions
"subkernel_await": builtins.fn_subkernel_await(),
"subkernel_preload": builtins.fn_subkernel_preload(),
"subkernel_send": builtins.fn_subkernel_send(),
"subkernel_recv": builtins.fn_subkernel_recv(),
} }

View File

@ -1,6 +1,6 @@
import os, sys, tempfile, subprocess, io import os, sys, tempfile, subprocess
from artiq.compiler import types, ir from artiq.compiler import types
from llvmlite import ir as ll, binding as llvm from llvmlite_artiq import ir as ll, binding as llvm
llvm.initialize() llvm.initialize()
llvm.initialize_all_targets() llvm.initialize_all_targets()
@ -8,46 +8,40 @@ llvm.initialize_all_asmprinters()
class RunTool: class RunTool:
def __init__(self, pattern, **tempdata): def __init__(self, pattern, **tempdata):
self._pattern = pattern self.files = []
self._tempdata = tempdata self.pattern = pattern
self._tempnames = {} self.tempdata = tempdata
self._tempfiles = {}
def maketemp(self, data):
f = tempfile.NamedTemporaryFile()
f.write(data)
f.flush()
self.files.append(f)
return f
def __enter__(self): def __enter__(self):
for key, data in self._tempdata.items(): tempfiles = {}
if data is None: tempnames = {}
fd, filename = tempfile.mkstemp() for key in self.tempdata:
os.close(fd) tempfiles[key] = self.maketemp(self.tempdata[key])
self._tempnames[key] = filename tempnames[key] = tempfiles[key].name
else:
with tempfile.NamedTemporaryFile(delete=False) as f:
f.write(data)
self._tempnames[key] = f.name
cmdline = [] cmdline = []
for argument in self._pattern: for argument in self.pattern:
cmdline.append(argument.format(**self._tempnames)) cmdline.append(argument.format(**tempnames))
# https://bugs.python.org/issue17023 process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
windows = os.name == "nt"
process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True, shell=windows)
stdout, stderr = process.communicate() stdout, stderr = process.communicate()
if process.returncode != 0: if process.returncode != 0:
raise Exception("{} invocation failed: {}". raise Exception("{} invocation failed: {}".
format(cmdline[0], stderr)) format(cmdline[0], stderr.decode('utf-8')))
self._tempfiles["__stdout__"] = io.StringIO(stdout) tempfiles["__stdout__"] = stdout.decode('utf-8')
for key in self._tempdata: return tempfiles
if self._tempdata[key] is None:
self._tempfiles[key] = open(self._tempnames[key], "rb")
return self._tempfiles
def __exit__(self, exc_typ, exc_value, exc_trace): def __exit__(self, exc_typ, exc_value, exc_trace):
for file in self._tempfiles.values(): for f in self.files:
file.close() f.close()
for filename in self._tempnames.values():
os.unlink(filename)
def _dump(target, kind, suffix, content): def _dump(target, kind, suffix, content):
if target is not None: if target is not None:
@ -69,46 +63,37 @@ class Target:
generated by the ARTIQ compiler will be deployed. generated by the ARTIQ compiler will be deployed.
:var triple: (string) :var triple: (string)
LLVM target triple, e.g. ``"riscv32"`` LLVM target triple, e.g. ``"or1k"``
:var data_layout: (string) :var data_layout: (string)
LLVM target data layout, e.g. ``"E-m:e-p:32:32-i64:32-f64:32-v64:32-v128:32-a:0:32-n32"`` LLVM target data layout, e.g. ``"E-m:e-p:32:32-i64:32-f64:32-v64:32-v128:32-a:0:32-n32"``
:var features: (list of string) :var features: (list of string)
LLVM target CPU features, e.g. ``["mul", "div", "ffl1"]`` LLVM target CPU features, e.g. ``["mul", "div", "ffl1"]``
:var additional_linker_options: (list of string)
Linker options for the target in addition to the target-independent ones, e.g. ``["--target2=rel"]``
:var print_function: (string) :var print_function: (string)
Name of a formatted print functions (with the signature of ``printf``) Name of a formatted print functions (with the signature of ``printf``)
provided by the target, e.g. ``"printf"``. provided by the target, e.g. ``"printf"``.
:var now_pinning: (boolean)
Whether the target implements the now-pinning RTIO optimization.
""" """
triple = "unknown" triple = "unknown"
data_layout = "" data_layout = ""
features = [] features = []
additional_linker_options = []
print_function = "printf" print_function = "printf"
now_pinning = True
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"
def __init__(self, subkernel_id=None): def __init__(self):
self.llcontext = ll.Context() self.llcontext = ll.Context()
self.subkernel_id = subkernel_id
def target_machine(self): def target_machine(self):
lltarget = llvm.Target.from_triple(self.triple) lltarget = llvm.Target.from_triple(self.triple)
llmachine = lltarget.create_target_machine( llmachine = lltarget.create_target_machine(
features=",".join(["+{}".format(f) for f in self.features]), features=",".join(["+{}".format(f) for f in self.features]),
reloc="pic", codemodel="default", reloc="pic", codemodel="default")
abiname="ilp32d" if isinstance(self, RV32GTarget) else "") llmachine.set_verbose(True)
llmachine.set_asm_verbosity(True)
return llmachine return llmachine
def optimize(self, llmodule): def optimize(self, llmodule):
llmachine = self.target_machine()
llpassmgr = llvm.create_module_pass_manager() llpassmgr = llvm.create_module_pass_manager()
llmachine.target_data.add_pass(llpassmgr)
llmachine.add_analysis_passes(llpassmgr)
# Register our alias analysis passes. # Register our alias analysis passes.
llpassmgr.add_basic_alias_analysis_pass() llpassmgr.add_basic_alias_analysis_pass()
@ -145,12 +130,8 @@ class Target:
print("====== MODULE_SIGNATURE DUMP ======", file=sys.stderr) print("====== MODULE_SIGNATURE DUMP ======", file=sys.stderr)
print(module, file=sys.stderr) print(module, file=sys.stderr)
if os.getenv("ARTIQ_IR_NO_LOC") is not None:
ir.BasicBlock._dump_loc = False
type_printer = types.TypePrinter() type_printer = types.TypePrinter()
suffix = "_subkernel_{}".format(self.subkernel_id) if self.subkernel_id is not None else "" _dump(os.getenv("ARTIQ_DUMP_IR"), "ARTIQ IR", ".txt",
_dump(os.getenv("ARTIQ_DUMP_IR"), "ARTIQ IR", suffix + ".txt",
lambda: "\n".join(fn.as_entity(type_printer) for fn in module.artiq_ir)) lambda: "\n".join(fn.as_entity(type_printer) for fn in module.artiq_ir))
llmod = module.build_llvm_ir(self) llmod = module.build_llvm_ir(self)
@ -162,12 +143,12 @@ class Target:
_dump("", "LLVM IR (broken)", ".ll", lambda: str(llmod)) _dump("", "LLVM IR (broken)", ".ll", lambda: str(llmod))
raise raise
_dump(os.getenv("ARTIQ_DUMP_UNOPT_LLVM"), "LLVM IR (generated)", suffix + "_unopt.ll", _dump(os.getenv("ARTIQ_DUMP_UNOPT_LLVM"), "LLVM IR (generated)", "_unopt.ll",
lambda: str(llparsedmod)) lambda: str(llparsedmod))
self.optimize(llparsedmod) self.optimize(llparsedmod)
_dump(os.getenv("ARTIQ_DUMP_LLVM"), "LLVM IR (optimized)", suffix + ".ll", _dump(os.getenv("ARTIQ_DUMP_LLVM"), "LLVM IR (optimized)", ".ll",
lambda: str(llparsedmod)) lambda: str(llparsedmod))
return llparsedmod return llparsedmod
@ -178,35 +159,30 @@ class Target:
_dump(os.getenv("ARTIQ_DUMP_ASM"), "Assembly", ".s", _dump(os.getenv("ARTIQ_DUMP_ASM"), "Assembly", ".s",
lambda: llmachine.emit_assembly(llmodule)) lambda: llmachine.emit_assembly(llmodule))
_dump(os.getenv("ARTIQ_DUMP_OBJ"), "Object file", ".o",
lambda: llmachine.emit_object(llmodule))
return llmachine.emit_object(llmodule) return llmachine.emit_object(llmodule)
def link(self, objects): def link(self, objects, init_fn):
"""Link the relocatable objects into a shared library for this target.""" """Link the relocatable objects into a shared library for this target."""
with RunTool([self.tool_ld, "-shared", "--eh-frame-hdr"] + with RunTool([self.triple + "-ld", "-shared", "--eh-frame-hdr", "-init", init_fn] +
self.additional_linker_options +
["-T" + os.path.join(os.path.dirname(__file__), "kernel.ld")] +
["{{obj{}}}".format(index) for index in range(len(objects))] + ["{{obj{}}}".format(index) for index in range(len(objects))] +
["-x"] +
["-o", "{output}"], ["-o", "{output}"],
output=None, output=b"",
**{"obj{}".format(index): obj for index, obj in enumerate(objects)}) \ **{"obj{}".format(index): obj for index, obj in enumerate(objects)}) \
as results: as results:
library = results["output"].read() library = results["output"].read()
_dump(os.getenv("ARTIQ_DUMP_ELF"), "Shared library", ".elf", _dump(os.getenv("ARTIQ_DUMP_ELF"), "Shared library", ".so",
lambda: library) lambda: library)
return library return library
def compile_and_link(self, modules): def compile_and_link(self, modules):
return self.link([self.assemble(self.compile(module)) for module in modules]) return self.link([self.assemble(self.compile(module)) for module in modules],
init_fn=modules[0].entry_point())
def strip(self, library): def strip(self, library):
with RunTool([self.tool_strip, "--strip-debug", "{library}", "-o", "{output}"], with RunTool([self.triple + "-strip", "--strip-debug", "{library}", "-o", "{output}"],
library=library, output=None) \ library=library, output=b"") \
as results: as results:
return results["output"].read() return results["output"].read()
@ -218,13 +194,12 @@ class Target:
# just after the call. Offset them back to get an address somewhere # just after the call. Offset them back to get an address somewhere
# inside the call instruction (or its delay slot), since that's what # inside the call instruction (or its delay slot), since that's what
# the backtrace entry should point at. # the backtrace entry should point at.
last_inlined = None
offset_addresses = [hex(addr - 1) for addr in addresses] offset_addresses = [hex(addr - 1) for addr in addresses]
with RunTool([self.tool_symbolizer, "--addresses", "--functions", "--inlines", with RunTool([self.triple + "-addr2line", "--addresses", "--functions", "--inlines",
"--demangle", "--output-style=GNU", "--exe={library}"] + offset_addresses, "--exe={library}"] + offset_addresses,
library=library) \ library=library) \
as results: as results:
lines = iter(results["__stdout__"].read().rstrip().split("\n")) lines = iter(results["__stdout__"].rstrip().split("\n"))
backtrace = [] backtrace = []
while True: while True:
try: try:
@ -234,76 +209,26 @@ class Target:
if address_or_function[:2] == "0x": if address_or_function[:2] == "0x":
address = int(address_or_function[2:], 16) + 1 # remove offset address = int(address_or_function[2:], 16) + 1 # remove offset
function = next(lines) function = next(lines)
inlined = False
else: else:
address = backtrace[-1][4] # inlined address = backtrace[-1][4] # inlined
function = address_or_function function = address_or_function
inlined = True
location = next(lines) location = next(lines)
filename, line = location.rsplit(":", 1) filename, line = location.rsplit(":", 1)
if filename == "??" or filename == "<synthesized>": if filename == "??" or filename == "<synthesized>":
continue continue
if line == "?":
line = -1
else:
line = int(line)
# can't get column out of addr2line D: # can't get column out of addr2line D:
if inlined: backtrace.append((filename, int(line), -1, function, address))
last_inlined.append((filename, line, -1, function, address))
else:
last_inlined = []
backtrace.append((filename, line, -1, function, address,
last_inlined))
return backtrace return backtrace
def demangle(self, names):
if not any(names):
return names
with RunTool([self.tool_cxxfilt] + names) as results:
return results["__stdout__"].read().rstrip().split("\n")
class NativeTarget(Target): class NativeTarget(Target):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.triple = llvm.get_default_triple() self.triple = llvm.get_default_triple()
self.data_layout = str(llvm.targets.Target.from_default_triple().create_target_machine().target_data)
class RV32IMATarget(Target): class OR1KTarget(Target):
triple = "riscv32-unknown-linux" triple = "or1k-linux"
data_layout = "e-m:e-p:32:32-i64:64-n32-S128" data_layout = "E-m:e-p:32:32-i8:8:8-i16:16:16-i64:32:32-" \
features = ["m", "a"] "f64:32:32-v64:32:32-v128:32:32-a0:0:32-n32"
additional_linker_options = ["-m", "elf32lriscv"] features = ["mul", "div", "ffl1", "cmov", "addc"]
print_function = "core_log" print_function = "core_log"
now_pinning = True
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"
class RV32GTarget(Target):
triple = "riscv32-unknown-linux"
data_layout = "e-m:e-p:32:32-i64:64-n32-S128"
features = ["m", "a", "f", "d"]
additional_linker_options = ["-m", "elf32lriscv"]
print_function = "core_log"
now_pinning = True
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"
class CortexA9Target(Target):
triple = "armv7-unknown-linux-gnueabihf"
data_layout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
features = ["dsp", "fp16", "neon", "vfp3"]
additional_linker_options = ["-m", "armelf_linux_eabi", "--target2=rel"]
print_function = "core_log"
now_pinning = False
tool_ld = "ld.lld"
tool_strip = "llvm-strip"
tool_symbolizer = "llvm-symbolizer"
tool_cxxfilt = "llvm-cxxfilt"

View File

@ -1,4 +1,4 @@
import sys, os, tokenize import sys, os
from artiq.master.databases import DeviceDB from artiq.master.databases import DeviceDB
from artiq.master.worker_db import DeviceManager from artiq.master.worker_db import DeviceManager
@ -24,10 +24,10 @@ def main():
else: else:
compile_only = False compile_only = False
ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py") ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.pyon")
dmgr = DeviceManager(DeviceDB(ddb_path)) dmgr = DeviceManager(DeviceDB(ddb_path))
with tokenize.open(sys.argv[1]) as f: with open(sys.argv[1]) as f:
testcase_code = compile(f.read(), f.name, "exec") testcase_code = compile(f.read(), f.name, "exec")
testcase_vars = {'__name__': 'testbench', 'dmgr': dmgr} testcase_vars = {'__name__': 'testbench', 'dmgr': dmgr}
exec(testcase_code, testcase_vars) exec(testcase_code, testcase_vars)
@ -38,6 +38,8 @@ def main():
core.compile(testcase_vars["entrypoint"], (), {}) core.compile(testcase_vars["entrypoint"], (), {})
else: else:
core.run(testcase_vars["entrypoint"], (), {}) core.run(testcase_vars["entrypoint"], (), {})
print(core.comm.get_log())
core.comm.clear_log()
except CompileError as error: except CompileError as error:
if not diag: if not diag:
exit(1) exit(1)

View File

@ -1,9 +1,8 @@
import sys, fileinput, os import sys, fileinput, os
from pythonparser import source, diagnostic, algorithm, parse_buffer from pythonparser import source, diagnostic, algorithm, parse_buffer
from .. import prelude, types from .. import prelude, types
from ..transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer, CastMonomorphizer from ..transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer
from ..transforms import IODelayEstimator from ..transforms import IODelayEstimator
from ..validators import ConstnessValidator
class Printer(algorithm.Visitor): class Printer(algorithm.Visitor):
""" """
@ -84,9 +83,7 @@ def main():
parsed, comments = parse_buffer(buf, engine=engine) parsed, comments = parse_buffer(buf, engine=engine)
typed = ASTTypedRewriter(engine=engine, prelude=prelude.globals()).visit(parsed) typed = ASTTypedRewriter(engine=engine, prelude=prelude.globals()).visit(parsed)
Inferencer(engine=engine).visit(typed) Inferencer(engine=engine).visit(typed)
ConstnessValidator(engine=engine).visit(typed)
if monomorphize: if monomorphize:
CastMonomorphizer(engine=engine).visit(typed)
IntMonomorphizer(engine=engine).visit(typed) IntMonomorphizer(engine=engine).visit(typed)
Inferencer(engine=engine).visit(typed) Inferencer(engine=engine).visit(typed)
if iodelay: if iodelay:

View File

@ -1,12 +1,8 @@
import sys, os, fileinput import sys, fileinput
from pythonparser import diagnostic from pythonparser import diagnostic
from .. import ir
from ..module import Module, Source from ..module import Module, Source
def main(): def main():
if os.getenv("ARTIQ_IR_NO_LOC") is not None:
ir.BasicBlock._dump_loc = False
def process_diagnostic(diag): def process_diagnostic(diag):
print("\n".join(diag.render())) print("\n".join(diag.render()))
if diag.level in ("fatal", "error"): if diag.level in ("fatal", "error"):

View File

@ -1,6 +1,6 @@
import os, sys, fileinput, ctypes import os, sys, fileinput, ctypes
from pythonparser import diagnostic from pythonparser import diagnostic
from llvmlite import binding as llvm from llvmlite_artiq import binding as llvm
from ..module import Module, Source from ..module import Module, Source
from ..targets import NativeTarget from ..targets import NativeTarget

View File

@ -1,6 +1,6 @@
import sys, fileinput import sys, fileinput
from pythonparser import diagnostic from pythonparser import diagnostic
from llvmlite import ir as ll from llvmlite_artiq import ir as ll
from ..module import Module, Source from ..module import Module, Source
from ..targets import NativeTarget from ..targets import NativeTarget

View File

@ -1,7 +1,7 @@
import sys, os import sys, os
from pythonparser import diagnostic from pythonparser import diagnostic
from ..module import Module, Source from ..module import Module, Source
from ..targets import RV32GTarget from ..targets import OR1KTarget
from . import benchmark from . import benchmark
def main(): def main():
@ -30,7 +30,7 @@ def main():
benchmark(lambda: Module(source), benchmark(lambda: Module(source),
"ARTIQ transforms and validators") "ARTIQ transforms and validators")
benchmark(lambda: RV32GTarget().compile_and_link([module]), benchmark(lambda: OR1KTarget().compile_and_link([module]),
"LLVM optimization and linking") "LLVM optimization and linking")
if __name__ == "__main__": if __name__ == "__main__":

View File

@ -1,14 +1,12 @@
import sys, os, tokenize import sys, os
from pythonparser import diagnostic from pythonparser import diagnostic
from ...language.environment import ProcessArgumentManager from ...master.databases import DeviceDB
from ...master.databases import DeviceDB, DatasetDB from ...master.worker_db import DeviceManager
from ...master.worker_db import DeviceManager, DatasetManager
from ..module import Module from ..module import Module
from ..embedding import Stitcher from ..embedding import Stitcher
from ..targets import RV32GTarget from ..targets import OR1KTarget
from . import benchmark from . import benchmark
def main(): def main():
if not len(sys.argv) == 2: if not len(sys.argv) == 2:
print("Expected exactly one module filename", file=sys.stderr) print("Expected exactly one module filename", file=sys.stderr)
@ -22,34 +20,28 @@ def main():
engine = diagnostic.Engine() engine = diagnostic.Engine()
engine.process = process_diagnostic engine.process = process_diagnostic
with tokenize.open(sys.argv[1]) as f: with open(sys.argv[1]) as f:
testcase_code = compile(f.read(), f.name, "exec") testcase_code = compile(f.read(), f.name, "exec")
testcase_vars = {'__name__': 'testbench'} testcase_vars = {'__name__': 'testbench'}
exec(testcase_code, testcase_vars) exec(testcase_code, testcase_vars)
device_db_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py") ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.pyon")
device_mgr = DeviceManager(DeviceDB(device_db_path)) dmgr = DeviceManager(DeviceDB(ddb_path))
dataset_db_path = os.path.join(os.path.dirname(sys.argv[1]), "dataset_db.mdb")
dataset_db = DatasetDB(dataset_db_path)
dataset_mgr = DatasetManager()
argument_mgr = ProcessArgumentManager({})
def embed(): def embed():
experiment = testcase_vars["Benchmark"]((device_mgr, dataset_mgr, argument_mgr)) experiment = testcase_vars["Benchmark"](dmgr)
stitcher = Stitcher(core=experiment.core, dmgr=device_mgr) stitcher = Stitcher(core=experiment.core, dmgr=dmgr)
stitcher.stitch_call(experiment.run, (), {}) stitcher.stitch_call(experiment.run, (), {})
stitcher.finalize() stitcher.finalize()
return stitcher return stitcher
stitcher = embed() stitcher = embed()
module = Module(stitcher) module = Module(stitcher)
target = RV32GTarget() target = OR1KTarget()
llvm_ir = target.compile(module) llvm_ir = target.compile(module)
elf_obj = target.assemble(llvm_ir) elf_obj = target.assemble(llvm_ir)
elf_shlib = target.link([elf_obj]) elf_shlib = target.link([elf_obj], init_fn=module.entry_point())
benchmark(lambda: embed(), benchmark(lambda: embed(),
"ARTIQ embedding") "ARTIQ embedding")
@ -63,13 +55,11 @@ def main():
benchmark(lambda: target.assemble(llvm_ir), benchmark(lambda: target.assemble(llvm_ir),
"LLVM machine code emission") "LLVM machine code emission")
benchmark(lambda: target.link([elf_obj]), benchmark(lambda: target.link([elf_obj], init_fn=module.entry_point()),
"Linking") "Linking")
benchmark(lambda: target.strip(elf_shlib), benchmark(lambda: target.strip(elf_shlib),
"Stripping debug information") "Stripping debug information")
dataset_db.close_db()
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -1,7 +1,7 @@
import sys, os import sys, os
from pythonparser import diagnostic from pythonparser import diagnostic
from ..module import Module, Source from ..module import Module, Source
from ..targets import RV32GTarget from ..targets import OR1KTarget
def main(): def main():
if not len(sys.argv) > 1: if not len(sys.argv) > 1:
@ -20,7 +20,7 @@ def main():
for filename in sys.argv[1:]: for filename in sys.argv[1:]:
modules.append(Module(Source.from_filename(filename, engine=engine))) modules.append(Module(Source.from_filename(filename, engine=engine)))
llobj = RV32GTarget().compile_and_link(modules) llobj = OR1KTarget().compile_and_link(modules)
basename, ext = os.path.splitext(sys.argv[-1]) basename, ext = os.path.splitext(sys.argv[-1])
with open(basename + ".so", "wb") as f: with open(basename + ".so", "wb") as f:

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -238,7 +238,7 @@ class ASTTypedRewriter(algorithm.Transformer):
body=node.body, decorator_list=node.decorator_list, body=node.body, decorator_list=node.decorator_list,
keyword_loc=node.keyword_loc, name_loc=node.name_loc, 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, arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs,
loc=node.loc, remote_fn=False) loc=node.loc)
try: try:
self.env_stack.append(node.typing_env) self.env_stack.append(node.typing_env)
@ -307,14 +307,8 @@ class ASTTypedRewriter(algorithm.Transformer):
self.in_class = old_in_class self.in_class = old_in_class
def visit_arg(self, node): def visit_arg(self, node):
if node.annotation is not None:
diag = diagnostic.Diagnostic("fatal",
"type annotations are not supported here", {},
node.annotation.loc)
self.engine.process(diag)
return asttyped.argT(type=self._find_name(node.arg, node.loc), return asttyped.argT(type=self._find_name(node.arg, node.loc),
arg=node.arg, annotation=None, arg=node.arg, annotation=self.visit(node.annotation),
arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc) arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc)
def visit_Num(self, node): def visit_Num(self, node):
@ -331,13 +325,8 @@ class ASTTypedRewriter(algorithm.Transformer):
n=node.n, loc=node.loc) n=node.n, loc=node.loc)
def visit_Str(self, node): def visit_Str(self, node):
if isinstance(node.s, str): return asttyped.StrT(type=builtins.TStr(),
typ = builtins.TStr() s=node.s,
elif isinstance(node.s, bytes):
typ = builtins.TBytes()
else:
assert False
return asttyped.StrT(type=typ, s=node.s,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
def visit_Name(self, node): def visit_Name(self, node):
@ -439,9 +428,8 @@ class ASTTypedRewriter(algorithm.Transformer):
def visit_Call(self, node): def visit_Call(self, node):
node = self.generic_visit(node) node = self.generic_visit(node)
node = asttyped.CallT(type=types.TVar(), iodelay=None, arg_exprs={}, node = asttyped.CallT(type=types.TVar(), iodelay=None, arg_exprs={},
remote_fn=False, func=node.func, func=node.func, args=node.args, keywords=node.keywords,
args=node.args, keywords=node.keywords,
starargs=node.starargs, kwargs=node.kwargs, starargs=node.starargs, kwargs=node.kwargs,
star_loc=node.star_loc, dstar_loc=node.dstar_loc, star_loc=node.star_loc, dstar_loc=node.dstar_loc,
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
@ -515,7 +503,7 @@ class ASTTypedRewriter(algorithm.Transformer):
visit_DictComp = visit_unsupported visit_DictComp = visit_unsupported
visit_Ellipsis = visit_unsupported visit_Ellipsis = visit_unsupported
visit_GeneratorExp = visit_unsupported visit_GeneratorExp = visit_unsupported
# visit_Set = visit_unsupported visit_Set = visit_unsupported
visit_SetComp = visit_unsupported visit_SetComp = visit_unsupported
visit_Starred = visit_unsupported visit_Starred = visit_unsupported
visit_Yield = visit_unsupported visit_Yield = visit_unsupported

View File

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

View File

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

View File

@ -15,26 +15,13 @@ class DeadCodeEliminator:
self.process_function(func) self.process_function(func)
def process_function(self, func): def process_function(self, func):
# defer removing those blocks, so our use checks will ignore deleted blocks modified = True
preserve = [func.entry()] while modified:
work_list = [func.entry()] modified = False
while any(work_list): for block in list(func.basic_blocks):
block = work_list.pop() if not any(block.predecessors()) and block != func.entry():
for succ in block.successors(): self.remove_block(block)
if succ not in preserve: modified = True
preserve.append(succ)
work_list.append(succ)
to_be_removed = []
for block in func.basic_blocks:
if block not in preserve:
block.is_removed = True
to_be_removed.append(block)
for insn in block.instructions:
insn.is_removed = True
for block in to_be_removed:
self.remove_block(block)
modified = True modified = True
while modified: while modified:
@ -46,8 +33,7 @@ class DeadCodeEliminator:
# it also has to run after the interleaver, but interleaver # it also has to run after the interleaver, but interleaver
# doesn't like to work with IR before DCE. # doesn't like to work with IR before DCE.
if isinstance(insn, (ir.Phi, ir.Alloc, ir.GetAttr, ir.GetElem, ir.Coerce, if isinstance(insn, (ir.Phi, ir.Alloc, ir.GetAttr, ir.GetElem, ir.Coerce,
ir.Arith, ir.Compare, ir.Select, ir.Quote, ir.Closure, ir.Arith, ir.Compare, ir.Select, ir.Quote, ir.Closure)) \
ir.Offset)) \
and not any(insn.uses): and not any(insn.uses):
insn.erase() insn.erase()
modified = True modified = True
@ -55,8 +41,6 @@ class DeadCodeEliminator:
def remove_block(self, block): def remove_block(self, block):
# block.uses are updated while iterating # block.uses are updated while iterating
for use in set(block.uses): for use in set(block.uses):
if use.is_removed:
continue
if isinstance(use, ir.Phi): if isinstance(use, ir.Phi):
use.remove_incoming_block(block) use.remove_incoming_block(block)
if not any(use.operands): if not any(use.operands):
@ -71,8 +55,6 @@ class DeadCodeEliminator:
def remove_instruction(self, insn): def remove_instruction(self, insn):
for use in set(insn.uses): for use in set(insn.uses):
if use.is_removed:
continue
if isinstance(use, ir.Phi): if isinstance(use, ir.Phi):
use.remove_incoming_value(insn) use.remove_incoming_value(insn)
if not any(use.operands): if not any(use.operands):

File diff suppressed because it is too large Load Diff

View File

@ -5,7 +5,7 @@ do not.
""" """
from pythonparser import algorithm, diagnostic from pythonparser import algorithm, diagnostic
from .. import types, builtins, asttyped from .. import types, builtins
class IntMonomorphizer(algorithm.Visitor): class IntMonomorphizer(algorithm.Visitor):
def __init__(self, engine): def __init__(self, engine):
@ -14,9 +14,9 @@ class IntMonomorphizer(algorithm.Visitor):
def visit_NumT(self, node): def visit_NumT(self, node):
if builtins.is_int(node.type): if builtins.is_int(node.type):
if types.is_var(node.type["width"]): if types.is_var(node.type["width"]):
if -2**31 <= node.n <= 2**31-1: if -2**31 < node.n < 2**31-1:
width = 32 width = 32
elif -2**63 <= node.n <= 2**63-1: elif -2**63 < node.n < 2**63-1:
width = 64 width = 64
else: else:
diag = diagnostic.Diagnostic("error", diag = diagnostic.Diagnostic("error",
@ -26,3 +26,12 @@ class IntMonomorphizer(algorithm.Visitor):
return return
node.type["width"].unify(types.TValue(width)) node.type["width"].unify(types.TValue(width))
def visit_CallT(self, node):
self.generic_visit(node)
if types.is_builtin(node.func.type, "int") or \
types.is_builtin(node.func.type, "round"):
typ = node.type.find()
if types.is_var(typ["width"]):
typ["width"].unify(types.TValue(32))

View File

@ -280,7 +280,7 @@ class IODelayEstimator(algorithm.Visitor):
context="as an argument for delay_mu()") context="as an argument for delay_mu()")
call_delay = value call_delay = value
elif not types.is_builtin(typ): elif not types.is_builtin(typ):
if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(typ): if types.is_function(typ) or types.is_rpc(typ):
offset = 0 offset = 0
elif types.is_method(typ): elif types.is_method(typ):
offset = 1 offset = 1
@ -288,7 +288,7 @@ class IODelayEstimator(algorithm.Visitor):
else: else:
assert False assert False
if types.is_rpc(typ) or types.is_subkernel(typ): if types.is_rpc(typ):
call_delay = iodelay.Const(0) call_delay = iodelay.Const(0)
else: else:
delay = typ.find().delay.find() delay = typ.find().delay.find()
@ -311,20 +311,13 @@ class IODelayEstimator(algorithm.Visitor):
args[arg_name] = arg_node args[arg_name] = arg_node
free_vars = delay.duration.free_vars() free_vars = delay.duration.free_vars()
try: node.arg_exprs = {
node.arg_exprs = { arg: self.evaluate(args[arg], abort=abort,
arg: self.evaluate(args[arg], abort=abort, context="in the expression for argument '{}' "
context="in the expression for argument '{}' " "that affects I/O delay".format(arg))
"that affects I/O delay".format(arg)) for arg in free_vars
for arg in free_vars }
} call_delay = delay.duration.fold(node.arg_exprs)
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: else:
assert False assert False
else: else:

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

@ -3,7 +3,6 @@ The :mod:`types` module contains the classes describing the types
in :mod:`asttyped`. in :mod:`asttyped`.
""" """
import builtins
import string import string
from collections import OrderedDict from collections import OrderedDict
from . import iodelay from . import iodelay
@ -56,39 +55,38 @@ class TVar(Type):
def __init__(self): def __init__(self):
self.parent = self self.parent = self
self.rank = 0
def find(self): def find(self):
parent = self.parent if self.parent is self:
if parent is self:
return self return self
else: else:
# The recursive find() invocation is turned into a loop # The recursive find() invocation is turned into a loop
# because paths resulting from unification of large arrays # because paths resulting from unification of large arrays
# can easily cause a stack overflow. # can easily cause a stack overflow.
root = self root = self
while parent.__class__ == TVar and root is not parent: while root.__class__ == TVar:
_, parent = root, root.parent = parent, parent.parent if root is root.parent:
return root.parent break
else:
root = root.parent
# path compression
iter = self
while iter.__class__ == TVar:
if iter is iter.parent:
break
else:
iter, iter.parent = iter.parent, root
return root
def unify(self, other): def unify(self, other):
if other is self: other = other.find()
return
x = other.find() if self.parent is self:
y = self.find() self.parent = other
if x is y:
return
if y.__class__ == TVar:
if x.__class__ == TVar:
if x.rank < y.rank:
x, y = y, x
y.parent = x
if x.rank == y.rank:
x.rank += 1
else:
y.parent = x
else: else:
y.unify(x) self.find().unify(other)
def fold(self, accum, fn): def fold(self, accum, fn):
if self.parent is self: if self.parent is self:
@ -97,8 +95,6 @@ class TVar(Type):
return self.find().fold(accum, fn) return self.find().fold(accum, fn)
def __repr__(self): def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
if self.parent is self: if self.parent is self:
return "<artiq.compiler.types.TVar %d>" % id(self) return "<artiq.compiler.types.TVar %d>" % id(self)
else: else:
@ -128,8 +124,6 @@ class TMono(Type):
return self return self
def unify(self, other): def unify(self, other):
if other is self:
return
if isinstance(other, TMono) and self.name == other.name: if isinstance(other, TMono) and self.name == other.name:
assert self.params.keys() == other.params.keys() assert self.params.keys() == other.params.keys()
for param in self.params: for param in self.params:
@ -145,8 +139,6 @@ class TMono(Type):
return fn(accum, self) return fn(accum, self)
def __repr__(self): def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TMono(%s, %s)" % (repr(self.name), repr(self.params)) return "artiq.compiler.types.TMono(%s, %s)" % (repr(self.name), repr(self.params))
def __getitem__(self, param): def __getitem__(self, param):
@ -179,8 +171,6 @@ class TTuple(Type):
return self return self
def unify(self, other): def unify(self, other):
if other is self:
return
if isinstance(other, TTuple) and len(self.elts) == len(other.elts): if isinstance(other, TTuple) and len(self.elts) == len(other.elts):
for selfelt, otherelt in zip(self.elts, other.elts): for selfelt, otherelt in zip(self.elts, other.elts):
selfelt.unify(otherelt) selfelt.unify(otherelt)
@ -195,8 +185,6 @@ class TTuple(Type):
return fn(accum, self) return fn(accum, self)
def __repr__(self): def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TTuple(%s)" % repr(self.elts) return "artiq.compiler.types.TTuple(%s)" % repr(self.elts)
def __eq__(self, other): def __eq__(self, other):
@ -206,14 +194,9 @@ class TTuple(Type):
def __ne__(self, other): def __ne__(self, other):
return not (self == other) return not (self == other)
def __hash__(self):
return hash(tuple(self.elts))
class _TPointer(TMono): class _TPointer(TMono):
def __init__(self, elt=None): def __init__(self):
if elt is None: super().__init__("pointer")
elt = TMono("int", {"width": 8}) # i8*
super().__init__("pointer", params={"elt": elt})
class TFunction(Type): class TFunction(Type):
""" """
@ -251,8 +234,6 @@ class TFunction(Type):
return self return self
def unify(self, other): def unify(self, other):
if other is self:
return
if isinstance(other, TFunction) and \ if isinstance(other, TFunction) and \
self.args.keys() == other.args.keys() and \ self.args.keys() == other.args.keys() and \
self.optargs.keys() == other.optargs.keys(): self.optargs.keys() == other.optargs.keys():
@ -275,8 +256,6 @@ class TFunction(Type):
return fn(accum, self) return fn(accum, self)
def __repr__(self): def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TFunction({}, {}, {})".format( return "artiq.compiler.types.TFunction({}, {}, {})".format(
repr(self.args), repr(self.optargs), repr(self.ret)) repr(self.args), repr(self.optargs), repr(self.ret))
@ -291,29 +270,20 @@ class TFunction(Type):
def __hash__(self): def __hash__(self):
return hash((_freeze(self.args), _freeze(self.optargs), self.ret)) return hash((_freeze(self.args), _freeze(self.optargs), self.ret))
class TExternalFunction(TFunction): class TCFunction(TFunction):
""" """
A type of an externally-provided function. A function type of a runtime-provided C function.
This can be any function following the C ABI, such as provided by the :ivar name: (str) C function name
C/Rust runtime, or a compiler backend intrinsic. The mangled name to link :ivar flags: (set of str) C function flags.
against is encoded as part of the type.
:ivar name: (str) external symbol name.
This will be the symbol linked against (following any extra C name
mangling rules).
:ivar flags: (set of str) function flags.
Flag ``nounwind`` means the function never raises an exception. Flag ``nounwind`` means the function never raises an exception.
Flag ``nowrite`` means the function never accesses any memory Flag ``nowrite`` means the function never writes any memory
that the ARTIQ Python code can observe. that the ARTIQ Python code can observe.
:ivar broadcast_across_arrays: (bool)
If True, the function is transparently applied element-wise when called
with TArray arguments.
""" """
attributes = OrderedDict() attributes = OrderedDict()
def __init__(self, args, ret, name, flags=set(), broadcast_across_arrays=False): def __init__(self, args, ret, name, flags={}):
assert isinstance(flags, set) assert isinstance(flags, set)
for flag in flags: for flag in flags:
assert flag in {'nounwind', 'nowrite'} assert flag in {'nounwind', 'nowrite'}
@ -321,12 +291,9 @@ class TExternalFunction(TFunction):
self.name = name self.name = name
self.delay = TFixedDelay(iodelay.Const(0)) self.delay = TFixedDelay(iodelay.Const(0))
self.flags = flags self.flags = flags
self.broadcast_across_arrays = broadcast_across_arrays
def unify(self, other): def unify(self, other):
if other is self: if isinstance(other, TCFunction) and \
return
if isinstance(other, TExternalFunction) and \
self.name == other.name: self.name == other.name:
super().unify(other) super().unify(other)
elif isinstance(other, TVar): elif isinstance(other, TVar):
@ -341,24 +308,20 @@ class TRPC(Type):
:ivar ret: (:class:`Type`) :ivar ret: (:class:`Type`)
return type return type
:ivar service: (int) RPC service number :ivar service: (int) RPC service number
:ivar is_async: (bool) whether the RPC blocks until return
""" """
attributes = OrderedDict() attributes = OrderedDict()
def __init__(self, ret, service, is_async=False): def __init__(self, ret, service):
assert isinstance(ret, Type) assert isinstance(ret, Type)
self.ret, self.service, self.is_async = ret, service, is_async self.ret, self.service = ret, service
def find(self): def find(self):
return self return self
def unify(self, other): def unify(self, other):
if other is self:
return
if isinstance(other, TRPC) and \ if isinstance(other, TRPC) and \
self.service == other.service and \ self.service == other.service:
self.is_async == other.is_async:
self.ret.unify(other.ret) self.ret.unify(other.ret)
elif isinstance(other, TVar): elif isinstance(other, TVar):
other.unify(self) other.unify(self)
@ -370,14 +333,11 @@ class TRPC(Type):
return fn(accum, self) return fn(accum, self)
def __repr__(self): def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TRPC({})".format(repr(self.ret)) return "artiq.compiler.types.TRPC({})".format(repr(self.ret))
def __eq__(self, other): def __eq__(self, other):
return isinstance(other, TRPC) and \ return isinstance(other, TRPC) and \
self.service == other.service and \ self.service == other.service
self.is_async == other.is_async
def __ne__(self, other): def __ne__(self, other):
return not (self == other) return not (self == other)
@ -385,50 +345,6 @@ class TRPC(Type):
def __hash__(self): def __hash__(self):
return hash(self.service) 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): class TBuiltin(Type):
""" """
An instance of builtin type. Every instance of a builtin An instance of builtin type. Every instance of a builtin
@ -444,8 +360,6 @@ class TBuiltin(Type):
return self return self
def unify(self, other): def unify(self, other):
if other is self:
return
if self != other: if self != other:
raise UnificationError(self, other) raise UnificationError(self, other)
@ -453,8 +367,6 @@ class TBuiltin(Type):
return fn(accum, self) return fn(accum, self)
def __repr__(self): def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.{}({})".format(type(self).__name__, repr(self.name)) return "artiq.compiler.types.{}({})".format(type(self).__name__, repr(self.name))
def __eq__(self, other): def __eq__(self, other):
@ -470,11 +382,6 @@ class TBuiltin(Type):
class TBuiltinFunction(TBuiltin): class TBuiltinFunction(TBuiltin):
""" """
A type of a builtin function. A type of a builtin function.
Builtin functions are treated specially throughout all stages of the
compilation process according to their name (e.g. calls may not actually
lower to a function call). See :class:`TExternalFunction` for externally
defined functions that are otherwise regular.
""" """
class TConstructor(TBuiltin): class TConstructor(TBuiltin):
@ -515,28 +422,9 @@ class TInstance(TMono):
self.constant_attributes = set() self.constant_attributes = set()
def __repr__(self): def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TInstance({}, {})".format( return "artiq.compiler.types.TInstance({}, {})".format(
repr(self.name), repr(self.attributes)) repr(self.name), repr(self.attributes))
class TModule(TMono):
"""
A type of a module.
"""
def __init__(self, name, attributes):
assert isinstance(attributes, OrderedDict)
super().__init__(name)
self.attributes = attributes
self.constant_attributes = set()
def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TModule({}, {})".format(
repr(self.name), repr(self.attributes))
class TMethod(TMono): class TMethod(TMono):
""" """
A type of a method. A type of a method.
@ -562,8 +450,6 @@ class TValue(Type):
return self return self
def unify(self, other): def unify(self, other):
if other is self:
return
if isinstance(other, TVar): if isinstance(other, TVar):
other.unify(self) other.unify(self)
elif self != other: elif self != other:
@ -573,8 +459,6 @@ class TValue(Type):
return fn(accum, self) return fn(accum, self)
def __repr__(self): def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
return "artiq.compiler.types.TValue(%s)" % repr(self.value) return "artiq.compiler.types.TValue(%s)" % repr(self.value)
def __eq__(self, other): def __eq__(self, other):
@ -633,8 +517,6 @@ class TDelay(Type):
return not (self == other) return not (self == other)
def __repr__(self): def __repr__(self):
if getattr(builtins, "__in_sphinx__", False):
return str(self)
if self.duration is None: if self.duration is None:
return "<{}.TIndeterminateDelay>".format(__name__) return "<{}.TIndeterminateDelay>".format(__name__)
elif self.cause is None: elif self.cause is None:
@ -658,15 +540,13 @@ def is_mono(typ, name=None, **params):
if not isinstance(typ, TMono): if not isinstance(typ, TMono):
return False return False
if name is not None and typ.name != name: params_match = True
return False
for param in params: for param in params:
if param not in typ.params: if param not in typ.params:
return False return False
if typ.params[param].find() != params[param].find(): params_match = params_match and \
return False typ.params[param].find() == params[param].find()
return True return name is None or (typ.name == name and params_match)
def is_polymorphic(typ): def is_polymorphic(typ):
return typ.fold(False, lambda accum, typ: accum or is_var(typ)) return typ.fold(False, lambda accum, typ: accum or is_var(typ))
@ -688,15 +568,12 @@ def is_function(typ):
def is_rpc(typ): def is_rpc(typ):
return isinstance(typ.find(), TRPC) return isinstance(typ.find(), TRPC)
def is_subkernel(typ): def is_c_function(typ, name=None):
return isinstance(typ.find(), TSubkernel)
def is_external_function(typ, name=None):
typ = typ.find() typ = typ.find()
if name is None: if name is None:
return isinstance(typ, TExternalFunction) return isinstance(typ, TCFunction)
else: else:
return isinstance(typ, TExternalFunction) and \ return isinstance(typ, TCFunction) and \
typ.name == name typ.name == name
def is_builtin(typ, name=None): def is_builtin(typ, name=None):
@ -707,23 +584,6 @@ def is_builtin(typ, name=None):
return isinstance(typ, TBuiltin) and \ return isinstance(typ, TBuiltin) and \
typ.name == name typ.name == name
def is_builtin_function(typ, name=None):
typ = typ.find()
if name is None:
return isinstance(typ, TBuiltinFunction)
else:
return isinstance(typ, TBuiltinFunction) and \
typ.name == name
def is_broadcast_across_arrays(typ):
# For now, broadcasting is only exposed to predefined external functions, and
# statically selected. Might be extended to user-defined functions if the design
# pans out.
typ = typ.find()
if not isinstance(typ, TExternalFunction):
return False
return typ.broadcast_across_arrays
def is_constructor(typ, name=None): def is_constructor(typ, name=None):
typ = typ.find() typ = typ.find()
if name is not None: if name is not None:
@ -748,14 +608,6 @@ def is_instance(typ, name=None):
else: else:
return isinstance(typ, TInstance) return isinstance(typ, TInstance)
def is_module(typ, name=None):
typ = typ.find()
if name is not None:
return isinstance(typ, TModule) and \
typ.name == name
else:
return isinstance(typ, TModule)
def is_method(typ): def is_method(typ):
return isinstance(typ.find(), TMethod) return isinstance(typ.find(), TMethod)
@ -795,8 +647,6 @@ class TypePrinter(object):
type variables sequential alphabetic names. type variables sequential alphabetic names.
""" """
custom_printers = {}
def __init__(self): def __init__(self):
self.gen = genalnum() self.gen = genalnum()
self.map = {} self.map = {}
@ -821,21 +671,17 @@ class TypePrinter(object):
self.recurse_guard.add(typ) self.recurse_guard.add(typ)
return "<instance {} {{}}>".format(typ.name) return "<instance {} {{}}>".format(typ.name)
elif isinstance(typ, TMono): elif isinstance(typ, TMono):
if typ.name in self.custom_printers: if typ.params == {}:
return self.custom_printers[typ.name](typ, self, depth + 1, max_depth)
elif typ.params == {}:
return typ.name return typ.name
else: else:
return "%s(%s)" % (typ.name, ", ".join( return "%s(%s)" % (typ.name, ", ".join(
["%s=%s" % (k, self.name(typ.params[k], depth + 1)) for k in typ.params])) ["%s=%s" % (k, self.name(typ.params[k], depth + 1)) for k in typ.params]))
elif isinstance(typ, _TPointer):
return "{}*".format(self.name(typ["elt"], depth + 1))
elif isinstance(typ, TTuple): elif isinstance(typ, TTuple):
if len(typ.elts) == 1: if len(typ.elts) == 1:
return "(%s,)" % self.name(typ.elts[0], depth + 1) return "(%s,)" % self.name(typ.elts[0], depth + 1)
else: else:
return "(%s)" % ", ".join([self.name(typ, depth + 1) for typ in typ.elts]) return "(%s)" % ", ".join([self.name(typ, depth + 1) for typ in typ.elts])
elif isinstance(typ, (TFunction, TExternalFunction)): elif isinstance(typ, (TFunction, TCFunction)):
args = [] args = []
args += [ "%s:%s" % (arg, self.name(typ.args[arg], depth + 1)) args += [ "%s:%s" % (arg, self.name(typ.args[arg], depth + 1))
for arg in typ.args] for arg in typ.args]
@ -849,18 +695,12 @@ class TypePrinter(object):
elif not (delay.is_fixed() and iodelay.is_zero(delay.duration)): elif not (delay.is_fixed() and iodelay.is_zero(delay.duration)):
signature += " " + self.name(delay, depth + 1) signature += " " + self.name(delay, depth + 1)
if isinstance(typ, TExternalFunction): if isinstance(typ, TCFunction):
return "[ffi {}]{}".format(repr(typ.name), signature) return "[ffi {}]{}".format(repr(typ.name), signature)
elif isinstance(typ, TFunction): elif isinstance(typ, TFunction):
return signature return signature
elif isinstance(typ, TRPC): elif isinstance(typ, TRPC):
return "[rpc{} #{}](...)->{}".format(typ.service, return "[rpc #{}](...)->{}".format(typ.service, self.name(typ.ret, depth + 1))
" 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): elif isinstance(typ, TBuiltinFunction):
return "<function {}>".format(typ.name) return "<function {}>".format(typ.name)
elif isinstance(typ, (TConstructor, TExceptionConstructor)): elif isinstance(typ, (TConstructor, TExceptionConstructor)):

View File

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

View File

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

View File

@ -51,6 +51,10 @@ class Region:
(other.range.begin_pos <= self.range.begin_pos <= other.range.end_pos and \ (other.range.begin_pos <= self.range.begin_pos <= other.range.end_pos and \
self.range.end_pos > other.range.end_pos) self.range.end_pos > other.range.end_pos)
def contract(self, other):
if not self.range:
self.range = other.range
def outlives(lhs, rhs): def outlives(lhs, rhs):
if not isinstance(lhs, Region): # lhs lives nonlexically if not isinstance(lhs, Region): # lhs lives nonlexically
return True return True
@ -65,11 +69,8 @@ class Region:
class RegionOf(algorithm.Visitor): class RegionOf(algorithm.Visitor):
""" """
Visit an expression and return the region that must be alive for the Visit an expression and return the list of regions that must
expression to execute. be alive for the expression to execute.
For expressions involving multiple regions, the shortest-lived one is
returned.
""" """
def __init__(self, env_stack, youngest_region): def __init__(self, env_stack, youngest_region):
@ -99,23 +100,11 @@ class RegionOf(algorithm.Visitor):
visit_BinOpT = visit_sometimes_allocating visit_BinOpT = visit_sometimes_allocating
def visit_CallT(self, node): def visit_CallT(self, node):
if types.is_external_function(node.func.type, "cache_get"): if types.is_c_function(node.func.type, "cache_get"):
# The cache is borrow checked dynamically # The cache is borrow checked dynamically
return Global() return Global()
else:
if (types.is_builtin_function(node.func.type, "array") self.visit_sometimes_allocating(node)
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, # Value lives as long as the object/container, if it's mutable,
# or else forever # or else forever
@ -168,7 +157,7 @@ class RegionOf(algorithm.Visitor):
visit_NameConstantT = visit_immutable visit_NameConstantT = visit_immutable
visit_NumT = visit_immutable visit_NumT = visit_immutable
visit_EllipsisT = visit_immutable visit_EllipsisT = visit_immutable
visit_UnaryOpT = visit_sometimes_allocating # possibly array op visit_UnaryOpT = visit_immutable
visit_CompareT = visit_immutable visit_CompareT = visit_immutable
# Value lives forever # Value lives forever
@ -309,32 +298,37 @@ class EscapeValidator(algorithm.Visitor):
# and exceptions can only refer to strings, so we don't actually check # and exceptions can only refer to strings, so we don't actually check
# this property. But we will need to, if string operations are ever added. # this property. But we will need to, if string operations are ever added.
def visit_assignment(self, target, value): def visit_assignment(self, target, value, is_aug_assign=False):
value_region = self._region_of(value) value_region = self._region_of(value) if not is_aug_assign else self.youngest_region
# If this is a variable, we might need to contract the live range.
if isinstance(value_region, Region):
for name in self._names_of(target):
region = self._region_of(name)
if isinstance(region, Region):
region.contract(value_region)
# If we assign to an attribute of a quoted value, there will be no names # If we assign to an attribute of a quoted value, there will be no names
# in the assignment lhs. # in the assignment lhs.
target_names = self._names_of(target) or [] target_names = self._names_of(target) or []
# Adopt the value region for any variables declared on the lhs.
for name in target_names:
region = self._region_of(name)
if isinstance(region, Region) and not region.present():
# Find the name's environment to overwrite the region.
for env in self.env_stack[::-1]:
if name.id in env:
env[name.id] = value_region
break
# The assigned value should outlive the assignee # The assigned value should outlive the assignee
target_regions = [self._region_of(name) for name in target_names] target_regions = [self._region_of(name) for name in target_names]
for target_region in target_regions: for target_region in target_regions:
if not Region.outlives(value_region, target_region): if not Region.outlives(value_region, target_region):
if is_aug_assign:
target_desc = "the assignment target, allocated here,"
else:
target_desc = "the assignment target"
note = diagnostic.Diagnostic("note",
"this expression has type {type}",
{"type": types.TypePrinter().name(value.type)},
value.loc)
diag = diagnostic.Diagnostic("error", diag = diagnostic.Diagnostic("error",
"the assigned value does not outlive the assignment target", {}, "the assigned value does not outlive the assignment target", {},
value.loc, [target.loc], value.loc, [target.loc],
notes=self._diagnostics_for(target_region, target.loc, notes=self._diagnostics_for(target_region, target.loc,
"the assignment target") + target_desc) +
self._diagnostics_for(value_region, value.loc, self._diagnostics_for(value_region, value.loc,
"the assigned value")) "the assigned value"))
self.engine.process(diag) self.engine.process(diag)
@ -344,20 +338,10 @@ class EscapeValidator(algorithm.Visitor):
self.visit_assignment(target, node.value) self.visit_assignment(target, node.value)
def visit_AugAssign(self, node): def visit_AugAssign(self, node):
if builtins.is_list(node.target.type): if builtins.is_allocated(node.target.type):
note = diagnostic.Diagnostic("note", # If the target is mutable, op-assignment will allocate
"try using `{lhs} = {lhs} {op} {rhs}` instead", # in the youngest region.
{"lhs": node.target.loc.source(), self.visit_assignment(node.target, node.value, is_aug_assign=True)
"rhs": node.value.loc.source(),
"op": node.op.loc.source()[:-1]},
node.loc)
diag = diagnostic.Diagnostic("error",
"lists cannot be mutated in-place", {},
node.op.loc, [node.target.loc],
notes=[note])
self.engine.process(diag)
self.visit_assignment(node.target, node.value)
def visit_Return(self, node): def visit_Return(self, node):
region = self._region_of(node.value) region = self._region_of(node.value)
@ -367,6 +351,6 @@ class EscapeValidator(algorithm.Visitor):
{"type": types.TypePrinter().name(node.value.type)}, {"type": types.TypePrinter().name(node.value.type)},
node.value.loc) node.value.loc)
diag = diagnostic.Diagnostic("error", diag = diagnostic.Diagnostic("error",
"cannot return an allocated value that does not live forever", {}, "cannot return a mutable value that does not live forever", {},
node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note]) node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note])
self.engine.process(diag) self.engine.process(diag)

View File

@ -1,3 +1,11 @@
from artiq.coredevice.exceptions import (RTIOUnderflow, RTIOOverflow) from artiq.coredevice import exceptions, dds, spi
from artiq.coredevice.exceptions import (RTIOUnderflow, RTIOSequenceError,
RTIOCollision, RTIOOverflow, RTIOBusy)
from artiq.coredevice.dds import (PHASE_MODE_CONTINUOUS, PHASE_MODE_ABSOLUTE,
PHASE_MODE_TRACKING)
__all__ = ["RTIOUnderflow", "RTIOOverflow"] __all__ = []
__all__ += ["RTIOUnderflow", "RTIOSequenceError", "RTIOCollision",
"RTIOOverflow", "RTIOBusy"]
__all__ += ["PHASE_MODE_CONTINUOUS", "PHASE_MODE_ABSOLUTE",
"PHASE_MODE_TRACKING"]

175
artiq/coredevice/ad5360.py Normal file
View File

@ -0,0 +1,175 @@
from artiq.language.core import (kernel, portable, delay_mu, delay,
seconds_to_mu)
from artiq.language.units import ns, us
from artiq.coredevice import spi
# Designed from the data sheets and somewhat after the linux kernel
# iio driver.
_AD5360_SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_CS_POLARITY |
0*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
_AD5360_CMD_DATA = 3 << 22
_AD5360_CMD_OFFSET = 2 << 22
_AD5360_CMD_GAIN = 1 << 22
_AD5360_CMD_SPECIAL = 0 << 22
@portable
def _AD5360_WRITE_CHANNEL(c):
return (c + 8) << 16
_AD5360_SPECIAL_NOP = 0 << 16
_AD5360_SPECIAL_CONTROL = 1 << 16
_AD5360_SPECIAL_OFS0 = 2 << 16
_AD5360_SPECIAL_OFS1 = 3 << 16
_AD5360_SPECIAL_READ = 5 << 16
@portable
def _AD5360_READ_CHANNEL(ch):
return (ch + 8) << 7
_AD5360_READ_X1A = 0x000 << 7
_AD5360_READ_X1B = 0x040 << 7
_AD5360_READ_OFFSET = 0x080 << 7
_AD5360_READ_GAIN = 0x0c0 << 7
_AD5360_READ_CONTROL = 0x101 << 7
_AD5360_READ_OFS0 = 0x102 << 7
_AD5360_READ_OFS1 = 0x103 << 7
class AD5360:
"""
Support for the Analog devices AD53[67][0123]
multi-channel Digital to Analog Converters
:param spi_device: Name of the SPI bus this device is on.
:param ldac_device: Name of the TTL device that LDAC is connected to
(optional). Needs to be explicitly initialized to high.
:param chip_select: Value to drive on the chip select lines
during transactions.
"""
def __init__(self, dmgr, spi_device, ldac_device=None, chip_select=1):
self.core = dmgr.get("core")
self.bus = dmgr.get(spi_device)
if ldac_device is not None:
self.ldac = dmgr.get(ldac_device)
self.chip_select = chip_select
@kernel
def setup_bus(self, write_div=4, read_div=7):
"""Configure the SPI bus and the SPI transaction parameters
for this device. This method has to be called before any other method
if the bus has been used to access a different device in the meantime.
This method advances the timeline by the duration of two
RTIO-to-Wishbone bus transactions.
:param write_div: Write clock divider.
:param read_div: Read clock divider.
"""
# write: 2*8ns >= 10ns = t_6 (clk falling to cs_n rising)
# read: 4*8*ns >= 25ns = t_22 (clk falling to miso valid)
self.bus.set_config_mu(_AD5360_SPI_CONFIG, write_div, read_div)
self.bus.set_xfer(self.chip_select, 24, 0)
@kernel
def write(self, data):
"""Write 24 bits of data.
This method advances the timeline by the duration of the SPI transfer
and the required CS high time.
"""
self.bus.write(data << 8)
delay_mu(self.bus.ref_period_mu) # get to 20ns min cs high
@kernel
def write_offsets(self, value=0x1fff):
"""Write the OFS0 and OFS1 offset DACs.
This method advances the timeline by twice the duration of
:meth:`write`.
:param value: Value to set both offset registers to.
"""
value &= 0x3fff
self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_OFS0 | value)
self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_OFS1 | value)
@kernel
def write_channel(self, channel=0, value=0, op=_AD5360_CMD_DATA):
"""Write to a channel register.
This method advances the timeline by the duration of :meth:`write`.
:param channel: Channel number to write to.
:param value: 16 bit value to write to the register.
:param op: Operation to perform, one of :const:`_AD5360_CMD_DATA`,
:const:`_AD5360_CMD_OFFSET`, :const:`_AD5360_CMD_GAIN`
(default: :const:`_AD5360_CMD_DATA`).
"""
channel &= 0x3f
value &= 0xffff
self.write(op | _AD5360_WRITE_CHANNEL(channel) | value)
@kernel
def read_channel_sync(self, channel=0, op=_AD5360_READ_X1A):
"""Read a channel register.
This method advances the timeline by the duration of :meth:`write` plus
three RTIO-to-Wishbone transactions.
:param channel: Channel number to read from.
:param op: Operation to perform, one of :const:`_AD5360_READ_X1A`,
:const:`_AD5360_READ_X1B`, :const:`_AD5360_READ_OFFSET`,
:const:`_AD5360_READ_GAIN` (default: :const:`_AD5360_READ_X1A`).
:return: The 16 bit register value.
"""
channel &= 0x3f
self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_READ | op |
_AD5360_READ_CHANNEL(channel))
self.bus.set_xfer(self.chip_select, 0, 24)
self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_NOP)
self.bus.read_async()
self.bus.set_xfer(self.chip_select, 24, 0)
return self.bus.input_async() & 0xffff
@kernel
def load(self):
"""Pulse the LDAC line.
This method advances the timeline by two RTIO clock periods (16 ns).
"""
self.ldac.off()
# t13 = 10ns ldac pulse width low
delay_mu(2*self.bus.ref_period_mu)
self.ldac.on()
@kernel
def set(self, values, op=_AD5360_CMD_DATA):
"""Write to several channels and pulse LDAC to update the channels.
This method does not advance the timeline. Write events are scheduled
in the past. The DACs will synchronously start changing their output
levels `now`.
:param values: List of 16 bit values to write to the channels.
:param op: Operation to perform, one of :const:`_AD5360_CMD_DATA`,
:const:`_AD5360_CMD_OFFSET`, :const:`_AD5360_CMD_GAIN`
(default: :const:`_AD5360_CMD_DATA`).
"""
# compensate all delays that will be applied
delay_mu(-len(values)*(self.bus.xfer_period_mu +
self.bus.write_period_mu +
self.bus.ref_period_mu) -
3*self.bus.ref_period_mu -
seconds_to_mu(1.5*us))
for i in range(len(values)):
self.write_channel(i, values[i], op)
delay_mu(3*self.bus.ref_period_mu + # latency alignment ttl to spi
seconds_to_mu(1.5*us)) # t10 max busy low for one channel
self.load()
delay_mu(-2*self.bus.ref_period_mu) # load(), t13

View File

@ -1,393 +0,0 @@
"""RTIO driver for the Analog Devices AD53[67][0123] family of multi-channel
Digital to Analog Converters.
Output event replacement is not supported and issuing commands at the same
time results in a collision error.
"""
# Designed from the data sheets and somewhat after the linux kernel
# iio driver.
from numpy import int32
from artiq.language.core import (kernel, portable, delay_mu, delay, now_mu,
at_mu)
from artiq.language.units import ns, us
from artiq.coredevice import spi2 as spi
SPI_AD53XX_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
0*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE |
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
AD53XX_CMD_DATA = 3 << 22
AD53XX_CMD_OFFSET = 2 << 22
AD53XX_CMD_GAIN = 1 << 22
AD53XX_CMD_SPECIAL = 0 << 22
AD53XX_SPECIAL_NOP = 0 << 16
AD53XX_SPECIAL_CONTROL = 1 << 16
AD53XX_SPECIAL_OFS0 = 2 << 16
AD53XX_SPECIAL_OFS1 = 3 << 16
AD53XX_SPECIAL_READ = 5 << 16
AD53XX_SPECIAL_AB0 = 6 << 16
AD53XX_SPECIAL_AB1 = 7 << 16
AD53XX_SPECIAL_AB2 = 8 << 16
AD53XX_SPECIAL_AB3 = 9 << 16
AD53XX_SPECIAL_AB = 11 << 16
# incorporate the channel offset (8, table 17) here
AD53XX_READ_X1A = 0x008 << 7
AD53XX_READ_X1B = 0x048 << 7
AD53XX_READ_OFFSET = 0x088 << 7
AD53XX_READ_GAIN = 0x0C8 << 7
AD53XX_READ_CONTROL = 0x101 << 7
AD53XX_READ_OFS0 = 0x102 << 7
AD53XX_READ_OFS1 = 0x103 << 7
AD53XX_READ_AB0 = 0x106 << 7
AD53XX_READ_AB1 = 0x107 << 7
AD53XX_READ_AB2 = 0x108 << 7
AD53XX_READ_AB3 = 0x109 << 7
@portable
def ad53xx_cmd_write_ch(channel, value, op):
"""Returns the word that must be written to the DAC to set a DAC
channel register to a given value.
:param channel: DAC channel to write to (8 bits)
:param value: 16-bit value to write to the register
:param op: The channel register to write to, one of
:const:`AD53XX_CMD_DATA`, :const:`AD53XX_CMD_OFFSET` or
:const:`AD53XX_CMD_GAIN`.
:return: The 24-bit word to be written to the DAC
"""
return op | (channel + 8) << 16 | (value & 0xffff)
@portable
def ad53xx_cmd_read_ch(channel, op):
"""Returns the word that must be written to the DAC to read a given
DAC channel register.
:param channel: DAC channel to read (8 bits)
:param op: The channel register to read, one of
:const:`AD53XX_READ_X1A`, :const:`AD53XX_READ_X1B`,
:const:`AD53XX_READ_OFFSET`, :const:`AD53XX_READ_GAIN` etc.
:return: The 24-bit word to be written to the DAC to initiate read
"""
return AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_READ | (op + (channel << 7))
# maintain function definition for backward compatibility
@portable
def voltage_to_mu(voltage, offset_dacs=0x2000, vref=5.):
"""Returns the 16-bit DAC register value required to produce a given output
voltage, assuming offset and gain errors have been trimmed out.
The 16-bit register value may also be used with 14-bit DACs. The additional
bits are disregarded by 14-bit DACs.
Also used to return offset register value required to produce a given
voltage when the DAC register is set to mid-scale.
An offset of V can be used to trim out a DAC offset error of -V.
:param voltage: Voltage in SI units.
Valid voltages are: [-2*vref, + 2*vref - 1 LSB] + voltage offset.
:param offset_dacs: Register value for the two offset DACs
(default: 0x2000)
:param vref: DAC reference voltage (default: 5.)
:return: The 16-bit DAC register value
"""
code = int(round((1 << 16) * (voltage / (4. * vref)) + offset_dacs * 0x4))
if code < 0x0 or code > 0xffff:
raise ValueError("Invalid DAC voltage!")
return code
class _DummyTTL:
@portable
def on(self):
pass
@portable
def off(self):
pass
class AD53xx:
"""Analog devices AD53[67][0123] family of multi-channel Digital to Analog
Converters.
:param spi_device: SPI bus device name
:param ldac_device: LDAC RTIO TTLOut channel name (optional)
:param clr_device: CLR RTIO TTLOut channel name (optional)
:param chip_select: Value to drive on SPI chip select lines during
transactions (default: 1)
:param div_write: SPI clock divider for write operations (default: 4,
50MHz max SPI clock with {t_high, t_low} >=8ns)
:param div_read: SPI clock divider for read operations (default: 16, not
optimized for speed; datasheet says t22: 25ns min SCLK edge to SDO
valid, and suggests the SPI speed for reads should be <=20 MHz)
:param vref: DAC reference voltage (default: 5.)
:param offset_dacs: Initial register value for the two offset DACs
(default: 8192). Device dependent and must be set correctly for
correct voltage-to-mu conversions. Knowledge of this state is
not transferred between experiments.
:param core_device: Core device name (default: "core")
"""
kernel_invariants = {"bus", "ldac", "clr", "chip_select", "div_write",
"div_read", "vref", "core"}
def __init__(self, dmgr, spi_device, ldac_device=None, clr_device=None,
chip_select=1, div_write=4, div_read=16, vref=5.,
offset_dacs=8192, core="core"):
self.bus = dmgr.get(spi_device)
self.bus.update_xfer_duration_mu(div_write, 24)
if ldac_device is None:
self.ldac = _DummyTTL()
else:
self.ldac = dmgr.get(ldac_device)
if clr_device is None:
self.clr = _DummyTTL()
else:
self.clr = dmgr.get(clr_device)
self.chip_select = chip_select
self.div_write = div_write
self.div_read = div_read
self.vref = vref
self.offset_dacs = offset_dacs
self.core = dmgr.get(core)
@kernel
def init(self, blind=False):
"""Configures the SPI bus, drives LDAC and CLR high, programmes
the offset DACs, and enables overtemperature shutdown.
This method must be called before any other method at start-up or if
the SPI bus has been accessed by another device.
:param blind: If ``True``, do not attempt to read back control register
or check for overtemperature.
"""
self.ldac.on()
self.clr.on()
self.bus.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write,
self.chip_select)
self.write_offset_dacs_mu(self.offset_dacs)
if not blind:
ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL)
if ctrl == 0xffff:
raise ValueError("DAC not found")
if ctrl & 0b10000:
raise ValueError("DAC over temperature")
delay(25*us)
self.bus.write( # enable power and overtemperature shutdown
(AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_CONTROL | 0b0010) << 8)
if not blind:
ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL)
if (ctrl & 0b10111) != 0b00010:
raise ValueError("DAC CONTROL readback mismatch")
delay(15*us)
@kernel
def read_reg(self, channel=0, op=AD53XX_READ_X1A):
"""Read a DAC register.
This method advances the timeline by the duration of two SPI transfers
plus two RTIO coarse cycles plus 270 ns and consumes all slack.
:param channel: Channel number to read from (default: 0)
:param op: Operation to perform, one of :const:`AD53XX_READ_X1A`,
:const:`AD53XX_READ_X1B`, :const:`AD53XX_READ_OFFSET`,
:const:`AD53XX_READ_GAIN` etc. (default: :const:`AD53XX_READ_X1A`).
:return: The 16-bit register value
"""
self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8)
self.bus.set_config_mu(SPI_AD53XX_CONFIG | spi.SPI_INPUT, 24,
self.div_read, self.chip_select)
delay(270*ns) # t_21 min sync high in readback
self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_NOP) << 8)
self.bus.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write,
self.chip_select)
# FIXME: the int32 should not be needed to resolve unification
return self.bus.read() & int32(0xffff)
@kernel
def write_offset_dacs_mu(self, value):
"""Program the OFS0 and OFS1 offset DAC registers.
Writes to the offset DACs take effect immediately without requiring
a LDAC. This method advances the timeline by the duration of two SPI
transfers.
:param value: Value to set both offset DAC registers to
"""
value &= 0x3fff
self.offset_dacs = value
self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_OFS0 | value) << 8)
self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_OFS1 | value) << 8)
@kernel
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`).
This method advances the timeline by the duration of one SPI transfer.
:param gain: 16-bit gain register value (default: 0xffff)
"""
self.bus.write(
ad53xx_cmd_write_ch(channel, gain, AD53XX_CMD_GAIN) << 8)
@kernel
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`).
This method advances the timeline by the duration of one SPI transfer.
:param offset: 16-bit offset register value (default: 0x8000)
"""
self.bus.write(
ad53xx_cmd_write_ch(channel, offset, AD53XX_CMD_OFFSET) << 8)
@kernel
def write_offset(self, channel, voltage):
"""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`).
This method advances the timeline by the duration of one SPI transfer.
:param voltage: the offset voltage
"""
self.write_offset_mu(channel, voltage_to_mu(voltage, self.offset_dacs,
self.vref))
@kernel
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`).
This method advances the timeline by the duration of one SPI transfer.
"""
self.bus.write(
ad53xx_cmd_write_ch(channel, value, AD53XX_CMD_DATA) << 8)
@kernel
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`).
This method advances the timeline by the duration of one SPI transfer.
"""
self.write_dac_mu(channel, voltage_to_mu(voltage, self.offset_dacs,
self.vref))
@kernel
def load(self):
"""Pulse the LDAC line.
Note that there is a <= 1.5us "BUSY" period (t10) after writing to a
DAC input/gain/offset register. All DAC registers may be programmed
normally during the busy period, however LDACs during the busy period
cause the DAC output to change *after* the BUSY period has completed,
instead of the usual immediate update on LDAC behaviour.
This method advances the timeline by two RTIO clock periods.
"""
self.ldac.off()
delay_mu(2*self.bus.ref_period_mu) # t13 = 10ns ldac pulse width low
self.ldac.on()
@kernel
def set_dac_mu(self, values, channels=list(range(40))):
"""Program multiple DAC channels and pulse LDAC to update the DAC
outputs.
This method does not advance the timeline; write events are scheduled
in the past. The DACs will synchronously start changing their output
levels ``now``.
If no LDAC device was defined, the LDAC pulse is skipped.
See :meth:`load`.
:param values: list of DAC values to program
:param channels: list of DAC channels to program. If not specified,
we program the DAC channels sequentially, starting at 0.
"""
t0 = now_mu()
# t10: max busy period after writing to DAC registers
t_10 = self.core.seconds_to_mu(1500*ns)
# compensate all delays that will be applied
delay_mu(-t_10-len(values)*self.bus.xfer_duration_mu)
for i in range(len(values)):
self.write_dac_mu(channels[i], values[i])
delay_mu(t_10)
self.load()
at_mu(t0)
@kernel
def set_dac(self, voltages, channels=list(range(40))):
"""Program multiple DAC channels and pulse LDAC to update the DAC
outputs.
This method does not advance the timeline; write events are scheduled
in the past. The DACs will synchronously start changing their output
levels `now`.
If no LDAC device was defined, the LDAC pulse is skipped.
:param voltages: list of voltages to program the DAC channels to
:param channels: list of DAC channels to program. If not specified,
we program the DAC channels sequentially, starting at 0.
"""
values = [voltage_to_mu(voltage, self.offset_dacs, self.vref)
for voltage in voltages]
self.set_dac_mu(values, channels)
@kernel
def calibrate(self, channel, vzs, vfs):
""" 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`).
Calibration consists of measuring the DAC output voltage for a channel
with the DAC set to zero-scale (0x0000) and full-scale (0xffff).
Note that only negative offsets and full-scale errors (DAC gain too
high) can be calibrated in this fashion.
:param channel: The number of the calibrated channel
:param vzs: Measured voltage with the DAC set to zero-scale (0x0000)
:param vfs: Measured voltage with the DAC set to full-scale (0xffff)
"""
offset_err = voltage_to_mu(vzs, self.offset_dacs, self.vref)
gain_err = voltage_to_mu(vfs, self.offset_dacs, self.vref) - (
offset_err + 0xffff)
assert offset_err <= 0
assert gain_err >= 0
self.core.break_realtime()
self.write_offset_mu(channel, 0x8000-offset_err)
self.write_gain_mu(channel, 0xffff-gain_err)
@portable
def voltage_to_mu(self, voltage):
"""Returns the 16-bit DAC register value required to produce a given
output voltage, assuming offset and gain errors have been trimmed out.
The 16-bit register value may also be used with 14-bit DACs. The
additional bits are disregarded by 14-bit DACs.
:param voltage: Voltage in SI units.
Valid voltages are: [-2*vref, + 2*vref - 1 LSB] + voltage offset.
:return: The 16-bit DAC register value
"""
return voltage_to_mu(voltage, self.offset_dacs, self.vref)

File diff suppressed because it is too large Load Diff

View File

@ -1,280 +0,0 @@
from numpy import int32, int64
from artiq.language.types import TInt32, TInt64, TFloat, TTuple, TBool
from artiq.language.core import kernel, delay, portable
from artiq.language.units import ms, us, ns
from artiq.coredevice.ad9912_reg import *
from artiq.coredevice import spi2 as spi
from artiq.coredevice import urukul
class AD9912:
"""
AD9912 DDS channel on Urukul.
This class supports a single DDS channel and exposes the DDS,
the digital step attenuator, and the RF switch.
:param chip_select: Chip select configuration. On Urukul this is an
encoded chip select and not "one-hot".
:param cpld_device: Name of the Urukul CPLD this device is on.
:param sw_device: Name of the RF switch device. The RF switch is a
TTLOut channel available as the :attr:`sw` attribute of this instance.
:param pll_n: DDS PLL multiplier. The DDS sample clock is
``f_ref / clk_div * pll_n`` where ``f_ref`` is the reference frequency and
``clk_div`` is the reference clock divider (both set in the parent
Urukul CPLD instance).
: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_en=1):
self.kernel_invariants = {"cpld", "core", "bus", "chip_select",
"pll_n", "pll_en", "ftw_per_hz"}
self.cpld = dmgr.get(cpld_device)
self.core = self.cpld.core
self.bus = self.cpld.bus
assert 4 <= chip_select <= 7
self.chip_select = chip_select
if sw_device:
self.sw = dmgr.get(sw_device)
self.kernel_invariants.add("sw")
self.pll_en = pll_en
self.pll_n = pll_n
if pll_en:
refclk = self.cpld.refclk
if refclk < 11e6:
# use SYSCLK PLL Doubler
refclk = refclk * 2
sysclk = refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n
else:
sysclk = self.cpld.refclk
assert sysclk <= 1e9
self.ftw_per_hz = 1 / sysclk * (int64(1) << 48)
@kernel
def write(self, addr: TInt32, data: TInt32, length: TInt32):
"""Variable length write to a register.
Up to 4 bytes.
:param addr: Register address
:param data: Data to be written: int32
:param length: Length in bytes (1-4)
"""
assert length > 0
assert length <= 4
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write((addr | ((length - 1) << 13)) << 16)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, length * 8,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write(data << (32 - length * 8))
@kernel
def read(self, addr: TInt32, length: TInt32) -> TInt32:
"""Variable length read from a register.
Up to 4 bytes.
:param addr: Register address
:param length: Length in bytes (1-4)
:return: Data read
"""
assert length > 0
assert length <= 4
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write((addr | ((length - 1) << 13) | 0x8000) << 16)
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END
| spi.SPI_INPUT, length * 8,
urukul.SPIT_DDS_RD, self.chip_select)
self.bus.write(0)
data = self.bus.read()
if length < 4:
data &= (1 << (length * 8)) - 1
return data
@kernel
def init(self):
"""Initialize and configure the DDS.
Sets up SPI mode, confirms chip presence, powers down unused blocks,
and configures the PLL. Does not wait for PLL lock. Uses the
``IO_UPDATE`` signal multiple times.
"""
# SPI mode
self.write(AD9912_SER_CONF, 0x99, length=1)
self.cpld.io_update.pulse(2 * us)
# Verify chip ID and presence
prodid = self.read(AD9912_PRODIDH, length=2)
if (prodid != 0x1982) and (prodid != 0x1902):
raise ValueError("Urukul AD9912 product id mismatch")
delay(50 * us)
# HSTL power down, CMOS power down
pwrcntrl1 = 0x80 | ((~self.pll_en & 1) << 4)
self.write(AD9912_PWRCNTRL1, pwrcntrl1, length=1)
self.cpld.io_update.pulse(2 * us)
if self.pll_en:
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1)
self.cpld.io_update.pulse(2 * us)
# I_cp = 375 µA, VCO high range
if self.cpld.refclk < 11e6:
# enable SYSCLK PLL Doubler
self.write(AD9912_PLLCFG, 0b00001101, length=1)
else:
self.write(AD9912_PLLCFG, 0b00000101, length=1)
self.cpld.io_update.pulse(2 * us)
delay(1 * ms)
@kernel
def set_att_mu(self, att: TInt32):
"""Set digital step attenuator in machine units.
This method will write the attenuator settings of all four channels.
See also :meth:`~artiq.coredevice.urukul.CPLD.set_att_mu`.
:param att: Attenuation setting, 8-bit digital.
"""
self.cpld.set_att_mu(self.chip_select - 4, att)
@kernel
def set_att(self, att: TFloat):
"""Set digital step attenuator in SI units.
This method will write the attenuator settings of all four channels.
See also :meth:`~artiq.coredevice.urukul.CPLD.set_att`.
:param att: Attenuation in dB. Higher values mean more attenuation.
"""
self.cpld.set_att(self.chip_select - 4, att)
@kernel
def get_att_mu(self) -> TInt32:
"""Get digital step attenuator value in machine units.
See also :meth:`~artiq.coredevice.urukul.CPLD.get_channel_att_mu`.
:return: Attenuation setting, 8-bit digital.
"""
return self.cpld.get_channel_att_mu(self.chip_select - 4)
@kernel
def get_att(self) -> TFloat:
"""Get digital step attenuator value in SI units.
See also :meth:`~artiq.coredevice.urukul.CPLD.get_channel_att`.
:return: Attenuation in dB.
"""
return self.cpld.get_channel_att(self.chip_select - 4)
@kernel
def set_mu(self, ftw: TInt64, pow_: TInt32 = 0):
"""Set profile 0 data in machine units.
After the SPI transfer, the shared IO update pin is pulsed to
activate the data.
:param ftw: Frequency tuning word: 48-bit unsigned.
:param pow_: Phase tuning word: 16-bit unsigned.
"""
# streaming transfer of FTW and POW
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write((AD9912_POW1 << 16) | (3 << 29))
self.bus.set_config_mu(urukul.SPI_CONFIG, 32,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write((pow_ << 16) | (int32(ftw >> 32) & 0xffff))
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
urukul.SPIT_DDS_WR, self.chip_select)
self.bus.write(int32(ftw))
self.cpld.io_update.pulse(10 * ns)
@kernel
def get_mu(self) -> TTuple([TInt64, TInt32]):
"""Get the frequency tuning word and phase offset word.
See also :meth:`AD9912.get`.
:return: A tuple (FTW, POW).
"""
# Read data
high = self.read(AD9912_POW1, 4)
self.core.break_realtime() # Regain slack to perform second read
low = self.read(AD9912_FTW3, 4)
# Extract and return fields
ftw = (int64(high & 0xffff) << 32) | (int64(low) & int64(0xffffffff))
pow_ = (high >> 16) & 0x3fff
return ftw, pow_
@portable(flags={"fast-math"})
def frequency_to_ftw(self, frequency: TFloat) -> TInt64:
"""Returns the 48-bit frequency tuning word corresponding to the given
frequency.
"""
return int64(round(self.ftw_per_hz * frequency)) & (
(int64(1) << 48) - 1)
@portable(flags={"fast-math"})
def ftw_to_frequency(self, ftw: TInt64) -> TFloat:
"""Returns the frequency corresponding to the given
frequency tuning word.
"""
return ftw / self.ftw_per_hz
@portable(flags={"fast-math"})
def turns_to_pow(self, phase: TFloat) -> TInt32:
"""Returns the 16-bit phase offset word corresponding to the given
phase.
"""
return int32(round((1 << 14) * phase)) & 0xffff
@portable(flags={"fast-math"})
def pow_to_turns(self, pow_: TInt32) -> TFloat:
"""Return the phase in turns corresponding to a given phase offset
word.
:param pow_: Phase offset word.
:return: Phase in turns.
"""
return pow_ / (1 << 14)
@kernel
def set(self, frequency: TFloat, phase: TFloat = 0.0):
"""Set profile 0 data in SI units.
See also :meth:`AD9912.set_mu`.
:param frequency: Frequency in Hz
:param phase: Phase tuning word in turns
"""
self.set_mu(self.frequency_to_ftw(frequency),
self.turns_to_pow(phase))
@kernel
def get(self) -> TTuple([TFloat, TFloat]):
"""Get the frequency and phase.
See also :meth:`AD9912.get_mu`.
:return: A tuple (frequency, phase).
"""
# Get values
ftw, pow_ = self.get_mu()
# Convert and return
return self.ftw_to_frequency(ftw), self.pow_to_turns(pow_)
@kernel
def cfg_sw(self, state: TBool):
"""Set CPLD CFG RF switch state. The RF switch is controlled by the
logical or of the CPLD configuration shift register
RF switch bit and the SW TTL line (if used).
:param state: CPLD CFG RF switch bit
"""
self.cpld.cfg_sw(self.chip_select - 4, state)

View File

@ -1,384 +0,0 @@
# auto-generated, do not edit
from artiq.language.core import portable
from artiq.language.types import TInt32
AD9912_SER_CONF = 0x000
# default: 0x00, access: R/W
@portable
def AD9912_SDOACTIVE_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 0
@portable
def AD9912_SDOACTIVE_GET(x: TInt32) -> TInt32:
return (x >> 0) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_LSBFIRST_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 1
@portable
def AD9912_LSBFIRST_GET(x: TInt32) -> TInt32:
return (x >> 1) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_SOFTRESET_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 2
@portable
def AD9912_SOFTRESET_GET(x: TInt32) -> TInt32:
return (x >> 2) & 0x1
# default: 0x01, access: R/W
@portable
def AD9912_LONGINSN_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 3
@portable
def AD9912_LONGINSN_GET(x: TInt32) -> TInt32:
return (x >> 3) & 0x1
# default: 0x01, access: R/W
@portable
def AD9912_LONGINSN_M_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 4
@portable
def AD9912_LONGINSN_M_GET(x: TInt32) -> TInt32:
return (x >> 4) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_SOFTRESET_M_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 5
@portable
def AD9912_SOFTRESET_M_GET(x: TInt32) -> TInt32:
return (x >> 5) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_LSBFIRST_M_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 6
@portable
def AD9912_LSBFIRST_M_GET(x: TInt32) -> TInt32:
return (x >> 6) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_SDOACTIVE_M_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 7
@portable
def AD9912_SDOACTIVE_M_GET(x: TInt32) -> TInt32:
return (x >> 7) & 0x1
AD9912_PRODIDL = 0x002
AD9912_PRODIDH = 0x003
AD9912_SER_OPT1 = 0x004
# default: 0x00, access: R/W
@portable
def AD9912_READ_BUF_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 0
@portable
def AD9912_READ_BUF_GET(x: TInt32) -> TInt32:
return (x >> 0) & 0x1
AD9912_SER_OPT2 = 0x005
# default: 0x00, access: R/W
@portable
def AD9912_RED_UPDATE_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 0
@portable
def AD9912_RED_UPDATE_GET(x: TInt32) -> TInt32:
return (x >> 0) & 0x1
AD9912_PWRCNTRL1 = 0x010
# default: 0x00, access: R/W
@portable
def AD9912_PD_DIGITAL_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 0
@portable
def AD9912_PD_DIGITAL_GET(x: TInt32) -> TInt32:
return (x >> 0) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_PD_FULL_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 1
@portable
def AD9912_PD_FULL_GET(x: TInt32) -> TInt32:
return (x >> 1) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_PD_SYSCLK_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 4
@portable
def AD9912_PD_SYSCLK_GET(x: TInt32) -> TInt32:
return (x >> 4) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_EN_DOUBLER_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 5
@portable
def AD9912_EN_DOUBLER_GET(x: TInt32) -> TInt32:
return (x >> 5) & 0x1
# default: 0x01, access: R/W
@portable
def AD9912_EN_CMOS_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 6
@portable
def AD9912_EN_CMOS_GET(x: TInt32) -> TInt32:
return (x >> 6) & 0x1
# default: 0x01, access: R/W
@portable
def AD9912_PD_HSTL_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 7
@portable
def AD9912_PD_HSTL_GET(x: TInt32) -> TInt32:
return (x >> 7) & 0x1
AD9912_PWRCNTRL2 = 0x012
# default: 0x00, access: R/W
@portable
def AD9912_DDS_RESET_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 0
@portable
def AD9912_DDS_RESET_GET(x: TInt32) -> TInt32:
return (x >> 0) & 0x1
AD9912_PWRCNTRL3 = 0x013
# default: 0x00, access: R/W
@portable
def AD9912_S_DIV_RESET_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 1
@portable
def AD9912_S_DIV_RESET_GET(x: TInt32) -> TInt32:
return (x >> 1) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_S_DIV2_RESET_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 3
@portable
def AD9912_S_DIV2_RESET_GET(x: TInt32) -> TInt32:
return (x >> 3) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_PD_FUND_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 7
@portable
def AD9912_PD_FUND_GET(x: TInt32) -> TInt32:
return (x >> 7) & 0x1
AD9912_N_DIV = 0x020
AD9912_PLLCFG = 0x022
# default: 0x00, access: R/W
@portable
def AD9912_PLL_ICP_SET(x: TInt32) -> TInt32:
return (x & 0x3) << 0
@portable
def AD9912_PLL_ICP_GET(x: TInt32) -> TInt32:
return (x >> 0) & 0x3
# default: 0x01, access: R/W
@portable
def AD9912_VCO_RANGE_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 2
@portable
def AD9912_VCO_RANGE_GET(x: TInt32) -> TInt32:
return (x >> 2) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_PLL_REF2X_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 3
@portable
def AD9912_PLL_REF2X_GET(x: TInt32) -> TInt32:
return (x >> 3) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_VCO_AUTO_RANGE_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 7
@portable
def AD9912_VCO_AUTO_RANGE_GET(x: TInt32) -> TInt32:
return (x >> 7) & 0x1
AD9912_S_DIVL = 0x104
AD9912_S_DIVH = 0x105
AD9912_S_DIV_CFG = 0x106
# default: 0x01, access: R/W
@portable
def AD9912_S_DIV2_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 0
@portable
def AD9912_S_DIV2_GET(x: TInt32) -> TInt32:
return (x >> 0) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_S_DIV_FALL_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 7
@portable
def AD9912_S_DIV_FALL_GET(x: TInt32) -> TInt32:
return (x >> 7) & 0x1
AD9912_FTW0 = 0x1a6
AD9912_FTW1 = 0x1a7
AD9912_FTW2 = 0x1a8
AD9912_FTW3 = 0x1a9
AD9912_FTW4 = 0x1aa
AD9912_FTW5 = 0x1ab
AD9912_POW0 = 0x1ac
AD9912_POW1 = 0x1ad
AD9912_HSTL = 0x200
# default: 0x01, access: R/W
@portable
def AD9912_HSTL_CFG_SET(x: TInt32) -> TInt32:
return (x & 0x3) << 0
@portable
def AD9912_HSTL_CFG_GET(x: TInt32) -> TInt32:
return (x >> 0) & 0x3
# default: 0x01, access: R/W
@portable
def AD9912_HSTL_OPOL_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 4
@portable
def AD9912_HSTL_OPOL_GET(x: TInt32) -> TInt32:
return (x >> 4) & 0x1
AD9912_CMOS = 0x201
# default: 0x00, access: R/W
@portable
def AD9912_CMOS_MUX_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 0
@portable
def AD9912_CMOS_MUX_GET(x: TInt32) -> TInt32:
return (x >> 0) & 0x1
AD9912_FSC0 = 0x40b
AD9912_FSC1 = 0x40c
AD9912_HSR_A_CFG = 0x500
# default: 0x00, access: R/W
@portable
def AD9912_HSR_A_HARMONIC_SET(x: TInt32) -> TInt32:
return (x & 0xf) << 0
@portable
def AD9912_HSR_A_HARMONIC_GET(x: TInt32) -> TInt32:
return (x >> 0) & 0xf
# default: 0x00, access: R/W
@portable
def AD9912_HSR_A_MAG2X_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 6
@portable
def AD9912_HSR_A_MAG2X_GET(x: TInt32) -> TInt32:
return (x >> 6) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_HSR_A_EN_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 7
@portable
def AD9912_HSR_A_EN_GET(x: TInt32) -> TInt32:
return (x >> 7) & 0x1
AD9912_HSR_A_MAG = 0x501
AD9912_HSR_A_POW0 = 0x503
AD9912_HSR_A_POW1 = 0x504
AD9912_HSR_B_CFG = 0x505
# default: 0x00, access: R/W
@portable
def AD9912_HSR_B_HARMONIC_SET(x: TInt32) -> TInt32:
return (x & 0xf) << 0
@portable
def AD9912_HSR_B_HARMONIC_GET(x: TInt32) -> TInt32:
return (x >> 0) & 0xf
# default: 0x00, access: R/W
@portable
def AD9912_HSR_B_MAG2X_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 6
@portable
def AD9912_HSR_B_MAG2X_GET(x: TInt32) -> TInt32:
return (x >> 6) & 0x1
# default: 0x00, access: R/W
@portable
def AD9912_HSR_B_EN_SET(x: TInt32) -> TInt32:
return (x & 0x1) << 7
@portable
def AD9912_HSR_B_EN_GET(x: TInt32) -> TInt32:
return (x >> 7) & 0x1
AD9912_HSR_B_MAG = 0x506
AD9912_HSR_B_POW0 = 0x508
AD9912_HSR_B_POW1 = 0x509

View File

@ -1,349 +0,0 @@
"""
Driver for the AD9914 DDS (with parallel bus) on RTIO.
"""
from artiq.language.core import *
from artiq.language.types import *
from artiq.language.units import *
from artiq.coredevice.rtio import rtio_output
from numpy import int32, int64
__all__ = [
"AD9914",
"PHASE_MODE_CONTINUOUS", "PHASE_MODE_ABSOLUTE", "PHASE_MODE_TRACKING"
]
_PHASE_MODE_DEFAULT = -1
PHASE_MODE_CONTINUOUS = 0
PHASE_MODE_ABSOLUTE = 1
PHASE_MODE_TRACKING = 2
AD9914_REG_CFR1L = 0x01
AD9914_REG_CFR1H = 0x03
AD9914_REG_CFR2L = 0x05
AD9914_REG_CFR2H = 0x07
AD9914_REG_CFR3L = 0x09
AD9914_REG_CFR3H = 0x0b
AD9914_REG_CFR4L = 0x0d
AD9914_REG_CFR4H = 0x0f
AD9914_REG_DRGFL = 0x11
AD9914_REG_DRGFH = 0x13
AD9914_REG_DRGBL = 0x15
AD9914_REG_DRGBH = 0x17
AD9914_REG_DRGAL = 0x19
AD9914_REG_DRGAH = 0x1b
AD9914_REG_POW = 0x31
AD9914_REG_ASF = 0x33
AD9914_REG_USR0 = 0x6d
AD9914_FUD = 0x80
AD9914_GPIO = 0x81
class AD9914:
"""Driver for one AD9914 DDS channel.
The time cursor is not modified by any function in this class.
Output event replacement is not supported and issuing commands at the same
time results in collision errors.
:param sysclk: DDS system frequency. The DDS system clock must be a
phase-locked multiple of the RTIO clock.
:param bus_channel: RTIO channel number of the DDS bus.
:param channel: channel number (on the bus) of the DDS device to control.
"""
kernel_invariants = {"core", "sysclk", "bus_channel", "channel",
"rtio_period_mu", "sysclk_per_mu", "write_duration_mu",
"dac_cal_duration_mu", "init_duration_mu", "init_sync_duration_mu",
"set_duration_mu", "set_x_duration_mu", "exit_x_duration_mu"}
def __init__(self, dmgr, sysclk, bus_channel, channel, core_device="core"):
self.core = dmgr.get(core_device)
self.sysclk = sysclk
self.bus_channel = bus_channel
self.channel = channel
self.phase_mode = PHASE_MODE_CONTINUOUS
self.rtio_period_mu = int64(8)
self.sysclk_per_mu = int32(self.sysclk * self.core.ref_period)
self.write_duration_mu = 5 * self.rtio_period_mu
self.dac_cal_duration_mu = 147000 * self.rtio_period_mu
self.init_duration_mu = 13 * self.write_duration_mu + self.dac_cal_duration_mu
self.init_sync_duration_mu = 21 * self.write_duration_mu + 2 * self.dac_cal_duration_mu
self.set_duration_mu = 7 * self.write_duration_mu
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)
delay_mu(self.write_duration_mu)
@kernel
def init(self):
"""Resets and initializes the DDS channel.
This needs to be done for each DDS channel before it can be used, and
it is recommended to use the startup kernel for this purpose.
"""
delay_mu(-self.init_duration_mu)
self.write(AD9914_GPIO, (1 << self.channel) << 1);
# Note another undocumented "feature" of the AD9914:
# Programmable modulus breaks if the digital ramp enable bit is
# not set at the same time.
self.write(AD9914_REG_CFR1H, 0x0000) # Enable cosine output
self.write(AD9914_REG_CFR2L, 0x8900) # Enable matched latency
self.write(AD9914_REG_CFR2H, 0x0089) # Enable profile mode + programmable modulus + DRG
self.write(AD9914_REG_DRGAL, 0) # Programmable modulus A = 0
self.write(AD9914_REG_DRGAH, 0)
self.write(AD9914_REG_DRGBH, 0x8000) # Programmable modulus B == 2**31
self.write(AD9914_REG_DRGBL, 0x0000)
self.write(AD9914_REG_ASF, 0x0fff) # Set amplitude to maximum
self.write(AD9914_REG_CFR4H, 0x0105) # Enable DAC calibration
self.write(AD9914_FUD, 0)
delay_mu(self.dac_cal_duration_mu)
self.write(AD9914_REG_CFR4H, 0x0005) # Disable DAC calibration
self.write(AD9914_FUD, 0)
@kernel
def init_sync(self, sync_delay):
"""Resets and initializes the DDS channel as well as configures
the AD9914 DDS for synchronisation. The synchronisation procedure
follows the steps outlined in the AN-1254 application note.
This needs to be done for each DDS channel before it can be used, and
it is recommended to use the startup kernel for this.
This function cannot be used in a batch; the correct way of
initializing multiple DDS channels is to call this function
sequentially with a delay between the calls. 10ms provides a good
timing margin.
:param sync_delay: integer from 0 to 0x3f that sets the value of
``SYNC_OUT`` (bits 3-5) and ``SYNC_IN`` (bits 0-2) delay ADJ bits.
"""
delay_mu(-self.init_sync_duration_mu)
self.write(AD9914_GPIO, (1 << self.channel) << 1)
self.write(AD9914_REG_CFR4H, 0x0105) # Enable DAC calibration
self.write(AD9914_FUD, 0)
delay_mu(self.dac_cal_duration_mu)
self.write(AD9914_REG_CFR4H, 0x0005) # Disable DAC calibration
self.write(AD9914_FUD, 0)
self.write(AD9914_REG_CFR2L, 0x8b00) # Enable matched latency and sync_out
self.write(AD9914_FUD, 0)
# Set cal with sync and set sync_out and sync_in delay
self.write(AD9914_REG_USR0, 0x0840 | (sync_delay & 0x3f))
self.write(AD9914_FUD, 0)
self.write(AD9914_REG_CFR4H, 0x0105) # Enable DAC calibration
self.write(AD9914_FUD, 0)
delay_mu(self.dac_cal_duration_mu)
self.write(AD9914_REG_CFR4H, 0x0005) # Disable DAC calibration
self.write(AD9914_FUD, 0)
self.write(AD9914_REG_CFR1H, 0x0000) # Enable cosine output
self.write(AD9914_REG_CFR2H, 0x0089) # Enable profile mode + programmable modulus + DRG
self.write(AD9914_REG_DRGAL, 0) # Programmable modulus A = 0
self.write(AD9914_REG_DRGAH, 0)
self.write(AD9914_REG_DRGBH, 0x8000) # Programmable modulus B == 2**31
self.write(AD9914_REG_DRGBL, 0x0000)
self.write(AD9914_REG_ASF, 0x0fff) # Set amplitude to maximum
self.write(AD9914_FUD, 0)
@kernel
def set_phase_mode(self, phase_mode):
"""Sets the phase mode of the DDS channel. Supported phase modes are:
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged when
switching frequencies. The DDS phase is the sum of the phase
accumulator and the phase offset. The only discrete jumps in the
DDS output phase come from changes to the phase offset.
* :const:`PHASE_MODE_ABSOLUTE`: the phase accumulator is reset when
switching frequencies. Thus, the phase of the DDS at the time of
the frequency change is equal to the phase offset.
* :const:`PHASE_MODE_TRACKING`: when switching frequencies, the phase
accumulator is set to the value it would have if the DDS had been
running at the specified frequency since the start of the
experiment.
.. warning:: This setting may become inconsistent when used as part of
a DMA recording. When using DMA, it is recommended to specify the
phase mode explicitly when calling :meth:`set` or :meth:`set_mu`.
"""
self.phase_mode = phase_mode
@kernel
def set_mu(self, ftw, pow=0, phase_mode=_PHASE_MODE_DEFAULT,
asf=0x0fff, ref_time_mu=-1):
"""Sets the DDS channel to the specified frequency and phase.
This uses machine units (FTW and POW). The frequency tuning word width
is 32, the phase offset word width is 16, and the amplitude scale factor
width is 12.
The "frequency update" pulse is sent to the DDS with a fixed latency
with respect to the current position of the time cursor.
:param ftw: frequency to generate.
:param pow: adds an offset to the phase.
:param phase_mode: if specified, overrides the default phase mode set
by :meth:`set_phase_mode` for this call.
:param ref_time_mu: reference time used to compute phase. Specifying this
makes it easier to have a well-defined phase relationship between
DDSes on the same bus that are updated at a similar time.
:return: Resulting phase offset word after application of phase
tracking offset. When using :const:`PHASE_MODE_CONTINUOUS` in
subsequent calls, use this value as the "current" phase.
"""
if phase_mode == _PHASE_MODE_DEFAULT:
phase_mode = self.phase_mode
if ref_time_mu < 0:
ref_time_mu = now_mu()
delay_mu(-self.set_duration_mu)
self.write(AD9914_GPIO, (1 << self.channel) << 1)
self.write(AD9914_REG_DRGFL, ftw & 0xffff)
self.write(AD9914_REG_DRGFH, (ftw >> 16) & 0xffff)
# We need the RTIO fine timestamp clock to be phase-locked
# to DDS SYSCLK, and divided by an integer self.sysclk_per_mu.
if phase_mode == PHASE_MODE_CONTINUOUS:
# Do not clear phase accumulator on FUD
# Disable autoclear phase accumulator and enables OSK.
self.write(AD9914_REG_CFR1L, 0x0108)
else:
# Clear phase accumulator on FUD
# Enable autoclear phase accumulator and enables OSK.
self.write(AD9914_REG_CFR1L, 0x2108)
fud_time = now_mu() + 2 * self.write_duration_mu
pow -= int32((ref_time_mu - fud_time) * self.sysclk_per_mu * ftw >> (32 - 16))
if phase_mode == PHASE_MODE_TRACKING:
pow += int32(ref_time_mu * self.sysclk_per_mu * ftw >> (32 - 16))
self.write(AD9914_REG_POW, pow)
self.write(AD9914_REG_ASF, asf)
self.write(AD9914_FUD, 0)
return pow
@portable(flags={"fast-math"})
def frequency_to_ftw(self, frequency):
"""Returns the 32-bit frequency tuning word corresponding to the given
frequency.
"""
return int32(round(float(int64(2)**32*frequency/self.sysclk)))
@portable(flags={"fast-math"})
def ftw_to_frequency(self, ftw):
"""Returns the frequency corresponding to the given frequency tuning
word.
"""
return ftw*self.sysclk/int64(2)**32
@portable(flags={"fast-math"})
def turns_to_pow(self, turns):
"""Returns the 16-bit phase offset word corresponding to the given
phase in turns."""
return round(float(turns*2**16)) & 0xffff
@portable(flags={"fast-math"})
def pow_to_turns(self, pow):
"""Returns the phase in turns corresponding to the given phase offset
word."""
return pow/2**16
@portable(flags={"fast-math"})
def amplitude_to_asf(self, amplitude):
"""Returns 12-bit amplitude scale factor corresponding to given
amplitude."""
code = round(float(amplitude * 0x0fff))
if code < 0 or code > 0xfff:
raise ValueError("Invalid AD9914 amplitude!")
return code
@portable(flags={"fast-math"})
def asf_to_amplitude(self, asf):
"""Returns the amplitude corresponding to the given amplitude scale
factor."""
return asf/0x0fff
@kernel
def set(self, frequency, phase=0.0, phase_mode=_PHASE_MODE_DEFAULT,
amplitude=1.0):
"""Like :meth:`set_mu`, but uses Hz and turns."""
return self.pow_to_turns(
self.set_mu(self.frequency_to_ftw(frequency),
self.turns_to_pow(phase), phase_mode,
self.amplitude_to_asf(amplitude)))
# Extended-resolution functions
@kernel
def set_x_mu(self, xftw, amplitude=0x0fff):
"""Set the DDS frequency and amplitude with an extended-resolution
(63-bit) frequency tuning word.
Phase control is not implemented in this mode; the phase offset
can assume any value.
After this function has been called, exit extended-resolution mode
before calling functions that use standard-resolution mode.
"""
delay_mu(-self.set_x_duration_mu)
self.write(AD9914_GPIO, (1 << self.channel) << 1)
self.write(AD9914_REG_DRGAL, xftw & 0xffff)
self.write(AD9914_REG_DRGAH, (xftw >> 16) & 0x7fff)
self.write(AD9914_REG_DRGFL, (xftw >> 31) & 0xffff)
self.write(AD9914_REG_DRGFH, (xftw >> 47) & 0xffff)
self.write(AD9914_REG_ASF, amplitude)
self.write(AD9914_FUD, 0)
@kernel
def exit_x(self):
"""Exits extended-resolution mode."""
delay_mu(-self.exit_x_duration_mu)
self.write(AD9914_GPIO, (1 << self.channel) << 1)
self.write(AD9914_REG_DRGAL, 0)
self.write(AD9914_REG_DRGAH, 0)
@portable(flags={"fast-math"})
def frequency_to_xftw(self, frequency):
"""Returns the 63-bit frequency tuning word corresponding to the given
frequency (extended resolution mode).
"""
return int64(round(2.0*float(int64(2)**62)*frequency/self.sysclk)) & (
(int64(1) << 63) - 1)
@portable(flags={"fast-math"})
def xftw_to_frequency(self, xftw):
"""Returns the frequency corresponding to the given frequency tuning
word (extended resolution mode).
"""
return xftw*self.sysclk/(2.0*float(int64(2)**62))
@kernel
def set_x(self, frequency, amplitude=1.0):
"""Like :meth:`set_x_mu`, but uses Hz and turns.
Note that the precision of ``float`` is less than the precision
of the extended frequency tuning word.
"""
self.set_x_mu(self.frequency_to_xftw(frequency),
self.amplitude_to_asf(amplitude))

View File

@ -1,599 +0,0 @@
"""RTIO driver for the Analog Devices ADF[45]35[56] family of GHz PLLs
on Mirny-style prefixed SPI buses.
"""
# https://github.com/analogdevicesinc/linux/blob/master/Documentation/devicetree/bindings/iio/frequency/adf5355.txt
# https://github.com/analogdevicesinc/linux/blob/master/drivers/iio/frequency/adf5355.c
# https://www.analog.com/media/en/technical-documentation/data-sheets/ADF5355.pdf
# https://www.analog.com/media/en/technical-documentation/data-sheets/ADF5355.pdf
# https://www.analog.com/media/en/technical-documentation/user-guides/EV-ADF5355SD1Z-UG-1087.pdf
from artiq.language.core import kernel, portable, delay
from artiq.language.units import us, GHz, MHz
from artiq.language.types import TInt32, TInt64
from artiq.coredevice import spi2 as spi
from artiq.coredevice.adf5356_reg import *
from numpy import int32, int64, floor, ceil
SPI_CONFIG = (
0 * spi.SPI_OFFLINE
| 0 * spi.SPI_END
| 0 * spi.SPI_INPUT
| 1 * spi.SPI_CS_POLARITY
| 0 * spi.SPI_CLK_POLARITY
| 0 * spi.SPI_CLK_PHASE
| 0 * spi.SPI_LSB_FIRST
| 0 * spi.SPI_HALF_DUPLEX
)
ADF5356_MIN_VCO_FREQ = int64(3.4 * GHz)
ADF5356_MAX_VCO_FREQ = int64(6.8 * GHz)
ADF5356_MAX_FREQ_PFD = int32(125.0 * MHz)
ADF5356_MODULUS1 = int32(1 << 24)
ADF5356_MAX_MODULUS2 = int32(1 << 28) # FIXME: ADF5356 has 28 bits MOD2
ADF5356_MAX_R_CNT = int32(1023)
class ADF5356:
"""Analog Devices AD[45]35[56] family of GHz PLLs.
:param cpld_device: Mirny CPLD device name
:param sw_device: Mirny RF switch device name
:param channel: Mirny RF channel index
:param ref_doubler: enable/disable reference clock doubler
:param ref_divider: enable/disable reference clock divide-by-2
:param core_device: Core device name (default: "core")
"""
kernel_invariants = {"cpld", "sw", "channel", "core", "sysclk"}
def __init__(
self,
dmgr,
cpld_device,
sw_device,
channel,
ref_doubler=False,
ref_divider=False,
core="core",
):
self.cpld = dmgr.get(cpld_device)
self.sw = dmgr.get(sw_device)
self.channel = channel
self.core = dmgr.get(core)
self.ref_doubler = ref_doubler
self.ref_divider = ref_divider
self.sysclk = self.cpld.refclk
assert 10 <= self.sysclk / 1e6 <= 600
self._init_registers()
@staticmethod
def get_rtio_channels(**kwargs):
return []
@kernel
def init(self, blind=False):
"""
Initialize and configure the PLL.
:param blind: Do not attempt to verify presence.
"""
self.sync()
if not blind:
# MUXOUT = VDD
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 1)
self.write(self.regs[4])
delay(1000 * us)
if not self.read_muxout():
raise ValueError("MUXOUT not high")
delay(800 * us)
# MUXOUT = DGND
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 2)
self.write(self.regs[4])
delay(1000 * us)
if self.read_muxout():
raise ValueError("MUXOUT not low")
delay(800 * us)
# MUXOUT = digital lock-detect
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 6)
self.write(self.regs[4])
@kernel
def set_att(self, att):
"""Set digital step attenuator in SI units.
This method will write the attenuator settings of the channel.
See also :meth:`Mirny.set_att<artiq.coredevice.mirny.Mirny.set_att>`.
:param att: Attenuation in dB.
"""
self.cpld.set_att(self.channel, att)
@kernel
def set_att_mu(self, att):
"""Set digital step attenuator in machine units.
:param att: Attenuation setting, 8-bit digital.
"""
self.cpld.set_att_mu(self.channel, att)
@kernel
def write(self, data):
self.cpld.write_ext(self.channel | 4, 32, data)
@kernel
def read_muxout(self):
"""
Read the state of the MUXOUT line.
By default, this is configured to be the digital lock detection.
"""
return bool(self.cpld.read_reg(0) & (1 << (self.channel + 8)))
@kernel
def set_output_power_mu(self, n):
"""
Set the power level at output A of the PLL chip in machine units.
This driver defaults to `n = 3` at init.
:param n: output power setting, 0, 1, 2, or 3 (see ADF5356 datasheet, fig. 44).
"""
if n not in [0, 1, 2, 3]:
raise ValueError("invalid power setting")
self.regs[6] = ADF5356_REG6_RF_OUTPUT_A_POWER_UPDATE(self.regs[6], n)
self.sync()
@portable
def output_power_mu(self):
"""
Return the power level at output A of the PLL chip in machine units.
"""
return ADF5356_REG6_RF_OUTPUT_A_POWER_GET(self.regs[6])
@kernel
def enable_output(self):
"""
Enable output A of the PLL chip. This is the default after init.
"""
self.regs[6] |= ADF5356_REG6_RF_OUTPUT_A_ENABLE(1)
self.sync()
@kernel
def disable_output(self):
"""
Disable output A of the PLL chip.
"""
self.regs[6] &= ~ADF5356_REG6_RF_OUTPUT_A_ENABLE(1)
self.sync()
@kernel
def set_frequency(self, f):
"""
Output given frequency on output A.
:param f: 53.125 MHz <= f <= 6800 MHz
"""
freq = int64(round(f))
if freq > ADF5356_MAX_VCO_FREQ:
raise ValueError("Requested too high frequency")
# select minimal output divider
rf_div_sel = 0
while freq < ADF5356_MIN_VCO_FREQ:
freq <<= 1
rf_div_sel += 1
if (1 << rf_div_sel) > 64:
raise ValueError("Requested too low frequency")
# choose reference divider that maximizes PFD frequency
self.regs[4] = ADF5356_REG4_R_COUNTER_UPDATE(
self.regs[4], self._compute_reference_counter()
)
f_pfd = self.f_pfd()
# choose prescaler
if freq > int64(6e9):
self.regs[0] |= ADF5356_REG0_PRESCALER(1) # 8/9
n_min, n_max = 75, 65535
# adjust reference divider to be able to match n_min constraint
while n_min * f_pfd > freq:
r = ADF5356_REG4_R_COUNTER_GET(self.regs[4])
self.regs[4] = ADF5356_REG4_R_COUNTER_UPDATE(self.regs[4], r + 1)
f_pfd = self.f_pfd()
else:
self.regs[0] &= ~ADF5356_REG0_PRESCALER(1) # 4/5
n_min, n_max = 23, 32767
# calculate PLL parameters
n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb) = calculate_pll(
freq, f_pfd
)
if not (n_min <= n <= n_max):
raise ValueError("Invalid INT value")
# configure PLL
self.regs[0] = ADF5356_REG0_INT_VALUE_UPDATE(self.regs[0], n)
self.regs[1] = ADF5356_REG1_MAIN_FRAC_VALUE_UPDATE(self.regs[1], frac1)
self.regs[2] = ADF5356_REG2_AUX_FRAC_LSB_VALUE_UPDATE(self.regs[2], frac2_lsb)
self.regs[2] = ADF5356_REG2_AUX_MOD_LSB_VALUE_UPDATE(self.regs[2], mod2_lsb)
self.regs[13] = ADF5356_REG13_AUX_FRAC_MSB_VALUE_UPDATE(
self.regs[13], frac2_msb
)
self.regs[13] = ADF5356_REG13_AUX_MOD_MSB_VALUE_UPDATE(self.regs[13], mod2_msb)
self.regs[6] = ADF5356_REG6_RF_DIVIDER_SELECT_UPDATE(self.regs[6], rf_div_sel)
self.regs[6] = ADF5356_REG6_CP_BLEED_CURRENT_UPDATE(
self.regs[6], int32(floor(24 * f_pfd / (61.44 * MHz)))
)
self.regs[9] = ADF5356_REG9_VCO_BAND_DIVISION_UPDATE(
self.regs[9], int32(ceil(f_pfd / 160e3))
)
# commit
self.sync()
@kernel
def sync(self):
"""
Write all registers to the device. Attempts to lock the PLL.
"""
f_pfd = self.f_pfd()
delay(200 * us) # Slack
if f_pfd <= 75.0 * MHz:
for i in range(13, 0, -1):
self.write(self.regs[i])
delay(200 * us)
self.write(self.regs[0] | ADF5356_REG0_AUTOCAL(1))
else:
# AUTOCAL AT HALF PFD FREQUENCY
# calculate PLL at f_pfd/2
n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb) = calculate_pll(
self.f_vco(), f_pfd >> 1
)
delay(200 * us) # Slack
self.write(
13
| ADF5356_REG13_AUX_FRAC_MSB_VALUE(frac2_msb)
| ADF5356_REG13_AUX_MOD_MSB_VALUE(mod2_msb)
)
for i in range(12, 4, -1):
self.write(self.regs[i])
self.write(
ADF5356_REG4_R_COUNTER_UPDATE(self.regs[4], 2 * self.ref_counter())
)
self.write(self.regs[3])
self.write(
2
| ADF5356_REG2_AUX_MOD_LSB_VALUE(mod2_lsb)
| ADF5356_REG2_AUX_FRAC_LSB_VALUE(frac2_lsb)
)
self.write(1 | ADF5356_REG1_MAIN_FRAC_VALUE(frac1))
delay(200 * us)
self.write(ADF5356_REG0_INT_VALUE(n) | ADF5356_REG0_AUTOCAL(1))
# RELOCK AT WANTED PFD FREQUENCY
for i in [4, 2, 1]:
self.write(self.regs[i])
# force-disable autocal
self.write(self.regs[0] & ~ADF5356_REG0_AUTOCAL(1))
@portable
def f_pfd(self) -> TInt64:
"""
Return the PFD frequency for the cached set of registers.
"""
r = ADF5356_REG4_R_COUNTER_GET(self.regs[4])
d = ADF5356_REG4_R_DOUBLER_GET(self.regs[4])
t = ADF5356_REG4_R_DIVIDER_GET(self.regs[4])
return self._compute_pfd_frequency(r, d, t)
@portable
def f_vco(self) -> TInt64:
"""
Return the VCO frequency for the cached set of registers.
"""
return int64(
self.f_pfd()
* (
self.pll_n()
+ (self.pll_frac1() + self.pll_frac2() / self.pll_mod2())
/ ADF5356_MODULUS1
)
)
@portable
def pll_n(self) -> TInt32:
"""
Return the PLL integer value (INT) for the cached set of registers.
"""
return ADF5356_REG0_INT_VALUE_GET(self.regs[0])
@portable
def pll_frac1(self) -> TInt32:
"""
Return the main fractional value (FRAC1) for the cached set of registers.
"""
return ADF5356_REG1_MAIN_FRAC_VALUE_GET(self.regs[1])
@portable
def pll_frac2(self) -> TInt32:
"""
Return the auxiliary fractional value (FRAC2) for the cached set of registers.
"""
return (
ADF5356_REG13_AUX_FRAC_MSB_VALUE_GET(self.regs[13]) << 14
) | ADF5356_REG2_AUX_FRAC_LSB_VALUE_GET(self.regs[2])
@portable
def pll_mod2(self) -> TInt32:
"""
Return the auxiliary modulus value (MOD2) for the cached set of registers.
"""
return (
ADF5356_REG13_AUX_MOD_MSB_VALUE_GET(self.regs[13]) << 14
) | ADF5356_REG2_AUX_MOD_LSB_VALUE_GET(self.regs[2])
@portable
def ref_counter(self) -> TInt32:
"""
Return the reference counter value (R) for the cached set of registers.
"""
return ADF5356_REG4_R_COUNTER_GET(self.regs[4])
@portable
def output_divider(self) -> TInt32:
"""
Return the value of the output A divider.
"""
return 1 << ADF5356_REG6_RF_DIVIDER_SELECT_GET(self.regs[6])
def info(self):
"""
Return a summary of high-level parameters as a dict.
"""
prescaler = ADF5356_REG0_PRESCALER_GET(self.regs[0])
return {
# output
"f_outA": self.f_vco() / self.output_divider(),
"f_outB": self.f_vco() * 2,
"output_divider": self.output_divider(),
# PLL parameters
"f_vco": self.f_vco(),
"pll_n": self.pll_n(),
"pll_frac1": self.pll_frac1(),
"pll_frac2": self.pll_frac2(),
"pll_mod2": self.pll_mod2(),
"prescaler": "4/5" if prescaler == 0 else "8/9",
# reference / PFD
"sysclk": self.sysclk,
"ref_doubler": self.ref_doubler,
"ref_divider": self.ref_divider,
"ref_counter": self.ref_counter(),
"f_pfd": self.f_pfd(),
}
@portable
def _init_registers(self):
"""
Initialize cached registers with sensible defaults.
"""
# fill with control bits
self.regs = [int32(i) for i in range(ADF5356_NUM_REGS)]
# REG2
# ====
# avoid divide-by-zero
self.regs[2] |= ADF5356_REG2_AUX_MOD_LSB_VALUE(1)
# REG4
# ====
# single-ended reference mode is recommended
# for references up to 250 MHz, even if the signal is differential
if self.sysclk <= 250 * MHz:
self.regs[4] |= ADF5356_REG4_REF_MODE(0)
else:
self.regs[4] |= ADF5356_REG4_REF_MODE(1)
# phase detector polarity: positive
self.regs[4] |= ADF5356_REG4_PD_POLARITY(1)
# charge pump current: 0.94 mA
self.regs[4] |= ADF5356_REG4_CURRENT_SETTING(2)
# MUXOUT: digital lock detect
self.regs[4] |= ADF5356_REG4_MUX_LOGIC(1) # 3v3 logic
self.regs[4] |= ADF5356_REG4_MUXOUT(6)
# setup reference path
if self.ref_doubler:
self.regs[4] |= ADF5356_REG4_R_DOUBLER(1)
if self.ref_divider:
self.regs[4] |= ADF5356_REG4_R_DIVIDER(1)
r = self._compute_reference_counter()
self.regs[4] |= ADF5356_REG4_R_COUNTER(r)
# REG5
# ====
# reserved values
self.regs[5] = int32(0x800025)
# REG6
# ====
# reserved values
self.regs[6] = int32(0x14000006)
# enable negative bleed
self.regs[6] |= ADF5356_REG6_NEGATIVE_BLEED(1)
# charge pump bleed current
self.regs[6] |= ADF5356_REG6_CP_BLEED_CURRENT(
int32(floor(24 * self.f_pfd() / (61.44 * MHz)))
)
# direct feedback from VCO to N counter
self.regs[6] |= ADF5356_REG6_FB_SELECT(1)
# mute until the PLL is locked
self.regs[6] |= ADF5356_REG6_MUTE_TILL_LD(1)
# enable output A
self.regs[6] |= ADF5356_REG6_RF_OUTPUT_A_ENABLE(1)
# set output A power to max power, is adjusted by extra attenuator
self.regs[6] |= ADF5356_REG6_RF_OUTPUT_A_POWER(3) # +5 dBm
# REG7
# ====
# reserved values
self.regs[7] = int32(0x10000007)
# sync load-enable to reference
self.regs[7] |= ADF5356_REG7_LE_SYNC(1)
# frac-N lock-detect precision: 12 ns
self.regs[7] |= ADF5356_REG7_FRAC_N_LD_PRECISION(3)
# REG8
# ====
# reserved values
self.regs[8] = int32(0x102D0428)
# REG9
# ====
# default timeouts (from eval software)
self.regs[9] |= (
ADF5356_REG9_SYNTH_LOCK_TIMEOUT(13)
| ADF5356_REG9_AUTOCAL_TIMEOUT(31)
| ADF5356_REG9_TIMEOUT(0x67)
)
self.regs[9] |= ADF5356_REG9_VCO_BAND_DIVISION(
int32(ceil(self.f_pfd() / 160e3))
)
# REG10
# =====
# reserved values
self.regs[10] = int32(0xC0000A)
# ADC defaults (from eval software)
self.regs[10] |= (
ADF5356_REG10_ADC_ENABLE(1)
| ADF5356_REG10_ADC_CLK_DIV(256)
| ADF5356_REG10_ADC_CONV(1)
)
# REG11
# =====
# reserved values
self.regs[11] = int32(0x61200B)
# REG12
# =====
# reserved values
self.regs[12] = int32(0x15FC)
@portable
def _compute_pfd_frequency(self, r, d, t) -> TInt64:
"""
Calculate the PFD frequency from the given reference path parameters.
"""
return int64(self.sysclk * ((1 + d) / (r * (1 + t))))
@portable
def _compute_reference_counter(self) -> TInt32:
"""
Determine the reference counter R that maximizes the PFD frequency.
"""
d = ADF5356_REG4_R_DOUBLER_GET(self.regs[4])
t = ADF5356_REG4_R_DIVIDER_GET(self.regs[4])
r = 1
while self._compute_pfd_frequency(r, d, t) > ADF5356_MAX_FREQ_PFD:
r += 1
return int32(r)
@portable
def gcd(a, b):
while b:
a, b = b, a % b
return a
@portable
def split_msb_lsb_28b(v):
return int32((v >> 14) & 0x3FFF), int32(v & 0x3FFF)
@portable
def calculate_pll(f_vco: TInt64, f_pfd: TInt64):
"""
Calculate fractional-N PLL parameters such that
``f_vco = f_pfd * (n + (frac1 + frac2/mod2) / mod1)``
where
``mod1 = 2**24`` and ``mod2 <= 2**28``
:param f_vco: target VCO frequency
:param f_pfd: PFD frequency
:return: (``n``, ``frac1``, ``(frac2_msb, frac2_lsb)``, ``(mod2_msb, mod2_lsb)``)
"""
f_pfd = int64(f_pfd)
f_vco = int64(f_vco)
# integral part
n, r = int32(f_vco // f_pfd), f_vco % f_pfd
# main fractional part
r *= ADF5356_MODULUS1
frac1, frac2 = int32(r // f_pfd), r % f_pfd
# auxiliary fractional part
mod2 = f_pfd
while mod2 > ADF5356_MAX_MODULUS2:
mod2 >>= 1
frac2 >>= 1
gcd_div = gcd(frac2, mod2)
mod2 //= gcd_div
frac2 //= gcd_div
return n, frac1, split_msb_lsb_28b(frac2), split_msb_lsb_28b(mod2)

View File

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

View File

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

View File

@ -0,0 +1,404 @@
from operator import itemgetter
from collections import namedtuple
from itertools import count
import struct
import logging
from artiq.protocols.analyzer import MessageType, ExceptionType
logger = logging.getLogger(__name__)
OutputMessage = namedtuple(
"OutputMessage", "channel timestamp rtio_counter address data")
InputMessage = namedtuple(
"InputMessage", "channel timestamp rtio_counter data")
ExceptionMessage = namedtuple(
"ExceptionMessage", "channel rtio_counter exception_type")
StoppedMessage = namedtuple(
"StoppedMessage", "rtio_counter")
def decode_message(data):
message_type_channel = struct.unpack(">I", data[28:32])[0]
message_type = MessageType(message_type_channel & 0b11)
channel = message_type_channel >> 2
if message_type == MessageType.output:
parts = struct.unpack(">QIQQ", data[:28])
data, address, rtio_counter, timestamp = parts
return OutputMessage(channel, timestamp, rtio_counter, address, data)
elif message_type == MessageType.input:
parts = struct.unpack(">QIQQ", data[:28])
data, _, rtio_counter, timestamp = parts
return InputMessage(channel, timestamp, rtio_counter, data)
elif message_type == MessageType.exception:
exception_type, rtio_counter = struct.unpack(">BQ", data[11:20])
return ExceptionMessage(channel, rtio_counter,
ExceptionType(exception_type))
elif message_type == MessageType.stopped:
rtio_counter = struct.unpack(">Q", data[12:20])[0]
return StoppedMessage(rtio_counter)
else:
raise ValueError
DecodedDump = namedtuple(
"DecodedDump", "log_channel dds_onehot_sel messages")
def decode_dump(data):
parts = struct.unpack(">IQbbb", data[:15])
(sent_bytes, total_byte_count,
overflow_occured, log_channel, dds_onehot_sel) = parts
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 overflow_occured:
logger.warning("analyzer FIFO overflow occured, "
"some messages have been lost")
if total_byte_count > sent_bytes:
logger.info("analyzer ring buffer has wrapped %d times",
total_byte_count//sent_bytes)
position = 15
messages = []
for _ in range(sent_bytes//32):
messages.append(decode_message(data[position:position+32]))
position += 32
return DecodedDump(log_channel, bool(dds_onehot_sel), messages)
def vcd_codes():
codechars = [chr(i) for i in range(33, 127)]
for n in count():
q, r = divmod(n, len(codechars))
code = codechars[r]
while q > 0:
q, r = divmod(q, len(codechars))
code = codechars[r] + code
yield code
class VCDChannel:
def __init__(self, out, code):
self.out = out
self.code = code
def set_value(self, value):
if len(value) > 1:
self.out.write("b" + value + " " + self.code + "\n")
else:
self.out.write(value + self.code + "\n")
def set_value_double(self, x):
integer_cast = struct.unpack(">Q", struct.pack(">d", x))[0]
self.set_value("{:064b}".format(integer_cast))
class VCDManager:
def __init__(self, fileobj):
self.out = fileobj
self.codes = vcd_codes()
self.current_time = None
def set_timescale_ps(self, timescale):
self.out.write("$timescale {}ps $end\n".format(round(timescale)))
def get_channel(self, name, width):
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)
def set_time(self, time):
if time != self.current_time:
self.out.write("#{}\n".format(time))
self.current_time = time
class TTLHandler:
def __init__(self, vcd_manager, name):
self.name = name
self.channel_value = vcd_manager.get_channel("ttl/" + name, 1)
self.last_value = "X"
self.oe = True
def process_message(self, message):
if isinstance(message, OutputMessage):
logger.debug("TTL write @%d %d to %d, name: %s",
message.timestamp, message.data, message.address, self.name)
if message.address == 0:
self.last_value = str(message.data)
if self.oe:
self.channel_value.set_value(self.last_value)
elif message.address == 1:
self.oe = bool(message.data)
if self.oe:
self.channel_value.set_value(self.last_value)
else:
self.channel_value.set_value("X")
elif isinstance(message, InputMessage):
logger.debug("TTL read @%d %d, name: %s",
message.timestamp, message.data, self.name)
self.channel_value.set_value(str(message.data))
class TTLClockGenHandler:
def __init__(self, vcd_manager, name, ref_period):
self.name = name
self.ref_period = ref_period
self.channel_frequency = vcd_manager.get_channel(
"ttl_clkgen/" + name, 64)
def process_message(self, message):
if isinstance(message, OutputMessage):
logger.debug("TTL_CLKGEN write @%d %d to %d, name: %s",
message.timestamp, message.data, message.address, self.name)
frequency = message.data/self.ref_period/2**24
self.channel_frequency.set_value_double(frequency)
class DDSHandler:
def __init__(self, vcd_manager, dds_type, onehot_sel, sysclk):
self.vcd_manager = vcd_manager
self.dds_type = dds_type
self.onehot_sel = onehot_sel
self.sysclk = sysclk
self.selected_dds_channels = set()
self.dds_channels = dict()
def add_dds_channel(self, name, dds_channel_nr):
dds_channel = dict()
dds_channel["vcd_frequency"] = \
self.vcd_manager.get_channel("dds/" + name + "/frequency", 64)
dds_channel["vcd_phase"] = \
self.vcd_manager.get_channel("dds/" + name + "/phase", 64)
if self.dds_type == "AD9858":
dds_channel["ftw"] = [None, None, None, None]
dds_channel["pow"] = [None, None]
elif self.dds_type == "AD9914":
dds_channel["ftw"] = [None, None]
dds_channel["pow"] = None
self.dds_channels[dds_channel_nr] = dds_channel
def _gpio_to_channels(self, gpio):
gpio >>= 1 # strip reset
if self.onehot_sel:
r = set()
nr = 0
mask = 1
while gpio >= mask:
if gpio & mask:
r.add(nr)
nr += 1
mask *= 2
return r
else:
return {gpio}
def _decode_ad9858_write(self, message):
if message.address == 0x41:
self.selected_dds_channels = self._gpio_to_channels(message.data)
for dds_channel_nr in self.selected_dds_channels:
dds_channel = self.dds_channels[dds_channel_nr]
if message.address in range(0x0a, 0x0e):
dds_channel["ftw"][message.address - 0x0a] = message.data
elif message.address in range(0x0e, 0x10):
dds_channel["pow"][message.address - 0x0e] = message.data
elif message.address == 0x40: # FUD
if None not in dds_channel["ftw"]:
ftw = sum(x << i*8
for i, x in enumerate(dds_channel["ftw"]))
frequency = ftw*self.sysclk/2**32
dds_channel["vcd_frequency"].set_value_double(frequency)
if None not in dds_channel["pow"]:
pow = dds_channel["pow"][0] | (dds_channel["pow"][1] & 0x3f) << 8
phase = pow/2**14
dds_channel["vcd_phase"].set_value_double(phase)
def _decode_ad9914_write(self, message):
if message.address == 0x81:
self.selected_dds_channels = self._gpio_to_channels(message.data)
for dds_channel_nr in self.selected_dds_channels:
dds_channel = self.dds_channels[dds_channel_nr]
if message.address == 0x2d:
dds_channel["ftw"][0] = message.data
elif message.address == 0x2f:
dds_channel["ftw"][1] = message.data
elif message.address == 0x31:
dds_channel["pow"] = message.data
elif message.address == 0x80: # FUD
if None not in dds_channel["ftw"]:
ftw = sum(x << i*16
for i, x in enumerate(dds_channel["ftw"]))
frequency = ftw*self.sysclk/2**32
dds_channel["vcd_frequency"].set_value_double(frequency)
if dds_channel["pow"] is not None:
phase = dds_channel["pow"]/2**16
dds_channel["vcd_phase"].set_value_double(phase)
def process_message(self, message):
if isinstance(message, OutputMessage):
logger.debug("DDS write @%d 0x%04x to 0x%02x, selected channels: %s",
message.timestamp, message.data, message.address,
self.selected_dds_channels)
if self.dds_type == "AD9858":
self._decode_ad9858_write(message)
elif self.dds_type == "AD9914":
self._decode_ad9914_write(message)
def _extract_log_chars(data):
r = ""
for i in range(4):
n = data >> 24
data = (data << 8) & 0xffffffff
if not n:
continue
r += chr(n)
return r
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)
self.current_entry = ""
def process_message(self, message):
if isinstance(message, OutputMessage):
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.current_entry = ""
def get_vcd_log_channels(log_channel, messages):
vcd_log_channels = dict()
log_entry = ""
for message in messages:
if (isinstance(message, OutputMessage)
and message.channel == log_channel):
log_entry += _extract_log_chars(message.data)
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
else:
vcd_log_channels[channel_name] = l
log_entry = ""
return vcd_log_channels
def get_single_device_argument(devices, module, cls, argument):
ref_period = None
for desc in devices.values():
if isinstance(desc, dict) and desc["type"] == "local":
if (desc["module"] == module
and desc["class"] == cls):
if ref_period is None:
ref_period = desc["arguments"][argument]
else:
return None # more than one device found
return ref_period
def get_ref_period(devices):
return get_single_device_argument(devices, "artiq.coredevice.core",
"Core", "ref_period")
def get_dds_sysclk(devices):
return get_single_device_argument(devices, "artiq.coredevice.dds",
"CoreDDS", "sysclk")
def create_channel_handlers(vcd_manager, devices, ref_period,
dds_sysclk, dds_onehot_sel):
channel_handlers = dict()
for name, desc in sorted(devices.items(), key=itemgetter(0)):
if isinstance(desc, dict) and desc["type"] == "local":
if (desc["module"] == "artiq.coredevice.ttl"
and desc["class"] in {"TTLOut", "TTLInOut"}):
channel = desc["arguments"]["channel"]
channel_handlers[channel] = TTLHandler(vcd_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)
if (desc["module"] == "artiq.coredevice.dds"
and desc["class"] in {"AD9858", "AD9914"}):
dds_bus_channel = desc["arguments"]["bus_channel"]
dds_channel = desc["arguments"]["channel"]
if dds_bus_channel in channel_handlers:
dds_handler = channel_handlers[dds_bus_channel]
if dds_handler.dds_type != desc["class"]:
raise ValueError("All DDS channels must have the same type")
else:
dds_handler = DDSHandler(vcd_manager, desc["class"],
dds_onehot_sel, dds_sysclk)
channel_handlers[dds_bus_channel] = dds_handler
dds_handler.add_dds_channel(name, dds_channel)
return channel_handlers
def get_message_time(message):
return getattr(message, "timestamp", message.rtio_counter)
def decoded_dump_to_vcd(fileobj, devices, dump):
vcd_manager = VCDManager(fileobj)
ref_period = get_ref_period(devices)
if ref_period is not None:
vcd_manager.set_timescale_ps(ref_period*1e12)
else:
logger.warning("unable to determine core device ref_period")
ref_period = 1e-9 # guess
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):
messages = dump.messages[:-1]
else:
logger.warning("StoppedMessage missing")
messages = dump.messages
messages = sorted(messages, key=get_message_time)
channel_handlers = create_channel_handlers(
vcd_manager, devices, ref_period,
dds_sysclk, dump.dds_onehot_sel)
vcd_log_channels = get_vcd_log_channels(dump.log_channel, messages)
channel_handlers[dump.log_channel] = LogHandler(
vcd_manager, vcd_log_channels)
slack = vcd_manager.get_channel("rtio_slack", 64)
vcd_manager.set_time(0)
if messages:
start_time = get_message_time(messages[0])
for message in messages:
if message.channel in channel_handlers:
vcd_manager.set_time(
get_message_time(message) - start_time)
channel_handlers[message.channel].process_message(message)
if isinstance(message, OutputMessage):
slack.set_value_double(
(message.timestamp - message.rtio_counter)*ref_period)

View File

@ -2,11 +2,11 @@ from artiq.language.core import *
from artiq.language.types import * from artiq.language.types import *
@syscall(flags={"nounwind"}) @syscall(flags={"nounwind", "nowrite"})
def cache_get(key: TStr) -> TList(TInt32): def cache_get(key: TStr) -> TList(TInt32):
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall @syscall(flags={"nowrite"})
def cache_put(key: TStr, value: TList(TInt32)) -> TNone: def cache_put(key: TStr, value: TList(TInt32)) -> TNone:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@ -21,9 +21,9 @@ class CoreCache:
"""Extract a value from the core device cache. """Extract a value from the core device cache.
After a value is extracted, it cannot be replaced with another value using After a value is extracted, it cannot be replaced with another value using
:meth:`put` until all kernel functions finish executing; attempting :meth:`put` until all kernel functions finish executing; attempting
to replace it will result in a :class:`~artiq.coredevice.exceptions.CacheError`. to replace it will result in a :class:`artiq.coredevice.exceptions.CacheError`.
If the cache does not contain any value associated with `key`, an empty list If the cache does not contain any value associated with ``key``, an empty list
is returned. is returned.
The value is not copied, so mutating it will change what's stored in the cache. The value is not copied, so mutating it will change what's stored in the cache.

View File

@ -1,778 +0,0 @@
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
exception = 0b10
stopped = 0b11
class ExceptionType(Enum):
legacy_reset = 0b000000
legacy_reset_falling = 0b000001
legacy_reset_phy = 0b000010
legacy_reset_phy_falling = 0b000011
legacy_o_underflow_reset = 0b010000
legacy_o_sequence_error_reset = 0b010001
legacy_o_collision_reset = 0b010010
legacy_i_overflow_reset = 0b100000
legacy_o_sequence_error = 0b010101
o_underflow = 0b010100
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:
r = bytes()
while True:
buf = sock.recv(8192)
if not buf:
break
r += buf
finally:
sock.close()
return r
OutputMessage = namedtuple(
"OutputMessage", "channel timestamp rtio_counter address data")
InputMessage = namedtuple(
"InputMessage", "channel timestamp rtio_counter data")
ExceptionMessage = namedtuple(
"ExceptionMessage", "channel rtio_counter exception_type")
StoppedMessage = namedtuple(
"StoppedMessage", "rtio_counter")
def decode_message(data):
message_type_channel = struct.unpack(">I", data[28:32])[0]
message_type = MessageType(message_type_channel & 0b11)
channel = message_type_channel >> 2
if message_type == MessageType.output:
parts = struct.unpack(">QIQQ", data[:28])
data, address, rtio_counter, timestamp = parts
return OutputMessage(channel, timestamp, rtio_counter, address, data)
elif message_type == MessageType.input:
parts = struct.unpack(">QIQQ", data[:28])
data, _, rtio_counter, timestamp = parts
return InputMessage(channel, timestamp, rtio_counter, data)
elif message_type == MessageType.exception:
exception_type, rtio_counter = struct.unpack(">BQ", data[11:20])
return ExceptionMessage(channel, rtio_counter,
ExceptionType(exception_type))
elif message_type == MessageType.stopped:
rtio_counter = struct.unpack(">Q", data[12:20])[0]
return StoppedMessage(rtio_counter)
else:
raise ValueError
DecodedDump = namedtuple(
"DecodedDump", "log_channel dds_onehot_sel messages")
def decode_dump(data):
# extract endian byte
if data[0] == ord('E'):
endian = '>'
elif data[0] == ord('e'):
endian = '<'
else:
raise ValueError
data = data[1:]
# only header is device endian
# messages are big endian
parts = struct.unpack(endian + "IQbbb", data[:15])
(sent_bytes, total_byte_count,
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_occurred:
logger.warning("error occurred within the analyzer, "
"data may be corrupted")
if total_byte_count > sent_bytes:
logger.info("analyzer ring buffer has wrapped %d times",
total_byte_count//sent_bytes)
if sent_bytes == 0:
logger.warning("analyzer dump is empty")
position = 15
messages = []
for _ in range(sent_bytes//32):
messages.append(decode_message(data[position:position+32]))
position += 32
if len(messages) == 1 and isinstance(messages[0], StoppedMessage):
logger.warning("analyzer dump is empty aside from stop message")
return DecodedDump(log_channel, bool(dds_onehot_sel), messages)
# simplified from sipyco broadcast Receiver
class AnalyzerProxyReceiver:
def __init__(self, receive_cb, disconnect_cb=None):
self.receive_cb = receive_cb
self.disconnect_cb = disconnect_cb
async def connect(self, host, port):
self.reader, self.writer = \
await keepalive.async_open_connection(host, port)
try:
line = await self.reader.readline()
assert line == ANALYZER_MAGIC
self.receive_task = asyncio.create_task(self._receive_cr())
except:
self.writer.close()
del self.reader
del self.writer
raise
async def close(self):
self.disconnect_cb = None
try:
self.receive_task.cancel()
try:
await self.receive_task
except asyncio.CancelledError:
pass
finally:
self.writer.close()
del self.reader
del self.writer
async def _receive_cr(self):
try:
while True:
data = bytearray()
data.extend(await self.reader.read(1))
if len(data) == 0:
# EOF reached, connection lost
return
if data[0] == ord("E"):
endian = '>'
elif data[0] == ord("e"):
endian = '<'
else:
raise ValueError
data.extend(await self.reader.readexactly(4))
payload_length = struct.unpack(endian + "I", data[1:5])[0]
if payload_length > 10 * 512 * 1024:
# 10x buffer size of firmware
raise ValueError
# The remaining header length is 11 bytes.
data.extend(await self.reader.readexactly(payload_length + 11))
self.receive_cb(data)
except Exception:
logger.error("analyzer receiver connection terminating with exception", exc_info=True)
finally:
if self.disconnect_cb is not None:
self.disconnect_cb()
def vcd_codes():
codechars = [chr(i) for i in range(33, 127)]
for n in count():
q, r = divmod(n, len(codechars))
code = codechars[r]
while q > 0:
q, r = divmod(q, len(codechars))
code = codechars[r] + code
yield code
class VCDChannel:
def __init__(self, out, code):
self.out = out
self.code = code
def set_value(self, value):
if len(value) > 1:
self.out.write("b" + value + " " + self.code + "\n")
else:
self.out.write(value + self.code + "\n")
def set_value_double(self, x):
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, 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, 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, manager, name):
self.name = name
self.channel_value = manager.get_channel("ttl/" + name, 1, ty=WaveformType.BIT)
self.last_value = "X"
self.oe = True
def process_message(self, message):
if isinstance(message, OutputMessage):
logger.debug("TTL write @%d %d to %d, name: %s",
message.timestamp, message.data, message.address, self.name)
if message.address == 0:
self.last_value = str(message.data)
if self.oe:
self.channel_value.set_value(self.last_value)
elif message.address == 1:
self.oe = bool(message.data)
if self.oe:
self.channel_value.set_value(self.last_value)
else:
self.channel_value.set_value("X")
elif isinstance(message, InputMessage):
logger.debug("TTL read @%d %d, name: %s",
message.timestamp, message.data, self.name)
self.channel_value.set_value(str(message.data))
class TTLClockGenHandler:
def __init__(self, manager, name, ref_period):
self.name = name
self.ref_period = ref_period
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):
logger.debug("TTL_CLKGEN write @%d %d to %d, name: %s",
message.timestamp, message.data, message.address, self.name)
frequency = message.data/self.ref_period/2**24
self.channel_frequency.set_value_double(frequency)
class DDSHandler:
def __init__(self, manager, onehot_sel, sysclk):
self.manager = manager
self.onehot_sel = onehot_sel
self.sysclk = sysclk
self.selected_dds_channels = set()
self.dds_channels = dict()
def add_dds_channel(self, name, dds_channel_nr):
dds_channel = dict()
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.manager.get_channel(name + "/frequency", 64,
ty=WaveformType.ANALOG,
precision=frequency_precision,
unit="MHz")
dds_channel["vcd_phase"] = \
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
def _gpio_to_channels(self, gpio):
gpio >>= 1 # strip reset
if self.onehot_sel:
r = set()
nr = 0
mask = 1
while gpio >= mask:
if gpio & mask:
r.add(nr)
nr += 1
mask *= 2
return r
else:
return {gpio}
def _decode_ad9914_write(self, message):
if message.address == 0x81:
self.selected_dds_channels = self._gpio_to_channels(message.data)
for dds_channel_nr in self.selected_dds_channels:
dds_channel = self.dds_channels[dds_channel_nr]
if message.address == 0x11:
dds_channel["ftw"][0] = message.data
elif message.address == 0x13:
dds_channel["ftw"][1] = message.data
elif message.address == 0x31:
dds_channel["pow"] = message.data
elif message.address == 0x80: # FUD
if None not in dds_channel["ftw"]:
ftw = sum(x << i*16
for i, x in enumerate(dds_channel["ftw"]))
frequency = ftw*self.sysclk/2**32
dds_channel["vcd_frequency"].set_value_double(frequency)
if dds_channel["pow"] is not None:
phase = dds_channel["pow"]/2**16
dds_channel["vcd_phase"].set_value_double(phase)
def process_message(self, message):
if isinstance(message, OutputMessage):
logger.debug("DDS write @%d 0x%04x to 0x%02x, selected channels: %s",
message.timestamp, message.data, message.address,
self.selected_dds_channels)
self._decode_ad9914_write(message)
class WishboneHandler:
def __init__(self, manager, name, read_bit):
self._reads = []
self._read_bit = read_bit
self.stb = manager.get_channel(name + "/stb", 1, ty=WaveformType.BIT)
def process_message(self, message):
self.stb.set_value("1")
self.stb.set_value("0")
if isinstance(message, OutputMessage):
logger.debug("Wishbone out @%d adr=0x%02x data=0x%08x",
message.timestamp, message.address, message.data)
if message.address & self._read_bit:
read = self._reads.pop(0)
self.process_read(
message.address & ~self._read_bit,
read.data,
read.rtio_counter - message.timestamp)
else:
self.process_write(message.address,
message.data)
if isinstance(message, InputMessage):
logger.debug("Wishbone in @%d data=0x%08x",
message.rtio_counter, message.data)
self._reads.append(message)
def process_write(self, address, data):
raise NotImplementedError
def process_read(self, address, data, read_slack):
raise NotImplementedError
class SPIMasterHandler(WishboneHandler):
def __init__(self, manager, name):
self.channels = {}
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] = manager.get_channel(
"{}/{}".format(name, reg_name), reg_width, ty=WaveformType.VECTOR)
def process_write(self, address, data):
if address == 0:
self.channels["write"].set_value("{:032b}".format(data))
elif address == 1:
self.channels["chip_select"].set_value(
"{:08b}".format(data & 0xffff))
self.channels["write_length"].set_value(
"{:08b}".format(data >> 16 & 0xff))
self.channels["read_length"].set_value(
"{:08b}".format(data >> 24 & 0xff))
elif address == 2:
self.channels["config"].set_value("{:032b}".format(data))
else:
raise ValueError("bad address %d", address)
def process_read(self, address, data, read_slack):
if address == 0:
self.channels["read"].set_value("{:032b}".format(data))
else:
raise ValueError("bad address %d", address)
class SPIMaster2Handler(WishboneHandler):
def __init__(self, manager, name):
self._reads = []
self.channels = {}
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),
("div", 8),
("chip_select", 8),
("write", 32),
("read", 32)]:
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")
self.stb.set_value("0")
if isinstance(message, OutputMessage):
data = message.data
address = message.address
if address == 1:
logger.debug("SPI config @%d data=0x%08x",
message.timestamp, data)
self.channels["chip_select"].set_value(
"{:08b}".format(data >> 24))
self.channels["div"].set_value(
"{:08b}".format(data >> 16 & 0xff))
self.channels["length"].set_value(
"{:08b}".format(data >> 8 & 0x1f))
self.channels["flags"].set_value(
"{:08b}".format(data & 0xff))
elif address == 0:
logger.debug("SPI write @%d data=0x%08x",
message.timestamp, data)
self.channels["write"].set_value("{:032b}".format(data))
else:
raise ValueError("bad address", address)
# process untimed reads and insert them here
while (self._reads and
self._reads[0].rtio_counter < message.timestamp):
read = self._reads.pop(0)
logger.debug("SPI read @%d data=0x%08x",
read.rtio_counter, read.data)
self.channels["read"].set_value("{:032b}".format(read.data))
elif isinstance(message, InputMessage):
self._reads.append(message)
def _extract_log_chars(data):
r = ""
for i in range(4):
n = data >> 24
data = (data << 8) & 0xffffffff
if not n:
continue
r += chr(n)
return r
class LogHandler:
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):
if isinstance(message, OutputMessage):
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)
self.channels[channel_name].set_log(log_message)
self.current_entry = ""
def get_log_channels(log_channel, messages):
log_channels = dict()
log_entry = ""
for message in messages:
if (isinstance(message, OutputMessage)
and message.channel == log_channel):
log_entry += _extract_log_chars(message.data)
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 log_channels:
if log_channels[channel_name] < l:
log_channels[channel_name] = l
else:
log_channels[channel_name] = l
log_entry = ""
return log_channels
def get_single_device_argument(devices, module, cls, argument):
found = None
for desc in devices.values():
if isinstance(desc, dict) and desc["type"] == "local":
if (desc["module"] == module
and desc["class"] in cls):
value = desc["arguments"][argument]
if found is None:
found = value
elif value != found:
return None # more than one value/device found
return found
def get_ref_period(devices):
return get_single_device_argument(devices, "artiq.coredevice.core",
("Core",), "ref_period")
def get_dds_sysclk(devices):
return get_single_device_argument(devices, "artiq.coredevice.ad9914",
("AD9914",), "sysclk")
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)):
if isinstance(desc, dict) and desc["type"] == "local":
if (desc["module"] == "artiq.coredevice.ttl"
and desc["class"] in {"TTLOut", "TTLInOut"}):
channel = desc["arguments"]["channel"]
channel_handlers[channel] = TTLHandler(manager, name)
if (desc["module"] == "artiq.coredevice.ttl"
and desc["class"] == "TTLClockGen"):
channel = desc["arguments"]["channel"]
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"]
dds_channel = desc["arguments"]["channel"]
if dds_bus_channel in channel_handlers:
dds_handler = channel_handlers[dds_bus_channel]
else:
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(
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 None:
logger.warning("unable to determine core device ref_period")
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")
messages = dump.messages
messages = sorted(messages, key=get_message_time)
channel_handlers = create_channel_handlers(
manager, devices, ref_period,
dds_sysclk, dump.dds_onehot_sel)
log_channels = get_log_channels(dump.log_channel, messages)
channel_handlers[dump.log_channel] = LogHandler(
manager, log_channels)
if uniform_interval:
# RTIO event timestamp in machine units
timestamp = manager.get_channel("timestamp", 64, ty=WaveformType.VECTOR)
# RTIO time interval between this and the next timed event
# in SI seconds
interval = manager.get_channel("interval", 64, ty=WaveformType.ANALOG)
slack = manager.get_channel("rtio_slack", 64, ty=WaveformType.ANALOG)
manager.set_time(0)
start_time = 0
for m in messages:
start_time = get_message_time(m)
if start_time:
break
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)
if t >= 0:
if uniform_interval:
interval.set_value_double((t - t0)*ref_period)
manager.set_time(i)
timestamp.set_value("{:064b}".format(t))
t0 = t
else:
manager.set_time(t)
channel_handlers[message.channel].process_message(message)
if isinstance(message, OutputMessage):
slack.set_value_double(
(message.timestamp - message.rtio_counter)*ref_period)

View File

@ -0,0 +1,27 @@
from operator import itemgetter
class Comm:
def __init__(self, dmgr):
super().__init__()
def switch_clock(self, external):
pass
def load(self, kernel_library):
pass
def run(self):
pass
def serve(self, object_map, symbolizer):
pass
def check_ident(self):
pass
def get_log(self):
return ""
def clear_log(self):
pass

View File

@ -0,0 +1,541 @@
import struct
import logging
import traceback
from enum import Enum
from fractions import Fraction
from collections import namedtuple
from artiq.coredevice import exceptions
from artiq.language.core import int as wrapping_int
from artiq import __version__ as software_version
logger = logging.getLogger(__name__)
class _H2DMsgType(Enum):
LOG_REQUEST = 1
LOG_CLEAR = 2
IDENT_REQUEST = 3
SWITCH_CLOCK = 4
LOAD_LIBRARY = 5
RUN_KERNEL = 6
RPC_REPLY = 7
RPC_EXCEPTION = 8
FLASH_READ_REQUEST = 9
FLASH_WRITE_REQUEST = 10
FLASH_ERASE_REQUEST = 11
FLASH_REMOVE_REQUEST = 12
class _D2HMsgType(Enum):
LOG_REPLY = 1
IDENT_REPLY = 2
CLOCK_SWITCH_COMPLETED = 3
CLOCK_SWITCH_FAILED = 4
LOAD_COMPLETED = 5
LOAD_FAILED = 6
KERNEL_FINISHED = 7
KERNEL_STARTUP_FAILED = 8
KERNEL_EXCEPTION = 9
RPC_REQUEST = 10
FLASH_READ_REPLY = 11
FLASH_OK_REPLY = 12
FLASH_ERROR_REPLY = 13
WATCHDOG_EXPIRED = 14
CLOCK_FAILURE = 15
class UnsupportedDevice(Exception):
pass
class RPCReturnValueError(ValueError):
pass
RPCKeyword = namedtuple('RPCKeyword', ['name', 'value'])
class CommGeneric:
def __init__(self):
self._read_type = self._write_type = None
self._read_length = 0
self._write_buffer = []
def open(self):
"""Opens the communication channel.
Must do nothing if already opened."""
raise NotImplementedError
def close(self):
"""Closes the communication channel.
Must do nothing if already closed."""
raise NotImplementedError
def read(self, length):
"""Reads exactly length bytes from the communication channel.
The channel is assumed to be opened."""
raise NotImplementedError
def write(self, data):
"""Writes exactly length bytes to the communication channel.
The channel is assumed to be opened."""
raise NotImplementedError
#
# Reader interface
#
def _read_header(self):
self.open()
if self._read_length > 0:
raise IOError("Read underrun ({} bytes remaining)".
format(self._read_length))
# Wait for a synchronization sequence, 5a 5a 5a 5a.
sync_count = 0
while sync_count < 4:
(sync_byte, ) = struct.unpack("B", self.read(1))
if sync_byte == 0x5a:
sync_count += 1
else:
sync_count = 0
# Read message header.
(self._read_length, ) = struct.unpack(">l", self.read(4))
if not self._read_length: # inband connection close
raise OSError("Connection closed")
(raw_type, ) = struct.unpack("B", self.read(1))
self._read_type = _D2HMsgType(raw_type)
if self._read_length < 9:
raise IOError("Read overrun in message header ({} remaining)".
format(self._read_length))
self._read_length -= 9
logger.debug("receiving message: type=%r length=%d",
self._read_type, self._read_length)
def _read_expect(self, ty):
if self._read_type != ty:
raise IOError("Incorrect reply from device: {} (expected {})".
format(self._read_type, ty))
def _read_empty(self, ty):
self._read_header()
self._read_expect(ty)
def _read_chunk(self, length):
if self._read_length < length:
raise IOError("Read overrun while trying to read {} bytes ({} remaining)"
" in packet {}".
format(length, self._read_length, self._read_type))
self._read_length -= length
return self.read(length)
def _read_int8(self):
(value, ) = struct.unpack("B", self._read_chunk(1))
return value
def _read_int32(self):
(value, ) = struct.unpack(">l", self._read_chunk(4))
return value
def _read_int64(self):
(value, ) = struct.unpack(">q", self._read_chunk(8))
return value
def _read_float64(self):
(value, ) = struct.unpack(">d", self._read_chunk(8))
return value
def _read_bytes(self):
return self._read_chunk(self._read_int32())
def _read_string(self):
return self._read_bytes()[:-1].decode("utf-8")
#
# Writer interface
#
def _write_header(self, ty):
self.open()
logger.debug("preparing to send message: type=%r", ty)
self._write_type = ty
self._write_buffer = []
def _write_flush(self):
# Calculate message size.
length = sum([len(chunk) for chunk in self._write_buffer])
logger.debug("sending message: type=%r length=%d", self._write_type, length)
# Write synchronization sequence, header and body.
self.write(struct.pack(">llB", 0x5a5a5a5a,
9 + length, self._write_type.value))
for chunk in self._write_buffer:
self.write(chunk)
def _write_empty(self, ty):
self._write_header(ty)
self._write_flush()
def _write_chunk(self, chunk):
self._write_buffer.append(chunk)
def _write_int8(self, value):
self._write_buffer.append(struct.pack("B", value))
def _write_int32(self, value):
self._write_buffer.append(struct.pack(">l", value))
def _write_int64(self, value):
self._write_buffer.append(struct.pack(">q", value))
def _write_float64(self, value):
self._write_buffer.append(struct.pack(">d", value))
def _write_bytes(self, value):
self._write_int32(len(value))
self._write_buffer.append(value)
def _write_string(self, value):
self._write_bytes(value.encode("utf-8") + b"\0")
#
# Exported APIs
#
def reset_session(self):
self.write(struct.pack(">ll", 0x5a5a5a5a, 0))
def check_ident(self):
self._write_empty(_H2DMsgType.IDENT_REQUEST)
self._read_header()
self._read_expect(_D2HMsgType.IDENT_REPLY)
runtime_id = self._read_chunk(4)
if runtime_id != b"AROR":
raise UnsupportedDevice("Unsupported runtime ID: {}"
.format(runtime_id))
gateware_version = self._read_chunk(self._read_length).decode("utf-8")
if gateware_version != software_version and \
gateware_version + ".dirty" != software_version:
logger.warning("Mismatch between gateware (%s) "
"and software (%s) versions",
gateware_version, software_version)
def switch_clock(self, external):
self._write_header(_H2DMsgType.SWITCH_CLOCK)
self._write_int8(external)
self._write_flush()
self._read_empty(_D2HMsgType.CLOCK_SWITCH_COMPLETED)
def get_log(self):
self._write_empty(_H2DMsgType.LOG_REQUEST)
self._read_header()
self._read_expect(_D2HMsgType.LOG_REPLY)
return self._read_chunk(self._read_length).decode("utf-8", "replace")
def clear_log(self):
self._write_empty(_H2DMsgType.LOG_CLEAR)
self._read_empty(_D2HMsgType.LOG_REPLY)
def flash_storage_read(self, key):
self._write_header(_H2DMsgType.FLASH_READ_REQUEST)
self._write_string(key)
self._write_flush()
self._read_header()
self._read_expect(_D2HMsgType.FLASH_READ_REPLY)
return self._read_chunk(self._read_length)
def flash_storage_write(self, key, value):
self._write_header(_H2DMsgType.FLASH_WRITE_REQUEST)
self._write_string(key)
self._write_bytes(value)
self._write_flush()
self._read_header()
if self._read_type == _D2HMsgType.FLASH_ERROR_REPLY:
raise IOError("Flash storage is full")
else:
self._read_expect(_D2HMsgType.FLASH_OK_REPLY)
def flash_storage_erase(self):
self._write_empty(_H2DMsgType.FLASH_ERASE_REQUEST)
self._read_empty(_D2HMsgType.FLASH_OK_REPLY)
def flash_storage_remove(self, key):
self._write_header(_H2DMsgType.FLASH_REMOVE_REQUEST)
self._write_string(key)
self._write_flush()
self._read_empty(_D2HMsgType.FLASH_OK_REPLY)
def load(self, kernel_library):
self._write_header(_H2DMsgType.LOAD_LIBRARY)
self._write_chunk(kernel_library)
self._write_flush()
self._read_empty(_D2HMsgType.LOAD_COMPLETED)
def run(self):
self._write_empty(_H2DMsgType.RUN_KERNEL)
logger.debug("running kernel")
_rpc_sentinel = object()
# See session.c:{send,receive}_rpc_value and llvm_ir_generator.py:_rpc_tag.
def _receive_rpc_value(self, object_map):
tag = chr(self._read_int8())
if tag == "\x00":
return self._rpc_sentinel
elif tag == "t":
length = self._read_int8()
return tuple(self._receive_rpc_value(object_map) for _ in range(length))
elif tag == "n":
return None
elif tag == "b":
return bool(self._read_int8())
elif tag == "i":
return wrapping_int(self._read_int32(), 32)
elif tag == "I":
return wrapping_int(self._read_int64(), 64)
elif tag == "f":
return self._read_float64()
elif tag == "F":
numerator = self._read_int64()
denominator = self._read_int64()
return Fraction(numerator, denominator)
elif tag == "s":
return self._read_string()
elif tag == "l":
length = self._read_int32()
return [self._receive_rpc_value(object_map) for _ in range(length)]
elif tag == "r":
start = self._receive_rpc_value(object_map)
stop = self._receive_rpc_value(object_map)
step = self._receive_rpc_value(object_map)
return range(start, stop, step)
elif tag == "k":
name = self._read_string()
value = self._receive_rpc_value(object_map)
return RPCKeyword(name, value)
elif tag == "O":
return object_map.retrieve(self._read_int32())
else:
raise IOError("Unknown RPC value tag: {}".format(repr(tag)))
def _receive_rpc_args(self, object_map):
args, kwargs = [], {}
while True:
value = self._receive_rpc_value(object_map)
if value is self._rpc_sentinel:
return args, kwargs
elif isinstance(value, RPCKeyword):
kwargs[value.name] = value.value
else:
args.append(value)
def _skip_rpc_value(self, tags):
tag = tags.pop(0)
if tag == "t":
length = tags.pop(0)
for _ in range(length):
self._skip_rpc_value(tags)
elif tag == "l":
self._skip_rpc_value(tags)
elif tag == "r":
self._skip_rpc_value(tags)
else:
pass
def _send_rpc_value(self, tags, value, root, function):
def check(cond, expected):
if not cond:
raise RPCReturnValueError(
"type mismatch: cannot serialize {value} as {type}"
" ({function} has returned {root})".format(
value=repr(value), type=expected(),
function=function, root=root))
tag = chr(tags.pop(0))
if tag == "t":
length = tags.pop(0)
check(isinstance(value, tuple) and length == len(value),
lambda: "tuple of {}".format(length))
for elt in value:
self._send_rpc_value(tags, elt, root, function)
elif tag == "n":
check(value is None,
lambda: "None")
elif tag == "b":
check(isinstance(value, bool),
lambda: "bool")
self._write_int8(value)
elif tag == "i":
check(isinstance(value, int) and (-2**31 < value < 2**31-1),
lambda: "32-bit int")
self._write_int32(value)
elif tag == "I":
check(isinstance(value, int) and (-2**63 < value < 2**63-1),
lambda: "64-bit int")
self._write_int64(value)
elif tag == "f":
check(isinstance(value, float),
lambda: "float")
self._write_float64(value)
elif tag == "F":
check(isinstance(value, Fraction) and
(-2**63 < value.numerator < 2**63-1) and
(-2**63 < value.denominator < 2**63-1),
lambda: "64-bit Fraction")
self._write_int64(value.numerator)
self._write_int64(value.denominator)
elif tag == "s":
check(isinstance(value, str) and "\x00" not in value,
lambda: "str")
self._write_string(value)
elif tag == "l":
check(isinstance(value, list),
lambda: "list")
self._write_int32(len(value))
for elt in value:
tags_copy = bytearray(tags)
self._send_rpc_value(tags_copy, elt, root, function)
self._skip_rpc_value(tags)
elif tag == "r":
check(isinstance(value, range),
lambda: "range")
tags_copy = bytearray(tags)
self._send_rpc_value(tags_copy, value.start, root, function)
tags_copy = bytearray(tags)
self._send_rpc_value(tags_copy, value.stop, root, function)
tags_copy = bytearray(tags)
self._send_rpc_value(tags_copy, value.step, root, function)
tags = tags_copy
else:
raise IOError("Unknown RPC value tag: {}".format(repr(tag)))
def _serve_rpc(self, object_map):
service_id = self._read_int32()
if service_id == 0:
service = lambda obj, attr, value: setattr(obj, attr, value)
else:
service = object_map.retrieve(service_id)
args, kwargs = self._receive_rpc_args(object_map)
return_tags = self._read_bytes()
logger.debug("rpc service: [%d]%r %r %r -> %s", service_id, service, args, kwargs, return_tags)
try:
result = service(*args, **kwargs)
logger.debug("rpc service: %d %r %r == %r", service_id, args, kwargs, result)
if service_id != 0:
self._write_header(_H2DMsgType.RPC_REPLY)
self._write_bytes(return_tags)
self._send_rpc_value(bytearray(return_tags), result, result, service)
self._write_flush()
except Exception as exn:
logger.debug("rpc service: %d %r %r ! %r", service_id, args, kwargs, exn)
self._write_header(_H2DMsgType.RPC_EXCEPTION)
if hasattr(exn, 'artiq_core_exception'):
exn = exn.artiq_core_exception
self._write_string(exn.name)
self._write_string(exn.message)
for index in range(3):
self._write_int64(exn.param[index])
filename, line, column, function = exn.traceback[-1]
self._write_string(filename)
self._write_int32(line)
self._write_int32(column)
self._write_string(function)
else:
exn_type = type(exn)
if exn_type in (ZeroDivisionError, ValueError, IndexError) or \
hasattr(exn, 'artiq_builtin'):
self._write_string("0:{}".format(exn_type.__name__))
else:
exn_id = object_map.store(exn_type)
self._write_string("{}:{}.{}".format(exn_id,
exn_type.__module__, exn_type.__qualname__))
self._write_string(str(exn))
for index in range(3):
self._write_int64(0)
tb = traceback.extract_tb(exn.__traceback__, 2)
if len(tb) == 2:
(_, (filename, line, function, _), ) = tb
elif len(tb) == 1:
((filename, line, function, _), ) = tb
else:
assert False
self._write_string(filename)
self._write_int32(line)
self._write_int32(-1) # column not known
self._write_string(function)
self._write_flush()
def _serve_exception(self, object_map, symbolizer):
name = self._read_string()
message = self._read_string()
params = [self._read_int64() for _ in range(3)]
filename = self._read_string()
line = self._read_int32()
column = self._read_int32()
function = self._read_string()
backtrace = [self._read_int32() for _ in range(self._read_int32())]
traceback = list(reversed(symbolizer(backtrace))) + \
[(filename, line, column, function, None)]
core_exn = exceptions.CoreException(name, message, params, traceback)
if core_exn.id == 0:
python_exn_type = getattr(exceptions, core_exn.name.split('.')[-1])
else:
python_exn_type = object_map.retrieve(core_exn.id)
python_exn = python_exn_type(message.format(*params))
python_exn.artiq_core_exception = core_exn
raise python_exn
def serve(self, object_map, symbolizer):
while True:
self._read_header()
if self._read_type == _D2HMsgType.RPC_REQUEST:
self._serve_rpc(object_map)
elif self._read_type == _D2HMsgType.KERNEL_EXCEPTION:
self._serve_exception(object_map, symbolizer)
elif self._read_type == _D2HMsgType.WATCHDOG_EXPIRED:
raise exceptions.WatchdogExpired
elif self._read_type == _D2HMsgType.CLOCK_FAILURE:
raise exceptions.ClockFailure
else:
self._read_expect(_D2HMsgType.KERNEL_FINISHED)
return

View File

@ -1,740 +0,0 @@
import struct
import logging
import traceback
import numpy
import socket
import builtins
from enum import Enum
from fractions import Fraction
from collections import namedtuple
from artiq.coredevice import exceptions
from artiq import __version__ as software_version
from sipyco.keepalive import create_connection
logger = logging.getLogger(__name__)
class Request(Enum):
SystemInfo = 3
LoadKernel = 5
RunKernel = 6
RPCReply = 7
RPCException = 8
SubkernelUpload = 9
class Reply(Enum):
SystemInfo = 2
LoadCompleted = 5
LoadFailed = 6
KernelFinished = 7
KernelStartupFailed = 8
KernelException = 9
RPCRequest = 10
ClockFailure = 15
class UnsupportedDevice(Exception):
pass
class LoadError(Exception):
pass
class RPCReturnValueError(ValueError):
pass
RPCKeyword = namedtuple('RPCKeyword', ['name', 'value'])
def _receive_fraction(kernel, embedding_map):
numerator = kernel._read_int64()
denominator = kernel._read_int64()
return Fraction(numerator, denominator)
def _receive_list(kernel, embedding_map):
length = kernel._read_int32()
tag = chr(kernel._read_int8())
if tag == "b":
buffer = kernel._read(length)
return list(struct.unpack(kernel.endian + "%s?" % length, buffer))
elif tag == "i":
buffer = kernel._read(4 * length)
return list(struct.unpack(kernel.endian + "%sl" % length, buffer))
elif tag == "I":
buffer = kernel._read(8 * length)
return list(numpy.ndarray((length, ), kernel.endian + 'i8', buffer))
elif tag == "f":
buffer = kernel._read(8 * length)
return list(struct.unpack(kernel.endian + "%sd" % length, buffer))
else:
fn = receivers[tag]
elems = []
for _ in range(length):
# discard tag, as our device would still send the tag for each
# non-primitive elements.
kernel._read_int8()
item = fn(kernel, embedding_map)
elems.append(item)
return elems
def _receive_array(kernel, embedding_map):
num_dims = kernel._read_int8()
shape = tuple(kernel._read_int32() for _ in range(num_dims))
tag = chr(kernel._read_int8())
fn = receivers[tag]
length = numpy.prod(shape)
if tag == "b":
buffer = kernel._read(length)
elems = numpy.ndarray((length, ), '?', buffer)
elif tag == "i":
buffer = kernel._read(4 * length)
elems = numpy.ndarray((length, ), kernel.endian + 'i4', buffer)
elif tag == "I":
buffer = kernel._read(8 * length)
elems = numpy.ndarray((length, ), kernel.endian + 'i8', buffer)
elif tag == "f":
buffer = kernel._read(8 * length)
elems = numpy.ndarray((length, ), kernel.endian + 'd', buffer)
else:
fn = receivers[tag]
elems = []
for _ in range(numpy.prod(shape)):
# discard the tag
kernel._read_int8()
item = fn(kernel, embedding_map)
elems.append(item)
elems = numpy.array(elems)
return elems.reshape(shape)
def _receive_range(kernel, embedding_map):
start = kernel._receive_rpc_value(embedding_map)
stop = kernel._receive_rpc_value(embedding_map)
step = kernel._receive_rpc_value(embedding_map)
return range(start, stop, step)
def _receive_keyword(kernel, embedding_map):
name = kernel._read_string()
value = kernel._receive_rpc_value(embedding_map)
return RPCKeyword(name, value)
receivers = {
"\x00": lambda kernel, embedding_map: kernel._rpc_sentinel,
"t": lambda kernel, embedding_map:
tuple(kernel._receive_rpc_value(embedding_map)
for _ in range(kernel._read_int8())),
"n": lambda kernel, embedding_map: None,
"b": lambda kernel, embedding_map: bool(kernel._read_int8()),
"i": lambda kernel, embedding_map: numpy.int32(kernel._read_int32()),
"I": lambda kernel, embedding_map: numpy.int64(kernel._read_int64()),
"f": lambda kernel, embedding_map: kernel._read_float64(),
"s": lambda kernel, embedding_map: kernel._read_string(),
"B": lambda kernel, embedding_map: kernel._read_bytes(),
"A": lambda kernel, embedding_map: kernel._read_bytes(),
"O": lambda kernel, embedding_map:
embedding_map.retrieve_object(kernel._read_int32()),
"F": _receive_fraction,
"l": _receive_list,
"a": _receive_array,
"r": _receive_range,
"k": _receive_keyword
}
class CommKernelDummy:
def __init__(self):
pass
def load(self, kernel_library):
pass
def run(self):
pass
def serve(self, embedding_map, symbolizer, demangler):
pass
def check_system_info(self):
pass
def incompatible_versions(v1, v2):
if v1.endswith(".beta") or v2.endswith(".beta"):
# Beta branches may introduce breaking changes. Check version strictly.
return v1 != v2
else:
# On stable branches, runtime/software protocol backward compatibility is kept.
# Runtime and software with the same major version number are compatible.
return v1.split(".", maxsplit=1)[0] != v2.split(".", maxsplit=1)[0]
class CommKernel:
warned_of_mismatch = False
def __init__(self, host, port=1381):
self._read_type = None
self.host = host
self.port = port
self.read_buffer = bytearray()
self.write_buffer = bytearray()
def open(self):
if hasattr(self, "socket"):
return
self.socket = create_connection(self.host, self.port)
self.socket.sendall(b"ARTIQ coredev\n")
endian = self._read(1)
if endian == b"e":
self.endian = "<"
elif endian == b"E":
self.endian = ">"
else:
raise IOError("Incorrect reply from device: expected e/E.")
self.unpack_int32 = struct.Struct(self.endian + "l").unpack
self.unpack_int64 = struct.Struct(self.endian + "q").unpack
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
def close(self):
if not hasattr(self, "socket"):
return
self.socket.close()
del self.socket
logger.debug("disconnected")
#
# Reader interface
#
def _read(self, length):
# cache the reads to avoid frequent call to recv
while len(self.read_buffer) < length:
# the number is just the maximum amount
# when there is not much data, it would return earlier
diff = length - len(self.read_buffer)
flag = 0
if diff > 8192:
flag |= socket.MSG_WAITALL
new_buffer = self.socket.recv(8192, flag)
if not new_buffer:
raise ConnectionResetError("Core device connection closed unexpectedly")
self.read_buffer += new_buffer
result = self.read_buffer[:length]
self.read_buffer = self.read_buffer[length:]
return result
def _read_header(self):
self.open()
# Wait for a synchronization sequence, 5a 5a 5a 5a.
sync_count = 0
while sync_count < 4:
sync_byte = self._read(1)[0]
if sync_byte == 0x5a:
sync_count += 1
else:
sync_count = 0
# Read message header.
raw_type = self._read(1)[0]
self._read_type = Reply(raw_type)
logger.debug("receiving message: type=%r",
self._read_type)
def _read_expect(self, ty):
if self._read_type != ty:
raise IOError("Incorrect reply from device: {} (expected {})".
format(self._read_type, ty))
def _read_empty(self, ty):
self._read_header()
self._read_expect(ty)
def _read_int8(self):
return self._read(1)[0]
def _read_int32(self):
(value, ) = self.unpack_int32(self._read(4))
return value
def _read_int64(self):
(value, ) = self.unpack_int64(self._read(8))
return value
def _read_float64(self):
(value, ) = self.unpack_float64(self._read(8))
return value
def _read_bool(self):
return True if self._read_int8() else False
def _read_bytes(self):
return self._read(self._read_int32())
def _read_string(self):
return self._read_bytes().decode("utf-8")
#
# Writer interface
#
def _write(self, data):
self.write_buffer += data
# if the buffer is already pretty large, send it
# the block size is arbitrary, tuning it may improve performance
if len(self.write_buffer) > 4096:
self._flush()
def _flush(self):
self.socket.sendall(self.write_buffer)
self.write_buffer.clear()
def _write_header(self, ty):
self.open()
logger.debug("sending message: type=%r", ty)
# Write synchronization sequence and header.
self._write(self.pack_header(0x5a5a5a5a, ty.value))
def _write_empty(self, ty):
self._write_header(ty)
def _write_chunk(self, chunk):
self._write(chunk)
def _write_int8(self, value):
self._write(self.pack_int8(value))
def _write_int32(self, value):
self._write(self.pack_int32(value))
def _write_int64(self, value):
self._write(self.pack_int64(value))
def _write_float64(self, value):
self._write(self.pack_float64(value))
def _write_bool(self, value):
self._write(b'\x01' if value else b'\x00')
def _write_bytes(self, value):
self._write_int32(len(value))
self._write(value)
def _write_string(self, value):
self._write_bytes(value.encode("utf-8"))
#
# Exported APIs
#
def check_system_info(self):
self._write_empty(Request.SystemInfo)
self._flush()
self._read_header()
self._read_expect(Reply.SystemInfo)
runtime_id = self._read(4)
if runtime_id == b"AROR":
gateware_version = self._read_string().split(";")[0]
if not self.warned_of_mismatch and incompatible_versions(gateware_version, software_version):
logger.warning("Mismatch between gateware (%s) "
"and software (%s) versions",
gateware_version, software_version)
CommKernel.warned_of_mismatch = True
finished_cleanly = self._read_bool()
if not finished_cleanly:
logger.warning("Previous kernel did not cleanly finish")
elif runtime_id == b"ARZQ":
pass
else:
raise UnsupportedDevice("Unsupported runtime ID: {}"
.format(runtime_id))
def load(self, kernel_library):
self._write_header(Request.LoadKernel)
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 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()
logger.debug("running kernel")
_rpc_sentinel = object()
# See rpc_proto.rs and compiler/ir.py:rpc_tag.
def _receive_rpc_value(self, embedding_map):
tag = chr(self._read_int8())
if tag in receivers:
return receivers.get(tag)(self, embedding_map)
else:
raise IOError("Unknown RPC value tag: {}".format(repr(tag)))
def _receive_rpc_args(self, embedding_map):
args, kwargs = [], {}
while True:
value = self._receive_rpc_value(embedding_map)
if value is self._rpc_sentinel:
return args, kwargs
elif isinstance(value, RPCKeyword):
kwargs[value.name] = value.value
else:
args.append(value)
def _skip_rpc_value(self, tags):
tag = chr(tags.pop(0))
if tag == "t":
length = tags.pop(0)
for _ in range(length):
self._skip_rpc_value(tags)
elif tag == "l":
self._skip_rpc_value(tags)
elif tag == "r":
self._skip_rpc_value(tags)
elif tag == "a":
_ndims = tags.pop(0)
self._skip_rpc_value(tags)
else:
pass
def _send_rpc_value(self, tags, value, root, function):
def check(cond, expected):
if not cond:
raise RPCReturnValueError(
"type mismatch: cannot serialize {value} as {type}"
" ({function} has returned {root})".format(
value=repr(value), type=expected(),
function=function, root=root))
tag = chr(tags.pop(0))
if tag == "t":
length = tags.pop(0)
check(isinstance(value, tuple) and length == len(value),
lambda: "tuple of {}".format(length))
for elt in value:
self._send_rpc_value(tags, elt, root, function)
elif tag == "n":
check(value is None,
lambda: "None")
elif tag == "b":
check(isinstance(value, bool),
lambda: "bool")
self._write_bool(value)
elif tag == "i":
check(isinstance(value, (int, numpy.int32)) and
(-2**31 <= value <= 2**31-1),
lambda: "32-bit int")
self._write_int32(value)
elif tag == "I":
check(isinstance(value, (int, numpy.int32, numpy.int64)) and
(-2**63 <= value <= 2**63-1),
lambda: "64-bit int")
self._write_int64(value)
elif tag == "f":
check(isinstance(value, float),
lambda: "float")
self._write_float64(value)
elif tag == "F":
check(isinstance(value, Fraction) and
(-2**63 <= value.numerator <= 2**63-1) and
(-2**63 <= value.denominator <= 2**63-1),
lambda: "64-bit Fraction")
self._write_int64(value.numerator)
self._write_int64(value.denominator)
elif tag == "s":
check(isinstance(value, str) and "\x00" not in value,
lambda: "str")
self._write_string(value)
elif tag == "B":
check(isinstance(value, bytes),
lambda: "bytes")
self._write_bytes(value)
elif tag == "A":
check(isinstance(value, bytearray),
lambda: "bytearray")
self._write_bytes(value)
elif tag == "l":
check(isinstance(value, list),
lambda: "list")
self._write_int32(len(value))
tag_element = chr(tags[0])
if tag_element == "b":
self._write(bytes(value))
elif tag_element == "i":
try:
self._write(struct.pack(self.endian + "%sl" % len(value), *value))
except struct.error:
raise RPCReturnValueError(
"type mismatch: cannot serialize {value} as {type}".format(
value=repr(value), type="32-bit integer list"))
elif tag_element == "I":
try:
self._write(struct.pack(self.endian + "%sq" % len(value), *value))
except struct.error:
raise RPCReturnValueError(
"type mismatch: cannot serialize {value} as {type}".format(
value=repr(value), type="64-bit integer list"))
elif tag_element == "f":
self._write(struct.pack(self.endian + "%sd" %
len(value), *value))
else:
for elt in value:
tags_copy = bytearray(tags)
self._send_rpc_value(tags_copy, elt, root, function)
self._skip_rpc_value(tags)
elif tag == "a":
check(isinstance(value, numpy.ndarray),
lambda: "numpy.ndarray")
num_dims = tags.pop(0)
check(num_dims == len(value.shape),
lambda: "{}-dimensional numpy.ndarray".format(num_dims))
for s in value.shape:
self._write_int32(s)
tag_element = chr(tags[0])
if tag_element == "b":
self._write(value.reshape((-1,), order="C").tobytes())
elif tag_element == "i":
array = value.reshape(
(-1,), order="C").astype(self.endian + 'i4')
self._write(array.tobytes())
elif tag_element == "I":
array = value.reshape(
(-1,), order="C").astype(self.endian + 'i8')
self._write(array.tobytes())
elif tag_element == "f":
array = value.reshape(
(-1,), order="C").astype(self.endian + 'd')
self._write(array.tobytes())
else:
for elt in value.reshape((-1,), order="C"):
tags_copy = bytearray(tags)
self._send_rpc_value(tags_copy, elt, root, function)
self._skip_rpc_value(tags)
elif tag == "r":
check(isinstance(value, range),
lambda: "range")
tags_copy = bytearray(tags)
self._send_rpc_value(tags_copy, value.start, root, function)
tags_copy = bytearray(tags)
self._send_rpc_value(tags_copy, value.stop, root, function)
tags_copy = bytearray(tags)
self._send_rpc_value(tags_copy, value.step, root, function)
tags = tags_copy
else:
raise IOError("Unknown RPC value tag: {}".format(repr(tag)))
def _truncate_message(self, msg, limit=4096):
if len(msg) > limit:
return msg[0:limit] + "... (truncated)"
else:
return msg
def _serve_rpc(self, embedding_map):
is_async = self._read_bool()
service_id = self._read_int32()
args, kwargs = self._receive_rpc_args(embedding_map)
return_tags = self._read_bytes()
if service_id == 0:
def service(obj, attr, value): return setattr(obj, attr, value)
else:
service = embedding_map.retrieve_object(service_id)
logger.debug("rpc service: [%d]%r%s %r %r -> %s", service_id, service,
(" (async)" if is_async else ""), args, kwargs, return_tags)
if is_async:
service(*args, **kwargs)
return
try:
result = service(*args, **kwargs)
except RPCReturnValueError as exn:
raise
except Exception as exn:
logger.debug("rpc service: %d %r %r ! %r",
service_id, args, kwargs, exn)
self._write_header(Request.RPCException)
# Note: instead of sending strings, we send object ID
# This is to avoid the need of allocatio on the device side
# This is a special case: this only applies to exceptions
if hasattr(exn, "artiq_core_exception"):
exn = exn.artiq_core_exception
self._write_int32(embedding_map.store_str(exn.name))
self._write_int32(embedding_map.store_str(self._truncate_message(exn.message)))
for index in range(3):
self._write_int64(exn.param[index])
filename, line, column, function = exn.traceback[-1]
self._write_int32(embedding_map.store_str(filename))
self._write_int32(line)
self._write_int32(column)
self._write_int32(embedding_map.store_str(function))
else:
exn_type = type(exn)
if exn_type in builtins.__dict__.values():
name = "0:{}".format(exn_type.__qualname__)
elif hasattr(exn, "artiq_builtin"):
name = "0:{}.{}".format(exn_type.__module__, exn_type.__qualname__)
else:
exn_id = embedding_map.store_object(exn_type)
name = "{}:{}.{}".format(exn_id,
exn_type.__module__,
exn_type.__qualname__)
self._write_int32(embedding_map.store_str(name))
self._write_int32(embedding_map.store_str(self._truncate_message(str(exn))))
for index in range(3):
self._write_int64(0)
tb = traceback.extract_tb(exn.__traceback__, 2)
if len(tb) == 2:
(_, (filename, line, function, _), ) = tb
elif len(tb) == 1:
((filename, line, function, _), ) = tb
else:
assert False
self._write_int32(embedding_map.store_str(filename))
self._write_int32(line)
self._write_int32(-1) # column not known
self._write_int32(embedding_map.store_str(function))
self._flush()
else:
logger.debug("rpc service: %d %r %r = %r",
service_id, args, kwargs, result)
self._write_header(Request.RPCReply)
self._write_bytes(return_tags)
self._send_rpc_value(bytearray(return_tags),
result, result, service)
self._flush()
def _serve_exception(self, embedding_map, symbolizer, demangler):
exception_count = self._read_int32()
nested_exceptions = []
def read_exception_string():
# note: if length == -1, the following int32 is the object key
length = self._read_int32()
if length == -1:
return embedding_map.retrieve_str(self._read_int32())
else:
return self._read(length).decode("utf-8")
for _ in range(exception_count):
name = embedding_map.retrieve_str(self._read_int32())
message = read_exception_string()
params = [self._read_int64() for _ in range(3)]
filename = read_exception_string()
line = self._read_int32()
column = self._read_int32()
function = read_exception_string()
nested_exceptions.append([name, message, params,
filename, line, column, function])
demangled_names = demangler([ex[6] for ex in nested_exceptions])
for i in range(exception_count):
nested_exceptions[i][6] = demangled_names[i]
exception_info = []
for _ in range(exception_count):
sp = self._read_int32()
initial_backtrace = self._read_int32()
current_backtrace = self._read_int32()
exception_info.append((sp, initial_backtrace, current_backtrace))
backtrace = []
stack_pointers = []
for _ in range(self._read_int32()):
backtrace.append(self._read_int32())
stack_pointers.append(self._read_int32())
self._process_async_error()
traceback = list(symbolizer(backtrace))
core_exn = exceptions.CoreException(nested_exceptions, exception_info,
traceback, stack_pointers)
if core_exn.id == 0:
python_exn_type = getattr(exceptions, core_exn.name.split('.')[-1])
else:
python_exn_type = embedding_map.retrieve_object(core_exn.id)
try:
python_exn = python_exn_type(
nested_exceptions[-1][1].format(*nested_exceptions[0][2]))
except Exception as ex:
python_exn = RuntimeError(
f"Exception type={python_exn_type}, which couldn't be "
f"reconstructed ({ex})"
)
python_exn.artiq_core_exception = core_exn
raise python_exn
def _process_async_error(self):
errors = self._read_int8()
if errors > 0:
map_name = lambda y, z: [f"{y}(s)"] if z else []
errors = map_name("collision", errors & 2 ** 0) + \
map_name("busy error", errors & 2 ** 1) + \
map_name("sequence error", errors & 2 ** 2)
logger.warning(f"{(', '.join(errors[:-1]) + ' and ') if len(errors) > 1 else ''}{errors[-1]} "
f"reported during kernel execution")
def serve(self, embedding_map, symbolizer, demangler):
while True:
self._read_header()
if self._read_type == Reply.RPCRequest:
self._serve_rpc(embedding_map)
elif self._read_type == Reply.KernelException:
self._serve_exception(embedding_map, symbolizer, demangler)
elif self._read_type == Reply.ClockFailure:
raise exceptions.ClockFailure
else:
self._read_expect(Reply.KernelFinished)
self._process_async_error()
return

View File

@ -1,196 +0,0 @@
from enum import Enum
import logging
import struct
from sipyco.keepalive import create_connection
logger = logging.getLogger(__name__)
class Request(Enum):
GetLog = 1
ClearLog = 2
PullLog = 7
SetLogFilter = 3
SetUartLogFilter = 6
ConfigRead = 12
ConfigWrite = 13
ConfigRemove = 14
ConfigErase = 15
Reboot = 5
DebugAllocator = 8
class Reply(Enum):
Success = 1
Error = 6
Unavailable = 4
LogContent = 2
ConfigData = 7
RebootImminent = 3
class LogLevel(Enum):
OFF = 0
ERROR = 1
WARN = 2
INFO = 3
DEBUG = 4
TRACE = 5
class CommMgmt:
def __init__(self, host, port=1380):
self.host = host
self.port = port
def open(self):
if hasattr(self, "socket"):
return
self.socket = create_connection(self.host, self.port)
self.socket.sendall(b"ARTIQ management\n")
endian = self._read(1)
if endian == b"e":
self.endian = "<"
elif endian == b"E":
self.endian = ">"
else:
raise IOError("Incorrect reply from device: expected e/E.")
def close(self):
if not hasattr(self, "socket"):
return
self.socket.close()
del self.socket
logger.debug("disconnected")
# Protocol elements
def _write(self, data):
self.socket.sendall(data)
def _write_header(self, ty):
self.open()
logger.debug("sending message: type=%r", ty)
self._write(struct.pack("B", ty.value))
def _write_int8(self, value):
self._write(struct.pack("B", value))
def _write_int32(self, value):
self._write(struct.pack(self.endian + "l", value))
def _write_bytes(self, value):
self._write_int32(len(value))
self._write(value)
def _write_string(self, value):
self._write_bytes(value.encode("utf-8"))
def _read(self, length):
r = bytes()
while len(r) < length:
rn = self.socket.recv(min(8192, length - len(r)))
if not rn:
raise ConnectionResetError("Connection closed")
r += rn
return r
def _read_header(self):
ty = Reply(*struct.unpack("B", self._read(1)))
logger.debug("receiving message: type=%r", ty)
return ty
def _read_expect(self, ty):
header = self._read_header()
if header != ty:
raise IOError("Incorrect reply from device: {} (expected {})".
format(header, ty))
def _read_int32(self):
(value, ) = struct.unpack(self.endian + "l", self._read(4))
return value
def _read_bytes(self):
return self._read(self._read_int32())
def _read_string(self):
return self._read_bytes().decode("utf-8")
# External API
def get_log(self):
self._write_header(Request.GetLog)
self._read_expect(Reply.LogContent)
return self._read_string()
def clear_log(self):
self._write_header(Request.ClearLog)
self._read_expect(Reply.Success)
def pull_log(self):
self._write_header(Request.PullLog)
self._read_expect(Reply.LogContent)
return self._read_string()
def set_log_level(self, level):
if level not in LogLevel.__members__:
raise ValueError("invalid log level {}".format(level))
self._write_header(Request.SetLogFilter)
self._write_int8(getattr(LogLevel, level).value)
self._read_expect(Reply.Success)
def set_uart_log_level(self, level):
if level not in LogLevel.__members__:
raise ValueError("invalid log level {}".format(level))
self._write_header(Request.SetUartLogFilter)
self._write_int8(getattr(LogLevel, level).value)
self._read_expect(Reply.Success)
def config_read(self, key):
self._write_header(Request.ConfigRead)
self._write_string(key)
ty = self._read_header()
if ty == Reply.Error:
raise IOError("Device failed to read config. The key may not exist.")
elif ty != Reply.ConfigData:
raise IOError("Incorrect reply from device: {} (expected {})".
format(ty, Reply.ConfigData))
return self._read_string()
def config_write(self, key, value):
self._write_header(Request.ConfigWrite)
self._write_string(key)
self._write_bytes(value)
ty = self._read_header()
if ty == Reply.Error:
raise IOError("Device failed to write config. More information may be available in the log.")
elif ty != Reply.Success:
raise IOError("Incorrect reply from device: {} (expected {})".
format(ty, Reply.Success))
def config_remove(self, key):
self._write_header(Request.ConfigRemove)
self._write_string(key)
self._read_expect(Reply.Success)
def config_erase(self):
self._write_header(Request.ConfigErase)
self._read_expect(Reply.Success)
def reboot(self):
self._write_header(Request.Reboot)
self._read_expect(Reply.RebootImminent)
def debug_allocator(self):
self._write_header(Request.DebugAllocator)

View File

@ -1,101 +0,0 @@
import asyncio
import logging
import struct
from enum import Enum
from sipyco.keepalive import async_open_connection
__all__ = ["TTLProbe", "TTLOverride", "CommMonInj"]
logger = logging.getLogger(__name__)
class TTLProbe(Enum):
level = 0
oe = 1
class TTLOverride(Enum):
en = 0
level = 1
oe = 2
class CommMonInj:
def __init__(self, monitor_cb, injection_status_cb, disconnect_cb=None):
self.monitor_cb = monitor_cb
self.injection_status_cb = injection_status_cb
self.disconnect_cb = disconnect_cb
async def connect(self, host, port=1383):
self._reader, self._writer = await async_open_connection(
host,
port,
after_idle=1,
interval=1,
max_fails=3,
)
try:
self._writer.write(b"ARTIQ moninj\n")
self._receive_task = asyncio.ensure_future(self._receive_cr())
except:
self._writer.close()
del self._reader
del self._writer
raise
def wait_terminate(self):
return self._receive_task
async def close(self):
self.disconnect_cb = None
try:
self._receive_task.cancel()
try:
await asyncio.wait_for(self._receive_task, None)
except asyncio.CancelledError:
pass
finally:
self._writer.close()
del self._reader
del self._writer
def monitor_probe(self, enable, channel, probe):
packet = struct.pack("<bblb", 0, enable, channel, probe)
self._writer.write(packet)
def monitor_injection(self, enable, channel, overrd):
packet = struct.pack("<bblb", 3, enable, channel, overrd)
self._writer.write(packet)
def inject(self, channel, override, value):
packet = struct.pack("<blbb", 1, channel, override, value)
self._writer.write(packet)
def get_injection_status(self, channel, override):
packet = struct.pack("<blb", 2, channel, override)
self._writer.write(packet)
async def _receive_cr(self):
try:
while True:
ty = await self._reader.read(1)
if not ty:
return
if ty == b"\x00":
payload = await self._reader.readexactly(13)
channel, probe, value = struct.unpack("<lbq", payload)
self.monitor_cb(channel, probe, value)
elif ty == b"\x01":
payload = await self._reader.readexactly(6)
channel, override, value = struct.unpack("<lbb", payload)
self.injection_status_cb(channel, override, value)
else:
raise ValueError("Unknown packet type", ty)
except Exception:
logger.error("Moninj connection terminating with exception", exc_info=True)
finally:
if self.disconnect_cb is not None:
self.disconnect_cb()

View File

@ -0,0 +1,76 @@
import logging
import socket
import sys
from artiq.coredevice.comm_generic import CommGeneric
logger = logging.getLogger(__name__)
def set_keepalive(sock, after_idle, interval, max_fails):
if sys.platform.startswith("linux"):
sock.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, after_idle)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, interval)
sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, max_fails)
elif sys.platform.startswith("win") or sys.platform.startswith("cygwin"):
# setting max_fails is not supported, typically ends up being 5 or 10
# depending on Windows version
sock.ioctl(socket.SIO_KEEPALIVE_VALS,
(1, after_idle*1000, interval*1000))
else:
logger.warning("TCP keepalive not supported on platform '%s', ignored",
sys.platform)
def initialize_connection(host, port):
sock = socket.create_connection((host, port), 5.0)
sock.settimeout(None)
set_keepalive(sock, 3, 2, 3)
logger.debug("connected to host %s on port %d", host, port)
sock.sendall(b"ARTIQ coredev\n")
return sock
class Comm(CommGeneric):
def __init__(self, dmgr, host, port=1381, port_analyzer=1382):
super().__init__()
self.host = host
self.port = port
self.port_analyzer = port_analyzer
def open(self):
if hasattr(self, "socket"):
return
self.socket = initialize_connection(self.host, self.port)
def close(self):
if not hasattr(self, "socket"):
return
self.socket.close()
del self.socket
logger.debug("disconnected")
def read(self, length):
r = bytes()
while len(r) < length:
rn = self.socket.recv(min(8192, length - len(r)))
if not rn:
raise IOError("Connection closed")
r += rn
return r
def write(self, data):
self.socket.sendall(data)
def get_analyzer_dump(self):
sock = initialize_connection(self.host, self.port_analyzer)
r = bytes()
while True:
buf = sock.recv(8192)
if not buf:
break
r += buf
sock.close()
return r

View File

@ -1,7 +1,4 @@
import os, sys import os, sys
import numpy
from inspect import getfullargspec
from functools import wraps
from pythonparser import diagnostic from pythonparser import diagnostic
@ -13,9 +10,8 @@ from artiq.language.units import *
from artiq.compiler.module import Module from artiq.compiler.module import Module
from artiq.compiler.embedding import Stitcher from artiq.compiler.embedding import Stitcher
from artiq.compiler.targets import RV32IMATarget, RV32GTarget, CortexA9Target from artiq.compiler.targets import OR1KTarget
from artiq.coredevice.comm_kernel import CommKernel, CommKernelDummy
# Import for side effects (creating the exception classes). # Import for side effects (creating the exception classes).
from artiq.coredevice import exceptions from artiq.coredevice import exceptions
@ -41,321 +37,92 @@ class CompileError(Exception):
return "\n" + _render_diagnostic(self.diagnostic, colored=colors_supported) return "\n" + _render_diagnostic(self.diagnostic, colored=colors_supported)
@syscall
def rtio_init() -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"})
def rtio_get_destination_status(linkno: TInt32) -> TBool:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) @syscall(flags={"nounwind", "nowrite"})
def rtio_get_counter() -> TInt64: def rtio_get_counter() -> TInt64:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall
def test_exception_id_sync(id: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
def get_target_cls(target):
if target == "rv32g":
return RV32GTarget
elif target == "rv32ima":
return RV32IMATarget
elif target == "cortexa9":
return CortexA9Target
else:
raise ValueError("Unsupported target")
class Core: class Core:
"""Core device driver. """Core device driver.
:param host: hostname or IP address of the core device.
:param ref_period: period of the reference clock for the RTIO subsystem. :param ref_period: period of the reference clock for the RTIO subsystem.
On platforms that use clock multiplication and SERDES-based PHYs, On platforms that use clock multiplication and SERDES-based PHYs,
this is the period after multiplication. For example, with a RTIO core this is the period after multiplication. For example, with a RTIO core
clocked at 125MHz and a SERDES multiplication factor of 8, the clocked at 125MHz and a SERDES multiplication factor of 8, the
reference period is ``1 ns``. reference period is 1ns.
The machine time unit (``mu``) is equal to this period. The time machine unit is equal to this period.
:param external_clock: whether the core device should switch to its
external RTIO clock input instead of using its internal oscillator.
:param ref_multiplier: ratio between the RTIO fine timestamp frequency :param ref_multiplier: ratio between the RTIO fine timestamp frequency
and the RTIO coarse timestamp frequency (e.g. SERDES multiplication and the RTIO coarse timestamp frequency (e.g. SERDES multiplication
factor). factor).
:param analyzer_proxy: name of the core device analyzer proxy to trigger :param comm_device: name of the device used for communications.
(optional).
:param analyze_at_run_end: automatically trigger the core device analyzer
proxy after the Experiment's run stage finishes.
""" """
kernel_invariants = { kernel_invariants = {
"core", "ref_period", "coarse_ref_period", "ref_multiplier", "core", "ref_period", "coarse_ref_period", "ref_multiplier",
"external_clock",
} }
def __init__(self, dmgr, def __init__(self, dmgr, ref_period, external_clock=False,
host, ref_period, ref_multiplier=8, comm_device="comm"):
analyzer_proxy=None, analyze_at_run_end=False,
ref_multiplier=8,
target="rv32g", satellite_cpu_targets={}):
self.ref_period = ref_period self.ref_period = ref_period
self.external_clock = external_clock
self.ref_multiplier = ref_multiplier self.ref_multiplier = ref_multiplier
self.satellite_cpu_targets = satellite_cpu_targets
self.target_cls = get_target_cls(target)
self.coarse_ref_period = ref_period*ref_multiplier self.coarse_ref_period = ref_period*ref_multiplier
if host is None: self.comm = dmgr.get(comm_device)
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.first_run = True
self.dmgr = dmgr self.dmgr = dmgr
self.core = self self.core = self
self.comm.core = self self.comm.core = self
self.analyzer_proxy = None
def notify_run_end(self): def compile(self, function, args, kwargs, set_result=None, with_attr_writeback=True):
if self.analyze_at_run_end:
self.trigger_analyzer_proxy()
def close(self):
"""Disconnect core device and close sockets.
"""
self.comm.close()
def compile(self, function, args, kwargs, set_result=None,
attribute_writeback=True, print_as_rpc=True,
target=None, destination=0, subkernel_arg_types=[],
old_embedding_map=None):
try: try:
engine = _DiagnosticEngine(all_errors_are_fatal=True) engine = _DiagnosticEngine(all_errors_are_fatal=True)
stitcher = Stitcher(engine=engine, core=self, dmgr=self.dmgr, stitcher = Stitcher(engine=engine, core=self, dmgr=self.dmgr)
print_as_rpc=print_as_rpc,
destination=destination, subkernel_arg_types=subkernel_arg_types,
old_embedding_map=old_embedding_map)
stitcher.stitch_call(function, args, kwargs, set_result) stitcher.stitch_call(function, args, kwargs, set_result)
stitcher.finalize() stitcher.finalize()
module = Module(stitcher, module = Module(stitcher, ref_period=self.ref_period)
ref_period=self.ref_period, target = OR1KTarget()
attribute_writeback=attribute_writeback)
target = target if target is not None else self.target_cls()
library = target.compile_and_link([module]) library = target.compile_and_link([module])
stripped_library = target.strip(library) stripped_library = target.strip(library)
return stitcher.embedding_map, stripped_library, \ return stitcher.object_map, stripped_library, \
lambda addresses: target.symbolize(library, addresses), \ lambda addresses: target.symbolize(library, addresses)
lambda symbols: target.demangle(symbols), \
module.subkernel_arg_types
except diagnostic.Error as error: except diagnostic.Error as error:
raise CompileError(error.diagnostic) from error raise CompileError(error.diagnostic) from error
def _run_compiled(self, kernel_library, embedding_map, symbolizer, demangler):
if self.first_run:
self.comm.check_system_info()
self.first_run = False
self.comm.load(kernel_library)
self.comm.run()
self.comm.serve(embedding_map, symbolizer, demangler)
def run(self, function, args, kwargs): def run(self, function, args, kwargs):
result = None result = None
@rpc(flags={"async"})
def set_result(new_result): def set_result(new_result):
nonlocal result nonlocal result
result = new_result result = new_result
embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
self.compile(function, args, kwargs, set_result) object_map, kernel_library, symbolizer = 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) if self.first_run:
self.comm.check_ident()
self.comm.switch_clock(self.external_clock)
self.first_run = False
self.comm.load(kernel_library)
self.comm.run()
self.comm.serve(object_map, symbolizer)
return result 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.
Arguments to the kernel are set at compilation time and passed to this function,
as additional positional and keyword arguments.
The returned callable accepts no arguments.
Precompiled kernels may use RPCs and subkernels.
Object attributes at the beginning of a precompiled kernel execution have the
values they had at precompilation time. If up-to-date values are required,
use RPC to read them.
Similarly, modified values are not written back, and explicit RPC should be used
to modify host objects.
Carefully review the source code of drivers calls used in precompiled kernels, as
they may rely on host object attributes being transferred between kernel calls.
Examples include code used to control DDS phase and Urukul RF switch control
via the CPLD register.
The return value of the callable is the return value of the kernel, if any.
The callable may be called several times.
"""
if not hasattr(function, "artiq_embedded"):
raise ValueError("Argument is not a kernel")
result = None
@rpc(flags={"async"})
def set_result(new_result):
nonlocal result
result = new_result
embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
self.compile(function, args, kwargs, set_result, attribute_writeback=False)
self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types)
@wraps(function)
def run_precompiled():
nonlocal result
self._run_compiled(kernel_library, embedding_map, symbolizer, demangler)
return result
return run_precompiled
@portable
def seconds_to_mu(self, seconds):
"""Convert seconds to the corresponding number of machine units
(fine RTIO cycles).
:param seconds: time (in seconds) to convert.
"""
return numpy.int64(seconds//self.ref_period)
@portable
def mu_to_seconds(self, mu):
"""Convert machine units (fine RTIO cycles) to seconds.
:param mu: cycle count to convert.
"""
return mu*self.ref_period
@kernel @kernel
def get_rtio_counter_mu(self): def get_rtio_counter_mu(self):
"""Retrieve the current value of the hardware RTIO timeline counter.
As the timing of kernel code executed on the CPU is inherently
non-deterministic, the return value is by necessity only a lower bound
for the actual value of the hardware register at the instant when
execution resumes in the caller.
For a more detailed description of these concepts, see :doc:`rtio`.
"""
return rtio_get_counter() return rtio_get_counter()
@kernel
def wait_until_mu(self, cursor_mu):
"""Block execution until the hardware RTIO counter reaches the given
value (see :meth:`get_rtio_counter_mu`).
If the hardware counter has already passed the given time, the function
returns immediately.
"""
while self.get_rtio_counter_mu() < cursor_mu:
pass
@kernel
def get_rtio_destination_status(self, destination):
"""Returns whether the specified RTIO destination is up.
This is particularly useful in startup kernels to delay
startup until certain DRTIO destinations are available."""
return rtio_get_destination_status(destination)
@kernel
def reset(self):
"""Clear RTIO FIFOs, release RTIO PHY reset, and set the time cursor
at the current value of the hardware RTIO counter plus a margin of
125000 machine units."""
rtio_init()
at_mu(rtio_get_counter() + 125000)
@kernel @kernel
def break_realtime(self): def break_realtime(self):
"""Set the time cursor after the current value of the hardware RTIO """Set the timeline to the current value of the hardware RTIO counter
counter plus a margin of 125000 machine units. plus a margin of 125000 machine units."""
If the time cursor is already after that position, this function
does nothing."""
min_now = rtio_get_counter() + 125000 min_now = rtio_get_counter() + 125000
if now_mu() < min_now: if now_mu() < min_now:
at_mu(min_now) at_mu(min_now)
def trigger_analyzer_proxy(self):
"""Causes the core analyzer proxy to retrieve a dump from the device,
and distribute it to all connected clients (typically dashboards).
Returns only after the dump has been retrieved from the device.
Raises :exc:`IOError` if no analyzer proxy has been configured, or if the
analyzer proxy fails. In the latter case, more details would be
available in the proxy log.
"""
if self.analyzer_proxy is None:
if self.analyzer_proxy_name is not None:
self.analyzer_proxy = self.dmgr.get(self.analyzer_proxy_name)
if self.analyzer_proxy is None:
raise IOError("No analyzer proxy configured")
else:
self.analyzer_proxy.trigger()

View File

@ -1,648 +0,0 @@
{
"$id": "https://m-labs.hk/kasli_generic.schema.json",
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "Kasli variant description",
"type": "object",
"properties": {
"_description": {
"type": "string",
"description": "Free-form description text"
},
"target": {
"type": "string",
"description": "Target board"
},
"variant": {
"type": "string",
"description": "Target board variant name"
},
"min_artiq_version": {
"type": "string",
"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": "Role that this device takes in a DRTIO network; 'standalone' means no DRTIO",
"default": "standalone"
},
"ext_ref_frequency": {
"type": "number",
"exclusiveMinimum": 0,
"description": "External reference frequency"
},
"rtio_frequency": {
"type": "number",
"exclusiveMinimum": 0,
"default": 125e6,
"description": "RTIO frequency"
},
"enable_wrpll": {
"type": "boolean",
"default": false
},
"core_addr": {
"type": "string",
"format": "ipv4",
"description": "IPv4 address",
"default": "192.168.1.70"
},
"vendor": {
"type": "string",
"description": "Board vendor"
},
"eui48": {
"type": "array",
"items": {
"type": "string",
"pattern": "^([0-9a-f]{2}-){5}[0-9a-f]{2}$",
"examples": ["80-1f-12-4c-22-7f"]
},
"description": "Ethernet MAC addresses"
},
"enable_sata_drtio": {
"type": "boolean",
"default": false
},
"sed_lanes": {
"type": "number",
"minimum": 1,
"maximum": 32,
"default": 8,
"description": "Number of FIFOs in the SED, must be a power of 2"
},
"peripherals": {
"type": "array",
"items": {
"$ref": "#/definitions/peripheral"
}
}
},
"if": {
"properties": {
"target": { "const": "kasli" },
"hw_rev": {
"not": {
"oneOf": [
{ "const": "v1.0" },
{ "const": "v1.1" }
]
}
}
}
},
"then": {
"properties": {
"enable_sata_drtio": {
"const": false
}
}
},
"required": ["target", "variant", "hw_rev", "base", "peripherals"],
"additionalProperties": false,
"oneOf": [
{
"properties": {
"target": {
"type": "string",
"const": "kasli"
},
"hw_rev": {
"type": "string",
"enum": ["v1.0", "v1.1", "v2.0"]
}
}
},
{
"properties": {
"target": {
"type": "string",
"const": "kasli_soc"
},
"hw_rev": {
"type": "string",
"enum": ["v1.0", "v1.1"]
}
}
}
],
"definitions": {
"peripheral": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"]
},
"board": {
"type": "string"
},
"hw_rev": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+"
}
},
"required": ["type"],
"allOf": [{
"title": "DIO",
"if": {
"properties": {
"type": {
"const": "dio"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 1
},
"edge_counter": {
"type": "boolean",
"default": false
},
"bank_direction_low": {
"type": "string",
"enum": ["input", "output", "clkgen"]
},
"bank_direction_high": {
"type": "string",
"enum": ["input", "output", "clkgen"]
}
},
"required": ["ports", "bank_direction_low", "bank_direction_high"]
}
}, {
"title": "DIO_SPI",
"if": {
"properties": {
"type": {
"const": "dio_spi"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 1
},
"spi": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"default": "dio_spi"
},
"clk": {
"type": "integer",
"minimum": 0,
"maximum": 7
},
"mosi": {
"type": "integer",
"minimum": 0,
"maximum": 7
},
"miso": {
"type": "integer",
"minimum": 0,
"maximum": 7
},
"cs": {
"type": "array",
"items": {
"type": "integer",
"minimum": 0,
"maximum": 7
}
}
},
"required": ["clk"]
},
"minItems": 1
},
"ttl": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string",
"default": "ttl"
},
"pin": {
"type": "integer",
"minimum": 0,
"maximum": 7
},
"direction": {
"type": "string",
"enum": ["input", "output"]
},
"edge_counter": {
"type": "boolean",
"default": false
}
},
"required": ["pin", "direction"]
},
"default": []
}
},
"required": ["ports", "spi"]
}
}, {
"title": "Urukul",
"if": {
"properties": {
"type": {
"const": "urukul"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 2
},
"synchronization": {
"type": "boolean",
"default": false
},
"refclk": {
"type": "number",
"minimum": 0
},
"clk_sel": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"clk_div": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"pll_n": {
"type": "integer"
},
"pll_en": {
"type": "integer",
"minimum": 0,
"maximum": 1,
"default": 1
},
"pll_vco": {
"type": "integer"
},
"dds": {
"type": "string",
"enum": ["ad9910", "ad9912"],
"default": "ad9910"
}
},
"required": ["ports"]
}
}, {
"title": "Novogorny",
"if": {
"properties": {
"type": {
"const": "novogorny"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 1
}
},
"required": ["ports"]
}
}, {
"title": "Sampler",
"if": {
"properties": {
"type": {
"const": "sampler"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 2
}
},
"required": ["ports"]
}
}, {
"title": "SUServo",
"if": {
"properties": {
"type": {
"const": "suservo"
}
}
},
"then": {
"properties": {
"sampler_ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 2,
"maxItems": 2
},
"sampler_hw_rev": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+",
"default": "v2.2"
},
"urukul0_ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 2,
"maxItems": 2
},
"urukul1_ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 2,
"maxItems": 2
},
"refclk": {
"type": "number",
"minimum": 0
},
"clk_sel": {
"type": "integer",
"minimum": 0,
"maximum": 3
},
"pll_n": {
"type": "integer",
"default": 32
},
"pll_en": {
"type": "integer",
"minimum": 0,
"maximum": 1,
"default": 1
},
"pll_vco": {
"type": "integer"
}
},
"required": ["sampler_ports", "urukul0_ports"]
}
}, {
"title": "Zotino",
"if": {
"properties": {
"type": {
"const": "zotino"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 1
}
},
"required": ["ports"]
}
}, {
"title": "Grabber",
"if": {
"properties": {
"type": {
"const": "grabber"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 3
}
},
"required": ["ports"]
}
}, {
"title": "Mirny",
"if": {
"properties": {
"type": {
"const": "mirny"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 1
},
"refclk": {
"type": "number",
"exclusiveMinimum": 0,
"default": 100e6
},
"clk_sel": {
"oneOf": [
{
"type": "integer",
"minimum": 0,
"maximum": 3
},
{
"type": "string",
"enum": ["xo", "mmcx", "sma"]
}
],
"default": 0
},
"almazny": {
"type": "boolean",
"default": false
},
"almazny_hw_rev": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+",
"default": "v1.2"
}
},
"required": ["ports"]
}
}, {
"title": "Fastino",
"if": {
"properties": {
"type": {
"const": "fastino"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 1
},
"log2_width": {
"type": "integer",
"default": 0,
"description": "Width of DAC channel group (logarithm base 2)"
}
},
"required": ["ports"]
}
}, {
"title": "Phaser",
"if": {
"properties": {
"type": {
"const": "phaser"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 1
},
"mode": {
"type": "string",
"enum": ["base", "miqro"],
"default": "base"
}
},
"required": ["ports"]
}
}, {
"title": "HVAmp",
"if": {
"properties": {
"type": {
"const": "hvamp"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 1
}
},
"required": ["ports"]
}
},{
"title": "Shuttler",
"if": {
"properties": {
"type": {
"const": "shuttler"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 2
},
"drtio_destination": {
"type": "integer"
},
"hw_rev": {
"type": "string",
"enum": ["v1.0", "v1.1"]
}
},
"required": ["ports"]
}
}]
}
}
}

View File

@ -1,277 +0,0 @@
class DAC34H84:
"""DAC34H84 settings and register map.
For possible values, documentation, and explanation, see the DAC datasheet
at https://www.ti.com/lit/pdf/slas751
"""
qmc_corr_ena = 0 # msb ab
qmc_offset_ena = 0 # msb ab
invsinc_ena = 0 # msb ab
interpolation = 1 # 2x
fifo_ena = 1
alarm_out_ena = 1
alarm_out_pol = 1
clkdiv_sync_ena = 1
iotest_ena = 0
cnt64_ena = 0
oddeven_parity = 0 # even
single_parity_ena = 1
dual_parity_ena = 0
rev_interface = 0
dac_complement = 0b0000 # msb A
alarm_fifo = 0b111 # msb 2-away
dacclkgone_ena = 1
dataclkgone_ena = 1
collisiongone_ena = 1
sif4_ena = 1
mixer_ena = 0
mixer_gain = 1
nco_ena = 0
revbus = 0
twos = 1
coarse_dac = 9 # 18.75 mA, 0-15
sif_txenable = 0
mask_alarm_from_zerochk = 0
mask_alarm_fifo_collision = 0
mask_alarm_fifo_1away = 0
mask_alarm_fifo_2away = 0
mask_alarm_dacclk_gone = 0
mask_alarm_dataclk_gone = 0
mask_alarm_output_gone = 0
mask_alarm_from_iotest = 0
mask_alarm_from_pll = 0
mask_alarm_parity = 0b0000 # msb a
qmc_offseta = 0 # 12b
fifo_offset = 2 # 0-7
qmc_offsetb = 0 # 12b
qmc_offsetc = 0 # 12b
qmc_offsetd = 0 # 12b
qmc_gaina = 0 # 11b
cmix_fs8 = 0
cmix_fs4 = 0
cmix_fs2 = 0
cmix_nfs4 = 0
qmc_gainb = 0 # 11b
qmc_gainc = 0 # 11b
output_delayab = 0b00
output_delaycd = 0b00
qmc_gaind = 0 # 11b
qmc_phaseab = 0 # 12b
qmc_phasecd = 0 # 12b
phase_offsetab = 0 # 16b
phase_offsetcd = 0 # 16b
phase_addab_lsb = 0 # 16b
phase_addab_msb = 0 # 16b
phase_addcd_lsb = 0 # 16b
phase_addcd_msb = 0 # 16b
pll_reset = 0
pll_ndivsync_ena = 1
pll_ena = 1
pll_cp = 0b01 # single charge pump
pll_p = 0b100 # p=4
pll_m2 = 1 # x2
pll_m = 8 # m = 8
pll_n = 0b0001 # n = 2
pll_vcotune = 0b01
pll_vco = 0x3f # 4 GHz
bias_sleep = 0
tsense_sleep = 0
pll_sleep = 0
clkrecv_sleep = 0
dac_sleep = 0b0000 # msb a
extref_ena = 0
fuse_sleep = 1
atest = 0b00000 # atest mode
syncsel_qmcoffsetab = 0b1001 # sif_sync and register write
syncsel_qmcoffsetcd = 0b1001 # sif_sync and register write
syncsel_qmccorrab = 0b1001 # sif_sync and register write
syncsel_qmccorrcd = 0b1001 # sif_sync and register write
syncsel_mixerab = 0b1001 # sif_sync and register write
syncsel_mixercd = 0b1001 # sif_sync and register write
syncsel_nco = 0b1000 # sif_sync
syncsel_fifo_input = 0b10 # external lvds istr
sif_sync = 0
syncsel_fifoin = 0b0010 # istr
syncsel_fifoout = 0b0100 # ostr
clkdiv_sync_sel = 0 # ostr
path_a_sel = 0
path_b_sel = 1
path_c_sel = 2
path_d_sel = 3
# swap dac pairs (CDAB) for layout
# swap I-Q dacs for spectral inversion
dac_a_sel = 3
dac_b_sel = 2
dac_c_sel = 1
dac_d_sel = 0
dac_sleep_en = 0b1111 # msb a
clkrecv_sleep_en = 1
pll_sleep_en = 1
lvds_data_sleep_en = 1
lvds_control_sleep_en = 1
temp_sense_sleep_en = 1
bias_sleep_en = 1
data_dly = 2
clk_dly = 0
ostrtodig_sel = 0
ramp_ena = 0
sifdac_ena = 0
grp_delaya = 0x00
grp_delayb = 0x00
grp_delayc = 0x00
grp_delayd = 0x00
sifdac = 0
def __init__(self, updates=None):
if updates is None:
return
for key, value in updates.items():
if not hasattr(self, key):
raise KeyError("invalid setting", key)
setattr(self, key, value)
def get_mmap(self):
mmap = []
mmap.append(
(0x00 << 16) |
(self.qmc_offset_ena << 14) | (self.qmc_corr_ena << 12) |
(self.interpolation << 8) | (self.fifo_ena << 7) |
(self.alarm_out_ena << 4) | (self.alarm_out_pol << 3) |
(self.clkdiv_sync_ena << 2) | (self.invsinc_ena << 0))
mmap.append(
(0x01 << 16) |
(self.iotest_ena << 15) | (self.cnt64_ena << 12) |
(self.oddeven_parity << 11) | (self.single_parity_ena << 10) |
(self.dual_parity_ena << 9) | (self.rev_interface << 8) |
(self.dac_complement << 4) | (self.alarm_fifo << 1))
mmap.append(
(0x02 << 16) |
(self.dacclkgone_ena << 14) | (self.dataclkgone_ena << 13) |
(self.collisiongone_ena << 12) | (self.sif4_ena << 7) |
(self.mixer_ena << 6) | (self.mixer_gain << 5) |
(self.nco_ena << 4) | (self.revbus << 3) | (self.twos << 1))
mmap.append((0x03 << 16) | (self.coarse_dac << 12) |
(self.sif_txenable << 0))
mmap.append(
(0x07 << 16) |
(self.mask_alarm_from_zerochk << 15) | (1 << 14) |
(self.mask_alarm_fifo_collision << 13) |
(self.mask_alarm_fifo_1away << 12) |
(self.mask_alarm_fifo_2away << 11) |
(self.mask_alarm_dacclk_gone << 10) |
(self.mask_alarm_dataclk_gone << 9) |
(self.mask_alarm_output_gone << 8) |
(self.mask_alarm_from_iotest << 7) | (1 << 6) |
(self.mask_alarm_from_pll << 5) | (self.mask_alarm_parity << 1))
mmap.append(
(0x08 << 16) | (self.qmc_offseta << 0))
mmap.append(
(0x09 << 16) | (self.fifo_offset << 13) | (self.qmc_offsetb << 0))
mmap.append((0x0a << 16) | (self.qmc_offsetc << 0))
mmap.append((0x0b << 16) | (self.qmc_offsetd << 0))
mmap.append((0x0c << 16) | (self.qmc_gaina << 0))
mmap.append(
(0x0d << 16) |
(self.cmix_fs8 << 15) | (self.cmix_fs4 << 14) |
(self.cmix_fs2 << 13) | (self.cmix_nfs4 << 12) |
(self.qmc_gainb << 0))
mmap.append((0x0e << 16) | (self.qmc_gainc << 0))
mmap.append(
(0x0f << 16) |
(self.output_delayab << 14) | (self.output_delaycd << 12) |
(self.qmc_gaind << 0))
mmap.append((0x10 << 16) | (self.qmc_phaseab << 0))
mmap.append((0x11 << 16) | (self.qmc_phasecd << 0))
mmap.append((0x12 << 16) | (self.phase_offsetab << 0))
mmap.append((0x13 << 16) | (self.phase_offsetcd << 0))
mmap.append((0x14 << 16) | (self.phase_addab_lsb << 0))
mmap.append((0x15 << 16) | (self.phase_addab_msb << 0))
mmap.append((0x16 << 16) | (self.phase_addcd_lsb << 0))
mmap.append((0x17 << 16) | (self.phase_addcd_msb << 0))
mmap.append(
(0x18 << 16) |
(0b001 << 13) | (self.pll_reset << 12) |
(self.pll_ndivsync_ena << 11) | (self.pll_ena << 10) |
(self.pll_cp << 6) | (self.pll_p << 3))
mmap.append(
(0x19 << 16) |
(self.pll_m2 << 15) | (self.pll_m << 8) | (self.pll_n << 4) |
(self.pll_vcotune << 2))
mmap.append(
(0x1a << 16) |
(self.pll_vco << 10) | (self.bias_sleep << 7) |
(self.tsense_sleep << 6) |
(self.pll_sleep << 5) | (self.clkrecv_sleep << 4) |
(self.dac_sleep << 0))
mmap.append(
(0x1b << 16) |
(self.extref_ena << 15) | (self.fuse_sleep << 11) |
(self.atest << 0))
mmap.append(
(0x1e << 16) |
(self.syncsel_qmcoffsetab << 12) |
(self.syncsel_qmcoffsetcd << 8) |
(self.syncsel_qmccorrab << 4) |
(self.syncsel_qmccorrcd << 0))
mmap.append(
(0x1f << 16) |
(self.syncsel_mixerab << 12) | (self.syncsel_mixercd << 8) |
(self.syncsel_nco << 4) | (self.syncsel_fifo_input << 2) |
(self.sif_sync << 1))
mmap.append(
(0x20 << 16) |
(self.syncsel_fifoin << 12) | (self.syncsel_fifoout << 8) |
(self.clkdiv_sync_sel << 0))
mmap.append(
(0x22 << 16) |
(self.path_a_sel << 14) | (self.path_b_sel << 12) |
(self.path_c_sel << 10) | (self.path_d_sel << 8) |
(self.dac_a_sel << 6) | (self.dac_b_sel << 4) |
(self.dac_c_sel << 2) | (self.dac_d_sel << 0))
mmap.append(
(0x23 << 16) |
(self.dac_sleep_en << 12) | (self.clkrecv_sleep_en << 11) |
(self.pll_sleep_en << 10) | (self.lvds_data_sleep_en << 9) |
(self.lvds_control_sleep_en << 8) |
(self.temp_sense_sleep_en << 7) | (1 << 6) |
(self.bias_sleep_en << 5) | (0x1f << 0))
mmap.append(
(0x24 << 16) | (self.data_dly << 13) | (self.clk_dly << 10))
mmap.append(
(0x2d << 16) |
(self.ostrtodig_sel << 14) | (self.ramp_ena << 13) |
(0x002 << 1) | (self.sifdac_ena << 0))
mmap.append(
(0x2e << 16) | (self.grp_delaya << 8) | (self.grp_delayb << 0))
mmap.append(
(0x2f << 16) | (self.grp_delayc << 8) | (self.grp_delayd << 0))
mmap.append((0x30 << 16) | self.sifdac)
return mmap

215
artiq/coredevice/dds.py Normal file
View File

@ -0,0 +1,215 @@
from artiq.language.core import *
from artiq.language.types import *
from artiq.language.units import *
_PHASE_MODE_DEFAULT = -1
# keep in sync with dds.h
PHASE_MODE_CONTINUOUS = 0
PHASE_MODE_ABSOLUTE = 1
PHASE_MODE_TRACKING = 2
@syscall(flags={"nowrite"})
def dds_init(time_mu: TInt64, bus_channel: TInt32, channel: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nowrite"})
def dds_set(time_mu: TInt64, bus_channel: TInt32, channel: TInt32, ftw: TInt32,
pow: TInt32, phase_mode: TInt32, amplitude: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nowrite"})
def dds_batch_enter(time_mu: TInt64) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall(flags={"nowrite"})
def dds_batch_exit() -> TNone:
raise NotImplementedError("syscall not simulated")
class _BatchContextManager:
kernel_invariants = {"core", "core_dds"}
def __init__(self, core_dds):
self.core_dds = core_dds
self.core = self.core_dds.core
@kernel
def __enter__(self):
self.core_dds.dds_batch_enter()
@kernel
def __exit__(self, type, value, traceback):
self.core_dds.dds_batch_exit()
class CoreDDS:
"""Core device Direct Digital Synthesis (DDS) driver.
Gives access to the DDS functionality of the core device.
:param sysclk: DDS system frequency. The DDS system clock must be a
phase-locked multiple of the RTIO clock.
"""
kernel_invariants = {"core", "sysclk", "batch"}
def __init__(self, dmgr, sysclk, core_device="core"):
self.core = dmgr.get(core_device)
self.sysclk = sysclk
self.batch = _BatchContextManager(self)
@kernel
def dds_batch_enter(self):
"""Starts a DDS command batch. All DDS commands are buffered
after this call, until ``batch_exit`` is called.
The time of execution of the DDS commands is the time cursor position
when the batch is entered."""
dds_batch_enter(now_mu())
@kernel
def dds_batch_exit(self):
"""Ends a DDS command batch. All buffered DDS commands are issued
on the bus."""
dds_batch_exit()
class _DDSGeneric:
"""Core device Direct Digital Synthesis (DDS) channel driver.
Controls one DDS channel managed directly by the core device's runtime.
This class should not be used directly, instead, use the chip-specific
drivers such as ``AD9858`` and ``AD9914``.
The time cursor is not modified by any function in this class.
:param bus: name of the DDS bus device that this DDS is connected to.
:param channel: channel number of the DDS device to control.
"""
kernel_invariants = {
"core", "core_dds", "bus_channel", "channel", "pow_width"
}
def __init__(self, dmgr, bus_channel, channel, core_dds_device="core_dds"):
self.core_dds = dmgr.get(core_dds_device)
self.core = self.core_dds.core
self.bus_channel = bus_channel
self.channel = channel
self.phase_mode = PHASE_MODE_CONTINUOUS
@portable(flags=["fast-math"])
def frequency_to_ftw(self, frequency):
"""Returns the frequency tuning word corresponding to the given
frequency.
"""
return round(int(2, width=64)**32*frequency/self.core_dds.sysclk)
@portable(flags=["fast-math"])
def ftw_to_frequency(self, ftw):
"""Returns the frequency corresponding to the given frequency tuning
word.
"""
return ftw*self.core_dds.sysclk/int(2, width=64)**32
@portable(flags=["fast-math"])
def turns_to_pow(self, turns):
"""Returns the phase offset word corresponding to the given phase
in turns."""
return round(turns*2**self.pow_width)
@portable(flags=["fast-math"])
def pow_to_turns(self, pow):
"""Returns the phase in turns corresponding to the given phase offset
word."""
return pow/2**self.pow_width
@portable(flags=["fast-math"])
def amplitude_to_asf(self, amplitude):
"""Returns amplitude scale factor corresponding to given amplitude."""
return round(amplitude*0x0fff)
@portable(flags=["fast-math"])
def asf_to_amplitude(self, asf):
"""Returns the amplitude corresponding to the given amplitude scale
factor."""
return asf/0x0fff
@kernel
def init(self):
"""Resets and initializes the DDS channel.
This needs to be done for each DDS channel before it can be used, and
it is recommended to use the startup kernel for this.
This function cannot be used in a batch; the correct way of
initializing multiple DDS channels is to call this function
sequentially with a delay between the calls. 2ms provides a good
timing margin."""
dds_init(now_mu(), self.bus_channel, self.channel)
@kernel
def set_phase_mode(self, phase_mode):
"""Sets the phase mode of the DDS channel. Supported phase modes are:
* ``PHASE_MODE_CONTINUOUS``: the phase accumulator is unchanged when
switching frequencies. The DDS phase is the sum of the phase
accumulator and the phase offset. The only discrete jumps in the
DDS output phase come from changes to the phase offset.
* ``PHASE_MODE_ABSOLUTE``: the phase accumulator is reset when
switching frequencies. Thus, the phase of the DDS at the time of
the frequency change is equal to the phase offset.
* ``PHASE_MODE_TRACKING``: when switching frequencies, the phase
accumulator is set to the value it would have if the DDS had been
running at the specified frequency since the start of the
experiment.
"""
self.phase_mode = phase_mode
@kernel
def set_mu(self, frequency, phase=0, phase_mode=_PHASE_MODE_DEFAULT,
amplitude=0x0fff):
"""Sets the DDS channel to the specified frequency and phase.
This uses machine units (FTW and POW). The frequency tuning word width
is 32, whereas the phase offset word width depends on the type of DDS
chip and can be retrieved via the ``pow_width`` attribute. The amplitude
width is 12.
The "frequency update" pulse is sent to the DDS with a fixed latency
with respect to the current position of the time cursor.
:param frequency: frequency to generate.
:param phase: adds an offset, in turns, to the phase.
:param phase_mode: if specified, overrides the default phase mode set
by ``set_phase_mode`` for this call.
"""
if phase_mode == _PHASE_MODE_DEFAULT:
phase_mode = self.phase_mode
dds_set(now_mu(), self.bus_channel, self.channel,
frequency, phase, phase_mode, amplitude)
@kernel
def set(self, frequency, phase=0.0, phase_mode=_PHASE_MODE_DEFAULT,
amplitude=1.0):
"""Like ``set_mu``, but uses Hz and turns."""
self.set_mu(self.frequency_to_ftw(frequency),
self.turns_to_pow(phase), phase_mode,
self.amplitude_to_asf(amplitude))
class AD9858(_DDSGeneric):
"""Driver for AD9858 DDS chips. See ``_DDSGeneric`` for a description
of the functionality."""
pow_width = 14
class AD9914(_DDSGeneric):
"""Driver for AD9914 DDS chips. See ``_DDSGeneric`` for a description
of the functionality."""
pow_width = 16

View File

@ -1,124 +0,0 @@
"""Direct Memory Access (DMA) extension.
This feature allows storing pre-defined sequences of output RTIO events into
the core device's SDRAM, and playing them back at higher speeds than the CPU
alone could achieve.
"""
from artiq.language.core import syscall, kernel
from artiq.language.types import TInt32, TInt64, TStr, TNone, TTuple, TBool
from artiq.coredevice.exceptions import DMAError
from numpy import int64
@syscall
def dma_record_start(name: TStr) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def dma_record_stop(duration: TInt64, enable_ddma: TBool) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def dma_erase(name: TStr) -> TNone:
raise NotImplementedError("syscall not simulated")
@syscall
def dma_retrieve(name: TStr) -> TTuple([TInt64, TInt32, TBool]):
raise NotImplementedError("syscall not simulated")
@syscall
def dma_playback(timestamp: TInt64, ptr: TInt32, enable_ddma: TBool) -> TNone:
raise NotImplementedError("syscall not simulated")
class DMARecordContextManager:
"""Context manager returned by :meth:`CoreDMA.record()`.
Upon entering, starts recording a DMA trace. All RTIO operations are
redirected to a newly created DMA buffer after this call, and ``now``
is reset to zero.
Upon leaving, stops recording a DMA trace. All recorded RTIO operations
are stored in a newly created trace, and ``now`` is restored to the value
it had before the context manager was entered.
"""
def __init__(self):
self.name = ""
self.saved_now_mu = int64(0)
self.enable_ddma = False
@kernel
def __enter__(self):
dma_record_start(self.name) # this may raise, so do it before altering now
self.saved_now_mu = now_mu()
at_mu(0)
@kernel
def __exit__(self, type, value, traceback):
dma_record_stop(now_mu(), self.enable_ddma) # see above
at_mu(self.saved_now_mu)
class CoreDMA:
"""Core device Direct Memory Access (DMA) driver.
Gives access to the DMA functionality of the core device.
"""
kernel_invariants = {"core", "recorder"}
def __init__(self, dmgr, core_device="core"):
self.core = dmgr.get(core_device)
self.recorder = DMARecordContextManager()
self.epoch = 0
@kernel
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.
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
def erase(self, name):
"""Removes the DMA trace with the given name from storage."""
self.epoch += 1
dma_erase(name)
@kernel
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, 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, 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 offloads the overhead of managing the handles onto the programmer."""
(epoch, advance_mu, ptr, uses_ddma) = handle
if self.epoch != epoch:
raise DMAError("Invalid handle")
dma_playback(now_mu(), ptr, uses_ddma)
delay_mu(advance_mu)

View File

@ -1,238 +0,0 @@
"""Driver for RTIO-enabled TTL edge counter.
As for the TTL input PHYs, sensitivity can be configured over RTIO
(:meth:`gate_rising<EdgeCounter.gate_rising>`, etc.). In contrast to the former, however, the count is
accumulated in gateware, and only a single input event is generated at the end
of each gate period: ::
with parallel:
doppler_cool()
self.pmt_counter.gate_rising(1 * ms)
with parallel:
readout()
self.pmt_counter.gate_rising(100 * us)
print("Doppler cooling counts:", self.pmt_counter.fetch_count())
print("Readout counts:", self.pmt_counter.fetch_count())
For applications where the timestamps of the individual input events are not
required, this has two advantages over :meth:`TTLInOut.count<artiq.coredevice.ttl.TTLInOut.count>`
beyond raw throughput. First, it is easy to count events during multiple separate
periods without blocking to read back counts in between, as illustrated in the
above example. Secondly, as each count total only takes up a single input event,
it is much easier to acquire counts on several channels in parallel without
risking input RTIO overflows: ::
# Using the TTLInOut driver, pmt_1 input events are only processed
# after pmt_0 is done counting. To avoid RTIOOverflows, a round-robin
# scheme would have to be implemented manually.
with parallel:
self.pmt_0.gate_rising(10 * ms)
self.pmt_1.gate_rising(10 * ms)
counts_0 = self.pmt_0.count(now_mu()) # blocks
counts_1 = self.pmt_1.count(now_mu())
# Using gateware counters, only a single input event each is
# generated, greatly reducing the load on the input FIFOs:
with parallel:
self.pmt_0_counter.gate_rising(10 * ms)
self.pmt_1_counter.gate_rising(10 * ms)
counts_0 = self.pmt_0_counter.fetch_count() # blocks
counts_1 = self.pmt_1_counter.fetch_count()
See the sources of :mod:`artiq.gateware.rtio.phy.edge_counter` and
:meth:`artiq.gateware.eem.DIO.add_std` for the gateware components.
"""
from artiq.language.core import *
from artiq.language.types import *
from artiq.coredevice.rtio import (rtio_output, rtio_input_data,
rtio_input_timestamped_data)
from numpy import int32, int64
CONFIG_COUNT_RISING = 0b0001
CONFIG_COUNT_FALLING = 0b0010
CONFIG_SEND_COUNT_EVENT = 0b0100
CONFIG_RESET_TO_ZERO = 0b1000
class CounterOverflow(Exception):
"""Raised when an edge counter value is read which indicates that the
counter might have overflowed."""
pass
class EdgeCounter:
"""RTIO TTL edge counter driver driver.
Like for regular TTL inputs, timeline periods where the counter is
sensitive to a chosen set of input transitions can be specified. Unlike the
former, however, the specified edges do not create individual input events;
rather, the total count can be requested as a single input event from the
core (typically at the end of the gate window).
:param channel: The RTIO channel of the gateware phy.
:param gateware_width: The width of the gateware counter register, in
bits. This is only used for overflow handling; to change the size,
the gateware needs to be rebuilt.
"""
kernel_invariants = {"core", "channel", "counter_max"}
def __init__(self, dmgr, channel, gateware_width=31, core_device="core"):
self.core = dmgr.get(core_device)
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
the end.
The counter is reset at the beginning of the gate period. Use
:meth:`set_config` directly for more detailed control.
:param duration: The duration for which the gate is to stay open.
:return: The timestamp at the end of the gate period, in machine units.
"""
return self.gate_rising_mu(self.core.seconds_to_mu(duration))
@kernel
def gate_falling(self, duration):
"""Count falling edges for the given duration and request the total at
the end.
The counter is reset at the beginning of the gate period. Use
:meth:`set_config` directly for more detailed control.
:param duration: The duration for which the gate is to stay open.
:return: The timestamp at the end of the gate period, in machine units.
"""
return self.gate_falling_mu(self.core.seconds_to_mu(duration))
@kernel
def gate_both(self, duration):
"""Count both rising and falling edges for the given duration, and
request the total at the end.
The counter is reset at the beginning of the gate period. Use
:meth:`set_config` directly for more detailed control.
:param duration: The duration for which the gate is to stay open.
:return: The timestamp at the end of the gate period, in machine units.
"""
return self.gate_both_mu(self.core.seconds_to_mu(duration))
@kernel
def gate_rising_mu(self, duration_mu):
"""See :meth:`gate_rising`."""
return self._gate_mu(
duration_mu, count_rising=True, count_falling=False)
@kernel
def gate_falling_mu(self, duration_mu):
"""See :meth:`gate_falling`."""
return self._gate_mu(
duration_mu, count_rising=False, count_falling=True)
@kernel
def gate_both_mu(self, duration_mu):
"""See :meth:`gate_both_mu`."""
return self._gate_mu(
duration_mu, count_rising=True, count_falling=True)
@kernel
def _gate_mu(self, duration_mu, count_rising, count_falling):
self.set_config(
count_rising=count_rising,
count_falling=count_falling,
send_count_event=False,
reset_to_zero=True)
delay_mu(duration_mu)
self.set_config(
count_rising=False,
count_falling=False,
send_count_event=True,
reset_to_zero=False)
return now_mu()
@kernel
def set_config(self, count_rising: TBool, count_falling: TBool,
send_count_event: TBool, reset_to_zero: TBool):
"""Emit an RTIO event at the current timeline position to set the
gateware configuration.
For most use cases, the ``gate_*`` wrappers will be more convenient.
:param count_rising: Whether to count rising signal edges.
:param count_falling: Whether to count falling signal edges.
:param send_count_event: If ``True``, an input event with the current
counter value is generated on the next clock cycle (once).
:param reset_to_zero: If ``True``, the counter value is reset to zero on
the next clock cycle (once).
"""
config = int32(0)
if count_rising:
config |= CONFIG_COUNT_RISING
if count_falling:
config |= CONFIG_COUNT_FALLING
if send_count_event:
config |= CONFIG_SEND_COUNT_EVENT
if reset_to_zero:
config |= CONFIG_RESET_TO_ZERO
rtio_output(self.channel << 8, config)
@kernel
def fetch_count(self) -> TInt32:
"""Wait for and return count total from previously requested input
event.
It is valid to trigger multiple gate periods without immediately
reading back the count total; the results will be returned in order on
subsequent fetch calls.
This function blocks until a result becomes available.
"""
count = rtio_input_data(self.channel)
if count == self.counter_max:
raise CounterOverflow(
"Input edge counter overflow on RTIO channel {0}",
int64(self.channel))
return count
@kernel
def fetch_timestamped_count(
self, timeout_mu=int64(-1)) -> TTuple([TInt64, TInt32]):
"""Wait for and return the timestamp and count total of a previously
requested input event.
It is valid to trigger multiple gate periods without immediately
reading back the count total; the results will be returned in order on
subsequent fetch calls.
This function blocks until a result becomes available or the given
timeout elapses.
:return: A tuple of timestamp (-1 if timeout elapsed) and counter
value. (The timestamp is that of the requested input event
typically the gate closing time and not that of any input edges.)
"""
timestamp, count = rtio_input_timestamped_data(timeout_mu,
self.channel)
if count == self.counter_max:
raise CounterOverflow(
"Input edge counter overflow on RTIO channel {0}",
int64(self.channel))
return timestamp, count

View File

@ -2,123 +2,62 @@ import builtins
import linecache import linecache
import re import re
import os import os
from numpy.linalg import LinAlgError
from artiq import __artiq_dir__ as artiq_dir from artiq import __artiq_dir__ as artiq_dir
from artiq.coredevice.runtime import source_loader from artiq.coredevice.runtime import source_loader
"""
This file provides class definition for all the exceptions declared in `EmbeddingMap` in `artiq.compiler.embedding`
For Python builtin exceptions, use the `builtins` module
For ARTIQ specific exceptions, inherit from `Exception` class
"""
AssertionError = builtins.AssertionError
AttributeError = builtins.AttributeError
IndexError = builtins.IndexError
IOError = builtins.IOError
KeyError = builtins.KeyError
NotImplementedError = builtins.NotImplementedError
OverflowError = builtins.OverflowError
RuntimeError = builtins.RuntimeError
TimeoutError = builtins.TimeoutError
TypeError = builtins.TypeError
ValueError = builtins.ValueError
ZeroDivisionError = builtins.ZeroDivisionError ZeroDivisionError = builtins.ZeroDivisionError
OSError = builtins.OSError ValueError = builtins.ValueError
IndexError = builtins.IndexError
class CoreException: class CoreException:
"""Information about an exception raised or passed through the core device.""" """Information about an exception raised or passed through the core device."""
def __init__(self, exceptions, exception_info, traceback, stack_pointers):
self.exceptions = exceptions
self.exception_info = exception_info
self.traceback = list(traceback)
self.stack_pointers = stack_pointers
first_exception = exceptions[0] def __init__(self, name, message, params, traceback):
name = first_exception[0]
if ':' in name: if ':' in name:
exn_id, self.name = name.split(':', 2) exn_id, self.name = name.split(':', 2)
self.id = int(exn_id) self.id = int(exn_id)
else: else:
self.id, self.name = 0, name self.id, self.name = 0, name
self.message = first_exception[1] self.message, self.params = message, params
self.params = first_exception[2] self.traceback = list(traceback)
def append_backtrace(self, record, inlined=False):
filename, line, column, function, address = record
stub_globals = {"__name__": filename, "__loader__": source_loader}
source_line = linecache.getline(filename, line, stub_globals)
indentation = re.search(r"^\s*", source_line).end()
if address is None:
formatted_address = ""
elif inlined:
formatted_address = " (inlined)"
else:
formatted_address = " (RA=+0x{:x})".format(address)
filename = filename.replace(artiq_dir, "<artiq>")
lines = []
if column == -1:
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
lines.append(" File \"{file}\", line {line}, in {function}{address}".
format(file=filename, line=line, function=function,
address=formatted_address))
else:
lines.append(" {}^".format(" " * (column - indentation)))
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
lines.append(" File \"{file}\", line {line}, column {column},"
" in {function}{address}".
format(file=filename, line=line, column=column + 1,
function=function, address=formatted_address))
return lines
def single_traceback(self, exception_index):
# note that we insert in reversed order
lines = []
last_sp = 0
start_backtrace_index = self.exception_info[exception_index][1]
zipped = list(zip(self.traceback[start_backtrace_index:],
self.stack_pointers[start_backtrace_index:]))
exception = self.exceptions[exception_index]
name = exception[0]
message = exception[1]
params = exception[2]
if ':' in name:
exn_id, name = name.split(':', 2)
exn_id = int(exn_id)
else:
exn_id = 0
lines.append("{}({}): {}".format(name, exn_id, message.format(*params)))
zipped.append(((exception[3], exception[4], exception[5], exception[6],
None, []), None))
for ((filename, line, column, function, address, inlined), sp) in zipped:
# backtrace of nested exceptions may be discontinuous
# but the stack pointer must increase monotonically
if sp is not None and sp <= last_sp:
continue
last_sp = sp
for record in reversed(inlined):
lines += self.append_backtrace(record, True)
lines += self.append_backtrace((filename, line, column, function,
address))
lines.append("Traceback (most recent call first):")
return "\n".join(reversed(lines))
def __str__(self): def __str__(self):
tracebacks = [self.single_traceback(i) for i in range(len(self.exceptions))] lines = []
traceback_str = ('\n\nDuring handling of the above exception, ' + lines.append("Core Device Traceback (most recent call last):")
'another exception occurred:\n\n').join(tracebacks) last_address = 0
return 'Core Device Traceback:\n' +\ for (filename, line, column, function, address) in self.traceback:
traceback_str +\ stub_globals = {"__name__": filename, "__loader__": source_loader}
'\n\nEnd of Core Device Traceback\n' source_line = linecache.getline(filename, line, stub_globals)
indentation = re.search(r"^\s*", source_line).end()
if address is None:
formatted_address = ""
elif address == last_address:
formatted_address = " (inlined)"
else:
formatted_address = " (RA=+0x{:x})".format(address)
last_address = address
filename = filename.replace(artiq_dir, "<artiq>")
if column == -1:
lines.append(" File \"{file}\", line {line}, in {function}{address}".
format(file=filename, line=line, function=function,
address=formatted_address))
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
else:
lines.append(" File \"{file}\", line {line}, column {column},"
" in {function}{address}".
format(file=filename, line=line, column=column + 1,
function=function, address=formatted_address))
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
lines.append(" {}^".format(" " * (column - indentation)))
lines.append("{}({}): {}".format(self.name, self.id,
self.message.format(*self.params)))
return "\n".join(lines)
class InternalError(Exception): class InternalError(Exception):
@ -132,13 +71,44 @@ class CacheError(Exception):
class RTIOUnderflow(Exception): class RTIOUnderflow(Exception):
"""Raised when the CPU or DMA core fails to submit a RTIO event early """Raised when the CPU fails to submit a RTIO event early enough
enough (with respect to the event's timestamp). (with respect to the event's timestamp).
The offending event is discarded and the RTIO core keeps operating. The offending event is discarded and the RTIO core keeps operating.
""" """
artiq_builtin = True artiq_builtin = True
class RTIOSequenceError(Exception):
"""Raised when an event is submitted on a given channel with a timestamp
not larger than the previous one.
The offending event is discarded and the RTIO core keeps operating.
"""
artiq_builtin = True
class RTIOCollision(Exception):
"""Raised when an event is submitted on a given channel with the same
coarse timestamp as the previous one but with a different fine timestamp.
Coarse timestamps correspond to the RTIO system clock (typically around
125MHz) whereas fine timestamps correspond to the RTIO SERDES clock
(typically around 1GHz).
The offending event is discarded and the RTIO core keeps operating.
"""
artiq_builtin = True
class RTIOBusy(Exception):
"""Raised when at least one output event could not be executed because
the given channel was already busy executing a previous event.
This exception is raised late: after the error condition occurred. More
specifically it is raised on submitting an event on the same channel after
the execution of the faulty event was attempted.
The offending event was discarded.
"""
artiq_builtin = True
class RTIOOverflow(Exception): class RTIOOverflow(Exception):
"""Raised when at least one event could not be registered into the RTIO """Raised when at least one event could not be registered into the RTIO
@ -150,40 +120,19 @@ class RTIOOverflow(Exception):
""" """
artiq_builtin = True artiq_builtin = True
class DDSError(Exception):
class RTIODestinationUnreachable(Exception): """Raised when attempting to start a DDS batch while already in a batch,
"""Raised when a RTIO operation could not be completed due to a DRTIO link when too many commands are batched, and when DDS channel settings are
being down. incorrect.
""" """
artiq_builtin = True artiq_builtin = True
class DMAError(Exception):
"""Raised when performing an invalid DMA operation."""
artiq_builtin = True
class SubkernelError(Exception):
"""Raised when an operation regarding a subkernel is invalid
or cannot be completed.
"""
artiq_builtin = True
class ClockFailure(Exception):
"""Raised when RTIO PLL has lost lock."""
artiq_builtin = True
class I2CError(Exception): class I2CError(Exception):
"""Raised when a I2C transaction fails.""" """Raised with a I2C transaction fails."""
artiq_builtin = True artiq_builtin = True
class WatchdogExpired(Exception):
"""Raised when a watchdog expires."""
class SPIError(Exception): class ClockFailure(Exception):
"""Raised when a SPI transaction fails.""" """Raised when RTIO PLL has lost lock."""
artiq_builtin = True
class UnwrapNoneError(Exception):
"""Raised when unwrapping a none Option."""
artiq_builtin = True

View File

@ -1,305 +0,0 @@
"""RTIO driver for the Fastino 32-channel, 16-bit, 2.5 MS/s per channel
streaming DAC.
"""
from numpy import int32, int64
from artiq.language.core import kernel, portable, delay, delay_mu
from artiq.coredevice.rtio import (rtio_output, rtio_output_wide,
rtio_input_data)
from artiq.language.units import ns
from artiq.language.types import TInt32, TList
class Fastino:
"""Fastino 32-channel, 16-bit, 2.5 MS/s per channel streaming DAC
The RTIO PHY supports staging DAC data before transmitting them by writing
to the DAC RTIO addresses, if a channel is not "held" by setting its bit
using :meth:`set_hold`, the next frame will contain the update. For the
DACs held, the update is triggered explicitly by setting the corresponding
bit using :meth:`update`. Update is self-clearing. This enables atomic
DAC updates synchronized to a frame edge.
The ``log2_width=0`` RTIO layout uses one DAC channel per RTIO address and a
dense RTIO address space. The RTIO words are narrow (32-bit) and
few-channel updates are efficient. There is the least amount of DAC state
tracking in kernels, at the cost of more DMA and RTIO data.
The setting here and in the RTIO PHY (gateware) must match.
Other ``log2_width`` (up to ``log2_width=5``) settings pack multiple
(in powers of two) DAC channels into one group and into one RTIO write.
The RTIO data width increases accordingly. The ``log2_width``
LSBs of the RTIO address for a DAC channel write must be zero and the
address space is sparse. For ``log2_width=5`` the RTIO data is 512-bit wide.
If ``log2_width`` is zero, the :meth:`set_dac`/:meth:`set_dac_mu` interface
must be used. If non-zero, the :meth:`set_group`/:meth:`set_group_mu`
interface must be used.
:param channel: RTIO channel number
:param core_device: Core device name (default: "core")
:param log2_width: Width of DAC channel group (logarithm base 2).
Value must match the corresponding value in the RTIO PHY (gateware).
"""
kernel_invariants = {"core", "channel", "width", "t_frame"}
def __init__(self, dmgr, channel, core_device="core", log2_width=0):
self.channel = channel << 8
self.core = dmgr.get(core_device)
self.width = 1 << log2_width
# frame duration in mu (14 words each 7 clock cycles each 4 ns)
# self.core.seconds_to_mu(14*7*4*ns) # unfortunately this may round wrong
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.
* disables RESET, DAC_CLR, enables AFE_PWR
* clears error counters, enables error counting
* turns LEDs off
* clears ``hold`` and ``continuous`` on all channels
* clear and resets interpolators to unit rate change on all
channels
It does not change set channel voltages and does not reset the PLLs or clock
domains.
.. warning::
On Fastino gateware before v0.2 this may lead to 0 voltage being emitted
transiently.
"""
self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=1)
delay_mu(self.t_frame)
self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=0)
delay_mu(self.t_frame)
self.set_continuous(0)
delay_mu(self.t_frame)
self.stage_cic(1)
delay_mu(self.t_frame)
self.apply_cic(0xffffffff)
delay_mu(self.t_frame)
self.set_leds(0)
delay_mu(self.t_frame)
self.set_hold(0)
delay_mu(self.t_frame)
@kernel
def write(self, addr, data):
"""Write data to a Fastino register.
:param addr: Address to write to.
:param data: Data to write.
"""
rtio_output(self.channel | addr, data)
@kernel
def read(self, addr):
"""Read from Fastino register.
TODO: untested
:param addr: Address to read from.
:return: The data read.
"""
raise NotImplementedError
# rtio_output(self.channel | addr | 0x80)
# return rtio_input_data(self.channel >> 8)
@kernel
def set_dac_mu(self, dac, data):
"""Write DAC data in machine units.
:param dac: DAC channel to write to (0-31).
:param data: DAC word to write, 16-bit unsigned integer, in machine
units.
"""
self.write(dac, data)
@kernel
def set_group_mu(self, dac: TInt32, data: TList(TInt32)):
"""Write a group of DAC channels in machine units.
:param dac: First channel in DAC channel group (0-31). The ``log2_width``
LSBs must be zero.
:param data: List of DAC data pairs (2x16-bit unsigned) to write,
in machine units. Data exceeding group size is ignored.
If the list length is less than group size, the remaining
DAC channels within the group are cleared to 0 (machine units).
"""
if dac & (self.width - 1):
raise ValueError("Group index LSBs must be zero")
rtio_output_wide(self.channel | dac, data)
@portable
def voltage_to_mu(self, voltage):
"""Convert SI volts to DAC machine units.
:param voltage: Voltage in SI volts.
:return: DAC data word in machine units, 16-bit integer.
"""
data = int32(round((0x8000/10.)*voltage)) + int32(0x8000)
if data < 0 or data > 0xffff:
raise ValueError("DAC voltage out of bounds")
return data
@portable
def voltage_group_to_mu(self, voltage, data):
"""Convert SI volts to packed DAC channel group machine units.
:param voltage: List of SI volt voltages.
:param data: List of DAC channel data pairs to write to.
Half the length of `voltage`.
"""
for i in range(len(voltage)):
v = self.voltage_to_mu(voltage[i])
if i & 1:
v = data[i // 2] | (v << 16)
data[i // 2] = int32(v)
@kernel
def set_dac(self, dac, voltage):
"""Set DAC data to given voltage.
:param dac: DAC channel (0-31).
:param voltage: Desired output voltage.
"""
self.write(dac, self.voltage_to_mu(voltage))
@kernel
def set_group(self, dac, voltage):
"""Set DAC group data to given voltage.
:param dac: DAC channel (0-31).
:param voltage: Desired output voltage.
"""
data = [int32(0)] * (len(voltage) // 2)
self.voltage_group_to_mu(voltage, data)
self.set_group_mu(dac, data)
@kernel
def update(self, update):
"""Schedule channels for update.
:param update: Bit mask of channels to update (32-bit).
"""
self.write(0x20, update)
@kernel
def set_hold(self, hold):
"""Set channels to manual update.
:param hold: Bit mask of channels to hold (32-bit).
"""
self.write(0x21, hold)
@kernel
def set_cfg(self, reset=0, afe_power_down=0, dac_clr=0, clr_err=0):
"""Set configuration bits.
:param reset: Reset SPI PLL and SPI clock domain.
:param afe_power_down: Disable AFE power.
:param dac_clr: Assert all 32 DAC_CLR signals setting all DACs to
mid-scale (0 V).
:param clr_err: Clear error counters and PLL reset indicator.
This clears the sticky red error LED. Must be cleared to enable
error counting.
"""
self.write(0x22, (reset << 0) | (afe_power_down << 1) |
(dac_clr << 2) | (clr_err << 3))
@kernel
def set_leds(self, leds):
"""Set the green user-defined LEDs.
:param leds: LED status, 8-bit integer each bit corresponding to one
green LED.
"""
self.write(0x23, leds)
@kernel
def set_continuous(self, channel_mask):
"""Enable continuous DAC updates on channels regardless of new data
being submitted.
"""
self.write(0x25, channel_mask)
@kernel
def stage_cic_mu(self, rate_mantissa, rate_exponent, gain_exponent):
"""Stage machine unit CIC interpolator configuration.
"""
if rate_mantissa < 0 or rate_mantissa >= 1 << 6:
raise ValueError("rate_mantissa out of bounds")
if rate_exponent < 0 or rate_exponent >= 1 << 4:
raise ValueError("rate_exponent out of bounds")
if gain_exponent < 0 or gain_exponent >= 1 << 6:
raise ValueError("gain_exponent out of bounds")
config = rate_mantissa | (rate_exponent << 6) | (gain_exponent << 10)
self.write(0x26, config)
@kernel
def stage_cic(self, rate) -> TInt32:
"""Compute and stage interpolator configuration.
This method approximates the desired interpolation rate using a 10-bit
floating point representation (6-bit mantissa, 4-bit exponent) and
then determines an optimal interpolation gain compensation exponent
to avoid clipping. Gains for rates that are powers of two are accurately
compensated. Other rates lead to overall less than unity gain (but more
than 0.5 gain).
The overall gain including gain compensation is ``actual_rate ** order /
2 ** ceil(log2(actual_rate ** order))``
where ``order = 3``.
Returns the actual interpolation rate.
"""
if rate <= 0 or rate > 1 << 16:
raise ValueError("rate out of bounds")
rate_mantissa = rate
rate_exponent = 0
while rate_mantissa > 1 << 6:
rate_exponent += 1
rate_mantissa >>= 1
order = 3
gain = 1
for i in range(order):
gain *= rate_mantissa
gain_exponent = 0
while gain > 1 << gain_exponent:
gain_exponent += 1
gain_exponent += order*rate_exponent
assert gain_exponent <= order*16
self.stage_cic_mu(rate_mantissa - 1, rate_exponent, gain_exponent)
return rate_mantissa << rate_exponent
@kernel
def apply_cic(self, channel_mask):
"""Apply the staged interpolator configuration on the specified channels.
Each Fastino channel starting with gateware v0.2 includes a fourth order
(cubic) CIC interpolator with variable rate change and variable output
gain compensation (see :meth:`stage_cic`).
Fastino gateware before v0.2 does not include the interpolators and the
methods affecting the CICs should not be used.
Channels using non-unity interpolation rate should have
continous DAC updates enabled (see :meth:`set_continuous`) unless
their output is supposed to be constant.
This method resets and settles the affected interpolators. There will be
no output updates for the next ``order = 3`` input samples.
Affected channels will only accept one input sample per input sample
period. This method synchronizes the input sample period to the current
frame on the affected channels.
If application of new interpolator settings results in a change of the
overall gain, there will be a corresponding output step.
"""
self.write(0x27, channel_mask)

View File

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

View File

@ -1,125 +0,0 @@
from numpy import int32, int64
from artiq.language.core import *
from artiq.language.types import *
from artiq.coredevice.rtio import rtio_output, rtio_input_timestamped_data
class OutOfSyncException(Exception):
"""Raised when an incorrect number of ROI engine outputs has been
retrieved from the RTIO input FIFO."""
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"}
def __init__(self, dmgr, channel_base, res_width=12, count_shift=0,
core_device="core"):
self.core = dmgr.get(core_device)
self.channel_base = channel_base
count_width = min(31, 2*res_width + 16 - count_shift)
# This value is inserted by the gateware to mark the start of a series of
# 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):
"""
Defines the coordinates of a ROI.
The coordinates are set around the current position of the RTIO time
cursor.
The user must keep the ROI engine disabled for the duration of more
than one video frame after calling this function, as the output
generated for that video frame is undefined.
Advances the timeline by 4 coarse RTIO cycles.
"""
c = int64(self.core.ref_multiplier)
rtio_output((self.channel_base << 8) | (4*n+0), x0)
delay_mu(c)
rtio_output((self.channel_base << 8) | (4*n+1), y0)
delay_mu(c)
rtio_output((self.channel_base << 8) | (4*n+2), x1)
delay_mu(c)
rtio_output((self.channel_base << 8) | (4*n+3), y1)
delay_mu(c)
@kernel
def gate_roi(self, mask):
"""
Defines which ROI engines produce input events.
At the end of each video frame, the output from each ROI engine that
has been enabled by the mask is enqueued into the RTIO input FIFO.
This function sets the mask at the current position of the RTIO time
cursor.
Setting the mask using this function is atomic; in other words,
if the system is in the middle of processing a frame and the mask
is changed, the processing will complete using the value of the mask
that it started with.
:param mask: bitmask enabling or disabling each ROI engine.
"""
rtio_output((self.channel_base + 1) << 8, mask)
@kernel
def gate_roi_pulse(self, mask, dt):
"""Sets a temporary mask for the specified duration (in seconds), before
disabling all ROI engines."""
self.gate_roi(mask)
delay(dt)
self.gate_roi(0)
@kernel
def input_mu(self, data, timeout_mu=-1):
"""
Retrieves the accumulated values for one frame from the ROI engines.
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
input list with the outputs of the enabled ROI engines, sorted by
number.
If the number of elements in the list does not match the number of
ROI engines that produced output, an exception will be raised during
this call or the next.
If the timeout is reached before data is available, the exception
:exc:`GrabberTimeoutException` is raised.
:param timeout_mu: Timestamp at which a timeout will occur. Set to -1
(default) to disable timeout.
"""
channel = self.channel_base + 1
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)):
timestamp, roi_output = rtio_input_timestamped_data(timeout_mu, channel)
if roi_output == self.sentinel:
raise OutOfSyncException
if timestamp == -1:
raise GrabberTimeoutException(
"Timeout retrieving ROIs (attempting to read more ROIs than enabled?)")
data[i] = roi_output

View File

@ -1,20 +1,15 @@
"""
Non-realtime drivers for I2C chips on the core device.
"""
from artiq.language.core import syscall, kernel from artiq.language.core import syscall, kernel
from artiq.language.types import TBool, TInt32, TNone from artiq.language.types import TBool, TInt32, TNone
from artiq.coredevice.exceptions import I2CError from artiq.coredevice.exceptions import I2CError
@syscall(flags={"nounwind", "nowrite"}) @syscall(flags={"nowrite"})
def i2c_start(busno: TInt32) -> TNone: def i2c_init(busno: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) @syscall(flags={"nounwind", "nowrite"})
def i2c_restart(busno: TInt32) -> TNone: def i2c_start(busno: TInt32) -> TNone:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@ -33,122 +28,8 @@ def i2c_read(busno: TInt32, ack: TBool) -> TInt32:
raise NotImplementedError("syscall not simulated") raise NotImplementedError("syscall not simulated")
@syscall(flags={"nounwind", "nowrite"}) class PCA9548:
def i2c_switch_select(busno: TInt32, address: TInt32, mask: TInt32) -> TNone: """Driver for the PCA9548 I2C bus switch.
raise NotImplementedError("syscall not simulated")
@kernel
def i2c_poll(busno, busaddr):
"""Poll I2C device at address.
:param busno: I2C bus number
:param busaddr: 8-bit I2C device address (LSB=0)
:returns: True if the poll was ACKed
"""
i2c_start(busno)
ack = i2c_write(busno, busaddr)
i2c_stop(busno)
return ack
@kernel
def i2c_write_byte(busno, busaddr, data, ack=True):
"""Write one byte to a device.
:param busno: I2C bus number
:param busaddr: 8-bit I2C device address (LSB=0)
:param data: Data byte to be written
:param nack: Allow NACK
"""
i2c_start(busno)
try:
if not i2c_write(busno, busaddr):
raise I2CError("failed to ack bus address")
if not i2c_write(busno, data) and ack:
raise I2CError("failed to ack write data")
finally:
i2c_stop(busno)
@kernel
def i2c_read_byte(busno, busaddr):
"""Read one byte from a device.
:param busno: I2C bus number
:param busaddr: 8-bit I2C device address (LSB=0)
:returns: Byte read
"""
i2c_start(busno)
data = 0
try:
if not i2c_write(busno, busaddr | 1):
raise I2CError("failed to ack bus read address")
data = i2c_read(busno, ack=False)
finally:
i2c_stop(busno)
return data
@kernel
def i2c_write_many(busno, busaddr, addr, data, ack_last=True):
"""Transfer multiple bytes to a device.
:param busno: I2c bus number
:param busaddr: 8-bit I2C device address (LSB=0)
:param addr: 8-bit data address
:param data: Data bytes to be written
:param ack_last: Expect I2C ACK of the last byte written. If ``False``,
the last byte may be NACKed (e.g. EEPROM full page writes).
"""
n = len(data)
i2c_start(busno)
try:
if not i2c_write(busno, busaddr):
raise I2CError("failed to ack bus address")
if not i2c_write(busno, addr):
raise I2CError("failed to ack data address")
for i in range(n):
if not i2c_write(busno, data[i]) and (
i < n - 1 or ack_last):
raise I2CError("failed to ack write data")
finally:
i2c_stop(busno)
@kernel
def i2c_read_many(busno, busaddr, addr, data):
"""Transfer multiple bytes from a device.
:param busno: I2c bus number
:param busaddr: 8-bit I2C device address (LSB=0)
:param addr: 8-bit data address
:param data: List of integers to be filled with the data read.
One entry ber byte.
"""
m = len(data)
i2c_start(busno)
try:
if not i2c_write(busno, busaddr):
raise I2CError("failed to ack bus address")
if not i2c_write(busno, addr):
raise I2CError("failed to ack data address")
i2c_restart(busno)
if not i2c_write(busno, busaddr | 1):
raise I2CError("failed to ack bus read address")
for i in range(m):
data[i] = i2c_read(busno, ack=i < m - 1)
finally:
i2c_stop(busno)
class I2CSwitch:
"""Driver for the I2C bus switch.
PCA954X (or other) type detection is done by the CPU during I2C init.
I2C transactions are not real-time, and are performed by the CPU without
involving RTIO.
On the KC705, this chip is used for selecting the I2C buses on the two FMC On the KC705, this chip is used for selecting the I2C buses on the two FMC
connectors. HPC=1, LPC=2. connectors. HPC=1, LPC=2.
@ -160,25 +41,40 @@ class I2CSwitch:
@kernel @kernel
def set(self, channel): def set(self, channel):
"""Enable one channel. """Select one channel.
Selecting multiple channels at the same time is not supported by this
driver.
:param channel: channel number (0-7) :param channel: channel number (0-7)
""" """
i2c_switch_select(self.busno, self.address >> 1, 1 << channel) i2c_init(self.busno)
i2c_start(self.busno)
try:
if not i2c_write(self.busno, self.address):
raise I2CError("PCA9548 failed to ack address")
if not i2c_write(self.busno, 1 << channel):
raise I2CError("PCA9548 failed to ack control word")
finally:
i2c_stop(self.busno)
@kernel @kernel
def unset(self): def readback(self):
"""Disable output of the I2C switch. i2c_init(self.busno)
""" i2c_start(self.busno)
i2c_switch_select(self.busno, self.address >> 1, 0) r = 0
try:
if not i2c_write(self.busno, self.address | 1):
raise I2CError("PCA9548 failed to ack address")
r = i2c_read(self.busno, False)
finally:
i2c_stop(self.busno)
return r
class TCA6424A: class TCA6424A:
"""Driver for the TCA6424A I2C I/O expander. """Driver for the TCA6424A I2C I/O expander.
I2C transactions are not real-time, and are performed by the CPU without
involving RTIO.
On the NIST QC2 hardware, this chip is used for switching the directions On the NIST QC2 hardware, this chip is used for switching the directions
of TTL buffers.""" of TTL buffers."""
def __init__(self, dmgr, busno=0, address=0x44, core_device="core"): def __init__(self, dmgr, busno=0, address=0x44, core_device="core"):
@ -187,9 +83,20 @@ class TCA6424A:
self.address = address self.address = address
@kernel @kernel
def _write24(self, addr, value): def _write24(self, command, value):
i2c_write_many(self.busno, self.address, addr, i2c_init(self.busno)
[value >> 16, value >> 8, value]) i2c_start(self.busno)
try:
if not i2c_write(self.busno, self.address):
raise I2CError("TCA6424A failed to ack address")
if not i2c_write(self.busno, command):
raise I2CError("TCA6424A failed to ack command")
for i in range(3):
if not i2c_write(self.busno, value >> 16):
raise I2CError("TCA6424A failed to ack data")
value <<= 8
finally:
i2c_stop(self.busno)
@kernel @kernel
def set(self, outputs): def set(self, outputs):
@ -208,46 +115,3 @@ class TCA6424A:
self._write24(0x8c, 0) # set all directions to output self._write24(0x8c, 0) # set all directions to output
self._write24(0x84, outputs_le) # set levels self._write24(0x84, outputs_le) # set levels
class PCF8574A:
"""Driver for the PCF8574 I2C remote 8-bit I/O expander.
I2C transactions are not real-time, and are performed by the CPU without
involving RTIO.
"""
def __init__(self, dmgr, busno=0, address=0x7c, core_device="core"):
self.core = dmgr.get(core_device)
self.busno = busno
self.address = address
@kernel
def set(self, data):
"""Drive data on the quasi-bidirectional pins.
:param data: Pin data. High bits are weakly driven high
(and thus inputs), low bits are strongly driven low.
"""
i2c_start(self.busno)
try:
if not i2c_write(self.busno, self.address):
raise I2CError("PCF8574A failed to ack address")
if not i2c_write(self.busno, data):
raise I2CError("PCF8574A failed to ack data")
finally:
i2c_stop(self.busno)
@kernel
def get(self):
"""Retrieve quasi-bidirectional pin input data.
:return: Pin data
"""
i2c_start(self.busno)
ret = 0
try:
if not i2c_write(self.busno, self.address | 1):
raise I2CError("PCF8574A failed to ack address")
ret = i2c_read(self.busno, False)
finally:
i2c_stop(self.busno)
return ret

View File

@ -1,38 +0,0 @@
from os import path
import json
from jsonschema import Draft7Validator, validators
def extend_with_default(validator_class):
validate_properties = validator_class.VALIDATORS["properties"]
def set_defaults(validator, properties, instance, schema):
for property, subschema in properties.items():
if "default" in subschema:
instance.setdefault(property, subschema["default"])
for error in validate_properties(
validator, properties, instance, schema,
):
yield error
return validators.extend(
validator_class, {"properties" : set_defaults},
)
schema_path = path.join(path.dirname(__file__), "coredevice_generic.schema.json")
with open(schema_path, "r") as f:
schema = json.load(f)
validator = extend_with_default(Draft7Validator)(schema)
def load(description_path):
with open(description_path, "r") as f:
result = json.load(f)
global validator
validator.validate(result)
if result["base"] != "use_drtio_role":
result["drtio_role"] = result["base"]
return result

View File

@ -1,77 +0,0 @@
from numpy import int32
from artiq.experiment import *
from artiq.coredevice.i2c import i2c_write_many, i2c_read_many, i2c_poll
port_mapping = {
"EEM0": 7,
"EEM1": 5,
"EEM2": 4,
"EEM3": 3,
"EEM4": 2,
"EEM5": 1,
"EEM6": 0,
"EEM7": 6,
"EEM8": 12,
"EEM9": 13,
"EEM10": 15,
"EEM11": 14,
"SFP0": 8,
"SFP1": 9,
"SFP2": 10,
"LOC0": 11,
}
class KasliEEPROM:
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 = address # i2c 8 bit
@kernel
def select(self):
mask = 1 << self.port
if self.port < 8:
self.sw0.set(self.port)
self.sw1.unset()
else:
self.sw0.unset()
self.sw1.set(self.port - 8)
@kernel
def deselect(self):
self.sw0.unset()
self.sw1.unset()
@kernel
def write_i32(self, addr, value):
self.select()
try:
data = [0]*4
for i in range(4):
data[i] = (value >> 24) & 0xff
value <<= 8
i2c_write_many(self.busno, self.address, addr, data)
i2c_poll(self.busno, self.address)
finally:
self.deselect()
@kernel
def read_i32(self, addr):
self.select()
try:
data = [0]*4
i2c_read_many(self.busno, self.address, addr, data)
value = int32(0)
for i in range(4):
value <<= 8
value |= data[i]
finally:
self.deselect()
return value

View File

@ -1,169 +0,0 @@
"""RTIO driver for Mirny (4-channel GHz PLLs)
"""
from artiq.language.core import kernel, delay, portable
from artiq.language.units import us
from numpy import int32
from artiq.coredevice import spi2 as spi
SPI_CONFIG = (
0 * spi.SPI_OFFLINE
| 0 * spi.SPI_END
| 0 * spi.SPI_INPUT
| 1 * spi.SPI_CS_POLARITY
| 0 * spi.SPI_CLK_POLARITY
| 0 * spi.SPI_CLK_PHASE
| 0 * spi.SPI_LSB_FIRST
| 0 * spi.SPI_HALF_DUPLEX
)
# SPI clock write and read dividers
SPIT_WR = 4
SPIT_RD = 16
SPI_CS = 1
WE = 1 << 24
# supported CPLD code version
PROTO_REV_MATCH = 0x0
class Mirny:
"""
Mirny PLL-based RF generator.
:param spi_device: SPI bus device
:param refclk: Reference clock (SMA, MMCX or on-board 100 MHz oscillator)
frequency in Hz
:param clk_sel: Reference clock selection.
Valid options are: "XO" - onboard crystal oscillator;
"SMA" - front-panel SMA connector; "MMCX" - internal MMCX connector.
Passing an integer writes it as ``clk_sel`` in the CPLD's register 1.
The effect depends on the hardware revision.
:param core_device: Core device name (default: "core")
"""
kernel_invariants = {"bus", "core", "refclk", "clk_sel_hw_rev"}
def __init__(self, dmgr, spi_device, refclk=100e6, clk_sel="XO", core_device="core"):
self.core = dmgr.get(core_device)
self.bus = dmgr.get(spi_device)
# reference clock frequency
self.refclk = refclk
if not (10 <= self.refclk / 1e6 <= 600):
raise ValueError("Invalid refclk")
# reference clock selection
try:
self.clk_sel_hw_rev = {
# clk source: [reserved, reserved, v1.1, v1.0]
"xo": [-1, -1, 0, 0],
"mmcx": [-1, -1, 3, 2],
"sma": [-1, -1, 2, 3],
}[clk_sel.lower()]
except AttributeError: # not a string, fallback to int
if clk_sel & 0x3 != clk_sel:
raise ValueError("Invalid clk_sel") from None
self.clk_sel_hw_rev = [clk_sel] * 4
except KeyError:
raise ValueError("Invalid clk_sel") from None
self.clk_sel = -1
# board hardware revision
self.hw_rev = 0 # v1.0: 3, v1.1: 2
# TODO: support clk_div on v1.0 boards
@kernel
def read_reg(self, addr):
"""Read a register."""
self.bus.set_config_mu(
SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 24, SPIT_RD, SPI_CS
)
self.bus.write((addr << 25))
return self.bus.read() & int32(0xFFFF)
@kernel
def write_reg(self, addr, data):
"""Write a register."""
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, SPIT_WR, SPI_CS)
self.bus.write((addr << 25) | WE | ((data & 0xFFFF) << 8))
@kernel
def init(self, blind=False):
"""
Initialize and detect Mirny.
Select the clock source based the board's hardware revision.
Raise :exc:`ValueError` if the board's hardware revision is not supported.
:param blind: Verify presence and protocol compatibility. Raise :exc:`ValueError` on failure.
"""
reg0 = self.read_reg(0)
self.hw_rev = reg0 & 0x3
if not blind:
if (reg0 >> 2) & 0x3 != PROTO_REV_MATCH:
raise ValueError("Mirny PROTO_REV mismatch")
delay(100 * us) # slack
# select clock source
self.clk_sel = self.clk_sel_hw_rev[self.hw_rev]
if self.clk_sel < 0:
raise ValueError("Hardware revision not supported")
self.write_reg(1, (self.clk_sel << 4))
delay(1000 * us)
@portable(flags={"fast-math"})
def att_to_mu(self, att):
"""Convert an attenuation setting in dB to machine units.
:param att: Attenuation setting in dB.
:return: Digital attenuation setting.
"""
code = int32(255) - int32(round(att * 8))
if code < 0 or code > 255:
raise ValueError("Invalid Mirny attenuation!")
return code
@kernel
def set_att_mu(self, channel, att):
"""Set digital step attenuator in machine units.
:param att: Attenuation setting, 8-bit digital.
"""
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 16, SPIT_WR, SPI_CS)
self.bus.write(((channel | 8) << 25) | (att << 16))
@kernel
def set_att(self, channel, att):
"""Set digital step attenuator in SI units.
This method will write the attenuator settings of the selected channel.
See also :meth:`Mirny.set_att_mu`.
:param channel: Attenuator channel (0-3).
:param att: Attenuation setting in dB. Higher value is more
attenuation. Minimum attenuation is 0*dB, maximum attenuation is
31.5*dB.
"""
self.set_att_mu(channel, self.att_to_mu(att))
@kernel
def write_ext(self, addr, length, data, ext_div=SPIT_WR):
"""Perform SPI write to a prefixed address."""
self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_WR, SPI_CS)
self.bus.write(addr << 25)
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, length, ext_div, SPI_CS)
if length < 32:
data <<= 32 - length
self.bus.write(data)

View File

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

File diff suppressed because it is too large Load Diff

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