forked from M-Labs/artiq
1
0
Fork 0

Compare commits

...

521 Commits

Author SHA1 Message Date
Simon Renblad 76fba538b1 artiq_ddb_template: fixed missing separator 2023-12-18 13:23:39 +08:00
Sebastien Bourdeauducq 8dd8cfa6b0 master: implement devarg_override 2023-12-18 12:11:40 +08:00
Sebastien Bourdeauducq 5df0721811 dashboard,client: add device argument overrides to expid 2023-12-17 19:43:41 +08:00
Sebastien Bourdeauducq 6326051052 flake: forward cmdline arguments in devshell wrappers 2023-12-17 19:42:56 +08:00
Sebastien Bourdeauducq 44a95b5dda dashboard: add repository revision clear button 2023-12-17 16:37:02 +08:00
Sebastien Bourdeauducq 645b9b8c5f flake: add executable wrappers for frontends to devshell 2023-12-17 13:41:49 +08:00
Sebastien Bourdeauducq 858f0479ba aqctl_coreanalyzer_proxy: permissions and shebang 2023-12-17 13:27:38 +08:00
Sebastien Bourdeauducq 133b26b6ce flake: add ARTIQ sources to PYTHONPATH in devshell 2023-12-17 13:05:16 +08:00
Sebastien Bourdeauducq d96213dbbc flake: update dependencies 2023-12-17 12:55:36 +08:00
Sebastien Bourdeauducq 413d33c3d1 core: document analyzer proxy options 2023-12-13 14:29:33 +08:00
Sebastien Bourdeauducq c2b53ecb43 core: add option to trigger analyzer proxy at run end 2023-12-13 14:27:48 +08:00
Sebastien Bourdeauducq ede0b37c6e devices: introduce notify_run_end API 2023-12-13 14:27:04 +08:00
Sebastien Bourdeauducq 795c4372fa DeviceManager: fix close exception error message 2023-12-13 14:06:53 +08:00
Sebastien Bourdeauducq 402a5d3376 core: connect lazily to analyzer proxy
Otherwise artiq_compile and other uses of Core that does not access hardware/network may fail.
2023-12-13 13:46:47 +08:00
Sebastien Bourdeauducq 85850ad9e8 wavesynth: remove 2023-12-13 13:36:21 +08:00
Sebastien Bourdeauducq 7a863b4f5e core: add trigger_analyzer_proxy API 2023-12-13 13:08:54 +08:00
Sebastien Bourdeauducq a26cee6ca7 coreanalyzer_proxy: cleanups/renames 2023-12-13 13:07:35 +08:00
Sebastien Bourdeauducq be08862606 logo: text to path 2023-12-08 19:34:47 +08:00
Sebastien Bourdeauducq 05a9422e67 aqctl_coreanalyzer_proxy: cleanup 2023-12-08 18:56:10 +08:00
Simon Renblad b09a39c82e
add aqctl_coreanalyzer_proxy 2023-12-08 18:55:07 +08:00
mwojcik 49267671f9 core: fix precompile 2023-12-04 12:10:11 +08:00
Sebastien Bourdeauducq 8ca75a3fb9 firmware: deal with rust nonsense
Fixes
"error: edition 2021 is unstable and only available with -Z unstable-options.
error: could not compile `alloc`"
2023-12-03 11:20:18 +08:00
Florian Agbuya 8381b34a79 flake: add new booktabs dependency for artiq-manual-pdf 2023-12-03 11:18:59 +08:00
Sebastien Bourdeauducq d458fc27bf switch to new nixpkgs release 2023-12-03 11:18:25 +08:00
mwojcik 9f4b8db2de repeater: fix setting tsc 2023-12-01 16:43:48 +08:00
Florian Agbuya 1108cebd75 flake: fix ncurses on vivado
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2023-11-28 17:36:36 +08:00
Florian Agbuya cf7cbd0c3b flake: update nixpkgs
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2023-11-28 17:36:36 +08:00
mwojcik 1a28069aa2 support for pre-compiling subkernels 2023-11-23 16:49:02 +08:00
Sebastien Bourdeauducq 56418e342e take into account VERSIONEER_REV in artiq._version.get_rev 2023-11-22 20:51:02 +08:00
Sebastien Bourdeauducq 77c6553725 always provide artiq._version.get_rev 2023-11-14 14:14:47 +08:00
Sebastien Bourdeauducq e81e8f28cf gateware: merge kasli_generic into kasli. Closes #2279 2023-11-14 14:01:17 +08:00
mwojcik de10e584f6 support .tar flashed idle/startup kernels 2023-11-13 18:14:35 +08:00
Florian Agbuya 875666f3ec doc: add section on new nix flakes config (closes #2232)
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2023-11-10 16:47:56 +08:00
Sebastien Bourdeauducq 3ad3fac828 update ARTIQ-8 release notes 2023-11-08 11:17:17 +08:00
Simon Renblad 49afa116b3 RELEASE_NOTES: artiq_ddb_template needs gateware 2023-11-08 10:51:39 +08:00
Simon Renblad 363afb5fc9 artiq_ddb_template: add support for user LEDs
Add support for additional user LEDs.
2023-11-08 10:51:39 +08:00
Simon Renblad e7af219505 kasli_generic: add support for user LEDs
Add additional LED RTIO devices.
2023-11-08 10:51:39 +08:00
linuswck ec2b86b08d kc705: fix gtx clock path durnig init 2023-11-07 18:36:48 +08:00
linuswck 8f7d138dbd gtx: Always enable IBUFDS_GTE2, add clk_path_ready
- Set clk_path_ready to High to start Initialization of GTP TX and RX
2023-11-07 18:36:48 +08:00
Sebastien Bourdeauducq bbe6ff8cac flake: update dependencies 2023-11-07 18:36:11 +08:00
Sebastien Bourdeauducq c0a6252e77 afws_client: improve compatibility with older versions of prettytable. Closes #2264 2023-11-07 14:06:31 +08:00
mwojcik 6640bf0e82 drtioaux/subkernel/ddma: introduce proper errors, more robust 2023-11-07 13:42:04 +08:00
mwojcik b3c0d084d4 drtio: better control state of bigger payloads 2023-11-07 13:42:04 +08:00
linuswck bb0b8a6c00 kasli: Correct the GTP TX clock path during init
- TXOUT must be fed back into TXUSRCLK during initialization
- Now, MMCM Clock Input is switched before GTP TX Init is started instead of after GTP TX Init is done
- Reset in Sys Clock domain is kept asserted when clock is switched and GTP TX Init is NOT done
2023-11-07 13:40:32 +08:00
Sebastien Bourdeauducq ce80bf5717 flake: update dependencies 2023-11-07 13:40:17 +08:00
Florian Agbuya 378dd0e5ca flake: fix and upgrade wavedrom (closes #2266)
Signed-off-by: Florian Agbuya <fa@m-labs.ph>
2023-10-30 08:09:00 +01:00
jfniedermeyer 9c68451cae Add hotkeys to organize experiments in dashboard
Signed-off-by: jfniedermeyer <justin.niedermeyer@colorado.edu>
2023-10-27 21:47:30 +02:00
linuswck 93c9d8bcdf artiq_ddb_template:set default Shuttler drtio_dest
- remove default Shuttler "drtio_destination" value in jsonschema
- set the default Shuttler "drtio_destination" value according to
    board "target" and "hw_rev"
2023-10-27 21:46:02 +02:00
mwojcik e480bbe8d8 artiq_ddb_template: move satellite_cpu_target to core 2023-10-27 21:45:12 +02:00
mwojcik b168f0bb4b subkernel: separate tags and data 2023-10-17 12:18:03 +02:00
Sebastien Bourdeauducq 6705c9fbfb flake: update dependencies 2023-10-17 15:37:06 +08:00
mwojcik 5f445f6b92 ad53xx: fix `load()` references in documentation 2023-10-16 13:54:38 +08:00
occheung 363f7327f1 io_expander: initialize before service 2023-10-15 07:45:20 +08:00
Sebastien Bourdeauducq f7abc156cb flake: update dependencies 2023-10-11 16:41:34 +08:00
linuswck de41bd6655 eem_7series: pass through kwargs for shuttler 2023-10-11 12:15:06 +08:00
Simon Renblad 96941d7c04 big_number: fix metadata scaling, add unit label 2023-10-09 15:35:14 +08:00
mwojcik f3c79e71e1 firmware: merge runtime and satman linker scripts 2023-10-09 15:33:29 +08:00
Simon Renblad 333b81f789 set_argument_value warning in browser 2023-10-09 10:38:17 +08:00
Sebastien Bourdeauducq d070826911 flake: update dependencies 2023-10-09 10:13:58 +08:00
Sebastien Bourdeauducq 9c90f923d2 test: check return value of subprocesses in test_compile 2023-10-09 10:07:04 +08:00
Sebastien Bourdeauducq e23e4d39d7 artiq_compile: ignore subkernel_arg_types 2023-10-09 10:03:43 +08:00
David Nadlinger 08eea09d44 compiler: Catch escaping numpy.{array, full, transpose}() results
Function calls in general can still be used to hide escaping
allocations from the compiler (issue #1497), but these calls in
particular always allocate, so we can easily and accurately handle
them.
2023-10-09 09:00:26 +08:00
mwojcik 7ab52af603 docs: subkernel support 2023-10-08 17:12:06 +08:00
mwojcik 973fd88b27 core: compile and upload subkernels 2023-10-08 17:11:51 +08:00
mwojcik 8d7194941e tests: add lit tests for subkernels 2023-10-08 17:11:51 +08:00
mwojcik 0a750c77e8 compiler: support subkernels 2023-10-08 17:11:51 +08:00
mwojcik 1a0fc317df satman: support subkernels 2023-10-08 17:11:32 +08:00
mwojcik e05be2f8e4 runtime: support subkernels 2023-10-08 17:11:32 +08:00
mwojcik 6f4b8c641e drtioaux_proto: use better payload names 2023-10-08 17:11:32 +08:00
mwojcik b42816582e ksupport: support subkernels 2023-10-08 17:11:32 +08:00
Hartmann Michael (IFAG PSS SIS SCE QSE) 76f1318bc0 doc: Extend documentation
Extend the paragraph "Pitfalls" in the documentation of "Compiler" by
problems caused by returning values from the stack.
2023-10-07 07:20:33 +08:00
Sebastien Bourdeauducq 0131a8bef2 shuttler: cleanup 2023-10-06 14:55:51 +08:00
mwojcik e63e2a2897 artiq_ddb_template: better satellite formatting 2023-10-06 13:01:57 +08:00
Simon Renblad 47fc640f75 applets: rename 'ctl' attribute to 'req' 2023-10-05 12:32:01 +08:00
Simon Renblad bb7caacb5f RELEASE_NOTES: applet API extensions 2023-10-05 12:32:01 +08:00
Simon Renblad da9f7cb58a applet extensions documentation 2023-10-05 12:32:01 +08:00
occheung 43926574da shuttler: remove sdm constants 2023-10-05 07:40:00 +08:00
Simon Renblad 4f3e58db52 gui.applets: add EntryArea 2023-10-04 15:35:52 +08:00
Simon Renblad 13271cea64
gui: remove copies of _WheelFilter and refactor with parameter 2023-10-04 13:35:01 +08:00
occheung 0e8fa8933f shuttler: init sigma-delta modulator 2023-09-30 11:51:43 +08:00
David Nadlinger 2eb89cb168 dashboard: Fix occasional "unexpected action" applet errors on startup
This turned out to be a race between the dashboard's dataset db
subscriber being initialised and the applet "embed" request, with
artiq.applet.simple not being able to handle the unexpected "mod"
message. We were only handling the other ordering outcome of this
race before.
2023-09-30 00:27:25 +01:00
occheung a772dee1cc shuttler: change 0th order accumulator width
It now truncates the LSBs instead of the MSBs.
2023-09-29 10:09:39 +08:00
Simon Renblad bafb85a274 custom_applet: change constructor, data_changed signatures 2023-09-28 10:35:14 +01:00
mwojcik 0e8aa33979 core: separate master target from compilation 2023-09-28 10:41:55 +08:00
mwojcik fcf6c90ba2 ddb_template: support different satellite targets 2023-09-28 10:41:55 +08:00
linuswck 0c1b572872 Shuttler: Correct spelling and grammar in docs 2023-09-27 17:29:16 +08:00
linuswck ab0d4c41c3 Shuttler: pdq, efc->shuttler pdq_words->coef_words 2023-09-27 17:29:16 +08:00
Jonathan Coates 6eb81494c5 Allow using Python types in type annotations
This maps basic Python types (float, str, bool, np.int32, np.int64) as well as
some generics (list, tuple) to ARTIQ's own type instances.

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

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

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

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

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

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

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

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

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

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

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

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

* replace clk100 clk ctrl with clk200 async reset

* fix comment (singular/plural)

* oob reset: invoke platform commands locally

* cleanup

* oob reset: add async reset import

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

fix spurious };

unwrap init in runtime and return err instead of panic

propagate error

del unnecessary use

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

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

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

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

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

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

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

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

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

3
.gitignore vendored
View File

@ -29,6 +29,7 @@ __pycache__/
/repository/ /repository/
/results /results
/last_rid.pyon /last_rid.pyon
/dataset_db.pyon /dataset_db.mdb
/dataset_db.mdb-lock
/device_db*.py /device_db*.py
/test* /test*

View File

@ -26,7 +26,6 @@ report if possible:
* Operating System * Operating System
* ARTIQ version (with recent versions of ARTIQ, run ``artiq_client --version``) * ARTIQ version (with recent versions of ARTIQ, run ``artiq_client --version``)
* Version of the gateware and runtime loaded in the core device (in the output of ``artiq_coremgmt -D .... log``) * Version of the gateware and runtime loaded in the core device (in the output of ``artiq_coremgmt -D .... log``)
* If using Conda, output of `conda list`
* Hardware involved * Hardware involved

View File

@ -13,7 +13,7 @@ ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardwar
ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers. ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers.
Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and of a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort. Several different configurations of a `FPGA evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-k7-kc705-g.html>`_ and of a `Zynq evaluation kit <https://www.xilinx.com/products/boards-and-kits/ek-z7-zc706-g.html>`_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort.
ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and Conda packages (for Windows and Linux). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions. ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual <https://m-labs.hk/experiment-control/resources/>`_ for installation instructions.
Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration. 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>`_. Like any open source software ARTIQ can equally be built and installed directly from `source <https://github.com/m-labs/artiq>`_.
@ -29,7 +29,7 @@ Website: https://m-labs.hk/artiq
License License
======= =======
Copyright (C) 2014-2022 M-Labs Limited. Copyright (C) 2014-2023 M-Labs Limited.
ARTIQ is free software: you can redistribute it and/or modify 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 it under the terms of the GNU Lesser General Public License as published by

View File

@ -3,6 +3,99 @@
Release notes Release notes
============= =============
ARTIQ-8 (Unreleased)
--------------------
Highlights:
* New hardware support:
- Support for Shuttler, a 16-channel 125MSPS DAC card intended for ion transport.
Waveform generator and user API are similar to the NIST PDQ.
- Implemented Phaser-servo. This requires recent gateware on Phaser.
- Almazny v1.2 with finer RF switch control.
- Metlino and Sayma support has been dropped due to complications with synchronous RTIO clocking.
- More user LEDs are exposed to RTIO on Kasli.
- Implemented Phaser-MIQRO support. This requires the proprietary Phaser MIQRO gateware
variant from QUARTIQ.
- Sampler: fixed ADC MU to Volt conversion factor for Sampler v2.2+.
For earlier hardware versions, specify the hardware version in the device
database file (e.g. ``"hw_rev": "v2.1"``) to use the correct conversion factor.
* Support for distributed DMA, where DMA is run directly on satellites for corresponding
RTIO events, increasing bandwidth in scenarios with heavy satellite usage.
* Support for subkernels, where kernels are run on satellite device CPUs to offload some
of the processing and RTIO operations.
* CPU (on softcore platforms) and AXI bus (on Zynq) are now clocked synchronously with the RTIO
clock, to facilitate implementation of local processing on DRTIO satellites, and to slightly
reduce RTIO latency.
* Support for DRTIO-over-EEM, used with Shuttler.
* Added channel names to RTIO error messages.
* GUI:
- Implemented Applet Request Interfaces which allow applets to modify datasets and set the
current values of widgets in the dashboard's experiment windows.
- Implemented a new EntryArea widget which allows argument entry widgets to be used in applets.
- The "Close all applets" command (shortcut: Ctrl-Alt-W) now ignores docked applets,
making it a convenient way to clean up after exploratory work without destroying a
carefully arranged default workspace.
- Hotkeys now organize experiment windows in the order they were last interacted with:
+ CTRL+SHIFT+T tiles experiment windows
+ CTRL+SHIFT+C cascades experiment windows
* Persistent datasets are now stored in a LMDB database for improved performance.
* Python's built-in types (such as ``float``, or ``List[...]``) can now be used in type annotations on
kernel functions.
* Full Python 3.10 support.
* MSYS2 packaging for Windows, which replaces Conda. Conda packages are still available to
support legacy installations, but may be removed in a future release.
Breaking changes:
* ``SimpleApplet`` now calls widget constructors with an additional ``ctl`` parameter for control
operations, which includes dataset operations. It can be ignored if not needed. For an example usage,
refer to the ``big_number.py`` applet.
* ``SimpleApplet`` and ``TitleApplet`` now call ``data_changed`` with additional parameters. Derived applets
should change the function signature as below:
::
# SimpleApplet
def data_changed(self, value, metadata, persist, mods)
# SimpleApplet (old version)
def data_changed(self, data, mods)
# TitleApplet
def data_changed(self, value, metadata, persist, mods, title)
# TitleApplet (old version)
def data_changed(self, data, mods, title)
Accesses to the data argument should be replaced as below:
::
data[key][0] ==> persist[key]
data[key][1] ==> value[key]
* The ``ndecimals`` parameter in ``NumberValue`` and ``Scannable`` has been renamed to ``precision``.
Parameters after and including ``scale`` in both constructors are now keyword-only.
Refer to the updated ``no_hardware/arguments_demo.py`` example for current usage.
* Almazny v1.2 is incompatible with the legacy versions and is the default.
To use legacy versions, specify ``almazny_hw_rev`` in the JSON description.
* kasli_generic.py has been merged into kasli.py, and the demonstration designs without JSON descriptions
have been removed. The base classes remain present in kasli.py to support third-party flows without
JSON descriptions.
* Legacy PYON databases should be converted to LMDB with the script below:
::
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 ARTIQ-7
------- -------
@ -17,7 +110,7 @@ Highlights:
- Almazny mezzanine board for Mirny - Almazny mezzanine board for Mirny
- Phaser: improved documentation, exposed the DAC coarse mixer and ``sif_sync``, exposed upconverter calibration - 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 and enabling/disabling of upconverter LO & RF outputs, added helpers to align Phaser updates to the
RTIO timeline (``get_next_frame_mu()`` RTIO timeline (``get_next_frame_mu()``).
- Urukul: ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912. - 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). * Softcore targets now use the RISC-V architecture (VexRiscv) instead of OR1K (mor1kx).
* Gateware FPU is supported on KC705 and Kasli 2.0. * Gateware FPU is supported on KC705 and Kasli 2.0.
@ -67,9 +160,9 @@ Breaking changes:
generated for some configurations. generated for some configurations.
* Phaser: fixed coarse mixer frequency configuration * Phaser: fixed coarse mixer frequency configuration
* Mirny: Added extra delays in ``ADF5356.sync()``. This avoids the need of an extra delay before * Mirny: Added extra delays in ``ADF5356.sync()``. This avoids the need of an extra delay before
calling `ADF5356.init()`. calling ``ADF5356.init()``.
* The deprecated ``set_dataset(..., save=...)`` is no longer supported. * The deprecated ``set_dataset(..., save=...)`` is no longer supported.
* The ``PCA9548`` I2C switch class was renamed to ``I2CSwitch``, to accomodate support for PCA9547, * The ``PCA9548`` I2C switch class was renamed to ``I2CSwitch``, to accommodate support for PCA9547,
and possibly other switches in future. Readback has been removed, and now only one channel per and possibly other switches in future. Readback has been removed, and now only one channel per
switch is supported. switch is supported.

View File

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

View File

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

View File

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

View File

@ -7,13 +7,13 @@ from artiq.applets.simple import SimpleApplet
class Image(pyqtgraph.ImageView): class Image(pyqtgraph.ImageView):
def __init__(self, args): def __init__(self, args, req):
pyqtgraph.ImageView.__init__(self) pyqtgraph.ImageView.__init__(self)
self.args = args self.args = args
def data_changed(self, data, mods): def data_changed(self, value, metadata, persist, mods):
try: try:
img = data[self.args.img][1] img = value[self.args.img]
except KeyError: except KeyError:
return return
self.setImage(img) self.setImage(img)

View File

@ -8,20 +8,20 @@ from artiq.applets.simple import TitleApplet
class HistogramPlot(pyqtgraph.PlotWidget): class HistogramPlot(pyqtgraph.PlotWidget):
def __init__(self, args): def __init__(self, args, req):
pyqtgraph.PlotWidget.__init__(self) pyqtgraph.PlotWidget.__init__(self)
self.args = args self.args = args
self.timer = QTimer() self.timer = QTimer()
self.timer.setSingleShot(True) self.timer.setSingleShot(True)
self.timer.timeout.connect(self.length_warning) self.timer.timeout.connect(self.length_warning)
def data_changed(self, data, mods, title): def data_changed(self, value, metadata, persist, mods, title):
try: try:
y = data[self.args.y][1] y = value[self.args.y]
if self.args.x is None: if self.args.x is None:
x = None x = None
else: else:
x = data[self.args.x][1] x = value[self.args.x]
except KeyError: except KeyError:
return return
if x is None: if x is None:

View File

@ -9,7 +9,7 @@ from artiq.applets.simple import TitleApplet
class XYPlot(pyqtgraph.PlotWidget): class XYPlot(pyqtgraph.PlotWidget):
def __init__(self, args): def __init__(self, args, req):
pyqtgraph.PlotWidget.__init__(self) pyqtgraph.PlotWidget.__init__(self)
self.args = args self.args = args
self.timer = QTimer() self.timer = QTimer()
@ -19,16 +19,16 @@ class XYPlot(pyqtgraph.PlotWidget):
'Error bars': False, 'Error bars': False,
'Fit values': False} 'Fit values': False}
def data_changed(self, data, mods, title): def data_changed(self, value, metadata, persist, mods, title):
try: try:
y = data[self.args.y][1] y = value[self.args.y]
except KeyError: except KeyError:
return return
x = data.get(self.args.x, (False, None))[1] x = value.get(self.args.x, (False, None))
if x is None: if x is None:
x = np.arange(len(y)) x = np.arange(len(y))
error = data.get(self.args.error, (False, None))[1] error = value.get(self.args.error, (False, None))
fit = data.get(self.args.fit, (False, None))[1] fit = value.get(self.args.fit, (False, None))
if not len(y) or len(y) != len(x): if not len(y) or len(y) != len(x):
self.mismatch['X values'] = True self.mismatch['X values'] = True

View File

@ -22,7 +22,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): def __init__(self, args, req):
QtWidgets.QSplitter.__init__(self) QtWidgets.QSplitter.__init__(self)
self.resize(1000, 600) self.resize(1000, 600)
self.setWindowTitle("XY/Histogram") self.setWindowTitle("XY/Histogram")
@ -124,11 +124,11 @@ class XYHistPlot(QtWidgets.QSplitter):
return False return False
return True return True
def data_changed(self, data, mods): def data_changed(self, value, metadata, persist, mods):
try: try:
xs = data[self.args.xs][1] xs = value[self.args.xs]
histogram_bins = data[self.args.histogram_bins][1] histogram_bins = value[self.args.histogram_bins]
histograms_counts = data[self.args.histograms_counts][1] histograms_counts = value[self.args.histograms_counts]
except KeyError: except KeyError:
return return
if len(xs) != histograms_counts.shape[0]: if len(xs) != histograms_counts.shape[0]:

View File

@ -6,18 +6,18 @@ from artiq.applets.simple import SimpleApplet
class ProgressWidget(QtWidgets.QProgressBar): class ProgressWidget(QtWidgets.QProgressBar):
def __init__(self, args): def __init__(self, args, req):
QtWidgets.QProgressBar.__init__(self) QtWidgets.QProgressBar.__init__(self)
self.setMinimum(args.min) self.setMinimum(args.min)
self.setMaximum(args.max) self.setMaximum(args.max)
self.dataset_value = args.value self.dataset_value = args.value
def data_changed(self, data, mods): def data_changed(self, value, metadata, persist, mods):
try: try:
value = round(data[self.dataset_value][1]) val = round(value[self.dataset_value])
except (KeyError, ValueError, TypeError): except (KeyError, ValueError, TypeError):
value = 0 val = 0
self.setValue(value) self.setValue(val)

View File

@ -7,13 +7,112 @@ import string
from qasync import QEventLoop, QtWidgets, QtCore from qasync import QEventLoop, QtWidgets, QtCore
from sipyco.sync_struct import Subscriber, process_mod from sipyco.sync_struct import Subscriber, process_mod
from sipyco.pc_rpc import AsyncioClient as RPCClient
from sipyco import pyon from sipyco import pyon
from sipyco.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 ``artiq.language.environment.set_dataset``.
"""
raise NotImplementedError
def mutate_dataset(self, key, index, value):
"""
Mutate a dataset.
See documentation of ``artiq.language.environment.mutate_dataset``.
"""
raise NotImplementedError
def append_to_dataset(self, key, value):
"""
Append to a dataset.
See documentation of ``artiq.language.environment.append_to_dataset``.
"""
raise NotImplementedError
def set_argument_value(self, expurl, name, 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 name: Name of the argument in the experiment.
:param value: Object representing the new temporary value of the argument. For ``Scannable`` arguments, this parameter
should be a ``ScanObject``. The type of the ``ScanObject`` will be set as the selected type when this function is called.
"""
raise NotImplementedError
class AppletRequestIPC(_AppletRequestInterface):
def __init__(self, ipc):
self.ipc = ipc
def set_dataset(self, key, value, unit=None, scale=None, precision=None, persist=None):
metadata = {}
if unit is not None:
metadata["unit"] = unit
if scale is not None:
metadata["scale"] = scale
if precision is not None:
metadata["precision"] = precision
self.ipc.set_dataset(key, value, metadata, persist)
def mutate_dataset(self, key, index, value):
mod = {"action": "setitem", "path": [key, 1], "key": index, "value": value}
self.ipc.update_dataset(mod)
def append_to_dataset(self, key, value):
mod = {"action": "append", "path": [key, 1], "x": value}
self.ipc.update_dataset(mod)
def set_argument_value(self, expurl, name, value):
if isinstance(value, ScanObject):
value = value.describe()
self.ipc.set_argument_value(expurl, name, 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
@ -64,13 +163,30 @@ 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=[]): def subscribe(self, datasets, init_cb, mod_cb, dataset_prefixes=[], *, loop):
self.write_pyon({"action": "subscribe", self.write_pyon({"action": "subscribe",
"datasets": datasets, "datasets": datasets,
"dataset_prefixes": dataset_prefixes}) "dataset_prefixes": dataset_prefixes})
self.init_cb = init_cb self.init_cb = init_cb
self.mod_cb = mod_cb self.mod_cb = mod_cb
asyncio.ensure_future(self.listen()) self.listen_task = loop.create_task(self.listen())
def set_dataset(self, key, value, metadata, persist=None):
self.write_pyon({"action": "set_dataset",
"key": key,
"value": value,
"metadata": metadata,
"persist": persist})
def update_dataset(self, mod):
self.write_pyon({"action": "update_dataset",
"mod": mod})
def set_argument_value(self, expurl, name, value):
self.write_pyon({"action": "set_argument_value",
"expurl": expurl,
"name": name,
"value": value})
class SimpleApplet: class SimpleApplet:
@ -92,8 +208,11 @@ class SimpleApplet:
"for dataset notifications " "for dataset notifications "
"(ignored in embedded mode)") "(ignored in embedded mode)")
group.add_argument( group.add_argument(
"--port", default=3250, type=int, "--port-notify", default=3250, type=int,
help="TCP port to connect to") help="TCP port to connect to for notifications (ignored in embedded mode)")
group.add_argument(
"--port-control", default=3251, type=int,
help="TCP port to connect to for control (ignored in embedded mode)")
self._arggroup_datasets = self.argparser.add_argument_group("datasets") self._arggroup_datasets = self.argparser.add_argument_group("datasets")
@ -132,8 +251,21 @@ class SimpleApplet:
if self.embed is not None: if self.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, "master_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.main_widget = self.main_widget_class(self.args, self.req)
if self.embed is not None: if self.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":
@ -189,7 +321,12 @@ class SimpleApplet:
return False return False
def emit_data_changed(self, data, mod_buffer): def emit_data_changed(self, data, mod_buffer):
self.main_widget.data_changed(data, mod_buffer) persist = dict()
value = dict()
metadata = dict()
for k, d in data.items():
persist[k], value[k], metadata[k] = d
self.main_widget.data_changed(value, metadata, persist, mod_buffer)
def flush_mod_buffer(self): def flush_mod_buffer(self):
self.emit_data_changed(self.data, self.mod_buffer) self.emit_data_changed(self.data, self.mod_buffer)
@ -204,8 +341,8 @@ class SimpleApplet:
self.mod_buffer.append(mod) self.mod_buffer.append(mod)
else: else:
self.mod_buffer = [mod] self.mod_buffer = [mod]
asyncio.get_event_loop().call_later(self.args.update_delay, self.loop.call_later(self.args.update_delay,
self.flush_mod_buffer) self.flush_mod_buffer)
else: else:
self.emit_data_changed(self.data, [mod]) self.emit_data_changed(self.data, [mod])
@ -214,10 +351,11 @@ class SimpleApplet:
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)) self.args.server, self.args.port_notify))
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) dataset_prefixes=self.dataset_prefixes,
loop=self.loop)
def unsubscribe(self): def unsubscribe(self):
if self.embed is None: if self.embed is None:
@ -229,12 +367,16 @@ class SimpleApplet:
try: try:
self.ipc_init() self.ipc_init()
try: try:
self.create_main_widget() self.req_init()
self.subscribe()
try: try:
self.loop.run_forever() self.create_main_widget()
self.subscribe()
try:
self.loop.run_forever()
finally:
self.unsubscribe()
finally: finally:
self.unsubscribe() self.req_close()
finally: finally:
self.ipc_close() self.ipc_close()
finally: finally:
@ -273,4 +415,9 @@ class TitleApplet(SimpleApplet):
title = self.args.title title = self.args.title
else: else:
title = None title = None
self.main_widget.data_changed(data, mod_buffer, title) persist = dict()
value = dict()
metadata = dict()
for k, d in data.items():
persist[k], value[k], metadata[k] = d
self.main_widget.data_changed(value, metadata, persist, mod_buffer, title)

View File

@ -20,11 +20,46 @@ class Model(DictSyncTreeSepModel):
DictSyncTreeSepModel.__init__(self, ".", ["Dataset", "Value"], init) DictSyncTreeSepModel.__init__(self, ".", ["Dataset", "Value"], init)
def convert(self, k, v, column): def convert(self, k, v, column):
return short_format(v[1]) return short_format(v[1], v[2])
class DatasetCtl:
def __init__(self, master_host, master_port):
self.master_host = master_host
self.master_port = master_port
async def _execute_rpc(self, op_name, key_or_mod, value=None, persist=None, metadata=None):
logger.info("Starting %s operation on %s", op_name, key_or_mod)
try:
remote = RPCClient()
await remote.connect_rpc(self.master_host, self.master_port,
"master_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): class DatasetsDock(QtWidgets.QDockWidget):
def __init__(self, datasets_sub, master_host, master_port): def __init__(self, dataset_sub, dataset_ctl):
QtWidgets.QDockWidget.__init__(self, "Datasets") QtWidgets.QDockWidget.__init__(self, "Datasets")
self.setObjectName("Datasets") self.setObjectName("Datasets")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
@ -62,10 +97,9 @@ class DatasetsDock(QtWidgets.QDockWidget):
self.table.addAction(upload_action) self.table.addAction(upload_action)
self.set_model(Model(dict())) self.set_model(Model(dict()))
datasets_sub.add_setmodel_callback(self.set_model) dataset_sub.add_setmodel_callback(self.set_model)
self.master_host = master_host self.dataset_ctl = dataset_ctl
self.master_port = master_port
def _search_datasets(self): def _search_datasets(self):
if hasattr(self, "table_model_filter"): if hasattr(self, "table_model_filter"):
@ -82,30 +116,14 @@ class DatasetsDock(QtWidgets.QDockWidget):
self.table_model_filter.setSourceModel(self.table_model) self.table_model_filter.setSourceModel(self.table_model)
self.table.setModel(self.table_model_filter) self.table.setModel(self.table_model_filter)
async def _upload_dataset(self, name, value,):
logger.info("Uploading dataset '%s' to master...", name)
try:
remote = RPCClient()
await remote.connect_rpc(self.master_host, self.master_port,
"master_dataset_db")
try:
await remote.set(name, value)
finally:
remote.close_rpc()
except:
logger.error("Failed uploading dataset '%s'",
name, exc_info=True)
else:
logger.info("Finished uploading dataset '%s'", name)
def upload_clicked(self): def upload_clicked(self):
idx = self.table.selectedIndexes() idx = self.table.selectedIndexes()
if idx: if idx:
idx = self.table_model_filter.mapToSource(idx[0]) idx = self.table_model_filter.mapToSource(idx[0])
key = self.table_model.index_to_key(idx) key = self.table_model.index_to_key(idx)
if key is not None: if key is not None:
persist, value = self.table_model.backing_store[key] persist, value, metadata = self.table_model.backing_store[key]
asyncio.ensure_future(self._upload_dataset(key, value)) asyncio.ensure_future(self.dataset_ctl.set(key, value, metadata=metadata))
def save_state(self): def save_state(self):
return bytes(self.table.header().saveState()) return bytes(self.table.header().saveState())

View File

@ -10,22 +10,14 @@ import h5py
from sipyco import pyon from sipyco import pyon
from artiq import __artiq_dir__ as artiq_dir 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.tools import (LayoutWidget, WheelFilter,
log_level_to_name, get_open_file_name)
from artiq.gui.entries import procdesc_to_entry from artiq.gui.entries import procdesc_to_entry
from artiq.master.worker import Worker, log_worker_exception from artiq.master.worker import Worker, log_worker_exception
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class _WheelFilter(QtCore.QObject):
def eventFilter(self, obj, event):
if (event.type() == QtCore.QEvent.Wheel and
event.modifiers() != QtCore.Qt.NoModifier):
event.ignore()
return True
return False
class _ArgumentEditor(QtWidgets.QTreeWidget): class _ArgumentEditor(QtWidgets.QTreeWidget):
def __init__(self, dock): def __init__(self, dock):
QtWidgets.QTreeWidget.__init__(self) QtWidgets.QTreeWidget.__init__(self)
@ -46,7 +38,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
self.setStyleSheet("QTreeWidget {background: " + self.setStyleSheet("QTreeWidget {background: " +
self.palette().midlight().color().name() + " ;}") self.palette().midlight().color().name() + " ;}")
self.viewport().installEventFilter(_WheelFilter(self.viewport())) self.viewport().installEventFilter(WheelFilter(self.viewport(), True))
self._groups = dict() self._groups = dict()
self._arg_to_widgets = dict() self._arg_to_widgets = dict()
@ -378,9 +370,9 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
class LocalDatasetDB: class LocalDatasetDB:
def __init__(self, datasets_sub): def __init__(self, dataset_sub):
self.datasets_sub = datasets_sub self.dataset_sub = dataset_sub
datasets_sub.add_setmodel_callback(self.init) dataset_sub.add_setmodel_callback(self.init)
def init(self, data): def init(self, data):
self._data = data self._data = data
@ -389,11 +381,11 @@ class LocalDatasetDB:
return self._data.backing_store[key][1] return self._data.backing_store[key][1]
def update(self, mod): def update(self, mod):
self.datasets_sub.update(mod) self.dataset_sub.update(mod)
class ExperimentsArea(QtWidgets.QMdiArea): class ExperimentsArea(QtWidgets.QMdiArea):
def __init__(self, root, datasets_sub): def __init__(self, root, dataset_sub):
QtWidgets.QMdiArea.__init__(self) QtWidgets.QMdiArea.__init__(self)
self.pixmap = QtGui.QPixmap(os.path.join( self.pixmap = QtGui.QPixmap(os.path.join(
artiq_dir, "gui", "logo_ver.svg")) artiq_dir, "gui", "logo_ver.svg"))
@ -402,11 +394,11 @@ class ExperimentsArea(QtWidgets.QMdiArea):
self.open_experiments = [] self.open_experiments = []
self._ddb = LocalDatasetDB(datasets_sub) self._ddb = LocalDatasetDB(dataset_sub)
self.worker_handlers = { self.worker_handlers = {
"get_device_db": lambda: {}, "get_device_db": lambda: {},
"get_device": lambda k: {"type": "dummy"}, "get_device": lambda key, resolve_alias=False: {"type": "dummy"},
"get_dataset": self._ddb.get, "get_dataset": self._ddb.get,
"update_dataset": self._ddb.update, "update_dataset": self._ddb.update,
} }
@ -516,5 +508,9 @@ class ExperimentsArea(QtWidgets.QMdiArea):
self.open_experiments.append(dock) self.open_experiments.append(dock)
return dock return dock
def set_argument_value(self, expurl, name, value):
logger.warning("Unable to set argument '%s', dropping change. "
"'set_argument_value' not supported in browser.", name)
def on_dock_closed(self, dock): def on_dock_closed(self, dock):
self.open_experiments.remove(dock) self.open_experiments.remove(dock)

View File

@ -102,13 +102,14 @@ class Hdf5FileSystemModel(QtWidgets.QFileSystemModel):
h5 = open_h5(info) h5 = open_h5(info)
if h5 is not None: if h5 is not None:
try: try:
expid = pyon.decode(h5["expid"][()]) expid = pyon.decode(h5["expid"][()]) if "expid" in h5 else dict()
start_time = datetime.fromtimestamp(h5["start_time"][()]) start_time = datetime.fromtimestamp(h5["start_time"][()]) if "start_time" in h5 else "<none>"
v = ("artiq_version: {}\nrepo_rev: {}\nfile: {}\n" v = ("artiq_version: {}\nrepo_rev: {}\nfile: {}\n"
"class_name: {}\nrid: {}\nstart_time: {}").format( "class_name: {}\nrid: {}\nstart_time: {}").format(
h5["artiq_version"][()], expid["repo_rev"], h5["artiq_version"].asstr()[()] if "artiq_version" in h5 else "<none>",
expid.get("file", "<none>"), expid["class_name"], expid.get("repo_rev", "<none>"),
h5["rid"][()], start_time) expid.get("file", "<none>"), expid.get("class_name", "<none>"),
h5["rid"][()] if "rid" in h5 else "<none>", start_time)
return v return v
except: except:
logger.warning("unable to read metadata from %s", logger.warning("unable to read metadata from %s",
@ -174,14 +175,14 @@ class FilesDock(QtWidgets.QDockWidget):
logger.debug("loading datasets from %s", info.filePath()) logger.debug("loading datasets from %s", info.filePath())
with f: with f:
try: try:
expid = pyon.decode(f["expid"][()]) expid = pyon.decode(f["expid"][()]) if "expid" in f else dict()
start_time = datetime.fromtimestamp(f["start_time"][()]) start_time = datetime.fromtimestamp(f["start_time"][()]) if "start_time" in f else "<none>"
v = { v = {
"artiq_version": f["artiq_version"][()], "artiq_version": f["artiq_version"].asstr()[()] if "artiq_version" in f else "<none>",
"repo_rev": expid["repo_rev"], "repo_rev": expid.get("repo_rev", "<none>"),
"file": expid.get("file", "<none>"), "file": expid.get("file", "<none>"),
"class_name": expid["class_name"], "class_name": expid.get("class_name", "<none>"),
"rid": f["rid"][()], "rid": f["rid"][()] if "rid" in f else "<none>",
"start_time": start_time, "start_time": start_time,
} }
self.metadata_changed.emit(v) self.metadata_changed.emit(v)
@ -193,7 +194,9 @@ class FilesDock(QtWidgets.QDockWidget):
if "archive" in f: if "archive" in f:
def visitor(k, v): def visitor(k, v):
if isinstance(v, h5py.Dataset): if isinstance(v, h5py.Dataset):
rd[k] = (True, v[()]) # v.attrs is a non-serializable h5py.AttributeManager, need to convert to dict
# See https://docs.h5py.org/en/stable/high/attr.html#h5py.AttributeManager
rd[k] = (True, v[()], dict(v.attrs))
f["archive"].visititems(visitor) f["archive"].visititems(visitor)
@ -203,7 +206,9 @@ class FilesDock(QtWidgets.QDockWidget):
if k in rd: if k in rd:
logger.warning("dataset '%s' is both in archive " logger.warning("dataset '%s' is both in archive "
"and outputs", k) "and outputs", k)
rd[k] = (True, v[()]) # v.attrs is a non-serializable h5py.AttributeManager, need to convert to dict
# See https://docs.h5py.org/en/stable/high/attr.html#h5py.AttributeManager
rd[k] = (True, v[()], dict(v.attrs))
f["datasets"].visititems(visitor) f["datasets"].visititems(visitor)

View File

@ -59,19 +59,18 @@ def build_artiq_soc(soc, argdict):
builder.software_packages = [] builder.software_packages = []
builder.add_software_package("bootloader", os.path.join(firmware_dir, "bootloader")) 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") is_kasli_v1 = isinstance(soc.platform, kasli.Platform) and soc.platform.hw_rev in ("v1.0", "v1.1")
if isinstance(soc, AMPSoC): kernel_cpu_type = "vexriscv" if is_kasli_v1 else "vexriscv-g"
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("libm", cpu_type=kernel_cpu_type) builder.add_software_package("libprintf", 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("libunwind", cpu_type=kernel_cpu_type) builder.add_software_package("ksupport", os.path.join(firmware_dir, "ksupport"), 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)
# Generate unwinder for soft float target (ARTIQ runtime) # If the kernel lacks FPU, then the runtime unwinder is already generated
# If the kernel lacks FPU, then the runtime unwinder is already generated if not is_kasli_v1:
if not is_kasli_v1: builder.add_software_package("libunwind")
builder.add_software_package("libunwind") if not soc.config["DRTIO_ROLE"] == "satellite":
builder.add_software_package("runtime", os.path.join(firmware_dir, "runtime")) builder.add_software_package("runtime", os.path.join(firmware_dir, "runtime"))
else: else:
# Assume DRTIO satellite.
builder.add_software_package("satman", os.path.join(firmware_dir, "satman")) builder.add_software_package("satman", os.path.join(firmware_dir, "satman"))
try: try:
builder.build() builder.build()

View File

@ -21,13 +21,19 @@ 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): class FunctionDefT(ast.FunctionDef, scoped, remote):
_types = ("signature_type",) _types = ("signature_type",)
class QuotedFunctionDefT(FunctionDefT): class QuotedFunctionDefT(FunctionDefT):
""" """
@ -58,7 +64,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): class CallT(ast.Call, commontyped, remote):
""" """
: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,6 +38,9 @@ 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))
@ -244,6 +247,12 @@ def fn_at_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")
# Accessors # Accessors
def is_none(typ): def is_none(typ):
@ -326,7 +335,7 @@ def get_iterable_elt(typ):
# n-dimensional arrays, rather than the n-1 dimensional result of iterating over # n-dimensional arrays, rather than the n-1 dimensional result of iterating over
# the first axis, which makes the name a bit misleading. # the first axis, which makes the name a bit misleading.
if is_str(typ) or is_bytes(typ) or is_bytearray(typ): if is_str(typ) or is_bytes(typ) or is_bytearray(typ):
return TInt(types.TValue(8)) return TInt8()
elif types._is_pointer(typ) or is_iterable(typ): elif types._is_pointer(typ) or is_iterable(typ):
return typ.find()["elt"].find() return typ.find()["elt"].find()
else: else:
@ -342,5 +351,5 @@ def is_allocated(typ):
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_external_function(typ) or types.is_rpc(typ) or
types.is_method(typ) or types.is_tuple(typ) or types.is_subkernel(typ) or types.is_method(typ) or
types.is_value(typ)) types.is_tuple(typ) or types.is_value(typ))

View File

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

View File

@ -706,6 +706,81 @@ 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,
@ -728,7 +803,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): if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(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
@ -1190,14 +1265,18 @@ 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, name=""): def __init__(self, value, remote_return=False, 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"

View File

@ -33,9 +33,19 @@ SECTIONS
KEEP(*(.eh_frame_hdr)) KEEP(*(.eh_frame_hdr))
} : text : eh_frame } : text : eh_frame
.got :
{
*(.got)
} : text
.got.plt :
{
*(.got.plt)
} : text
.data : .data :
{ {
*(.data) *(.data .data.*)
} : data } : data
.dynamic : .dynamic :
@ -51,6 +61,10 @@ SECTIONS
_end = .; _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); . = ALIGN(0x1000);
_sstack_guard = .; _sstack_guard = .;
} }

View File

@ -84,6 +84,8 @@ class Module:
constant_hoister.process(self.artiq_ir) constant_hoister.process(self.artiq_ir)
if remarks: if remarks:
invariant_detection.process(self.artiq_ir) 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."""

View File

@ -37,6 +37,7 @@ def globals():
# 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(), "rpc": builtins.fn_kernel(),
@ -54,4 +55,8 @@ def globals():
# 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(),
} }

View File

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

View File

@ -30,8 +30,9 @@ def main():
device_db_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py") device_db_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py")
device_mgr = DeviceManager(DeviceDB(device_db_path)) device_mgr = DeviceManager(DeviceDB(device_db_path))
dataset_db_path = os.path.join(os.path.dirname(sys.argv[1]), "dataset_db.pyon") dataset_db_path = os.path.join(os.path.dirname(sys.argv[1]), "dataset_db.mdb")
dataset_mgr = DatasetManager(DatasetDB(dataset_db_path)) dataset_db = DatasetDB(dataset_db_path)
dataset_mgr = DatasetManager()
argument_mgr = ProcessArgumentManager({}) argument_mgr = ProcessArgumentManager({})
@ -68,5 +69,7 @@ def main():
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

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

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) loc=node.loc, remote_fn=False)
try: try:
self.env_stack.append(node.typing_env) self.env_stack.append(node.typing_env)
@ -440,7 +440,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={},
func=node.func, args=node.args, keywords=node.keywords, remote_fn=False, func=node.func,
args=node.args, keywords=node.keywords,
starargs=node.starargs, kwargs=node.kwargs, 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)

View File

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

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): if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(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): if types.is_rpc(typ) or types.is_subkernel(typ):
call_delay = iodelay.Const(0) call_delay = iodelay.Const(0)
else: else:
delay = typ.find().delay.find() delay = typ.find().delay.find()
@ -311,13 +311,20 @@ 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()
node.arg_exprs = { try:
arg: self.evaluate(args[arg], abort=abort, node.arg_exprs = {
context="in the expression for argument '{}' " arg: self.evaluate(args[arg], abort=abort,
"that affects I/O delay".format(arg)) context="in the expression for argument '{}' "
for arg in free_vars "that affects I/O delay".format(arg))
} 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:

View File

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

View File

@ -385,6 +385,50 @@ 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
@ -644,6 +688,9 @@ 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):
return isinstance(typ.find(), TSubkernel)
def is_external_function(typ, name=None): def is_external_function(typ, name=None):
typ = typ.find() typ = typ.find()
if name is None: if name is None:
@ -810,6 +857,10 @@ class TypePrinter(object):
return "[rpc{} #{}](...)->{}".format(typ.service, return "[rpc{} #{}](...)->{}".format(typ.service,
" async" if typ.is_async else "", " async" if typ.is_async else "",
self.name(typ.ret, depth + 1)) 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

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

View File

@ -233,7 +233,7 @@ class AD53xx:
def write_gain_mu(self, channel, gain=0xffff): def write_gain_mu(self, channel, gain=0xffff):
"""Program the gain register for a DAC channel. """Program the gain register for a DAC channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:). The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
This method advances the timeline by the duration of one SPI transfer. This method advances the timeline by the duration of one SPI transfer.
:param gain: 16-bit gain register value (default: 0xffff) :param gain: 16-bit gain register value (default: 0xffff)
@ -245,7 +245,7 @@ class AD53xx:
def write_offset_mu(self, channel, offset=0x8000): def write_offset_mu(self, channel, offset=0x8000):
"""Program the offset register for a DAC channel. """Program the offset register for a DAC channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:). The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
This method advances the timeline by the duration of one SPI transfer. This method advances the timeline by the duration of one SPI transfer.
:param offset: 16-bit offset register value (default: 0x8000) :param offset: 16-bit offset register value (default: 0x8000)
@ -258,7 +258,7 @@ class AD53xx:
"""Program the DAC offset voltage for a channel. """Program the DAC offset voltage for a channel.
An offset of +V can be used to trim out a DAC offset error of -V. An offset of +V can be used to trim out a DAC offset error of -V.
The DAC output is not updated until LDAC is pulsed (see :meth load:). The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
This method advances the timeline by the duration of one SPI transfer. This method advances the timeline by the duration of one SPI transfer.
:param voltage: the offset voltage :param voltage: the offset voltage
@ -270,7 +270,7 @@ class AD53xx:
def write_dac_mu(self, channel, value): def write_dac_mu(self, channel, value):
"""Program the DAC input register for a channel. """Program the DAC input register for a channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:). The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
This method advances the timeline by the duration of one SPI transfer. This method advances the timeline by the duration of one SPI transfer.
""" """
self.bus.write( self.bus.write(
@ -280,7 +280,7 @@ class AD53xx:
def write_dac(self, channel, voltage): def write_dac(self, channel, voltage):
"""Program the DAC output voltage for a channel. """Program the DAC output voltage for a channel.
The DAC output is not updated until LDAC is pulsed (see :meth load:). The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
This method advances the timeline by the duration of one SPI transfer. This method advances the timeline by the duration of one SPI transfer.
""" """
self.write_dac_mu(channel, voltage_to_mu(voltage, self.offset_dacs, self.write_dac_mu(channel, voltage_to_mu(voltage, self.offset_dacs,
@ -313,7 +313,7 @@ class AD53xx:
If no LDAC device was defined, the LDAC pulse is skipped. If no LDAC device was defined, the LDAC pulse is skipped.
See :meth load:. See :meth:`load`.
:param values: list of DAC values to program :param values: list of DAC values to program
:param channels: list of DAC channels to program. If not specified, :param channels: list of DAC channels to program. If not specified,
@ -355,7 +355,7 @@ class AD53xx:
""" Two-point calibration of a DAC channel. """ Two-point calibration of a DAC channel.
Programs the offset and gain register to trim out DAC errors. Does not Programs the offset and gain register to trim out DAC errors. Does not
take effect until LDAC is pulsed (see :meth load:). take effect until LDAC is pulsed (see :meth:`load`).
Calibration consists of measuring the DAC output voltage for a channel Calibration consists of measuring the DAC output voltage for a channel
with the DAC set to zero-scale (0x0000) and full-scale (0xffff). with the DAC set to zero-scale (0x0000) and full-scale (0xffff).

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -25,12 +25,14 @@ class AD9912:
f_ref/clk_div*pll_n where f_ref is the reference frequency and clk_div f_ref/clk_div*pll_n where f_ref is the reference frequency and clk_div
is the reference clock divider (both set in the parent Urukul CPLD is the reference clock divider (both set in the parent Urukul CPLD
instance). instance).
:param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1).
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, def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
pll_n=10): pll_n=10, pll_en=1):
self.kernel_invariants = {"cpld", "core", "bus", "chip_select", self.kernel_invariants = {"cpld", "core", "bus", "chip_select",
"pll_n", "ftw_per_hz"} "pll_n", "pll_en", "ftw_per_hz"}
self.cpld = dmgr.get(cpld_device) self.cpld = dmgr.get(cpld_device)
self.core = self.cpld.core self.core = self.cpld.core
self.bus = self.cpld.bus self.bus = self.cpld.bus
@ -39,8 +41,12 @@ class AD9912:
if sw_device: if sw_device:
self.sw = dmgr.get(sw_device) self.sw = dmgr.get(sw_device)
self.kernel_invariants.add("sw") self.kernel_invariants.add("sw")
self.pll_en = pll_en
self.pll_n = pll_n self.pll_n = pll_n
sysclk = self.cpld.refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n if pll_en:
sysclk = self.cpld.refclk / [1, 1, 2, 4][self.cpld.clk_div] * pll_n
else:
sysclk = self.cpld.refclk
assert sysclk <= 1e9 assert sysclk <= 1e9
self.ftw_per_hz = 1 / sysclk * (int64(1) << 48) self.ftw_per_hz = 1 / sysclk * (int64(1) << 48)
@ -102,13 +108,15 @@ class AD9912:
raise ValueError("Urukul AD9912 product id mismatch") raise ValueError("Urukul AD9912 product id mismatch")
delay(50 * us) delay(50 * us)
# HSTL power down, CMOS power down # HSTL power down, CMOS power down
self.write(AD9912_PWRCNTRL1, 0x80, length=1) pwrcntrl1 = 0x80 | ((~self.pll_en & 1) << 4)
self.cpld.io_update.pulse(2 * us) self.write(AD9912_PWRCNTRL1, pwrcntrl1, length=1)
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1)
self.cpld.io_update.pulse(2 * us)
# I_cp = 375 µA, VCO high range
self.write(AD9912_PLLCFG, 0b00000101, length=1)
self.cpld.io_update.pulse(2 * us) self.cpld.io_update.pulse(2 * us)
if self.pll_en:
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1)
self.cpld.io_update.pulse(2 * us)
# I_cp = 375 µA, VCO high range
self.write(AD9912_PLLCFG, 0b00000101, length=1)
self.cpld.io_update.pulse(2 * us)
delay(1 * ms) delay(1 * ms)
@kernel @kernel

View File

@ -80,6 +80,13 @@ class AD9914:
self.set_x_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 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 @kernel
def write(self, addr, data): def write(self, addr, data):
rtio_output((self.bus_channel << 8) | addr, data) rtio_output((self.bus_channel << 8) | addr, data)

View File

@ -73,6 +73,10 @@ class ADF5356:
self._init_registers() self._init_registers()
@staticmethod
def get_rtio_channels(**kwargs):
return []
@kernel @kernel
def init(self, blind=False): def init(self, blind=False):
""" """

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

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

View File

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

View File

@ -102,15 +102,15 @@ def decode_dump(data):
# messages are big endian # messages are big endian
parts = struct.unpack(endian + "IQbbb", data[:15]) parts = struct.unpack(endian + "IQbbb", data[:15])
(sent_bytes, total_byte_count, (sent_bytes, total_byte_count,
error_occured, log_channel, dds_onehot_sel) = parts error_occurred, log_channel, dds_onehot_sel) = parts
expected_len = sent_bytes + 15 expected_len = sent_bytes + 15
if expected_len != len(data): if expected_len != len(data):
raise ValueError("analyzer dump has incorrect length " raise ValueError("analyzer dump has incorrect length "
"(got {}, expected {})".format( "(got {}, expected {})".format(
len(data), expected_len)) len(data), expected_len))
if error_occured: if error_occurred:
logger.warning("error occured within the analyzer, " logger.warning("error occurred within the analyzer, "
"data may be corrupted") "data may be corrupted")
if total_byte_count > sent_bytes: if total_byte_count > sent_bytes:
logger.info("analyzer ring buffer has wrapped %d times", logger.info("analyzer ring buffer has wrapped %d times",

View File

@ -23,6 +23,8 @@ class Request(Enum):
RPCReply = 7 RPCReply = 7
RPCException = 8 RPCException = 8
SubkernelUpload = 9
class Reply(Enum): class Reply(Enum):
SystemInfo = 2 SystemInfo = 2
@ -208,6 +210,7 @@ class CommKernel:
self.unpack_float64 = struct.Struct(self.endian + "d").unpack self.unpack_float64 = struct.Struct(self.endian + "d").unpack
self.pack_header = struct.Struct(self.endian + "lB").pack self.pack_header = struct.Struct(self.endian + "lB").pack
self.pack_int8 = struct.Struct(self.endian + "B").pack
self.pack_int32 = struct.Struct(self.endian + "l").pack self.pack_int32 = struct.Struct(self.endian + "l").pack
self.pack_int64 = struct.Struct(self.endian + "q").pack self.pack_int64 = struct.Struct(self.endian + "q").pack
self.pack_float64 = struct.Struct(self.endian + "d").pack self.pack_float64 = struct.Struct(self.endian + "d").pack
@ -322,7 +325,7 @@ class CommKernel:
self._write(chunk) self._write(chunk)
def _write_int8(self, value): def _write_int8(self, value):
self._write(value) self._write(self.pack_int8(value))
def _write_int32(self, value): def _write_int32(self, value):
self._write(self.pack_int32(value)) self._write(self.pack_int32(value))
@ -382,6 +385,19 @@ class CommKernel:
else: else:
self._read_expect(Reply.LoadCompleted) self._read_expect(Reply.LoadCompleted)
def upload_subkernel(self, kernel_library, id, destination):
self._write_header(Request.SubkernelUpload)
self._write_int32(id)
self._write_int8(destination)
self._write_bytes(kernel_library)
self._flush()
self._read_header()
if self._read_type == Reply.LoadFailed:
raise LoadError(self._read_string())
else:
self._read_expect(Reply.LoadCompleted)
def run(self): def run(self):
self._write_empty(Request.RunKernel) self._write_empty(Request.RunKernel)
self._flush() self._flush()
@ -686,8 +702,14 @@ class CommKernel:
else: else:
python_exn_type = embedding_map.retrieve_object(core_exn.id) python_exn_type = embedding_map.retrieve_object(core_exn.id)
python_exn = python_exn_type( try:
nested_exceptions[-1][1].format(*nested_exceptions[0][2])) 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 python_exn.artiq_core_exception = core_exn
raise python_exn raise python_exn

View File

@ -1,5 +1,6 @@
import os, sys import os, sys
import numpy import numpy
from inspect import getfullargspec
from functools import wraps from functools import wraps
from pythonparser import diagnostic from pythonparser import diagnostic
@ -53,6 +54,17 @@ def rtio_get_counter() -> TInt64:
raise NotImplementedError("syscall not simulated") 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.
@ -66,58 +78,70 @@ class Core:
:param ref_multiplier: ratio between the RTIO fine timestamp frequency :param ref_multiplier: ratio between the RTIO fine timestamp frequency
and the RTIO coarse timestamp frequency (e.g. SERDES multiplication and the RTIO coarse timestamp frequency (e.g. SERDES multiplication
factor). factor).
:param analyzer_proxy: name of the core device analyzer proxy to trigger
(optional).
:param analyze_at_run_end: automatically trigger the core device analyzer
proxy after the Experiment's run stage finishes.
""" """
kernel_invariants = { kernel_invariants = {
"core", "ref_period", "coarse_ref_period", "ref_multiplier", "core", "ref_period", "coarse_ref_period", "ref_multiplier",
} }
def __init__(self, dmgr, host, ref_period, ref_multiplier=8, target="rv32g"): def __init__(self, dmgr,
host, ref_period,
analyzer_proxy=None, analyze_at_run_end=False,
ref_multiplier=8,
target="rv32g", satellite_cpu_targets={}):
self.ref_period = ref_period self.ref_period = ref_period
self.ref_multiplier = ref_multiplier self.ref_multiplier = ref_multiplier
if target == "rv32g": self.satellite_cpu_targets = satellite_cpu_targets
self.target_cls = RV32GTarget self.target_cls = get_target_cls(target)
elif target == "rv32ima":
self.target_cls = RV32IMATarget
elif target == "cortexa9":
self.target_cls = CortexA9Target
else:
raise ValueError("Unsupported target")
self.coarse_ref_period = ref_period*ref_multiplier self.coarse_ref_period = ref_period*ref_multiplier
if host is None: if host is None:
self.comm = CommKernelDummy() self.comm = CommKernelDummy()
else: else:
self.comm = CommKernel(host) self.comm = CommKernel(host)
self.analyzer_proxy_name = analyzer_proxy
self.analyze_at_run_end = analyze_at_run_end
self.first_run = True self.first_run = True
self.dmgr = dmgr self.dmgr = dmgr
self.core = self self.core = self
self.comm.core = self self.comm.core = self
self.analyzer_proxy = None
def notify_run_end(self):
if self.analyze_at_run_end:
self.trigger_analyzer_proxy()
def close(self): def close(self):
self.comm.close() self.comm.close()
def compile(self, function, args, kwargs, set_result=None, def compile(self, function, args, kwargs, set_result=None,
attribute_writeback=True, print_as_rpc=True): attribute_writeback=True, print_as_rpc=True,
target=None, destination=0, subkernel_arg_types=[]):
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) print_as_rpc=print_as_rpc,
destination=destination, subkernel_arg_types=subkernel_arg_types)
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,
attribute_writeback=attribute_writeback) attribute_writeback=attribute_writeback)
target = self.target_cls() target = target if target is not None else self.target_cls()
library = target.compile_and_link([module]) library = target.compile_and_link([module])
stripped_library = target.strip(library) stripped_library = target.strip(library)
return stitcher.embedding_map, stripped_library, \ return stitcher.embedding_map, stripped_library, \
lambda addresses: target.symbolize(library, addresses), \ lambda addresses: target.symbolize(library, addresses), \
lambda symbols: target.demangle(symbols) lambda symbols: target.demangle(symbols), \
module.subkernel_arg_types
except diagnostic.Error as error: except diagnostic.Error as error:
raise CompileError(error.diagnostic) from error raise CompileError(error.diagnostic) from error
@ -135,11 +159,38 @@ class Core:
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 = \ embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
self.compile(function, args, kwargs, set_result) 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) self._run_compiled(kernel_library, embedding_map, symbolizer, demangler)
return result return result
def compile_subkernel(self, sid, subkernel_fn, embedding_map, args, subkernel_arg_types):
# pass self to subkernels (if applicable)
# assuming the first argument is self
subkernel_args = getfullargspec(subkernel_fn.artiq_embedded.function)
self_arg = []
if len(subkernel_args[0]) > 0:
if subkernel_args[0][0] == 'self':
self_arg = args[:1]
destination = subkernel_fn.artiq_embedded.destination
destination_tgt = self.satellite_cpu_targets[destination]
target = get_target_cls(destination_tgt)(subkernel_id=sid)
object_map, kernel_library, _, _, _ = \
self.compile(subkernel_fn, self_arg, {}, attribute_writeback=False,
print_as_rpc=False, target=target, destination=destination,
subkernel_arg_types=subkernel_arg_types.get(sid, []))
if object_map.has_rpc_or_subkernel():
raise ValueError("Subkernel must not use RPC or subkernels in other destinations")
return destination, kernel_library
def compile_and_upload_subkernels(self, embedding_map, args, subkernel_arg_types):
for sid, subkernel_fn in embedding_map.subkernels().items():
destination, kernel_library = \
self.compile_subkernel(sid, subkernel_fn, embedding_map,
args, subkernel_arg_types)
self.comm.upload_subkernel(kernel_library, sid, destination)
def precompile(self, function, *args, **kwargs): def precompile(self, function, *args, **kwargs):
"""Precompile a kernel and return a callable that executes it on the core device """Precompile a kernel and return a callable that executes it on the core device
at a later time. at a later time.
@ -148,7 +199,7 @@ class Core:
as additional positional and keyword arguments. as additional positional and keyword arguments.
The returned callable accepts no arguments. The returned callable accepts no arguments.
Precompiled kernels may use RPCs. Precompiled kernels may use RPCs and subkernels.
Object attributes at the beginning of a precompiled kernel execution have the 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, values they had at precompilation time. If up-to-date values are required,
@ -173,8 +224,9 @@ class Core:
nonlocal result nonlocal result
result = new_result result = new_result
embedding_map, kernel_library, symbolizer, demangler = \ embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
self.compile(function, args, kwargs, set_result, attribute_writeback=False) self.compile(function, args, kwargs, set_result, attribute_writeback=False)
self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types)
@wraps(function) @wraps(function)
def run_precompiled(): def run_precompiled():
@ -250,3 +302,23 @@ class Core:
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 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:
success = self.analyzer_proxy.trigger()
if not success:
raise IOError("Analyzer proxy reported failure")

View File

@ -19,16 +19,24 @@
}, },
"min_artiq_version": { "min_artiq_version": {
"type": "string", "type": "string",
"description": "Minimum required ARTIQ version" "description": "Minimum required ARTIQ version",
"default": "0"
}, },
"hw_rev": { "hw_rev": {
"type": "string", "type": "string",
"description": "Hardware revision" "description": "Hardware revision"
}, },
"base": { "base": {
"type": "string",
"enum": ["use_drtio_role", "standalone", "master", "satellite"],
"description": "Deprecated, use drtio_role instead",
"default": "use_drtio_role"
},
"drtio_role": {
"type": "string", "type": "string",
"enum": ["standalone", "master", "satellite"], "enum": ["standalone", "master", "satellite"],
"description": "SoC base; value depends on intended system topology" "description": "Role that this device takes in a DRTIO network; 'standalone' means no DRTIO",
"default": "standalone"
}, },
"ext_ref_frequency": { "ext_ref_frequency": {
"type": "number", "type": "number",
@ -122,7 +130,7 @@
}, },
"hw_rev": { "hw_rev": {
"type": "string", "type": "string",
"enum": ["v1.0"] "enum": ["v1.0", "v1.1"]
} }
} }
} }
@ -134,7 +142,7 @@
"properties": { "properties": {
"type": { "type": {
"type": "string", "type": "string",
"enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp"] "enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"]
}, },
"board": { "board": {
"type": "string" "type": "string"
@ -204,7 +212,8 @@
"type": "object", "type": "object",
"properties": { "properties": {
"name": { "name": {
"type": "string" "type": "string",
"default": "dio_spi"
}, },
"clk": { "clk": {
"type": "integer", "type": "integer",
@ -240,7 +249,8 @@
"type": "object", "type": "object",
"properties": { "properties": {
"name": { "name": {
"type": "string" "type": "string",
"default": "ttl"
}, },
"pin": { "pin": {
"type": "integer", "type": "integer",
@ -257,7 +267,8 @@
} }
}, },
"required": ["pin", "direction"] "required": ["pin", "direction"]
} },
"default": []
} }
}, },
"required": ["ports", "spi"] "required": ["ports", "spi"]
@ -297,11 +308,18 @@
"clk_div": { "clk_div": {
"type": "integer", "type": "integer",
"minimum": 0, "minimum": 0,
"maximum": 3 "maximum": 3,
"default": 0
}, },
"pll_n": { "pll_n": {
"type": "integer" "type": "integer"
}, },
"pll_en": {
"type": "integer",
"minimum": 0,
"maximum": 1,
"default": 1
},
"pll_vco": { "pll_vco": {
"type": "integer" "type": "integer"
}, },
@ -376,6 +394,11 @@
"minItems": 2, "minItems": 2,
"maxItems": 2 "maxItems": 2
}, },
"sampler_hw_rev": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+",
"default": "v2.2"
},
"urukul0_ports": { "urukul0_ports": {
"type": "array", "type": "array",
"items": { "items": {
@ -405,6 +428,12 @@
"type": "integer", "type": "integer",
"default": 32 "default": 32
}, },
"pll_en": {
"type": "integer",
"minimum": 0,
"maximum": 1,
"default": 1
},
"pll_vco": { "pll_vco": {
"type": "integer" "type": "integer"
} }
@ -496,6 +525,11 @@
"almazny": { "almazny": {
"type": "boolean", "type": "boolean",
"default": false "default": false
},
"almazny_hw_rev": {
"type": "string",
"pattern": "^v[0-9]+\\.[0-9]+",
"default": "v1.2"
} }
}, },
"required": ["ports"] "required": ["ports"]
@ -545,6 +579,11 @@
}, },
"minItems": 1, "minItems": 1,
"maxItems": 1 "maxItems": 1
},
"mode": {
"type": "string",
"enum": ["base", "miqro"],
"default": "base"
} }
}, },
"required": ["ports"] "required": ["ports"]
@ -571,6 +610,31 @@
}, },
"required": ["ports"] "required": ["ports"]
} }
},{
"title": "Shuttler",
"if": {
"properties": {
"type": {
"const": "shuttler"
}
}
},
"then": {
"properties": {
"ports": {
"type": "array",
"items": {
"type": "integer"
},
"minItems": 1,
"maxItems": 2
},
"drtio_destination": {
"type": "integer"
}
},
"required": ["ports"]
}
}] }]
} }
} }

View File

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

View File

@ -91,6 +91,10 @@ class EdgeCounter:
self.channel = channel self.channel = channel
self.counter_max = (1 << (gateware_width - 1)) - 1 self.counter_max = (1 << (gateware_width - 1)) - 1
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel @kernel
def gate_rising(self, duration): def gate_rising(self, duration):
"""Count rising edges for the given duration and request the total at """Count rising edges for the given duration and request the total at

View File

@ -148,6 +148,13 @@ class DMAError(Exception):
artiq_builtin = True 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): class ClockFailure(Exception):
"""Raised when RTIO PLL has lost lock.""" """Raised when RTIO PLL has lost lock."""

View File

@ -21,7 +21,7 @@ class Fastino:
DAC updates synchronized to a frame edge. DAC updates synchronized to a frame edge.
The `log2_width=0` RTIO layout uses one DAC channel per RTIO address and a The `log2_width=0` RTIO layout uses one DAC channel per RTIO address and a
dense RTIO address space. The RTIO words are narrow. (32 bit) and dense RTIO address space. The RTIO words are narrow (32 bit) and
few-channel updates are efficient. There is the least amount of DAC state 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. tracking in kernels, at the cost of more DMA and RTIO data.
The setting here and in the RTIO PHY (gateware) must match. The setting here and in the RTIO PHY (gateware) must match.
@ -52,6 +52,10 @@ class Fastino:
assert self.core.ref_period == 1*ns assert self.core.ref_period == 1*ns
self.t_frame = int64(14*7*4) self.t_frame = int64(14*7*4)
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel @kernel
def init(self): def init(self):
"""Initialize the device. """Initialize the device.

View File

@ -25,6 +25,10 @@ class Grabber:
# ROI engine outputs for one video frame. # ROI engine outputs for one video frame.
self.sentinel = int32(int64(2**count_width)) 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 @kernel
def setup_roi(self, n, x0, y0, x1, y1): def setup_roi(self, n, x0, y0, x1, y1):
""" """

View File

@ -161,6 +161,7 @@ class I2CSwitch:
@kernel @kernel
def set(self, channel): def set(self, channel):
"""Enable one channel. """Enable one channel.
:param channel: channel number (0-7) :param channel: channel number (0-7)
""" """
i2c_switch_select(self.busno, self.address >> 1, 1 << channel) i2c_switch_select(self.busno, self.address >> 1, 1 << channel)

View File

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

View File

@ -31,16 +31,6 @@ WE = 1 << 24
# supported CPLD code version # supported CPLD code version
PROTO_REV_MATCH = 0x0 PROTO_REV_MATCH = 0x0
# almazny-specific data
ALMAZNY_REG_BASE = 0x0C
ALMAZNY_OE_SHIFT = 12
# higher SPI write divider to match almazny shift register timing
# min SER time before SRCLK rise = 125ns
# -> div=32 gives 125ns for data before clock rise
# works at faster dividers too but could be less reliable
ALMAZNY_SPIT_WR = 32
class Mirny: class Mirny:
""" """
@ -177,106 +167,3 @@ class Mirny:
if length < 32: if length < 32:
data <<= 32 - length data <<= 32 - length
self.bus.write(data) self.bus.write(data)
class Almazny:
"""
Almazny (High frequency mezzanine board for Mirny)
:param host_mirny - Mirny device Almazny is connected to
"""
def __init__(self, dmgr, host_mirny):
self.mirny_cpld = dmgr.get(host_mirny)
self.att_mu = [0x3f] * 4
self.channel_sw = [0] * 4
self.output_enable = False
@kernel
def init(self):
self.output_toggle(self.output_enable)
@kernel
def att_to_mu(self, att):
"""
Convert an attenuator setting in dB to machine units.
:param att: attenuator setting in dB [0-31.5]
:return: attenuator setting in machine units
"""
mu = round(att * 2.0)
if mu > 63 or mu < 0:
raise ValueError("Invalid Almazny attenuator settings!")
return mu
@kernel
def mu_to_att(self, att_mu):
"""
Convert a digital attenuator setting to dB.
:param att_mu: attenuator setting in machine units
:return: attenuator setting in dB
"""
return att_mu / 2
@kernel
def set_att(self, channel, att, rf_switch=True):
"""
Sets attenuators on chosen shift register (channel).
:param channel - index of the register [0-3]
:param att_mu - attenuation setting in dBm [0-31.5]
:param rf_switch - rf switch (bool)
"""
self.set_att_mu(channel, self.att_to_mu(att), rf_switch)
@kernel
def set_att_mu(self, channel, att_mu, rf_switch=True):
"""
Sets attenuators on chosen shift register (channel).
:param channel - index of the register [0-3]
:param att_mu - attenuation setting in machine units [0-63]
:param rf_switch - rf switch (bool)
"""
self.channel_sw[channel] = 1 if rf_switch else 0
self.att_mu[channel] = att_mu
self._update_register(channel)
@kernel
def output_toggle(self, oe):
"""
Toggles output on all shift registers on or off.
:param oe - toggle output enable (bool)
"""
self.output_enable = oe
cfg_reg = self.mirny_cpld.read_reg(1)
en = 1 if self.output_enable else 0
delay(100 * us)
new_reg = (en << ALMAZNY_OE_SHIFT) | (cfg_reg & 0x3FF)
self.mirny_cpld.write_reg(1, new_reg)
delay(100 * us)
@kernel
def _flip_mu_bits(self, mu):
# in this form MSB is actually 0.5dB attenuator
# unnatural for users, so we flip the six bits
return (((mu & 0x01) << 5)
| ((mu & 0x02) << 3)
| ((mu & 0x04) << 1)
| ((mu & 0x08) >> 1)
| ((mu & 0x10) >> 3)
| ((mu & 0x20) >> 5))
@kernel
def _update_register(self, ch):
self.mirny_cpld.write_ext(
ALMAZNY_REG_BASE + ch,
8,
self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6),
ALMAZNY_SPIT_WR
)
delay(100 * us)
@kernel
def _update_all_registers(self):
for i in range(4):
self._update_register(i)

View File

@ -9,6 +9,10 @@ from artiq.coredevice.trf372017 import TRF372017
PHASER_BOARD_ID = 19 PHASER_BOARD_ID = 19
PHASER_GW_BASE = 1
PHASER_GW_MIQRO = 2
PHASER_ADDR_BOARD_ID = 0x00 PHASER_ADDR_BOARD_ID = 0x00
PHASER_ADDR_HW_REV = 0x01 PHASER_ADDR_HW_REV = 0x01
PHASER_ADDR_GW_REV = 0x02 PHASER_ADDR_GW_REV = 0x02
@ -40,6 +44,20 @@ PHASER_ADDR_DUC1_P = 0x26
PHASER_ADDR_DAC1_DATA = 0x28 PHASER_ADDR_DAC1_DATA = 0x28
PHASER_ADDR_DAC1_TEST = 0x2c PHASER_ADDR_DAC1_TEST = 0x2c
# servo registers
PHASER_ADDR_SERVO_CFG0 = 0x30
PHASER_ADDR_SERVO_CFG1 = 0x31
# 0x32 - 0x71 servo coefficients + offset data
PHASER_ADDR_SERVO_DATA_BASE = 0x32
# 0x72 - 0x78 Miqro channel profile/window memories
PHASER_ADDR_MIQRO_MEM_ADDR = 0x72
PHASER_ADDR_MIQRO_MEM_DATA = 0x74
# Miqro profile memory select
PHASER_MIQRO_SEL_PROFILE = 1 << 14
PHASER_SEL_DAC = 1 << 0 PHASER_SEL_DAC = 1 << 0
PHASER_SEL_TRF0 = 1 << 1 PHASER_SEL_TRF0 = 1 << 1
PHASER_SEL_TRF1 = 1 << 2 PHASER_SEL_TRF1 = 1 << 2
@ -58,6 +76,11 @@ PHASER_DAC_SEL_TEST = 1
PHASER_HW_REV_VARIANT = 1 << 4 PHASER_HW_REV_VARIANT = 1 << 4
SERVO_COEFF_WIDTH = 16
SERVO_DATA_WIDTH = 16
SERVO_COEFF_SHIFT = 14
SERVO_T_CYCLE = (32+12+192+24+4)*ns # Must match gateware ADC parameters
class Phaser: class Phaser:
"""Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver. """Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver.
@ -65,6 +88,26 @@ class Phaser:
Phaser contains a 4 channel, 1 GS/s DAC chip with integrated upconversion, Phaser contains a 4 channel, 1 GS/s DAC chip with integrated upconversion,
quadrature modulation compensation and interpolation features. quadrature modulation compensation and interpolation features.
The coredevice RTIO PHY and the Phaser gateware come in different modes
that have different features. Phaser mode and coredevice PHY mode are both
selected at their respective gateware compile-time and need to match.
=============== ============== ===================================
Phaser gateware Coredevice PHY Features per :class:`PhaserChannel`
=============== ============== ===================================
Base <= v0.5 Base Base (5 :class:`PhaserOscillator`)
Base >= v0.6 Base Base + Servo
Miqro >= v0.6 Miqro :class:`Miqro`
=============== ============== ===================================
The coredevice driver (this class and :class:`PhaserChannel`) exposes
the superset of all functionality regardless of the Coredevice RTIO PHY
or Phaser gateware modes. This is to evade type unification limitations.
Features absent in Coredevice PHY/Phaser gateware will not work and
should not be accessed.
**Base mode**
The coredevice produces 2 IQ (in-phase and quadrature) data streams with 25 The coredevice produces 2 IQ (in-phase and quadrature) data streams with 25
MS/s and 14 bit per quadrature. Each data stream supports 5 independent MS/s and 14 bit per quadrature. Each data stream supports 5 independent
numerically controlled IQ oscillators (NCOs, DDSs with 32 bit frequency, 16 numerically controlled IQ oscillators (NCOs, DDSs with 32 bit frequency, 16
@ -95,6 +138,14 @@ class Phaser:
absolute phase with respect to other RTIO input and output events absolute phase with respect to other RTIO input and output events
(see `get_next_frame_mu()`). (see `get_next_frame_mu()`).
**Miqro mode**
See :class:`Miqro`
Here the DAC operates in 4x interpolation.
**Analog flow**
The four analog DAC outputs are passed through anti-aliasing filters. The four analog DAC outputs are passed through anti-aliasing filters.
In the baseband variant, the even/in-phase DAC channels feed 31.5 dB range In the baseband variant, the even/in-phase DAC channels feed 31.5 dB range
@ -112,6 +163,33 @@ class Phaser:
configured through a shared SPI bus that is accessed and controlled via configured through a shared SPI bus that is accessed and controlled via
FPGA registers. FPGA registers.
**Servo**
Each phaser output channel features a servo to control the RF output amplitude
using feedback from an ADC. The servo consists of a first order IIR (infinite
impulse response) filter fed by the ADC and a multiplier that scales the I
and Q datastreams from the DUC by the IIR output. The IIR state is updated at
the 3.788 MHz ADC sampling rate.
Each channel IIR features 4 profiles, each consisting of the [b0, b1, a1] filter
coefficients as well as an output offset. The coefficients and offset can be
set for each profile individually and the profiles each have their own ``y0``,
``y1`` output registers (the ``x0``, ``x1`` inputs are shared). To avoid
transient effects, care should be taken to not update the coefficents in the
currently selected profile.
The servo can be en- or disabled for each channel. When disabled, the servo
output multiplier is simply bypassed and the datastream reaches the DAC unscaled.
The IIR output can be put on hold for each channel. In hold mode, the filter
still ingests samples and updates its input ``x0`` and ``x1`` registers, but
does not update the ``y0``, ``y1`` output registers.
After power-up the servo is disabled, in profile 0, with coefficients [0, 0, 0]
and hold is enabled. If older gateware without ther servo is loaded onto the
Phaser FPGA, the device simply behaves as if the servo is disabled and none of
the servo functions have any effect.
.. note:: Various register settings of the DAC and the quadrature .. note:: Various register settings of the DAC and the quadrature
upconverters are available to be modified through the `dac`, `trf0`, upconverters are available to be modified through the `dac`, `trf0`,
`trf1` dictionaries. These can be set through the device database `trf1` dictionaries. These can be set through the device database
@ -151,7 +229,7 @@ class Phaser:
"dac_mmap"} "dac_mmap"}
def __init__(self, dmgr, channel_base, miso_delay=1, tune_fifo_offset=True, def __init__(self, dmgr, channel_base, miso_delay=1, tune_fifo_offset=True,
clk_sel=0, sync_dly=0, dac=None, trf0=None, trf1=None, clk_sel=0, sync_dly=0, dac=None, trf0=None, trf1=None, gw_rev=PHASER_GW_BASE,
core_device="core"): core_device="core"):
self.channel_base = channel_base self.channel_base = channel_base
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
@ -165,12 +243,25 @@ class Phaser:
self.clk_sel = clk_sel self.clk_sel = clk_sel
self.tune_fifo_offset = tune_fifo_offset self.tune_fifo_offset = tune_fifo_offset
self.sync_dly = sync_dly self.sync_dly = sync_dly
self.gw_rev = gw_rev # verified in init()
self.dac_mmap = DAC34H84(dac).get_mmap() self.dac_mmap = DAC34H84(dac).get_mmap()
self.channel = [PhaserChannel(self, ch, trf) self.channel = [PhaserChannel(self, ch, trf)
for ch, trf in enumerate([trf0, trf1])] for ch, trf in enumerate([trf0, trf1])]
@staticmethod
def get_rtio_channels(channel_base, gw_rev=PHASER_GW_BASE, **kwargs):
if gw_rev == PHASER_GW_MIQRO:
return [(channel_base, "base"), (channel_base + 1, "ch0"), (channel_base + 2, "ch1")]
elif gw_rev == PHASER_GW_BASE:
return [(channel_base, "base"),
(channel_base + 1, "ch0 frequency"),
(channel_base + 2, "ch0 phase amplitude"),
(channel_base + 3, "ch1 frequency"),
(channel_base + 4, "ch1 phase amplitude")]
raise ValueError("invalid gw_rev `{}`".format(gw_rev))
@kernel @kernel
def init(self, debug=False): def init(self, debug=False):
"""Initialize the board. """Initialize the board.
@ -190,8 +281,9 @@ class Phaser:
gw_rev = self.read8(PHASER_ADDR_GW_REV) gw_rev = self.read8(PHASER_ADDR_GW_REV)
if debug: if debug:
print("gw_rev:", gw_rev) print("gw_rev:", self.gw_rev)
self.core.break_realtime() self.core.break_realtime()
assert gw_rev == self.gw_rev
delay(.1*ms) # slack delay(.1*ms) # slack
# allow a few errors during startup and alignment since boot # allow a few errors during startup and alignment since boot
@ -237,7 +329,7 @@ class Phaser:
for data in self.dac_mmap: for data in self.dac_mmap:
self.dac_write(data >> 16, data) self.dac_write(data >> 16, data)
delay(40*us) delay(120*us)
self.dac_sync() self.dac_sync()
delay(40*us) delay(40*us)
@ -308,34 +400,40 @@ class Phaser:
if channel.get_att_mu() != 0x5a: if channel.get_att_mu() != 0x5a:
raise ValueError("attenuator test failed") raise ValueError("attenuator test failed")
delay(.1*ms) delay(.1*ms)
channel.set_att_mu(0x00) # minimum attenuation channel.set_att_mu(0x00) # maximum attenuation
# test oscillators and DUC channel.set_servo(profile=0, enable=0, hold=1)
for i in range(len(channel.oscillator)):
oscillator = channel.oscillator[i] if self.gw_rev == PHASER_GW_BASE:
asf = 0 # test oscillators and DUC
if i == 0: for i in range(len(channel.oscillator)):
asf = 0x7fff oscillator = channel.oscillator[i]
# 6pi/4 phase asf = 0
oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=1) if i == 0:
asf = 0x7fff
# 6pi/4 phase
oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=1)
delay(1*us)
# 3pi/4
channel.set_duc_phase_mu(0x6000)
channel.set_duc_cfg(select=0, clr=1)
self.duc_stb()
delay(.1*ms) # settle link, pipeline and impulse response
data = channel.get_dac_data()
delay(1*us) delay(1*us)
# 3pi/4 channel.oscillator[0].set_amplitude_phase_mu(asf=0, pow=0xc000,
channel.set_duc_phase_mu(0x6000) clr=1)
channel.set_duc_cfg(select=0, clr=1) delay(.1*ms)
self.duc_stb() sqrt2 = 0x5a81 # 0x7fff/sqrt(2)
delay(.1*ms) # settle link, pipeline and impulse response data_i = data & 0xffff
data = channel.get_dac_data() data_q = (data >> 16) & 0xffff
delay(1*us) # allow ripple
channel.oscillator[0].set_amplitude_phase_mu(asf=0, pow=0xc000, if (data_i < sqrt2 - 30 or data_i > sqrt2 or
clr=1) abs(data_i - data_q) > 2):
delay(.1*ms) raise ValueError("DUC+oscillator phase/amplitude test failed")
sqrt2 = 0x5a81 # 0x7fff/sqrt(2)
data_i = data & 0xffff if self.gw_rev == PHASER_GW_MIQRO:
data_q = (data >> 16) & 0xffff channel.miqro.reset()
# allow ripple
if (data_i < sqrt2 - 30 or data_i > sqrt2 or
abs(data_i - data_q) > 2):
raise ValueError("DUC+oscillator phase/amplitude test failed")
if is_baseband: if is_baseband:
continue continue
@ -382,6 +480,12 @@ class Phaser:
response = rtio_input_data(self.channel_base) response = rtio_input_data(self.channel_base)
return response >> self.miso_delay return response >> self.miso_delay
@kernel
def write16(self, addr, data: TInt32):
"""Write 16 bit to a sequence of FPGA registers."""
self.write8(addr, data >> 8)
self.write8(addr + 1, data)
@kernel @kernel
def write32(self, addr, data: TInt32): def write32(self, addr, data: TInt32):
"""Write 32 bit to a sequence of FPGA registers.""" """Write 32 bit to a sequence of FPGA registers."""
@ -616,7 +720,7 @@ class Phaser:
.. note:: Synchronising the NCO clears the phase-accumulator .. note:: Synchronising the NCO clears the phase-accumulator
""" """
config1f = self.dac_read(0x1f) config1f = self.dac_read(0x1f)
delay(.1*ms) delay(.4*ms)
self.dac_write(0x1f, config1f & ~int32(1 << 1)) self.dac_write(0x1f, config1f & ~int32(1 << 1))
self.dac_write(0x1f, config1f | (1 << 1)) self.dac_write(0x1f, config1f | (1 << 1))
@ -736,6 +840,8 @@ class Phaser:
if good & (1 << o): if good & (1 << o):
sum += o sum += o
count += 1 count += 1
if count == 0:
raise ValueError("no good fifo offset")
best = ((sum // count) + offset) % 8 best = ((sum // count) + offset) % 8
self.dac_write(0x09, (config9 & 0x1fff) | (best << 13)) self.dac_write(0x09, (config9 & 0x1fff) | (best << 13))
return best return best
@ -746,8 +852,9 @@ class PhaserChannel:
A Phaser channel contains: A Phaser channel contains:
* multiple oscillators (in the coredevice phy), * multiple :class:`PhaserOscillator` (in the coredevice phy),
* an interpolation chain and digital upconverter (DUC) on Phaser, * an interpolation chain and digital upconverter (DUC) on Phaser,
* a :class:`Miqro` instance on Phaser,
* several channel-specific settings in the DAC: * several channel-specific settings in the DAC:
* quadrature modulation compensation QMC * quadrature modulation compensation QMC
@ -759,6 +866,7 @@ class PhaserChannel:
Attributes: Attributes:
* :attr:`oscillator`: List of five :class:`PhaserOscillator`. * :attr:`oscillator`: List of five :class:`PhaserOscillator`.
* :attr:`miqro`: A :class:`Miqro`.
.. note:: The amplitude sum of the oscillators must be less than one to .. note:: The amplitude sum of the oscillators must be less than one to
avoid clipping or overflow. If any of the DDS or DUC frequencies are avoid clipping or overflow. If any of the DDS or DUC frequencies are
@ -771,6 +879,8 @@ class PhaserChannel:
changes in oscillator parameters, the overshoot can lead to clipping changes in oscillator parameters, the overshoot can lead to clipping
or overflow after the interpolation. Either band-limit any changes or overflow after the interpolation. Either band-limit any changes
in the oscillator parameters or back off the amplitude sufficiently. in the oscillator parameters or back off the amplitude sufficiently.
Miqro is not affected by this. But both the oscillators and Miqro can
be affected by intrinsic overshoot of the interpolator on the DAC.
""" """
kernel_invariants = {"index", "phaser", "trf_mmap"} kernel_invariants = {"index", "phaser", "trf_mmap"}
@ -780,6 +890,7 @@ class PhaserChannel:
self.trf_mmap = TRF372017(trf).get_mmap() self.trf_mmap = TRF372017(trf).get_mmap()
self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)] self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)]
self.miqro = Miqro(self)
@kernel @kernel
def get_dac_data(self) -> TInt32: def get_dac_data(self) -> TInt32:
@ -1039,6 +1150,133 @@ class PhaserChannel:
data = data ^ ((1 << 12) | (1 << 13)) data = data ^ ((1 << 12) | (1 << 13))
self.trf_write(data) self.trf_write(data)
@kernel
def set_servo(self, profile=0, enable=0, hold=0):
"""Set the servo configuration.
:param enable: 1 to enable servo, 0 to disable servo (default). If disabled,
the servo is bypassed and hold is enforced since the control loop is broken.
:param hold: 1 to hold the servo IIR filter output constant, 0 for normal operation.
:param profile: Profile index to select for channel. (0 to 3)
"""
if (profile < 0) or (profile > 3):
raise ValueError("invalid profile index")
addr = PHASER_ADDR_SERVO_CFG0 + self.index
# enforce hold if the servo is disabled
data = (profile << 2) | (((hold | ~enable) & 1) << 1) | (enable & 1)
self.phaser.write8(addr, data)
@kernel
def set_iir_mu(self, profile, b0, b1, a1, offset):
"""Load a servo profile consiting of the three filter coefficients and an output offset.
Avoid setting the IIR parameters of the currently active profile.
The recurrence relation is (all data signed and MSB aligned):
.. math::
a_0 y_n = a_1 y_{n - 1} + b_0 x_n + b_1 x_{n - 1} + o
Where:
* :math:`y_n` and :math:`y_{n-1}` are the current and previous
filter outputs, clipped to :math:`[0, 1[`.
* :math:`x_n` and :math:`x_{n-1}` are the current and previous
filter inputs in :math:`[-1, 1[`.
* :math:`o` is the offset
* :math:`a_0` is the normalization factor :math:`2^{14}`
* :math:`a_1` is the feedback gain
* :math:`b_0` and :math:`b_1` are the feedforward gains for the two
delays
.. seealso:: :meth:`set_iir`
:param profile: Profile to set (0 to 3)
:param b0: b0 filter coefficient (16 bit signed)
:param b1: b1 filter coefficient (16 bit signed)
:param a1: a1 filter coefficient (16 bit signed)
:param offset: Output offset (16 bit signed)
"""
if (profile < 0) or (profile > 3):
raise ValueError("invalid profile index")
# 32 byte-sized data registers per channel and 8 (2 bytes * (3 coefficients + 1 offset)) registers per profile
addr = PHASER_ADDR_SERVO_DATA_BASE + (8 * profile) + (self.index * 32)
for data in [b0, b1, a1, offset]:
self.phaser.write16(addr, data)
addr += 2
@kernel
def set_iir(self, profile, kp, ki=0., g=0., x_offset=0., y_offset=0.):
"""Set servo profile IIR coefficients.
Avoid setting the IIR parameters of the currently active profile.
Gains are given in units of output full per scale per input full scale.
.. note:: Due to inherent constraints of the fixed point datatypes and IIR
filters, the ``x_offset`` (setpoint) resolution depends on the selected
gains. Low ``ki`` gains will lead to a low ``x_offset`` resolution.
The transfer function is (up to time discretization and
coefficient quantization errors):
.. math::
H(s) = k_p + \\frac{k_i}{s + \\frac{k_i}{g}}
Where:
* :math:`s = \\sigma + i\\omega` is the complex frequency
* :math:`k_p` is the proportional gain
* :math:`k_i` is the integrator gain
* :math:`g` is the integrator gain limit
:param profile: Profile number (0-3)
:param kp: Proportional gain. This is usually negative (closed
loop, positive ADC voltage, positive setpoint). When 0, this
implements a pure I controller.
:param ki: Integrator gain (rad/s). Equivalent to the gain at 1 Hz.
When 0 (the default) this implements a pure P controller.
Same sign as ``kp``.
:param g: Integrator gain limit (1). When 0 (the default) the
integrator gain limit is infinite. Same sign as ``ki``.
:param x_offset: IIR input offset. Used as the negative
setpoint when stabilizing to a desired input setpoint. Will
be converted to an equivalent output offset and added to y_offset.
:param y_offset: IIR output offset.
"""
NORM = 1 << SERVO_COEFF_SHIFT
COEFF_MAX = 1 << SERVO_COEFF_WIDTH - 1
DATA_MAX = 1 << SERVO_DATA_WIDTH - 1
kp *= NORM
if ki == 0.:
# pure P
a1 = 0
b1 = 0
b0 = int(round(kp))
else:
# I or PI
ki *= NORM*SERVO_T_CYCLE/2.
if g == 0.:
c = 1.
a1 = NORM
else:
c = 1./(1. + ki/(g*NORM))
a1 = int(round((2.*c - 1.)*NORM))
b0 = int(round(kp + ki*c))
b1 = int(round(kp + (ki - 2.*kp)*c))
if b1 == -b0:
raise ValueError("low integrator gain and/or gain limit")
if (b0 >= COEFF_MAX or b0 < -COEFF_MAX or
b1 >= COEFF_MAX or b1 < -COEFF_MAX):
raise ValueError("high gains")
forward_gain = (b0 + b1) * (1 << SERVO_DATA_WIDTH - 1 - SERVO_COEFF_SHIFT)
effective_offset = int(round(DATA_MAX * y_offset + forward_gain * x_offset))
self.set_iir_mu(profile, b0, b1, a1, effective_offset)
class PhaserOscillator: class PhaserOscillator:
"""Phaser IQ channel oscillator (NCO/DDS). """Phaser IQ channel oscillator (NCO/DDS).
@ -1096,3 +1334,305 @@ class PhaserOscillator:
raise ValueError("amplitude out of bounds") raise ValueError("amplitude out of bounds")
pow = int32(round(phase*(1 << 16))) pow = int32(round(phase*(1 << 16)))
self.set_amplitude_phase_mu(asf, pow, clr) self.set_amplitude_phase_mu(asf, pow, clr)
class Miqro:
"""
Miqro pulse generator.
A Miqro instance represents one RF output. The DSP components are fully
contained in the Phaser gateware. The output is generated by with
the following data flow:
**Oscillators**
* There are n_osc = 16 oscillators with oscillator IDs 0..n_osc-1.
* Each oscillator outputs one tone at any given time
* I/Q (quadrature, a.k.a. complex) 2x16 bit signed data
at tau = 4 ns sample intervals, 250 MS/s, Nyquist 125 MHz, bandwidth 200 MHz
(from f = -100..+100 MHz, taking into account the interpolation anti-aliasing
filters in subsequent interpolators),
* 32 bit frequency (f) resolution (~ 1/16 Hz),
* 16 bit unsigned amplitude (a) resolution
* 16 bit phase offset (p) resolution
* The output phase p' of each oscillator at time t (boot/reset/initialization of the
device at t=0) is then p' = f*t + p (mod 1 turn) where f and p are the (currently
active) profile frequency and phase offset.
* Note: The terms "phase coherent" and "phase tracking" are defined to refer to this
choice of oscillator output phase p'. Note that the phase offset p is not relative to
(on top of previous phase/profiles/oscillator history).
It is "absolute" in the sense that frequency f and phase offset p fully determine
oscillator output phase p' at time t. This is unlike typical DDS behavior.
* Frequency, phase, and amplitude of each oscillator are configurable by selecting one of
n_profile = 32 profiles 0..n_profile-1. This selection is fast and can be done for
each pulse. The phase coherence defined above is guaranteed for each
profile individually.
* Note: one profile per oscillator (usually profile index 0) should be reserved
for the NOP (no operation, identity) profile, usually with zero amplitude.
* Data for each profile for each oscillator can be configured
individually. Storing profile data should be considered "expensive".
* Note: The annotation that some operation is "expensive" does not mean it is
impossible, just that it may take a significant amount of time and
resources to execute such that it may be impractical when used often or
during fast pulse sequences. They are intended for use in calibration and
initialization.
**Summation**
* The oscillator outputs are added together (wrapping addition).
* The user must ensure that the sum of oscillators outputs does not exceed the
data range. In general that means that the sum of the amplitudes must not
exceed one.
**Shaper**
* The summed complex output stream is then multiplied with a the complex-valued
output of a triggerable shaper.
* Triggering the shaper corresponds to passing a pulse from all oscillators to
the RF output.
* Selected profiles become active simultaneously (on the same output sample) when
triggering the shaper with the first shaper output sample.
* The shaper reads (replays) window samples from a memory of size n_window = 1 << 10.
* The window memory can be segmented by choosing different start indices
to support different windows.
* Each window memory segment starts with a header determining segment
length and interpolation parameters.
* The window samples are interpolated by a factor (rate change) between 1 and
r = 1 << 12.
* The interpolation order is constant, linear, quadratic, or cubic. This
corresponds to interpolation modes from rectangular window (1st order CIC)
or zero order hold) to Parzen window (4th order CIC or cubic spline).
* This results in support for single shot pulse lengths (envelope support) between
tau and a bit more than r * n_window * tau = (1 << 12 + 10) tau ~ 17 ms.
* Windows can be configured to be head-less and/or tail-less, meaning, they
do not feed zero-amplitude samples into the shaper before and after
each window respectively. This is used to implement pulses with arbitrary
length or CW output.
**Overall properties**
* The DAC may upconvert the signal by applying a frequency offset f1 with
phase p1.
* In the Upconverter Phaser variant, the analog quadrature upconverter
applies another frequency of f2 and phase p2.
* The resulting phase of the signal from one oscillator at the SMA output is
(f + f1 + f2)*t + p + s(t - t0) + p1 + p2 (mod 1 turn)
where s(t - t0) is the phase of the interpolated
shaper output, and t0 is the trigger time (fiducial of the shaper).
Unsurprisingly the frequency is the derivative of the phase.
* Group delays between pulse parameter updates are matched across oscillators,
shapers, and channels.
* The minimum time to change profiles and phase offsets is ~128 ns (estimate, TBC).
This is the minimum pulse interval.
The sustained pulse rate of the RTIO PHY/Fastlink is one pulse per Fastlink frame
(may be increased, TBC).
"""
def __init__(self, channel):
self.channel = channel
self.base_addr = (self.channel.phaser.channel_base + 1 +
self.channel.index) << 8
@kernel
def reset(self):
"""Establish no-output profiles and no-output window and execute them.
This establishes the first profile (index 0) on all oscillators as zero
amplitude, creates a trivial window (one sample with zero amplitude,
minimal interpolation), and executes a corresponding pulse.
"""
for osc in range(16):
self.set_profile_mu(osc, profile=0, ftw=0, asf=0)
delay(20*us)
self.set_window_mu(start=0, iq=[0], order=0)
self.pulse(window=0, profiles=[0])
@kernel
def set_profile_mu(self, oscillator, profile, ftw, asf, pow_=0):
"""Store an oscillator profile (machine units).
:param oscillator: Oscillator index (0 to 15)
:param profile: Profile index (0 to 31)
:param ftw: Frequency tuning word (32 bit signed integer on a 250 MHz clock)
:param asf: Amplitude scale factor (16 bit unsigned integer)
:param pow_: Phase offset word (16 bit integer)
"""
if oscillator >= 16:
raise ValueError("invalid oscillator index")
if profile >= 32:
raise ValueError("invalid profile index")
self.channel.phaser.write16(PHASER_ADDR_MIQRO_MEM_ADDR,
(self.channel.index << 15) | PHASER_MIQRO_SEL_PROFILE |
(oscillator << 6) | (profile << 1))
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA, ftw)
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA,
(asf & 0xffff) | (pow_ << 16))
@kernel
def set_profile(self, oscillator, profile, frequency, amplitude, phase=0.):
"""Store an oscillator profile.
:param oscillator: Oscillator index (0 to 15)
:param profile: Profile index (0 to 31)
:param frequency: Frequency in Hz (passband -100 to 100 MHz).
Interpreted in the Nyquist sense, i.e. aliased.
:param amplitude: Amplitude in units of full scale (0. to 1.)
:param phase: Phase in turns. See :class:`Miqro` for a definition of
phase in this context.
:return: The quantized 32 bit frequency tuning word
"""
ftw = int32(round(frequency*((1 << 30)/(62.5*MHz))))
asf = int32(round(amplitude*0xffff))
if asf < 0 or asf > 0xffff:
raise ValueError("amplitude out of bounds")
pow_ = int32(round(phase*(1 << 16)))
self.set_profile_mu(oscillator, profile, ftw, asf, pow_)
return ftw
@kernel
def set_window_mu(self, start, iq, rate=1, shift=0, order=3, head=1, tail=1):
"""Store a window segment (machine units)
:param start: Window start address (0 to 0x3ff)
:param iq: List of IQ window samples. Each window sample is an integer
containing the signed I part in the 16 LSB and the signed Q part in
the 16 MSB. The maximum window length is 0x3fe. The user must
ensure that this window does not overlap with other windows in the
memory.
:param rate: Interpolation rate change (1 to 1 << 12)
:param shift: Interpolator amplitude gain compensation in powers of 2 (0 to 63)
:param order: Interpolation order from 0 (corresponding to
constant/rectangular window/zero-order-hold/1st order CIC interpolation)
to 3 (corresponding to cubic/Parzen window/4th order CIC interpolation)
:param head: Update the interpolator settings and clear its state at the start
of the window. This also implies starting the envelope from zero.
:param tail: Feed zeros into the interpolator after the window samples.
In the absence of further pulses this will return the output envelope
to zero with the chosen interpolation.
:return: Next available window memory address after this segment.
"""
if start >= 1 << 10:
raise ValueError("start out of bounds")
if len(iq) >= 1 << 10:
raise ValueError("window length out of bounds")
if rate < 1 or rate > 1 << 12:
raise ValueError("rate out of bounds")
if shift > 0x3f:
raise ValueError("shift out of bounds")
if order > 3:
raise ValueError("order out of bounds")
self.channel.phaser.write16(PHASER_ADDR_MIQRO_MEM_ADDR,
(self.channel.index << 15) | start)
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA,
(len(iq) & 0x3ff) |
((rate - 1) << 10) |
(shift << 22) |
(order << 28) |
((head & 1) << 30) |
((tail & 1) << 31)
)
for iqi in iq:
self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA, iqi)
delay(20*us) # slack for long windows
return (start + 1 + len(iq)) & 0x3ff
@kernel
def set_window(self, start, iq, period=4*ns, order=3, head=1, tail=1):
"""Store a window segment
:param start: Window start address (0 to 0x3ff)
:param iq: List of IQ window samples. Each window sample is a pair of
two float numbers -1 to 1, one for each I and Q in units of full scale.
The maximum window length is 0x3fe. The user must ensure that this window
does not overlap with other windows in the memory.
:param period: Desired window sample period in SI units (4*ns to (4 << 12)*ns).
:param order: Interpolation order from 0 (corresponding to
constant/zero-order-hold/1st order CIC interpolation) to 3 (corresponding
to cubic/Parzen/4th order CIC interpolation)
:param head: Update the interpolator settings and clear its state at the start
of the window. This also implies starting the envelope from zero.
:param tail: Feed zeros into the interpolator after the window samples.
In the absence of further pulses this will return the output envelope
to zero with the chosen interpolation.
:return: Actual sample period in SI units
"""
rate = int32(round(period/(4*ns)))
gain = 1.
for _ in range(order):
gain *= rate
shift = 0
while gain >= 2.:
shift += 1
gain *= .5
scale = ((1 << 15) - 1)/gain
iq_mu = [
(int32(round(iqi[0]*scale)) & 0xffff) |
(int32(round(iqi[1]*scale)) << 16)
for iqi in iq
]
self.set_window_mu(start, iq_mu, rate, shift, order, head, tail)
return (len(iq) + order)*rate*4*ns
@kernel
def encode(self, window, profiles, data):
"""Encode window and profile selection
:param window: Window start address (0 to 0x3ff)
:param profiles: List of profile indices for the oscillators. Maximum
length 16. Unused oscillators will be set to profile 0.
:param data: List of integers to store the encoded data words into.
Unused entries will remain untouched. Must contain at least three
lements if all oscillators are used and should be initialized to
zeros.
:return: Number of words from `data` used.
"""
if len(profiles) > 16:
raise ValueError("too many oscillators")
if window > 0x3ff:
raise ValueError("window start out of bounds")
data[0] = window
word = 0
idx = 10
for profile in profiles:
if profile > 0x1f:
raise ValueError("profile out of bounds")
if idx > 32 - 5:
word += 1
idx = 0
data[word] |= profile << idx
idx += 5
return word + 1
@kernel
def pulse_mu(self, data):
"""Emit a pulse (encoded)
The pulse fiducial timing resolution is 4 ns.
:param data: List of up to 3 words containing an encoded MIQRO pulse as
returned by :meth:`encode`.
"""
word = len(data)
delay_mu(-8*word) # back shift to align
while word > 0:
word -= 1
delay_mu(8)
# final write sets pulse stb
rtio_output(self.base_addr + word, data[word])
@kernel
def pulse(self, window, profiles):
"""Emit a pulse
This encodes the window and profiles (see :meth:`encode`) and emits them
(see :meth:`pulse_mu`).
:param window: Window start address (0 to 0x3ff)
:param profiles: List of profile indices for the oscillators. Maximum
length 16. Unused oscillators will select profile 0.
"""
data = [0, 0, 0]
words = self.encode(window, profiles, data)
self.pulse_mu(data[:words])

View File

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

View File

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

View File

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

View File

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

View File

@ -72,6 +72,10 @@ class SPIMaster:
self.channel = channel self.channel = channel
self.update_xfer_duration_mu(div, length) self.update_xfer_duration_mu(div, length)
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@portable @portable
def frequency_to_div(self, f): def frequency_to_div(self, f):
"""Convert a SPI clock frequency to the closest SPI clock divider.""" """Convert a SPI clock frequency to the closest SPI clock divider."""
@ -273,9 +277,8 @@ class NRTSPIMaster:
def set_config_mu(self, flags=0, length=8, div=6, cs=1): def set_config_mu(self, flags=0, length=8, div=6, cs=1):
"""Set the ``config`` register. """Set the ``config`` register.
Note that the non-realtime SPI cores are usually clocked by the system In many cases, the SPI configuration is already set by the firmware
clock and not the RTIO clock. In many cases, the SPI configuration is and you do not need to call this method.
already set by the firmware and you do not need to call this method.
""" """
spi_set_config(self.busno, flags, length, div, cs) spi_set_config(self.busno, flags, length, div, cs)

View File

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

View File

@ -23,12 +23,12 @@ def y_mu_to_full_scale(y):
@portable @portable
def adc_mu_to_volts(x, gain): def adc_mu_to_volts(x, gain, corrected_fs=True):
"""Convert servo ADC data from machine units to Volt.""" """Convert servo ADC data from machine units to Volt."""
val = (x >> 1) & 0xffff val = (x >> 1) & 0xffff
mask = 1 << 15 mask = 1 << 15
val = -(val & mask) + (val & ~mask) val = -(val & mask) + (val & ~mask)
return sampler.adc_mu_to_volt(val, gain) return sampler.adc_mu_to_volt(val, gain, corrected_fs)
class SUServo: class SUServo:
@ -62,14 +62,15 @@ class SUServo:
:param gains: Initial value for PGIA gains shift register :param gains: Initial value for PGIA gains shift register
(default: 0x0000). Knowledge of this state is not transferred (default: 0x0000). Knowledge of this state is not transferred
between experiments. between experiments.
:param sampler_hw_rev: Sampler's revision string
:param core_device: Core device name :param core_device: Core device name
""" """
kernel_invariants = {"channel", "core", "pgia", "cplds", "ddses", kernel_invariants = {"channel", "core", "pgia", "cplds", "ddses",
"ref_period_mu"} "ref_period_mu", "corrected_fs"}
def __init__(self, dmgr, channel, pgia_device, def __init__(self, dmgr, channel, pgia_device,
cpld_devices, dds_devices, cpld_devices, dds_devices,
gains=0x0000, core_device="core"): gains=0x0000, sampler_hw_rev="v2.2", core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
self.pgia = dmgr.get(pgia_device) self.pgia = dmgr.get(pgia_device)
@ -81,8 +82,13 @@ class SUServo:
self.gains = gains self.gains = gains
self.ref_period_mu = self.core.seconds_to_mu( self.ref_period_mu = self.core.seconds_to_mu(
self.core.coarse_ref_period) self.core.coarse_ref_period)
self.corrected_fs = sampler.Sampler.use_corrected_fs(sampler_hw_rev)
assert self.ref_period_mu == self.core.ref_multiplier assert self.ref_period_mu == self.core.ref_multiplier
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel @kernel
def init(self): def init(self):
"""Initialize the servo, Sampler and both Urukuls. """Initialize the servo, Sampler and both Urukuls.
@ -234,7 +240,7 @@ class SUServo:
""" """
val = self.get_adc_mu(channel) val = self.get_adc_mu(channel)
gain = (self.gains >> (channel*2)) & 0b11 gain = (self.gains >> (channel*2)) & 0b11
return adc_mu_to_volts(val, gain) return adc_mu_to_volts(val, gain, self.corrected_fs)
class Channel: class Channel:
@ -255,6 +261,10 @@ class Channel:
self.servo.channel) self.servo.channel)
self.dds = self.servo.ddses[self.servo_channel // 4] self.dds = self.servo.ddses[self.servo_channel // 4]
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel @kernel
def set(self, en_out, en_iir=0, profile=0): def set(self, en_out, en_iir=0, profile=0):
"""Operate channel. """Operate channel.

View File

@ -36,6 +36,10 @@ class TTLOut:
self.channel = channel self.channel = channel
self.target_o = channel << 8 self.target_o = channel << 8
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel @kernel
def output(self): def output(self):
pass pass
@ -128,6 +132,10 @@ class TTLInOut:
self.target_sens = (channel << 8) + 2 self.target_sens = (channel << 8) + 2
self.target_sample = (channel << 8) + 3 self.target_sample = (channel << 8) + 3
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@kernel @kernel
def set_oe(self, oe): def set_oe(self, oe):
rtio_output(self.target_oe, 1 if oe else 0) rtio_output(self.target_oe, 1 if oe else 0)
@ -465,6 +473,10 @@ class TTLClockGen:
self.acc_width = numpy.int64(acc_width) self.acc_width = numpy.int64(acc_width)
@staticmethod
def get_rtio_channels(channel, **kwargs):
return [(channel, None)]
@portable @portable
def frequency_to_ftw(self, frequency): def frequency_to_ftw(self, frequency):
"""Returns the frequency tuning word corresponding to the given """Returns the frequency tuning word corresponding to the given

View File

@ -5,7 +5,7 @@ import numpy as np
from PyQt5 import QtCore, QtWidgets from PyQt5 import QtCore, QtWidgets
from sipyco import pyon from sipyco import pyon
from artiq.tools import short_format, exc_to_warning from artiq.tools import scale_from_metadata, short_format, exc_to_warning
from artiq.gui.tools import LayoutWidget, QRecursiveFilterProxyModel from artiq.gui.tools import LayoutWidget, QRecursiveFilterProxyModel
from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.models import DictSyncTreeSepModel
from artiq.gui.scientific_spinbox import ScientificSpinBox from artiq.gui.scientific_spinbox import ScientificSpinBox
@ -14,92 +14,18 @@ from artiq.gui.scientific_spinbox import ScientificSpinBox
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
async def rename(key, newkey, value, dataset_ctl): async def rename(key, new_key, value, metadata, persist, dataset_ctl):
if key != newkey: if key != new_key:
await dataset_ctl.delete(key) await dataset_ctl.delete(key)
await dataset_ctl.set(newkey, value) await dataset_ctl.set(new_key, value, metadata=metadata, persist=persist)
class Editor(QtWidgets.QDialog): class CreateEditDialog(QtWidgets.QDialog):
def __init__(self, parent, dataset_ctl, key, value): def __init__(self, parent, dataset_ctl, key=None, value=None, metadata=None, persist=False):
QtWidgets.QDialog.__init__(self, parent=parent)
self.dataset_ctl = dataset_ctl
self.key = key
self.initial_type = type(value)
self.setWindowTitle("Edit dataset")
grid = QtWidgets.QGridLayout()
self.setLayout(grid)
grid.addWidget(QtWidgets.QLabel("Name:"), 0, 0)
self.name_widget = QtWidgets.QLineEdit()
self.name_widget.setText(key)
grid.addWidget(self.name_widget, 0, 1)
grid.addWidget(QtWidgets.QLabel("Value:"), 1, 0)
grid.addWidget(self.get_edit_widget(value), 1, 1)
buttons = QtWidgets.QDialogButtonBox(
QtWidgets.QDialogButtonBox.Ok | QtWidgets.QDialogButtonBox.Cancel)
grid.setRowStretch(2, 1)
grid.addWidget(buttons, 3, 0, 1, 2)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
def accept(self):
newkey = self.name_widget.text()
value = self.initial_type(self.get_edit_widget_value())
asyncio.ensure_future(rename(self.key, newkey, value, self.dataset_ctl))
QtWidgets.QDialog.accept(self)
def get_edit_widget(self, initial_value):
raise NotImplementedError
def get_edit_widget_value(self):
raise NotImplementedError
class NumberEditor(Editor):
def get_edit_widget(self, initial_value):
self.edit_widget = ScientificSpinBox()
self.edit_widget.setDecimals(13)
self.edit_widget.setPrecision()
self.edit_widget.setRelativeStep()
self.edit_widget.setValue(float(initial_value))
return self.edit_widget
def get_edit_widget_value(self):
return self.edit_widget.value()
class BoolEditor(Editor):
def get_edit_widget(self, initial_value):
self.edit_widget = QtWidgets.QCheckBox()
self.edit_widget.setChecked(bool(initial_value))
return self.edit_widget
def get_edit_widget_value(self):
return self.edit_widget.isChecked()
class StringEditor(Editor):
def get_edit_widget(self, initial_value):
self.edit_widget = QtWidgets.QLineEdit()
self.edit_widget.setText(initial_value)
return self.edit_widget
def get_edit_widget_value(self):
return self.edit_widget.text()
class Creator(QtWidgets.QDialog):
def __init__(self, parent, dataset_ctl):
QtWidgets.QDialog.__init__(self, parent=parent) QtWidgets.QDialog.__init__(self, parent=parent)
self.dataset_ctl = dataset_ctl self.dataset_ctl = dataset_ctl
self.setWindowTitle("Create dataset") self.setWindowTitle("Create dataset" if key is None else "Edit dataset")
grid = QtWidgets.QGridLayout() grid = QtWidgets.QGridLayout()
grid.setRowMinimumHeight(1, 40) grid.setRowMinimumHeight(1, 40)
grid.setColumnMinimumWidth(2, 60) grid.setColumnMinimumWidth(2, 60)
@ -117,9 +43,21 @@ class Creator(QtWidgets.QDialog):
grid.addWidget(self.data_type, 1, 2) grid.addWidget(self.data_type, 1, 2)
self.value_widget.textChanged.connect(self.dtype) self.value_widget.textChanged.connect(self.dtype)
grid.addWidget(QtWidgets.QLabel("Persist:"), 2, 0) grid.addWidget(QtWidgets.QLabel("Unit:"), 2, 0)
self.unit_widget = QtWidgets.QLineEdit()
grid.addWidget(self.unit_widget, 2, 1)
grid.addWidget(QtWidgets.QLabel("Scale:"), 3, 0)
self.scale_widget = QtWidgets.QLineEdit()
grid.addWidget(self.scale_widget, 3, 1)
grid.addWidget(QtWidgets.QLabel("Precision:"), 4, 0)
self.precision_widget = QtWidgets.QLineEdit()
grid.addWidget(self.precision_widget, 4, 1)
grid.addWidget(QtWidgets.QLabel("Persist:"), 5, 0)
self.box_widget = QtWidgets.QCheckBox() self.box_widget = QtWidgets.QCheckBox()
grid.addWidget(self.box_widget, 2, 1) grid.addWidget(self.box_widget, 5, 1)
self.ok = QtWidgets.QPushButton('&Ok') self.ok = QtWidgets.QPushButton('&Ok')
self.ok.setEnabled(False) self.ok.setEnabled(False)
@ -129,23 +67,62 @@ class Creator(QtWidgets.QDialog):
self.ok, QtWidgets.QDialogButtonBox.AcceptRole) self.ok, QtWidgets.QDialogButtonBox.AcceptRole)
self.buttons.addButton( self.buttons.addButton(
self.cancel, QtWidgets.QDialogButtonBox.RejectRole) self.cancel, QtWidgets.QDialogButtonBox.RejectRole)
grid.setRowStretch(3, 1) grid.setRowStretch(6, 1)
grid.addWidget(self.buttons, 4, 0, 1, 3) grid.addWidget(self.buttons, 7, 0, 1, 3, alignment=QtCore.Qt.AlignHCenter)
self.buttons.accepted.connect(self.accept) self.buttons.accepted.connect(self.accept)
self.buttons.rejected.connect(self.reject) self.buttons.rejected.connect(self.reject)
self.key = key
self.name_widget.setText(key)
value_edit_string = self.value_to_edit_string(value)
if metadata is not None:
scale = scale_from_metadata(metadata)
t = value.dtype if value is np.ndarray else type(value)
if scale != 1 and np.issubdtype(t, np.number):
# degenerates to float type
value_edit_string = self.value_to_edit_string(
np.float64(value / scale))
self.unit_widget.setText(metadata.get('unit', ''))
self.scale_widget.setText(str(metadata.get('scale', '')))
self.precision_widget.setText(str(metadata.get('precision', '')))
self.value_widget.setText(value_edit_string)
self.box_widget.setChecked(persist)
def accept(self): def accept(self):
key = self.name_widget.text() key = self.name_widget.text()
value = self.value_widget.text() value = self.value_widget.text()
persist = self.box_widget.isChecked() persist = self.box_widget.isChecked()
asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set( unit = self.unit_widget.text()
key, pyon.decode(value), persist))) scale = self.scale_widget.text()
precision = self.precision_widget.text()
metadata = {}
if unit != "":
metadata['unit'] = unit
if scale != "":
metadata['scale'] = float(scale)
if precision != "":
metadata['precision'] = int(precision)
scale = scale_from_metadata(metadata)
value = self.parse_edit_string(value)
t = value.dtype if value is np.ndarray else type(value)
if scale != 1 and np.issubdtype(t, np.number):
# degenerates to float type
value = np.float64(value * scale)
if self.key and self.key != key:
asyncio.ensure_future(exc_to_warning(rename(self.key, key, value, metadata, persist, self.dataset_ctl)))
else:
asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set(key, value, metadata=metadata, persist=persist)))
self.key = key
QtWidgets.QDialog.accept(self) QtWidgets.QDialog.accept(self)
def dtype(self): def dtype(self):
txt = self.value_widget.text() txt = self.value_widget.text()
try: try:
result = pyon.decode(txt) result = self.parse_edit_string(txt)
# ensure only pyon compatible types are permissable
pyon.encode(result)
except: except:
pixmap = self.style().standardPixmap( pixmap = self.style().standardPixmap(
QtWidgets.QStyle.SP_MessageBoxWarning) QtWidgets.QStyle.SP_MessageBoxWarning)
@ -155,6 +132,35 @@ class Creator(QtWidgets.QDialog):
self.data_type.setText(type(result).__name__) self.data_type.setText(type(result).__name__)
self.ok.setEnabled(True) self.ok.setEnabled(True)
@staticmethod
def parse_edit_string(s):
if s == "":
raise TypeError
_eval_dict = {
"__builtins__": {},
"array": np.array,
"null": np.nan,
"inf": np.inf
}
for t_ in pyon._numpy_scalar:
_eval_dict[t_] = eval("np.{}".format(t_), {"np": np})
return eval(s, _eval_dict, {})
@staticmethod
def value_to_edit_string(v):
t = type(v)
r = ""
if isinstance(v, np.generic):
r += t.__name__
r += "("
r += repr(v)
r += ")"
elif v is None:
return r
else:
r += repr(v)
return r
class Model(DictSyncTreeSepModel): class Model(DictSyncTreeSepModel):
def __init__(self, init): def __init__(self, init):
@ -166,13 +172,13 @@ class Model(DictSyncTreeSepModel):
if column == 1: if column == 1:
return "Y" if v[0] else "N" return "Y" if v[0] else "N"
elif column == 2: elif column == 2:
return short_format(v[1]) return short_format(v[1], v[2])
else: else:
raise ValueError raise ValueError
class DatasetsDock(QtWidgets.QDockWidget): class DatasetsDock(QtWidgets.QDockWidget):
def __init__(self, datasets_sub, dataset_ctl): def __init__(self, dataset_sub, dataset_ctl):
QtWidgets.QDockWidget.__init__(self, "Datasets") QtWidgets.QDockWidget.__init__(self, "Datasets")
self.setObjectName("Datasets") self.setObjectName("Datasets")
self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable | self.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable |
@ -212,7 +218,7 @@ class DatasetsDock(QtWidgets.QDockWidget):
self.table.addAction(delete_action) self.table.addAction(delete_action)
self.table_model = Model(dict()) self.table_model = Model(dict())
datasets_sub.add_setmodel_callback(self.set_model) dataset_sub.add_setmodel_callback(self.set_model)
def _search_datasets(self): def _search_datasets(self):
if hasattr(self, "table_model_filter"): if hasattr(self, "table_model_filter"):
@ -226,7 +232,7 @@ class DatasetsDock(QtWidgets.QDockWidget):
self.table.setModel(self.table_model_filter) self.table.setModel(self.table_model_filter)
def create_clicked(self): def create_clicked(self):
Creator(self, self.dataset_ctl).open() CreateEditDialog(self, self.dataset_ctl).open()
def edit_clicked(self): def edit_clicked(self):
idx = self.table.selectedIndexes() idx = self.table.selectedIndexes()
@ -234,19 +240,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
idx = self.table_model_filter.mapToSource(idx[0]) idx = self.table_model_filter.mapToSource(idx[0])
key = self.table_model.index_to_key(idx) key = self.table_model.index_to_key(idx)
if key is not None: if key is not None:
persist, value = self.table_model.backing_store[key] persist, value, metadata = self.table_model.backing_store[key]
t = type(value) CreateEditDialog(self, self.dataset_ctl, key, value, metadata, persist).open()
if np.issubdtype(t, np.number):
dialog_cls = NumberEditor
elif np.issubdtype(t, np.bool_):
dialog_cls = BoolEditor
elif np.issubdtype(t, np.unicode_):
dialog_cls = StringEditor
else:
logger.error("Cannot edit dataset %s: "
"type %s is not supported", key, t)
return
dialog_cls(self, self.dataset_ctl, key, value).open()
def delete_clicked(self): def delete_clicked(self):
idx = self.table.selectedIndexes() idx = self.table.selectedIndexes()

View File

@ -11,7 +11,9 @@ from sipyco import pyon
from artiq.gui.entries import procdesc_to_entry, ScanEntry from artiq.gui.entries import procdesc_to_entry, ScanEntry
from artiq.gui.fuzzy_select import FuzzySelectWidget from artiq.gui.fuzzy_select import FuzzySelectWidget
from artiq.gui.tools import LayoutWidget, log_level_to_name, get_open_file_name from artiq.gui.tools import (LayoutWidget, WheelFilter,
log_level_to_name, get_open_file_name)
from artiq.tools import parse_devarg_override, unparse_devarg_override
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -23,15 +25,6 @@ logger = logging.getLogger(__name__)
# 2. file:<class name>@<file name> # 2. file:<class name>@<file name>
class _WheelFilter(QtCore.QObject):
def eventFilter(self, obj, event):
if (event.type() == QtCore.QEvent.Wheel and
event.modifiers() != QtCore.Qt.NoModifier):
event.ignore()
return True
return False
class _ArgumentEditor(QtWidgets.QTreeWidget): class _ArgumentEditor(QtWidgets.QTreeWidget):
def __init__(self, manager, dock, expurl): def __init__(self, manager, dock, expurl):
self.manager = manager self.manager = manager
@ -55,7 +48,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
self.setStyleSheet("QTreeWidget {background: " + self.setStyleSheet("QTreeWidget {background: " +
self.palette().midlight().color().name() + " ;}") self.palette().midlight().color().name() + " ;}")
self.viewport().installEventFilter(_WheelFilter(self.viewport())) self.viewport().installEventFilter(WheelFilter(self.viewport(), True))
self._groups = dict() self._groups = dict()
self._arg_to_widgets = dict() self._arg_to_widgets = dict()
@ -159,6 +152,23 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
self._groups[name] = group self._groups[name] = group
return group return group
def update_argument(self, name, argument):
widgets = self._arg_to_widgets[name]
# Qt needs a setItemWidget() to handle layout correctly,
# simply replacing the entry inside the LayoutWidget
# results in a bug.
widgets["entry"].deleteLater()
widgets["entry"] = procdesc_to_entry(argument["desc"])(argument)
widgets["disable_other_scans"].setVisible(
isinstance(widgets["entry"], ScanEntry))
widgets["fix_layout"].deleteLater()
widgets["fix_layout"] = LayoutWidget()
widgets["fix_layout"].addWidget(widgets["entry"])
self.setItemWidget(widgets["widget_item"], 1, widgets["fix_layout"])
self.updateGeometries()
def _recompute_argument_clicked(self, name): def _recompute_argument_clicked(self, name):
asyncio.ensure_future(self._recompute_argument(name)) asyncio.ensure_future(self._recompute_argument(name))
@ -175,22 +185,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget):
state = procdesc_to_entry(procdesc).default_state(procdesc) state = procdesc_to_entry(procdesc).default_state(procdesc)
argument["desc"] = procdesc argument["desc"] = procdesc
argument["state"] = state argument["state"] = state
self.update_argument(name, argument)
# Qt needs a setItemWidget() to handle layout correctly,
# simply replacing the entry inside the LayoutWidget
# results in a bug.
widgets = self._arg_to_widgets[name]
widgets["entry"].deleteLater()
widgets["entry"] = procdesc_to_entry(procdesc)(argument)
widgets["disable_other_scans"].setVisible(
isinstance(widgets["entry"], ScanEntry))
widgets["fix_layout"].deleteLater()
widgets["fix_layout"] = LayoutWidget()
widgets["fix_layout"].addWidget(widgets["entry"])
self.setItemWidget(widgets["widget_item"], 1, widgets["fix_layout"])
self.updateGeometries()
def _disable_other_scans(self, current_name): def _disable_other_scans(self, current_name):
for name, widgets in self._arg_to_widgets.items(): for name, widgets in self._arg_to_widgets.items():
@ -268,7 +263,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
datetime.setDate(QtCore.QDate.currentDate()) datetime.setDate(QtCore.QDate.currentDate())
else: else:
datetime.setDateTime(QtCore.QDateTime.fromMSecsSinceEpoch( datetime.setDateTime(QtCore.QDateTime.fromMSecsSinceEpoch(
scheduling["due_date"]*1000)) int(scheduling["due_date"]*1000)))
datetime_en.setChecked(scheduling["due_date"] is not None) datetime_en.setChecked(scheduling["due_date"] is not None)
def update_datetime(dt): def update_datetime(dt):
@ -311,7 +306,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
flush = self.flush flush = self.flush
flush.setToolTip("Flush the pipeline (of current- and higher-priority " flush.setToolTip("Flush the pipeline (of current- and higher-priority "
"experiments) before starting the experiment") "experiments) before starting the experiment")
self.layout.addWidget(flush, 2, 2, 1, 2) self.layout.addWidget(flush, 2, 2)
flush.setChecked(scheduling["flush"]) flush.setChecked(scheduling["flush"])
@ -319,6 +314,20 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
scheduling["flush"] = bool(checked) scheduling["flush"] = bool(checked)
flush.stateChanged.connect(update_flush) flush.stateChanged.connect(update_flush)
devarg_override = QtWidgets.QComboBox()
devarg_override.setEditable(True)
devarg_override.lineEdit().setPlaceholderText("Override device arguments")
devarg_override.lineEdit().setClearButtonEnabled(True)
devarg_override.insertItem(0, "core:analyze_at_run_end=True")
self.layout.addWidget(devarg_override, 2, 3)
devarg_override.setCurrentText(options["devarg_override"])
def update_devarg_override(text):
options["devarg_override"] = text
devarg_override.editTextChanged.connect(update_devarg_override)
self.devarg_override = devarg_override
log_level = QtWidgets.QComboBox() log_level = QtWidgets.QComboBox()
log_level.addItems(log_levels) log_level.addItems(log_levels)
log_level.setCurrentIndex(1) log_level.setCurrentIndex(1)
@ -339,6 +348,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
if "repo_rev" in options: if "repo_rev" in options:
repo_rev = QtWidgets.QLineEdit() repo_rev = QtWidgets.QLineEdit()
repo_rev.setPlaceholderText("current") repo_rev.setPlaceholderText("current")
repo_rev.setClearButtonEnabled(True)
repo_rev_label = QtWidgets.QLabel("Revision:") repo_rev_label = QtWidgets.QLabel("Revision:")
repo_rev_label.setToolTip("Experiment repository revision " repo_rev_label.setToolTip("Experiment repository revision "
"(commit ID) to use") "(commit ID) to use")
@ -471,6 +481,9 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
return return
try: try:
if "devarg_override" in expid:
self.devarg_override.setCurrentText(
unparse_devarg_override(expid["devarg_override"]))
self.log_level.setCurrentIndex(log_levels.index( self.log_level.setCurrentIndex(log_levels.index(
log_level_to_name(expid["log_level"]))) log_level_to_name(expid["log_level"])))
if ("repo_rev" in expid and if ("repo_rev" in expid and
@ -644,7 +657,8 @@ class ExperimentManager:
else: else:
# mutated by _ExperimentDock # mutated by _ExperimentDock
options = { options = {
"log_level": logging.WARNING "log_level": logging.WARNING,
"devarg_override": ""
} }
if expurl[:5] == "repo:": if expurl[:5] == "repo:":
options["repo_rev"] = None options["repo_rev"] = None
@ -665,6 +679,20 @@ class ExperimentManager:
self.argument_ui_names[expurl] = ui_name self.argument_ui_names[expurl] = ui_name
return arguments return arguments
def set_argument_value(self, expurl, name, value):
try:
argument = self.submission_arguments[expurl][name]
if argument["desc"]["ty"] == "Scannable":
ty = value["ty"]
argument["state"]["selected"] = ty
argument["state"][ty] = value
else:
argument["state"] = value
if expurl in self.open_experiments.keys():
self.open_experiments[expurl].argeditor.update_argument(name, argument)
except:
logger.warn("Failed to set value for argument \"{}\" in experiment: {}.".format(name, expurl), exc_info=1)
def get_submission_arguments(self, expurl): def get_submission_arguments(self, expurl):
if expurl in self.submission_arguments: if expurl in self.submission_arguments:
return self.submission_arguments[expurl] return self.submission_arguments[expurl]
@ -725,7 +753,14 @@ class ExperimentManager:
entry_cls = procdesc_to_entry(argument["desc"]) entry_cls = procdesc_to_entry(argument["desc"])
argument_values[name] = entry_cls.state_to_value(argument["state"]) argument_values[name] = entry_cls.state_to_value(argument["state"])
try:
devarg_override = parse_devarg_override(options["devarg_override"])
except:
logger.error("Failed to parse device argument overrides for %s", expurl)
return
expid = { expid = {
"devarg_override": devarg_override,
"log_level": options["log_level"], "log_level": options["log_level"],
"file": file, "file": file,
"class_name": class_name, "class_name": class_name,

View File

@ -8,8 +8,11 @@ from PyQt5 import QtCore, QtWidgets, QtGui
from sipyco.sync_struct import Subscriber from sipyco.sync_struct import Subscriber
from artiq.coredevice.comm_moninj import * from artiq.coredevice.comm_moninj import *
from artiq.coredevice.ad9910 import _AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7, _AD9910_REG_FTW from artiq.coredevice.ad9910 import (
from artiq.coredevice.ad9912_reg import AD9912_POW1 _AD9910_REG_PROFILE0, _AD9910_REG_PROFILE7,
_AD9910_REG_FTW, _AD9910_REG_CFR1
)
from artiq.coredevice.ad9912_reg import AD9912_POW1, AD9912_SER_CONF
from artiq.gui.tools import LayoutWidget from artiq.gui.tools import LayoutWidget
from artiq.gui.flowlayout import FlowLayout from artiq.gui.flowlayout import FlowLayout
@ -311,6 +314,10 @@ class _DDSWidget(QtWidgets.QFrame):
apply.clicked.connect(self.apply_changes) apply.clicked.connect(self.apply_changes)
if self.dds_model.is_urukul: if self.dds_model.is_urukul:
off_btn.clicked.connect(self.off_clicked) off_btn.clicked.connect(self.off_clicked)
off_btn.setToolTip(textwrap.dedent(
"""Note: If TTL RTIO sw for the channel is switched high,
this button will not disable the channel.
Use the TTL override instead."""))
self.value_edit.returnPressed.connect(lambda: self.apply_changes(None)) self.value_edit.returnPressed.connect(lambda: self.apply_changes(None))
self.value_edit.escapePressedConnect(self.cancel_changes) self.value_edit.escapePressedConnect(self.cancel_changes)
cancel.clicked.connect(self.cancel_changes) cancel.clicked.connect(self.cancel_changes)
@ -534,7 +541,7 @@ class _DeviceManager:
"log_level": logging.WARNING, "log_level": logging.WARNING,
"content": content, "content": content,
"class_name": class_name, "class_name": class_name,
"arguments": [] "arguments": {}
} }
scheduling = { scheduling = {
"pipeline_name": "main", "pipeline_name": "main",
@ -549,33 +556,55 @@ class _DeviceManager:
scheduling["flush"]) scheduling["flush"])
logger.info("Submitted '%s', RID is %d", title, rid) logger.info("Submitted '%s', RID is %d", title, rid)
def dds_set_frequency(self, dds_channel, dds_model, freq): def _dds_faux_injection(self, dds_channel, dds_model, action, title, log_msg):
# create kernel and fill it in and send-by-content # create kernel and fill it in and send-by-content
# initialize CPLD (if applicable)
if dds_model.is_urukul: if dds_model.is_urukul:
# urukuls need CPLD init and switch to on # urukuls need CPLD init and switch to on
# keep previous config if it was set already
cpld_dev = """self.setattr_device("core_cache") cpld_dev = """self.setattr_device("core_cache")
self.setattr_device("{}")""".format(dds_model.cpld) self.setattr_device("{}")""".format(dds_model.cpld)
cpld_init = """cfg = self.core_cache.get("_{cpld}_cfg")
if len(cfg) > 0: # `sta`/`rf_sw`` variables are guaranteed for urukuls
self.{cpld}.cfg_reg = cfg[0] # so {action} can use it
else: # if there's no RF enabled, CPLD may have not been initialized
# but if there is, it has been initialised - no need to do again
cpld_init = """delay(15*ms)
was_init = self.core_cache.get("_{cpld}_init")
sta = self.{cpld}.sta_read()
rf_sw = urukul_sta_rf_sw(sta)
if rf_sw == 0 and len(was_init) == 0:
delay(15*ms) delay(15*ms)
self.{cpld}.init() self.{cpld}.init()
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg]) self.core_cache.put("_{cpld}_init", [1])
cfg = self.core_cache.get("_{cpld}_cfg")
""".format(cpld=dds_model.cpld) """.format(cpld=dds_model.cpld)
cfg_sw = """self.{}.cfg_sw(True)
cfg[0] = self.{}.cfg_reg
""".format(dds_channel, dds_model.cpld)
else: else:
cpld_dev = "" cpld_dev = ""
cpld_init = "" cpld_init = ""
cfg_sw = ""
# AD9912/9910: init channel (if uninitialized)
if dds_model.dds_type == "AD9912":
# 0xFF before init, 0x99 after
channel_init = """
if self.{dds_channel}.read({cfgreg}, length=1) == 0xFF:
delay(10*ms)
self.{dds_channel}.init()
""".format(dds_channel=dds_channel, cfgreg=AD9912_SER_CONF)
elif dds_model.dds_type == "AD9910":
# -1 before init, 2 after
channel_init = """
if self.{dds_channel}.read32({cfgreg}) == -1:
delay(10*ms)
self.{dds_channel}.init()
""".format(dds_channel=dds_channel, cfgreg=AD9912_SER_CONF)
else:
channel_init = "self.{dds_channel}.init()".format(dds_channel=dds_channel)
dds_exp = textwrap.dedent(""" dds_exp = textwrap.dedent("""
from artiq.experiment import * from artiq.experiment import *
from artiq.coredevice.urukul import *
class SetDDS(EnvExperiment): class {title}(EnvExperiment):
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("{dds_channel}") self.setattr_device("{dds_channel}")
@ -583,55 +612,57 @@ class _DeviceManager:
@kernel @kernel
def run(self): def run(self):
self.core.reset() self.core.break_realtime()
{cpld_init} {cpld_init}
delay(5*ms) delay(10*ms)
self.{dds_channel}.init() {channel_init}
self.{dds_channel}.set({freq}) delay(15*ms)
{cfg_sw} {action}
""".format(dds_channel=dds_channel, freq=freq, """.format(title=title, action=action,
dds_channel=dds_channel,
cpld_dev=cpld_dev, cpld_init=cpld_init, cpld_dev=cpld_dev, cpld_init=cpld_init,
cfg_sw=cfg_sw)) channel_init=channel_init))
asyncio.ensure_future( asyncio.ensure_future(
self._submit_by_content( self._submit_by_content(
dds_exp, dds_exp,
"SetDDS", title,
"Set DDS {} {}MHz".format(dds_channel, freq/1e6))) log_msg))
def dds_set_frequency(self, dds_channel, dds_model, freq):
action = "self.{ch}.set({freq})".format(
freq=freq, ch=dds_channel)
if dds_model.is_urukul:
action += """
ch_no = self.{ch}.chip_select - 4
self.{cpld}.cfg_switches(rf_sw | 1 << ch_no)
""".format(ch=dds_channel, cpld=dds_model.cpld)
self._dds_faux_injection(
dds_channel,
dds_model,
action,
"SetDDS",
"Set DDS {} {}MHz".format(dds_channel, freq/1e6))
def dds_channel_toggle(self, dds_channel, dds_model, sw=True): def dds_channel_toggle(self, dds_channel, dds_model, sw=True):
# urukul only # urukul only
toggle_exp = textwrap.dedent(""" if sw:
from artiq.experiment import * switch = "| 1 << ch_no"
else:
class ToggleDDS(EnvExperiment): switch = "& ~(1 << ch_no)"
def build(self): action = """
self.setattr_device("core") ch_no = self.{dds_channel}.chip_select - 4
self.setattr_device("{ch}") self.{cpld}.cfg_switches(rf_sw {switch})
self.setattr_device("core_cache") """.format(
self.setattr_device("{cpld}") dds_channel=dds_channel,
cpld=dds_model.cpld,
@kernel switch=switch
def run(self):
self.core.reset()
cfg = self.core_cache.get("_{cpld}_cfg")
if len(cfg) > 0:
self.{cpld}.cfg_reg = cfg[0]
else:
delay(15*ms)
self.{cpld}.init()
self.core_cache.put("_{cpld}_cfg", [self.{cpld}.cfg_reg])
cfg = self.core_cache.get("_{cpld}_cfg")
delay(5*ms)
self.{ch}.init()
self.{ch}.cfg_sw({sw})
cfg[0] = self.{cpld}.cfg_reg
""".format(ch=dds_channel, cpld=dds_model.cpld, sw=sw))
asyncio.ensure_future(
self._submit_by_content(
toggle_exp,
"ToggleDDS",
"Toggle DDS {} {}".format(dds_channel, "on" if sw else "off"))
) )
self._dds_faux_injection(
dds_channel,
dds_model,
action,
"ToggleDDS",
"Toggle DDS {} {}".format(dds_channel, "on" if sw else "off"))
def setup_ttl_monitoring(self, enable, channel): def setup_ttl_monitoring(self, enable, channel):
if self.mi_connection is not None: if self.mi_connection is not None:
@ -695,11 +726,11 @@ class _DeviceManager:
logger.info("cancelled connection to moninj") logger.info("cancelled connection to moninj")
break break
except: except:
logger.error("failed to connect to moninj", exc_info=True) logger.error("failed to connect to moninj. Is aqctl_moninj_proxy running?", exc_info=True)
await asyncio.sleep(10.) await asyncio.sleep(10.)
self.reconnect_mi.set() self.reconnect_mi.set()
else: else:
logger.info("ARTIQ dashboard connected to moninj proxy (%s)", logger.info("ARTIQ dashboard connected to moninj (%s)",
self.mi_addr) self.mi_addr)
self.mi_connection = new_mi_connection self.mi_connection = new_mi_connection
for ttl_channel in self.ttl_widgets.keys(): for ttl_channel in self.ttl_widgets.keys():

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -4,13 +4,13 @@ from artiq.applets.simple import SimpleApplet
class DemoWidget(QtWidgets.QLabel): class DemoWidget(QtWidgets.QLabel):
def __init__(self, args): def __init__(self, args, ctl):
QtWidgets.QLabel.__init__(self) QtWidgets.QLabel.__init__(self)
self.dataset_name = args.dataset self.dataset_name = args.dataset
def data_changed(self, data, mods): def data_changed(self, value, metadata, persist, mods):
try: try:
n = str(data[self.dataset_name][1]) n = str(value[self.dataset_name])
except (KeyError, ValueError, TypeError): except (KeyError, ValueError, TypeError):
n = "---" n = "---"
n = "<font size=15>" + n + "</font>" n = "<font size=15>" + n + "</font>"

View File

@ -1,173 +0,0 @@
core_addr = "192.168.1.60"
device_db = {
"core": {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {"host": core_addr, "ref_period": 1/(8*150e6)}
},
"core_log": {
"type": "controller",
"host": "::1",
"port": 1068,
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
},
"core_moninj": {
"type": "controller",
"host": "::1",
"port_proxy": 1383,
"port": 1384,
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",
"class": "CoreCache"
},
"core_dma": {
"type": "local",
"module": "artiq.coredevice.dma",
"class": "CoreDMA"
},
}
for i in range(4):
device_db["led" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": i},
}
for i in range(2):
device_db["ttl" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLInOut",
"arguments": {"channel": 4 + i},
}
device_db.update(
fmcdio_dirctl_clk={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 6}
},
fmcdio_dirctl_ser={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 7}
},
fmcdio_dirctl_latch={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 8}
},
fmcdio_dirctl={
"type": "local",
"module": "artiq.coredevice.shiftreg",
"class": "ShiftReg",
"arguments": {"clk": "fmcdio_dirctl_clk",
"ser": "fmcdio_dirctl_ser",
"latch": "fmcdio_dirctl_latch"}
}
)
device_db.update(
spi_urukul0={
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 17}
},
ttl_urukul0_io_update={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 18}
},
ttl_urukul0_sw0={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 19}
},
ttl_urukul0_sw1={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 20}
},
ttl_urukul0_sw2={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 21}
},
ttl_urukul0_sw3={
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 22}
},
urukul0_cpld={
"type": "local",
"module": "artiq.coredevice.urukul",
"class": "CPLD",
"arguments": {
"spi_device": "spi_urukul0",
"io_update_device": "ttl_urukul0_io_update",
"refclk": 125e6,
"clk_sel": 0
}
}
)
for i in range(4):
device_db["urukul0_ch" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ad9910",
"class": "AD9910",
"arguments": {
"pll_n": 32,
"chip_select": 4 + i,
"cpld_device": "urukul0_cpld",
"sw_device": "ttl_urukul0_sw" + str(i)
}
}
device_db["spi_zotino0"] = {
"type": "local",
"module": "artiq.coredevice.spi2",
"class": "SPIMaster",
"arguments": {"channel": 23}
}
device_db["ttl_zotino0_ldac"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 24}
}
device_db["ttl_zotino0_clr"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": 25}
}
device_db["zotino0"] = {
"type": "local",
"module": "artiq.coredevice.zotino",
"class": "Zotino",
"arguments": {
"spi_device": "spi_zotino0",
"ldac_device": "ttl_zotino0_ldac",
"clr_device": "ttl_zotino0_clr"
}
}

View File

@ -1,41 +0,0 @@
from artiq.experiment import *
from artiq.coredevice.fmcdio_vhdci_eem import *
class Demo(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("fmcdio_dirctl")
self.ttls = [self.get_device("ttl" + str(i)) for i in range(8)]
self.setattr_device("urukul0_cpld")
self.urukul_chs = [self.get_device("urukul0_ch" + str(i)) for i in range(4)]
self.setattr_device("zotino0")
self.dirctl_word = (
shiftreg_bits(1, urukul_out_pins) |
shiftreg_bits(0, urukul_aux_out_pins) |
shiftreg_bits(2, dio_bank0_out_pins | dio_bank1_out_pins) |
shiftreg_bits(3, zotino_out_pins))
@kernel
def run(self):
self.core.reset()
delay(10*ms)
self.fmcdio_dirctl.set(self.dirctl_word)
delay(10*ms)
self.urukul0_cpld.init()
delay(10*ms)
self.zotino0.init()
delay(1*ms)
for i in range(32):
self.zotino0.write_dac(i, i/4)
delay(1*ms)
while True:
for ttl in self.ttls:
ttl.pulse(100*ms)
for urukul_ch in self.urukul_chs:
urukul_ch.sw.pulse(100*ms)

View File

@ -13,6 +13,12 @@ dependencies = [
name = "alloc_list" name = "alloc_list"
version = "0.0.0" version = "0.0.0"
[[package]]
name = "arrayvec"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711"
[[package]] [[package]]
name = "bare-metal" name = "bare-metal"
version = "0.2.5" version = "0.2.5"
@ -320,6 +326,7 @@ dependencies = [
"build_misoc", "build_misoc",
"byteorder", "byteorder",
"cslice", "cslice",
"dyld",
"eh", "eh",
"failure", "failure",
"failure_derive", "failure_derive",
@ -331,6 +338,7 @@ dependencies = [
"proto_artiq", "proto_artiq",
"riscv", "riscv",
"smoltcp", "smoltcp",
"tar-no-std",
"unwind_backtrace", "unwind_backtrace",
] ]
@ -347,10 +355,15 @@ dependencies = [
name = "satman" name = "satman"
version = "0.0.0" version = "0.0.0"
dependencies = [ dependencies = [
"alloc_list",
"board_artiq", "board_artiq",
"board_misoc", "board_misoc",
"build_misoc", "build_misoc",
"cslice",
"eh",
"io",
"log", "log",
"proto_artiq",
"riscv", "riscv",
] ]
@ -371,9 +384,9 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]] [[package]]
name = "smoltcp" name = "smoltcp"
version = "0.8.0" version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2308a1657c8db1f5b4993bab4e620bdbe5623bd81f254cf60326767bb243237" checksum = "ee34c1e1bfc7e9206cc0fb8030a90129b4e319ab53856249bb27642cab914fb3"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"byteorder", "byteorder",
@ -410,6 +423,16 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "tar-no-std"
version = "0.1.8"
source = "git+https://git.m-labs.hk/M-Labs/tar-no-std?rev=2ab6dc5#2ab6dc58e5249c59c4eb03eaf3a119bcdd678d32"
dependencies = [
"arrayvec",
"bitflags",
"log",
]
[[package]] [[package]]
name = "unicode-xid" name = "unicode-xid"
version = "0.0.4" version = "0.0.4"

View File

@ -16,5 +16,5 @@ build_misoc = { path = "../libbuild_misoc" }
byteorder = { version = "1.0", default-features = false } byteorder = { version = "1.0", default-features = false }
crc = { version = "1.7", default-features = false } crc = { version = "1.7", default-features = false }
board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp"] } board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp"] }
smoltcp = { version = "0.8.0", default-features = false, features = ["medium-ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp"] } smoltcp = { version = "0.8.2", default-features = false, features = ["medium-ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp"] }
riscv = { version = "0.6.0", features = ["inline-asm"] } riscv = { version = "0.6.0", features = ["inline-asm"] }

View File

@ -18,8 +18,10 @@ use board_misoc::slave_fpga;
use board_misoc::{clock, ethmac, net_settings}; use board_misoc::{clock, ethmac, net_settings};
use board_misoc::uart_console::Console; use board_misoc::uart_console::Console;
use riscv::register::{mcause, mepc, mtval}; use riscv::register::{mcause, mepc, mtval};
use smoltcp::iface::SocketStorage; #[cfg(has_ethmac)]
use smoltcp::wire::{HardwareAddress, IpAddress, Ipv4Address}; use smoltcp::iface::{Routes, SocketStorage};
#[cfg(has_ethmac)]
use smoltcp::wire::{HardwareAddress, IpAddress, Ipv4Address, Ipv6Address};
fn check_integrity() -> bool { fn check_integrity() -> bool {
extern { extern {
@ -411,28 +413,29 @@ fn network_boot() {
println!("Network addresses: {}", net_addresses); println!("Network addresses: {}", net_addresses);
let mut ip_addrs = [ let mut ip_addrs = [
IpCidr::new(IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), 0), IpCidr::new(IpAddress::Ipv4(Ipv4Address::UNSPECIFIED), 0),
IpCidr::new(net_addresses.ipv6_ll_addr, 0), net_addresses.ipv6_ll_addr,
IpCidr::new(net_addresses.ipv6_ll_addr, 0) IpCidr::new(IpAddress::Ipv6(Ipv6Address::UNSPECIFIED), 0)
]; ];
if let net_settings::Ipv4AddrConfig::Static(ipv4) = net_addresses.ipv4_addr { if let net_settings::Ipv4AddrConfig::Static(ipv4) = net_addresses.ipv4_addr {
ip_addrs[0] = IpCidr::new(IpAddress::Ipv4(ipv4), 0); ip_addrs[0] = IpCidr::Ipv4(ipv4);
} }
let mut interface = match net_addresses.ipv6_addr { if let Some(ipv6) = net_addresses.ipv6_addr {
Some(addr) => { ip_addrs[2] = IpCidr::Ipv6(ipv6);
ip_addrs[2] = IpCidr::new(addr, 0);
smoltcp::iface::InterfaceBuilder::new(net_device, &mut sockets[..])
.hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr))
.ip_addrs(&mut ip_addrs[..])
.neighbor_cache(neighbor_cache)
.finalize()
}
None =>
smoltcp::iface::InterfaceBuilder::new(net_device, &mut sockets[..])
.hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr))
.ip_addrs(&mut ip_addrs[..2])
.neighbor_cache(neighbor_cache)
.finalize()
}; };
let mut routes = [None; 2];
let mut interface = smoltcp::iface::InterfaceBuilder::new(net_device, &mut sockets[..])
.hardware_addr(HardwareAddress::Ethernet(net_addresses.hardware_addr))
.ip_addrs(&mut ip_addrs[..])
.neighbor_cache(neighbor_cache)
.routes(Routes::new(&mut routes[..]))
.finalize();
if let Some(default_route) = net_addresses.ipv4_default_route {
interface.routes_mut().add_default_ipv4_route(default_route).unwrap();
}
if let Some(default_route) = net_addresses.ipv6_default_route {
interface.routes_mut().add_default_ipv6_route(default_route).unwrap();
}
let mut rx_storage = [0; 4096]; let mut rx_storage = [0; 4096];
let mut tx_storage = [0; 128]; let mut tx_storage = [0; 128];
@ -497,7 +500,7 @@ pub extern fn main() -> i32 {
println!(r"|_| |_|_|____/ \___/ \____|"); println!(r"|_| |_|_|____/ \___/ \____|");
println!(""); println!("");
println!("MiSoC Bootloader"); println!("MiSoC Bootloader");
println!("Copyright (c) 2017-2022 M-Labs Limited"); println!("Copyright (c) 2017-2023 M-Labs Limited");
println!(""); println!("");
#[cfg(has_ethmac)] #[cfg(has_ethmac)]

View File

@ -6,7 +6,7 @@ ENTRY(_reset_handler)
* ld does not allow this expression here. * ld does not allow this expression here.
*/ */
MEMORY { MEMORY {
runtime (RWX) : ORIGIN = 0x40000000, LENGTH = 0x4000000 /* 64M */ firmware (RWX) : ORIGIN = 0x40000000, LENGTH = 0x4000000 /* 64M */
} }
SECTIONS SECTIONS
@ -14,24 +14,24 @@ SECTIONS
.vectors : .vectors :
{ {
*(.vectors) *(.vectors)
} > runtime } > firmware
.text : .text :
{ {
*(.text .text.*) *(.text .text.*)
} > runtime } > firmware
.eh_frame : .eh_frame :
{ {
__eh_frame_start = .; __eh_frame_start = .;
KEEP(*(.eh_frame)) KEEP(*(.eh_frame))
__eh_frame_end = .; __eh_frame_end = .;
} > runtime } > firmware
.eh_frame_hdr : .eh_frame_hdr :
{ {
KEEP(*(.eh_frame_hdr)) KEEP(*(.eh_frame_hdr))
} > runtime } > firmware
__eh_frame_hdr_start = SIZEOF(.eh_frame_hdr) > 0 ? ADDR(.eh_frame_hdr) : 0; __eh_frame_hdr_start = SIZEOF(.eh_frame_hdr) > 0 ? ADDR(.eh_frame_hdr) : 0;
__eh_frame_hdr_end = SIZEOF(.eh_frame_hdr) > 0 ? . : 0; __eh_frame_hdr_end = SIZEOF(.eh_frame_hdr) > 0 ? . : 0;
@ -39,35 +39,35 @@ SECTIONS
.gcc_except_table : .gcc_except_table :
{ {
*(.gcc_except_table) *(.gcc_except_table)
} > runtime } > firmware
/* https://sourceware.org/bugzilla/show_bug.cgi?id=20475 */ /* https://sourceware.org/bugzilla/show_bug.cgi?id=20475 */
.got : .got :
{ {
*(.got) *(.got)
} > runtime } > firmware
.got.plt : .got.plt :
{ {
*(.got.plt) *(.got.plt)
} > runtime } > firmware
.rodata : .rodata :
{ {
*(.rodata .rodata.*) *(.rodata .rodata.*)
} > runtime } > firmware
.data : .data :
{ {
*(.data .data.*) *(.data .data.*)
} > runtime } > firmware
.bss (NOLOAD) : ALIGN(4) .bss (NOLOAD) : ALIGN(4)
{ {
_fbss = .; _fbss = .;
*(.sbss .sbss.* .bss .bss.*); *(.sbss .sbss.* .bss .bss.*);
_ebss = .; _ebss = .;
} > runtime } > firmware
.stack (NOLOAD) : ALIGN(0x1000) .stack (NOLOAD) : ALIGN(0x1000)
{ {
@ -76,12 +76,12 @@ SECTIONS
_estack = .; _estack = .;
. += 0x10000; . += 0x10000;
_fstack = . - 16; _fstack = . - 16;
} > runtime } > firmware
.heap (NOLOAD) : ALIGN(16) .heap (NOLOAD) : ALIGN(16)
{ {
_fheap = .; _fheap = .;
. = ORIGIN(runtime) + LENGTH(runtime); . = ORIGIN(firmware) + LENGTH(firmware);
_eheap = .; _eheap = .;
} > runtime } > firmware
} }

View File

@ -26,7 +26,7 @@ $(RUSTOUT)/libksupport.a:
ksupport.elf: $(RUSTOUT)/libksupport.a glue.o ksupport.elf: $(RUSTOUT)/libksupport.a glue.o
$(link) -T $(KSUPPORT_DIRECTORY)/ksupport.ld \ $(link) -T $(KSUPPORT_DIRECTORY)/ksupport.ld \
-lunwind-$(CPU)-elf -lprintf-float -lm -lunwind-$(CPU)-libc -lprintf-float -lm
%.o: $(KSUPPORT_DIRECTORY)/%.c %.o: $(KSUPPORT_DIRECTORY)/%.c
$(compile) $(compile)

View File

@ -157,6 +157,11 @@ static mut API: &'static [(&'static str, *const ())] = &[
api!(dma_retrieve = ::dma_retrieve), api!(dma_retrieve = ::dma_retrieve),
api!(dma_playback = ::dma_playback), api!(dma_playback = ::dma_playback),
api!(subkernel_load_run = ::subkernel_load_run),
api!(subkernel_send_message = ::subkernel_send_message),
api!(subkernel_await_message = ::subkernel_await_message),
api!(subkernel_await_finish = ::subkernel_await_finish),
api!(i2c_start = ::nrt_bus::i2c::start), api!(i2c_start = ::nrt_bus::i2c::start),
api!(i2c_restart = ::nrt_bus::i2c::restart), api!(i2c_restart = ::nrt_bus::i2c::restart),
api!(i2c_stop = ::nrt_bus::i2c::stop), api!(i2c_stop = ::nrt_bus::i2c::stop),

View File

@ -333,7 +333,7 @@ extern fn stop_fn(_version: c_int,
} }
} }
static EXCEPTION_ID_LOOKUP: [(&str, u32); 11] = [ static EXCEPTION_ID_LOOKUP: [(&str, u32); 12] = [
("RuntimeError", 0), ("RuntimeError", 0),
("RTIOUnderflow", 1), ("RTIOUnderflow", 1),
("RTIOOverflow", 2), ("RTIOOverflow", 2),
@ -345,6 +345,7 @@ static EXCEPTION_ID_LOOKUP: [(&str, u32); 11] = [
("ZeroDivisionError", 8), ("ZeroDivisionError", 8),
("IndexError", 9), ("IndexError", 9),
("UnwrapNoneError", 10), ("UnwrapNoneError", 10),
("SubkernelError", 11)
]; ];
pub fn get_exception_id(name: &str) -> u32 { pub fn get_exception_id(name: &str) -> u32 {

View File

@ -17,7 +17,7 @@ void send_to_rtio_log(struct slice data);
#define KERNELCPU_EXEC_ADDRESS 0x45000000 #define KERNELCPU_EXEC_ADDRESS 0x45000000
#define KERNELCPU_PAYLOAD_ADDRESS 0x45060000 #define KERNELCPU_PAYLOAD_ADDRESS 0x45060000
#define KERNELCPU_LAST_ADDRESS 0x4fffffff #define KERNELCPU_LAST_ADDRESS 0x4fffffff
#define KSUPPORT_HEADER_SIZE 0x80 #define KSUPPORT_HEADER_SIZE 0x74
FILE *stderr; FILE *stderr;

View File

@ -35,16 +35,6 @@ SECTIONS
*(.text .text.*) *(.text .text.*)
} :text } :text
/* https://sourceware.org/bugzilla/show_bug.cgi?id=20475 */
.got : {
PROVIDE(_GLOBAL_OFFSET_TABLE_ = .);
*(.got)
} :text
.got.plt : {
*(.got.plt)
} :text
.rodata : .rodata :
{ {
*(.rodata .rodata.*) *(.rodata .rodata.*)
@ -60,6 +50,11 @@ SECTIONS
KEEP(*(.eh_frame_hdr)) KEEP(*(.eh_frame_hdr))
} > ksupport :text :eh_frame } > ksupport :text :eh_frame
.gcc_except_table :
{
*(.gcc_except_table)
} > ksupport
.data : .data :
{ {
*(.data .data.*) *(.data .data.*)

View File

@ -21,7 +21,6 @@ use dyld::Library;
use board_artiq::{mailbox, rpc_queue}; use board_artiq::{mailbox, rpc_queue};
use proto_artiq::{kernel_proto, rpc_proto}; use proto_artiq::{kernel_proto, rpc_proto};
use kernel_proto::*; use kernel_proto::*;
#[cfg(has_rtio_dma)]
use board_misoc::csr; use board_misoc::csr;
use riscv::register::{mcause, mepc, mtval}; use riscv::register::{mcause, mepc, mtval};
@ -139,7 +138,7 @@ extern fn rpc_send_async(service: u32, tag: &CSlice<u8>, data: *const *const ())
rpc_queue::enqueue(|mut slice| { rpc_queue::enqueue(|mut slice| {
let length = { let length = {
let mut writer = Cursor::new(&mut slice[4..]); let mut writer = Cursor::new(&mut slice[4..]);
rpc_proto::send_args(&mut writer, service, tag.as_ref(), data)?; rpc_proto::send_args(&mut writer, service, tag.as_ref(), data, true)?;
writer.position() writer.position()
}; };
io::ProtoWrite::write_u32(&mut slice, length as u32) io::ProtoWrite::write_u32(&mut slice, length as u32)
@ -156,6 +155,21 @@ extern fn rpc_send_async(service: u32, tag: &CSlice<u8>, data: *const *const ())
}) })
} }
/// Receives the result from an RPC call into the given memory buffer.
///
/// To handle aggregate objects with an a priori unknown size and number of
/// sub-allocations (e.g. a list of list of lists, where, at each level, the number of
/// elements is not statically known), this function needs to be called in a loop:
///
/// On the first call, `slot` should be a buffer of suitable size and alignment for
/// the top-level return value (e.g. in the case of a list, the pointer/length pair).
/// A return value of zero indicates that the value has been completely received.
/// As long as the return value is positive, another allocation with the given number of
/// bytes is needed, so the function should be called again with such a buffer (aligned
/// to the maximum required for any of the possible types according to the target ABI).
///
/// If the RPC call resulted in an exception, it is reconstructed and raised.
#[unwind(allowed)] #[unwind(allowed)]
extern fn rpc_recv(slot: *mut ()) -> usize { extern fn rpc_recv(slot: *mut ()) -> usize {
send(&RpcRecvRequest(slot)); send(&RpcRecvRequest(slot));
@ -254,7 +268,7 @@ extern fn dma_record_start(name: &CSlice<u8>) {
} }
#[unwind(allowed)] #[unwind(allowed)]
extern fn dma_record_stop(duration: i64) { extern fn dma_record_stop(duration: i64, enable_ddma: bool) {
unsafe { unsafe {
dma_record_flush(); dma_record_flush();
@ -270,7 +284,8 @@ extern fn dma_record_stop(duration: i64) {
DMA_RECORDER.active = false; DMA_RECORDER.active = false;
send(&DmaRecordStop { send(&DmaRecordStop {
duration: duration as u64 duration: duration as u64,
enable_ddma: enable_ddma
}); });
} }
} }
@ -327,7 +342,7 @@ extern fn dma_record_output(target: i32, word: i32) {
} }
#[unwind(aborts)] #[unwind(aborts)]
extern fn dma_record_output_wide(target: i32, words: CSlice<i32>) { extern fn dma_record_output_wide(target: i32, words: &CSlice<i32>) {
assert!(words.len() <= 16); // enforce the hardware limit assert!(words.len() <= 16); // enforce the hardware limit
unsafe { unsafe {
@ -356,6 +371,7 @@ extern fn dma_erase(name: &CSlice<u8>) {
struct DmaTrace { struct DmaTrace {
duration: i64, duration: i64,
address: i32, address: i32,
uses_ddma: bool,
} }
#[unwind(allowed)] #[unwind(allowed)]
@ -363,11 +379,12 @@ extern fn dma_retrieve(name: &CSlice<u8>) -> DmaTrace {
let name = str::from_utf8(name.as_ref()).unwrap(); let name = str::from_utf8(name.as_ref()).unwrap();
send(&DmaRetrieveRequest { name: name }); send(&DmaRetrieveRequest { name: name });
recv!(&DmaRetrieveReply { trace, duration } => { recv!(&DmaRetrieveReply { trace, duration, uses_ddma } => {
match trace { match trace {
Some(bytes) => Ok(DmaTrace { Some(bytes) => Ok(DmaTrace {
address: bytes.as_ptr() as i32, address: bytes.as_ptr() as i32,
duration: duration as i64 duration: duration as i64,
uses_ddma: uses_ddma,
}), }),
None => Err(()) None => Err(())
} }
@ -378,9 +395,9 @@ extern fn dma_retrieve(name: &CSlice<u8>) -> DmaTrace {
}) })
} }
#[cfg(has_rtio_dma)] #[cfg(kernel_has_rtio_dma)]
#[unwind(allowed)] #[unwind(allowed)]
extern fn dma_playback(timestamp: i64, ptr: i32) { extern fn dma_playback(timestamp: i64, ptr: i32, _uses_ddma: bool) {
assert!(ptr % 64 == 0); assert!(ptr % 64 == 0);
unsafe { unsafe {
@ -389,6 +406,10 @@ extern fn dma_playback(timestamp: i64, ptr: i32) {
csr::cri_con::selected_write(1); csr::cri_con::selected_write(1);
csr::rtio_dma::enable_write(1); csr::rtio_dma::enable_write(1);
#[cfg(has_drtio)]
if _uses_ddma {
send(&DmaStartRemoteRequest { id: ptr as i32, timestamp: timestamp });
}
while csr::rtio_dma::enable_read() != 0 {} while csr::rtio_dma::enable_read() != 0 {}
csr::cri_con::selected_write(0); csr::cri_con::selected_write(0);
@ -399,22 +420,107 @@ extern fn dma_playback(timestamp: i64, ptr: i32) {
csr::rtio_dma::error_write(1); csr::rtio_dma::error_write(1);
if error & 1 != 0 { if error & 1 != 0 {
raise!("RTIOUnderflow", raise!("RTIOUnderflow",
"RTIO underflow at {0} mu, channel {1}", "RTIO underflow at channel {rtio_channel_info:0}, {1} mu",
timestamp as i64, channel as i64, 0); channel as i64, timestamp as i64, 0);
} }
if error & 2 != 0 { if error & 2 != 0 {
raise!("RTIODestinationUnreachable", raise!("RTIODestinationUnreachable",
"RTIO destination unreachable, output, at {0} mu, channel {1}", "RTIO destination unreachable, output, at channel {rtio_channel_info:0}, {1} mu",
timestamp as i64, channel as i64, 0); channel as i64, timestamp as i64, 0);
} }
} }
} }
#[cfg(has_drtio)]
if _uses_ddma {
send(&DmaAwaitRemoteRequest { id: ptr as i32 });
recv!(&DmaAwaitRemoteReply { timeout, error, channel, timestamp } => {
if timeout {
raise!("DMAError",
"Error running DMA on satellite device, timed out waiting for results");
}
if error & 1 != 0 {
raise!("RTIOUnderflow",
"RTIO underflow at channel {rtio_channel_info:0}, {1} mu",
channel as i64, timestamp as i64, 0);
}
if error & 2 != 0 {
raise!("RTIODestinationUnreachable",
"RTIO destination unreachable, output, at channel {rtio_channel_info:0}, {1} mu",
channel as i64, timestamp as i64, 0);
}
});
}
} }
#[cfg(not(has_rtio_dma))] #[cfg(not(kernel_has_rtio_dma))]
#[unwind(allowed)] #[unwind(allowed)]
extern fn dma_playback(_timestamp: i64, _ptr: i32) { extern fn dma_playback(_timestamp: i64, _ptr: i32, _uses_ddma: bool) {
unimplemented!("not(has_rtio_dma)") unimplemented!("not(kernel_has_rtio_dma)")
}
#[unwind(allowed)]
extern fn subkernel_load_run(id: u32, run: bool) {
send(&SubkernelLoadRunRequest { id: id, run: run });
recv!(&SubkernelLoadRunReply { succeeded } => {
if !succeeded {
raise!("SubkernelError",
"Error loading or running the subkernel");
}
});
}
#[unwind(allowed)]
extern fn subkernel_await_finish(id: u32, timeout: u64) {
send(&SubkernelAwaitFinishRequest { id: id, timeout: timeout });
recv!(SubkernelAwaitFinishReply { status } => {
match status {
SubkernelStatus::NoError => (),
SubkernelStatus::IncorrectState => raise!("SubkernelError",
"Subkernel not running"),
SubkernelStatus::Timeout => raise!("SubkernelError",
"Subkernel timed out"),
SubkernelStatus::CommLost => raise!("SubkernelError",
"Lost communication with satellite"),
SubkernelStatus::OtherError => raise!("SubkernelError",
"An error occurred during subkernel operation")
}
})
}
#[unwind(aborts)]
extern fn subkernel_send_message(id: u32, count: u8, tag: &CSlice<u8>, data: *const *const ()) {
send(&SubkernelMsgSend {
id: id,
count: count,
tag: tag.as_ref(),
data: data
});
}
#[unwind(allowed)]
extern fn subkernel_await_message(id: u32, timeout: u64, tags: &CSlice<u8>, min: u8, max: u8) -> u8 {
send(&SubkernelMsgRecvRequest { id: id, timeout: timeout, tags: tags.as_ref() });
recv!(SubkernelMsgRecvReply { status, count } => {
match status {
SubkernelStatus::NoError => {
if count < &min || count > &max {
raise!("SubkernelError",
"Received less or more arguments than expected");
}
*count
}
SubkernelStatus::IncorrectState => raise!("SubkernelError",
"Subkernel not running"),
SubkernelStatus::Timeout => raise!("SubkernelError",
"Subkernel timed out"),
SubkernelStatus::CommLost => raise!("SubkernelError",
"Lost communication with satellite"),
SubkernelStatus::OtherError => raise!("SubkernelError",
"An error occurred during subkernel operation")
}
})
// RpcRecvRequest should be called `count` times after this to receive message data
} }
unsafe fn attribute_writeback(typeinfo: *const ()) { unsafe fn attribute_writeback(typeinfo: *const ()) {
@ -456,6 +562,8 @@ unsafe fn attribute_writeback(typeinfo: *const ()) {
} }
} }
static mut STACK_GUARD_BASE: usize = 0x0;
#[no_mangle] #[no_mangle]
pub unsafe fn main() { pub unsafe fn main() {
eh_artiq::reset_exception_buffer(KERNELCPU_PAYLOAD_ADDRESS); eh_artiq::reset_exception_buffer(KERNELCPU_PAYLOAD_ADDRESS);
@ -487,6 +595,7 @@ pub unsafe fn main() {
ptr::write_bytes(__bss_start as *mut u8, 0, (_end - __bss_start) as usize); ptr::write_bytes(__bss_start as *mut u8, 0, (_end - __bss_start) as usize);
board_misoc::pmp::init_stack_guard(_sstack_guard as usize); board_misoc::pmp::init_stack_guard(_sstack_guard as usize);
STACK_GUARD_BASE = _sstack_guard as usize;
board_misoc::cache::flush_cpu_dcache(); board_misoc::cache::flush_cpu_dcache();
board_misoc::cache::flush_cpu_icache(); board_misoc::cache::flush_cpu_icache();
@ -516,10 +625,20 @@ pub unsafe fn main() {
#[no_mangle] #[no_mangle]
#[unwind(allowed)] #[unwind(allowed)]
pub extern fn exception(_regs: *const u32) { pub unsafe extern fn exception(_regs: *const u32) {
let pc = mepc::read(); let pc = mepc::read();
let cause = mcause::read().cause(); let cause = mcause::read().cause();
let mtval = mtval::read(); let mtval = mtval::read();
if let mcause::Trap::Exception(mcause::Exception::LoadFault)
| mcause::Trap::Exception(mcause::Exception::StoreFault) = cause
{
if mtval >= STACK_GUARD_BASE
&& mtval < (STACK_GUARD_BASE + board_misoc::pmp::STACK_GUARD_SIZE)
{
panic!("{:?} at PC {:#08x} in stack guard page ({:#08x}); stack overflow in user kernel code?",
cause, u32::try_from(pc).unwrap(), mtval);
}
}
panic!("{:?} at PC {:#08x}, trap value {:#08x}", cause, u32::try_from(pc).unwrap(), mtval); panic!("{:?} at PC {:#08x}, trap value {:#08x}", cause, u32::try_from(pc).unwrap(), mtval);
} }

View File

@ -67,13 +67,13 @@ mod imp {
} }
if status & RTIO_O_STATUS_UNDERFLOW != 0 { if status & RTIO_O_STATUS_UNDERFLOW != 0 {
raise!("RTIOUnderflow", raise!("RTIOUnderflow",
"RTIO underflow at {0} mu, channel {1}, slack {2} mu", "RTIO underflow at channel {rtio_channel_info:0}, {1} mu, slack {2} mu",
timestamp, channel as i64, timestamp - get_counter()); channel as i64, timestamp, timestamp - get_counter());
} }
if status & RTIO_O_STATUS_DESTINATION_UNREACHABLE != 0 { if status & RTIO_O_STATUS_DESTINATION_UNREACHABLE != 0 {
raise!("RTIODestinationUnreachable", raise!("RTIODestinationUnreachable",
"RTIO destination unreachable, output, at {0} mu, channel {1}", "RTIO destination unreachable, output, at channel {rtio_channel_info:0}, {1} mu",
timestamp, channel as i64, 0); channel as i64, timestamp, 0);
} }
} }
@ -89,7 +89,7 @@ mod imp {
} }
} }
pub extern fn output_wide(target: i32, data: CSlice<i32>) { pub extern fn output_wide(target: i32, data: &CSlice<i32>) {
unsafe { unsafe {
csr::rtio::target_write(target as u32); csr::rtio::target_write(target as u32);
// writing target clears o_data // writing target clears o_data
@ -115,7 +115,7 @@ mod imp {
if status & RTIO_I_STATUS_OVERFLOW != 0 { if status & RTIO_I_STATUS_OVERFLOW != 0 {
raise!("RTIOOverflow", raise!("RTIOOverflow",
"RTIO input overflow on channel {0}", "RTIO input overflow on channel {rtio_channel_info:0}",
channel as i64, 0, 0); channel as i64, 0, 0);
} }
if status & RTIO_I_STATUS_WAIT_EVENT != 0 { if status & RTIO_I_STATUS_WAIT_EVENT != 0 {
@ -123,7 +123,7 @@ mod imp {
} }
if status & RTIO_I_STATUS_DESTINATION_UNREACHABLE != 0 { if status & RTIO_I_STATUS_DESTINATION_UNREACHABLE != 0 {
raise!("RTIODestinationUnreachable", raise!("RTIODestinationUnreachable",
"RTIO destination unreachable, input, on channel {0}", "RTIO destination unreachable, input, on channel {rtio_channel_info:0}",
channel as i64, 0, 0); channel as i64, 0, 0);
} }
@ -143,12 +143,12 @@ mod imp {
if status & RTIO_I_STATUS_OVERFLOW != 0 { if status & RTIO_I_STATUS_OVERFLOW != 0 {
raise!("RTIOOverflow", raise!("RTIOOverflow",
"RTIO input overflow on channel {0}", "RTIO input overflow on channel {rtio_channel_info:0}",
channel as i64, 0, 0); channel as i64, 0, 0);
} }
if status & RTIO_I_STATUS_DESTINATION_UNREACHABLE != 0 { if status & RTIO_I_STATUS_DESTINATION_UNREACHABLE != 0 {
raise!("RTIODestinationUnreachable", raise!("RTIODestinationUnreachable",
"RTIO destination unreachable, input, on channel {0}", "RTIO destination unreachable, input, on channel {rtio_channel_info:0}",
channel as i64, 0, 0); channel as i64, 0, 0);
} }
@ -168,7 +168,7 @@ mod imp {
if status & RTIO_I_STATUS_OVERFLOW != 0 { if status & RTIO_I_STATUS_OVERFLOW != 0 {
raise!("RTIOOverflow", raise!("RTIOOverflow",
"RTIO input overflow on channel {0}", "RTIO input overflow on channel {rtio_channel_info:0}",
channel as i64, 0, 0); channel as i64, 0, 0);
} }
if status & RTIO_I_STATUS_WAIT_EVENT != 0 { if status & RTIO_I_STATUS_WAIT_EVENT != 0 {
@ -176,7 +176,7 @@ mod imp {
} }
if status & RTIO_I_STATUS_DESTINATION_UNREACHABLE != 0 { if status & RTIO_I_STATUS_DESTINATION_UNREACHABLE != 0 {
raise!("RTIODestinationUnreachable", raise!("RTIODestinationUnreachable",
"RTIO destination unreachable, input, on channel {0}", "RTIO destination unreachable, input, on channel {rtio_channel_info:0}",
channel as i64, 0, 0); channel as i64, 0, 0);
} }
@ -235,7 +235,7 @@ mod imp {
unimplemented!("not(has_rtio)") unimplemented!("not(has_rtio)")
} }
pub extern fn output_wide(_target: i32, _data: CSlice<i32>) { pub extern fn output_wide(_target: i32, _data: &CSlice<i32>) {
unimplemented!("not(has_rtio)") unimplemented!("not(has_rtio)")
} }

View File

@ -24,3 +24,4 @@ proto_artiq = { path = "../libproto_artiq" }
[features] [features]
uart_console = [] uart_console = []
alloc = []

View File

@ -0,0 +1,77 @@
use spi;
use board_misoc::{csr, clock};
const DATA_CTRL_REG : u8 = 0x02;
const IRCML_REG : u8 = 0x05;
const QRCML_REG : u8 = 0x08;
const CLKMODE_REG : u8 = 0x14;
const VERSION_REG : u8 = 0x1F;
const RETIMER_CLK_PHASE : u8 = 0b11;
fn hard_reset() {
unsafe {
// Min Pulse Width: 50ns
csr::dac_rst::out_write(1);
clock::spin_us(1);
csr::dac_rst::out_write(0);
}
}
fn spi_setup(dac_sel: u8, half_duplex: bool, end: bool) -> Result<(), &'static str> {
// Clear the cs_polarity and cs config
spi::set_config(0, 0, 8, 64, 0b1111)?;
spi::set_config(0, 1 << 3, 8, 64, (7 - dac_sel) << 1)?;
spi::set_config(0, (half_duplex as u8) << 7 | (end as u8) << 1, 8, 64, 0b0001)?;
Ok(())
}
fn write(dac_sel: u8, reg: u8, val: u8) -> Result<(), &'static str> {
spi_setup(dac_sel, false, false)?;
spi::write(0, (reg as u32) << 24)?;
spi_setup(dac_sel, false, true)?;
spi::write(0, (val as u32) << 24)?;
Ok(())
}
fn read(dac_sel: u8, reg: u8) -> Result<u8, &'static str> {
spi_setup(dac_sel, false, false)?;
spi::write(0, ((reg | 1 << 7) as u32) << 24)?;
spi_setup(dac_sel, true, true)?;
spi::write(0, 0)?;
Ok(spi::read(0)? as u8)
}
pub fn init() -> Result<(), &'static str> {
hard_reset();
for channel in 0..8 {
let reg = read(channel, VERSION_REG)?;
if reg != 0x0A {
debug!("DAC AD9117 Channel {} has incorrect hardware version. VERSION reg: {:02x}", channel, reg);
return Err("DAC AD9117 hardware version is not equal to 0x0A");
}
// Check for the presence of DCLKIO and CLKIN
let reg = read(channel, CLKMODE_REG)?;
if reg >> 4 & 1 != 0 {
debug!("DAC AD9117 Channel {} retiming fails. CLKMODE reg: {:02x}", channel, reg);
return Err("DAC AD9117 retiming failure");
}
// Force RETIMER-CLK to be Phase 1 as DCLKIO and CLKIN is known to be safe at Phase 1
// See Issue #2200
write(channel, CLKMODE_REG, RETIMER_CLK_PHASE << 6 | 1 << 2 | RETIMER_CLK_PHASE)?;
// Set the DACs input data format to be twos complement
// Set IFIRST and IRISING to True
write(channel, DATA_CTRL_REG, 1 << 7 | 1 << 5 | 1 << 4)?;
// Enable internal common mode resistors of both channels
write(channel, IRCML_REG, 1 << 7)?;
write(channel, QRCML_REG, 1 << 7)?;
}
Ok(())
}

View File

@ -1,549 +0,0 @@
use board_misoc::{csr, clock};
use ad9154_reg;
fn spi_setup(dacno: u8) {
unsafe {
while csr::converter_spi::idle_read() == 0 {}
csr::converter_spi::offline_write(0);
csr::converter_spi::end_write(1);
csr::converter_spi::cs_polarity_write(0b0001);
csr::converter_spi::clk_polarity_write(0);
csr::converter_spi::clk_phase_write(0);
csr::converter_spi::lsb_first_write(0);
csr::converter_spi::half_duplex_write(0);
csr::converter_spi::length_write(24 - 1);
csr::converter_spi::div_write(16 - 2);
csr::converter_spi::cs_write(1 << (csr::CONFIG_CONVERTER_SPI_FIRST_AD9154_CS + dacno as u32));
}
}
fn write(addr: u16, data: u8) {
unsafe {
while csr::converter_spi::writable_read() == 0 {}
csr::converter_spi::data_write(
((addr as u32) << 16) | ((data as u32) << 8));
}
}
fn read(addr: u16) -> u8 {
unsafe {
write((1 << 15) | addr, 0);
while csr::converter_spi::writable_read() == 0 {}
csr::converter_spi::data_read() as u8
}
}
// ad9154 mode 1
// linerate 5Gbps or 6Gbps
// deviceclock_fpga 125MHz or 150MHz
// deviceclock_dac 500MHz or 600MHz
struct JESDSettings {
did: u8,
bid: u8,
l: u8, // lanes
m: u8, // converters
n: u8, // bits/converter
np: u8, // bits/sample
f: u8, // octets/(lane and frame)
s: u8, // samples/(converter and frame)
k: u8, // frames/multiframe
cs: u8, // control bits/sample
subclassv: u8,
jesdv: u8
}
fn jesd_checksum(settings: &JESDSettings) -> u8 {
let mut r: u8 = 0;
for field in [
settings.did,
settings.bid,
settings.l - 1,
settings.f - 1,
settings.k - 1,
settings.m - 1,
settings.n - 1,
settings.cs,
settings.np - 1,
settings.subclassv,
settings.s - 1,
settings.jesdv,
].iter() {
r = r.overflowing_add(*field).0;
}
r
}
const JESD_SETTINGS: JESDSettings = JESDSettings {
did: 0x5a,
bid: 0x5,
l: 8,
m: 4,
n: 16,
np: 16,
f: 2,
s: 2,
k: 16,
cs: 0,
subclassv: 1,
jesdv: 1
};
pub fn reset_and_detect(dacno: u8) -> Result<(), &'static str> {
spi_setup(dacno);
// reset
write(ad9154_reg::SPI_INTFCONFA,
1*ad9154_reg::SOFTRESET_M | 1*ad9154_reg::SOFTRESET |
0*ad9154_reg::LSBFIRST_M | 0*ad9154_reg::LSBFIRST |
0*ad9154_reg::ADDRINC_M | 0*ad9154_reg::ADDRINC |
1*ad9154_reg::SDOACTIVE_M | 1*ad9154_reg::SDOACTIVE);
clock::spin_us(100);
write(ad9154_reg::SPI_INTFCONFA,
0*ad9154_reg::SOFTRESET_M | 0*ad9154_reg::SOFTRESET |
0*ad9154_reg::LSBFIRST_M | 0*ad9154_reg::LSBFIRST |
0*ad9154_reg::ADDRINC_M | 0*ad9154_reg::ADDRINC |
1*ad9154_reg::SDOACTIVE_M | 1*ad9154_reg::SDOACTIVE);
clock::spin_us(100);
if (read(ad9154_reg::PRODIDH) as u16) << 8 | (read(ad9154_reg::PRODIDL) as u16) != 0x9154 {
return Err("invalid AD9154 identification");
} else {
info!("AD9154-{} found", dacno);
}
Ok(())
}
pub fn setup(dacno: u8, linerate: u64) -> Result<(), &'static str> {
spi_setup(dacno);
info!("AD9154-{} initializing...", dacno);
write(ad9154_reg::PWRCNTRL0,
0*ad9154_reg::PD_DAC0 | 0*ad9154_reg::PD_DAC1 |
0*ad9154_reg::PD_DAC2 | 0*ad9154_reg::PD_DAC3 |
0*ad9154_reg::PD_BG);
clock::spin_us(100);
write(ad9154_reg::TXENMASK1, 0*ad9154_reg::DACA_MASK |
0*ad9154_reg::DACB_MASK); // DAC PD not controlled by TXEN pins
write(ad9154_reg::PWRCNTRL3, 1*ad9154_reg::ENA_SPI_TXEN |
1*ad9154_reg::SPI_TXEN);
write(ad9154_reg::CLKCFG0,
0*ad9154_reg::REF_CLKDIV_EN | 1*ad9154_reg::RF_SYNC_EN |
1*ad9154_reg::DUTY_EN | 0*ad9154_reg::PD_CLK_REC |
0*ad9154_reg::PD_SERDES_PCLK | 0*ad9154_reg::PD_CLK_DIG |
0*ad9154_reg::PD_CLK23 | 0*ad9154_reg::PD_CLK01);
write(ad9154_reg::DACPLLCNTRL,
0*ad9154_reg::ENABLE_DACPLL | 0*ad9154_reg::RECAL_DACPLL);
write(ad9154_reg::SYSREF_ACTRL0, // jesd204b subclass 1
0*ad9154_reg::HYS_CNTRL1 | 0*ad9154_reg::SYSREF_RISE |
0*ad9154_reg::HYS_ON | 0*ad9154_reg::PD_SYSREF_BUFFER);
write(ad9154_reg::DEVICE_CONFIG_REG_0, 0x8b); // magic
write(ad9154_reg::DEVICE_CONFIG_REG_1, 0x01); // magic
write(ad9154_reg::DEVICE_CONFIG_REG_2, 0x01); // magic
write(ad9154_reg::SPI_PAGEINDX, 0x3); // A and B dual
write(ad9154_reg::INTERP_MODE, 0x03); // 4x
write(ad9154_reg::MIX_MODE, 0);
write(ad9154_reg::DATA_FORMAT, 0*ad9154_reg::BINARY_FORMAT); // s16
write(ad9154_reg::DATAPATH_CTRL,
0*ad9154_reg::I_TO_Q | 0*ad9154_reg::SEL_SIDEBAND |
0*ad9154_reg::MODULATION_TYPE | 0*ad9154_reg::PHASE_ADJ_ENABLE |
1*ad9154_reg::DIG_GAIN_ENABLE | 0*ad9154_reg::INVSINC_ENABLE);
write(ad9154_reg::IDAC_DIG_GAIN0, 0x00);
write(ad9154_reg::IDAC_DIG_GAIN1, 0x8);
write(ad9154_reg::QDAC_DIG_GAIN0, 0x00);
write(ad9154_reg::QDAC_DIG_GAIN1, 0x8);
write(ad9154_reg::DC_OFFSET_CTRL, 0);
write(ad9154_reg::IPATH_DC_OFFSET_1PART0, 0x00);
write(ad9154_reg::IPATH_DC_OFFSET_1PART1, 0x00);
write(ad9154_reg::IPATH_DC_OFFSET_2PART, 0x00);
write(ad9154_reg::QPATH_DC_OFFSET_1PART0, 0x00);
write(ad9154_reg::QPATH_DC_OFFSET_1PART1, 0x00);
write(ad9154_reg::QPATH_DC_OFFSET_2PART, 0x00);
write(ad9154_reg::PHASE_ADJ0, 0);
write(ad9154_reg::PHASE_ADJ1, 0);
write(ad9154_reg::GROUP_DLY, 0x8*ad9154_reg::COARSE_GROUP_DELAY |
0x8*ad9154_reg::GROUP_DELAY_RESERVED);
write(ad9154_reg::GROUPDELAY_COMP_BYP,
1*ad9154_reg::GROUPCOMP_BYPQ |
1*ad9154_reg::GROUPCOMP_BYPI);
write(ad9154_reg::GROUPDELAY_COMP_I, 0);
write(ad9154_reg::GROUPDELAY_COMP_Q, 0);
write(ad9154_reg::PDP_AVG_TIME, 0*ad9154_reg::PDP_ENABLE);
write(ad9154_reg::MASTER_PD, 0);
write(ad9154_reg::PHY_PD, 0x00); // lanes 0-7 enabled
write(ad9154_reg::GENERIC_PD,
0*ad9154_reg::PD_SYNCOUT0B |
1*ad9154_reg::PD_SYNCOUT1B);
write(ad9154_reg::GENERAL_JRX_CTRL_0,
0x0*ad9154_reg::LINK_EN | 0*ad9154_reg::LINK_PAGE |
0*ad9154_reg::LINK_MODE | 0*ad9154_reg::CHECKSUM_MODE);
write(ad9154_reg::ILS_DID, JESD_SETTINGS.did);
write(ad9154_reg::ILS_BID, JESD_SETTINGS.bid);
write(ad9154_reg::ILS_LID0, 0x00); // lane id
write(ad9154_reg::ILS_SCR_L,
(JESD_SETTINGS.l - 1)*ad9154_reg::L_1 |
1*ad9154_reg::SCR);
write(ad9154_reg::ILS_F, JESD_SETTINGS.f - 1);
write(ad9154_reg::ILS_K, JESD_SETTINGS.k - 1);
write(ad9154_reg::ILS_M, JESD_SETTINGS.m - 1);
write(ad9154_reg::ILS_CS_N,
(JESD_SETTINGS.n - 1)*ad9154_reg::N_1 |
0*ad9154_reg::CS);
write(ad9154_reg::ILS_NP,
(JESD_SETTINGS.np - 1)*ad9154_reg::NP_1 |
JESD_SETTINGS.subclassv*ad9154_reg::SUBCLASSV);
write(ad9154_reg::ILS_S,
(JESD_SETTINGS.s - 1)*ad9154_reg::S_1 |
JESD_SETTINGS.jesdv*ad9154_reg::JESDV);
write(ad9154_reg::ILS_HD_CF,
0*ad9154_reg::HD | 0*ad9154_reg::CF);
write(ad9154_reg::ILS_CHECKSUM, jesd_checksum(&JESD_SETTINGS));
write(ad9154_reg::LANEDESKEW, 0xff);
for i in 0..8 {
write(ad9154_reg::BADDISPARITY, 0*ad9154_reg::RST_IRQ_DIS |
0*ad9154_reg::DISABLE_ERR_CNTR_DIS |
1*ad9154_reg::RST_ERR_CNTR_DIS | i*ad9154_reg::LANE_ADDR_DIS);
write(ad9154_reg::BADDISPARITY, 0*ad9154_reg::RST_IRQ_DIS |
0*ad9154_reg::DISABLE_ERR_CNTR_DIS |
0*ad9154_reg::RST_ERR_CNTR_DIS | i*ad9154_reg::LANE_ADDR_DIS);
write(ad9154_reg::NIT_W, 0*ad9154_reg::RST_IRQ_NIT |
0*ad9154_reg::DISABLE_ERR_CNTR_NIT |
1*ad9154_reg::RST_ERR_CNTR_NIT | i*ad9154_reg::LANE_ADDR_NIT);
write(ad9154_reg::NIT_W, 0*ad9154_reg::RST_IRQ_NIT |
0*ad9154_reg::DISABLE_ERR_CNTR_NIT |
0*ad9154_reg::RST_ERR_CNTR_NIT | i*ad9154_reg::LANE_ADDR_NIT);
write(ad9154_reg::UNEXPECTEDCONTROL_W, 0*ad9154_reg::RST_IRQ_UCC |
0*ad9154_reg::DISABLE_ERR_CNTR_UCC |
1*ad9154_reg::RST_ERR_CNTR_UCC | i*ad9154_reg::LANE_ADDR_UCC);
write(ad9154_reg::BADDISPARITY, 0*ad9154_reg::RST_IRQ_UCC |
0*ad9154_reg::DISABLE_ERR_CNTR_UCC |
0*ad9154_reg::RST_ERR_CNTR_UCC | i*ad9154_reg::LANE_ADDR_UCC);
}
write(ad9154_reg::CTRLREG1, JESD_SETTINGS.f);
write(ad9154_reg::CTRLREG2, 0*ad9154_reg::ILAS_MODE |
0*ad9154_reg::THRESHOLD_MASK_EN);
write(ad9154_reg::KVAL, 1); // *4*K multiframes during ILAS
write(ad9154_reg::LANEENABLE, 0xff); // CGS _after_ this
write(ad9154_reg::TERM_BLK1_CTRLREG0, 1);
write(ad9154_reg::TERM_BLK2_CTRLREG0, 1);
write(ad9154_reg::SERDES_SPI_REG, 1);
if linerate > 5_650_000_000 {
write(ad9154_reg::CDR_OPERATING_MODE_REG_0,
0*ad9154_reg::CDR_OVERSAMP | 0x2*ad9154_reg::CDR_RESERVED |
1*ad9154_reg::ENHALFRATE);
} else {
write(ad9154_reg::CDR_OPERATING_MODE_REG_0,
0*ad9154_reg::CDR_OVERSAMP | 0x2*ad9154_reg::CDR_RESERVED |
0*ad9154_reg::ENHALFRATE);
}
write(ad9154_reg::CDR_RESET, 0);
write(ad9154_reg::CDR_RESET, 1);
if linerate > 5_650_000_000 {
write(ad9154_reg::REF_CLK_DIVIDER_LDO,
0*ad9154_reg::SPI_CDR_OVERSAMP |
1*ad9154_reg::SPI_LDO_BYPASS_FILT |
0*ad9154_reg::SPI_LDO_REF_SEL);
} else {
write(ad9154_reg::REF_CLK_DIVIDER_LDO,
1*ad9154_reg::SPI_CDR_OVERSAMP |
1*ad9154_reg::SPI_LDO_BYPASS_FILT |
0*ad9154_reg::SPI_LDO_REF_SEL);
}
write(ad9154_reg::LDO_FILTER_1, 0x62); // magic
write(ad9154_reg::LDO_FILTER_2, 0xc9); // magic
write(ad9154_reg::LDO_FILTER_3, 0x0e); // magic
write(ad9154_reg::CP_CURRENT_SPI,
0x12*ad9154_reg::SPI_CP_CURRENT |
0*ad9154_reg::SPI_SERDES_LOGEN_POWER_MODE);
write(ad9154_reg::VCO_LDO, 0x7b); // magic
write(ad9154_reg::PLL_RD_REG,
0*ad9154_reg::SPI_SERDES_LOGEN_PD_CORE |
0*ad9154_reg::SPI_SERDES_LDO_PD | 0*ad9154_reg::SPI_SYN_PD |
0*ad9154_reg::SPI_VCO_PD_ALC | 0*ad9154_reg::SPI_VCO_PD_PTAT |
0*ad9154_reg::SPI_VCO_PD);
write(ad9154_reg::ALC_VARACTOR,
0x9*ad9154_reg::SPI_VCO_VARACTOR |
0x8*ad9154_reg::SPI_INIT_ALC_VALUE);
write(ad9154_reg::VCO_OUTPUT,
0xc*ad9154_reg::SPI_VCO_OUTPUT_LEVEL |
0x4*ad9154_reg::SPI_VCO_OUTPUT_RESERVED);
write(ad9154_reg::CP_CONFIG,
0*ad9154_reg::SPI_CP_TEST |
1*ad9154_reg::SPI_CP_CAL_EN |
0*ad9154_reg::SPI_CP_FORCE_CALBITS |
0*ad9154_reg::SPI_CP_OFFSET_OFF |
1*ad9154_reg::SPI_CP_ENABLE_MACHINE |
0*ad9154_reg::SPI_CP_DITHER_MODE |
0*ad9154_reg::SPI_CP_HALF_VCO_CAL_CLK);
write(ad9154_reg::VCO_BIAS_1,
0x3*ad9154_reg::SPI_VCO_BIAS_REF |
0x3*ad9154_reg::SPI_VCO_BIAS_TCF);
write(ad9154_reg::VCO_BIAS_2,
0x1*ad9154_reg::SPI_PRESCALE_BIAS |
1*ad9154_reg::SPI_LAST_ALC_EN |
0x1*ad9154_reg::SPI_PRESCALE_BYPASS_R |
0*ad9154_reg::SPI_VCO_COMP_BYPASS_BIASR |
0*ad9154_reg::SPI_VCO_BYPASS_DAC_R);
write(ad9154_reg::VCO_PD_OVERRIDES,
0*ad9154_reg::SPI_VCO_PD_OVERRIDE_VCO_BUF |
1*ad9154_reg::SPI_VCO_PD_OVERRIDE_CAL_TCF |
0*ad9154_reg::SPI_VCO_PD_OVERRIDE_VAR_REF_TCF |
0*ad9154_reg::SPI_VCO_PD_OVERRIDE_VAR_REF);
write(ad9154_reg::VCO_CAL,
0x2*ad9154_reg::SPI_FB_CLOCK_ADV |
0x3*ad9154_reg::SPI_VCO_CAL_COUNT |
0*ad9154_reg::SPI_VCO_CAL_ALC_WAIT |
1*ad9154_reg::SPI_VCO_CAL_EN);
write(ad9154_reg::CP_LEVEL_DETECT,
0x2*ad9154_reg::SPI_CP_LEVEL_THRESHOLD_HIGH |
0x5*ad9154_reg::SPI_CP_LEVEL_THRESHOLD_LOW |
0*ad9154_reg::SPI_CP_LEVEL_DET_PD);
write(ad9154_reg::VCO_VARACTOR_CTRL_0,
0xe*ad9154_reg::SPI_VCO_VARACTOR_OFFSET |
0x7*ad9154_reg::SPI_VCO_VARACTOR_REF_TCF);
write(ad9154_reg::VCO_VARACTOR_CTRL_1,
0x6*ad9154_reg::SPI_VCO_VARACTOR_REF);
// ensure link is txing
//write(ad9154_reg::SERDESPLL_ENABLE_CNTRL,
// 1*ad9154_reg::ENABLE_SERDESPLL | 1*ad9154_reg::RECAL_SERDESPLL)
write(ad9154_reg::SERDESPLL_ENABLE_CNTRL,
1*ad9154_reg::ENABLE_SERDESPLL | 0*ad9154_reg::RECAL_SERDESPLL);
let t = clock::get_ms();
while read(ad9154_reg::PLL_STATUS) & ad9154_reg::SERDES_PLL_LOCK_RB == 0 {
if clock::get_ms() > t + 200 {
return Err("SERDES PLL lock timeout");
}
}
write(ad9154_reg::EQ_BIAS_REG, 0x22*ad9154_reg::EQ_BIAS_RESERVED |
1*ad9154_reg::EQ_POWER_MODE);
write(ad9154_reg::GENERAL_JRX_CTRL_1, 1); // subclass 1
write(ad9154_reg::LMFC_DELAY_0, 0);
write(ad9154_reg::LMFC_DELAY_1, 0);
write(ad9154_reg::LMFC_VAR_0, 0x0a); // receive buffer delay
write(ad9154_reg::LMFC_VAR_1, 0x0a);
write(ad9154_reg::SYNC_ERRWINDOW, 0); // +- 1/2 DAC clock
// datasheet seems to say ENABLE and ARM should be separate steps,
// so enable now so it can be armed in sync().
write(ad9154_reg::SYNC_CONTROL,
0x1*ad9154_reg::SYNCMODE | 1*ad9154_reg::SYNCENABLE |
0*ad9154_reg::SYNCARM | 0*ad9154_reg::SYNCCLRSTKY);
write(ad9154_reg::XBAR_LN_0_1,
0*ad9154_reg::LOGICAL_LANE0_SRC | 1*ad9154_reg::LOGICAL_LANE1_SRC);
write(ad9154_reg::XBAR_LN_2_3,
2*ad9154_reg::LOGICAL_LANE2_SRC | 3*ad9154_reg::LOGICAL_LANE3_SRC);
write(ad9154_reg::XBAR_LN_4_5,
4*ad9154_reg::LOGICAL_LANE4_SRC | 5*ad9154_reg::LOGICAL_LANE5_SRC);
write(ad9154_reg::XBAR_LN_6_7,
6*ad9154_reg::LOGICAL_LANE6_SRC | 7*ad9154_reg::LOGICAL_LANE7_SRC);
write(ad9154_reg::JESD_BIT_INVERSE_CTRL, 0x00);
write(ad9154_reg::GENERAL_JRX_CTRL_0,
0x1*ad9154_reg::LINK_EN | 0*ad9154_reg::LINK_PAGE |
0*ad9154_reg::LINK_MODE | 0*ad9154_reg::CHECKSUM_MODE);
info!(" ...done");
Ok(())
}
pub fn status(dacno: u8) {
spi_setup(dacno);
info!("Printing status of AD9154-{}", dacno);
info!("PRODID: 0x{:04x}", (read(ad9154_reg::PRODIDH) as u16) << 8 | (read(ad9154_reg::PRODIDL) as u16));
info!("SERDES_PLL_LOCK: {}",
(read(ad9154_reg::PLL_STATUS) & ad9154_reg::SERDES_PLL_LOCK_RB));
info!("");
info!("CODEGRPSYNC: 0x{:02x}", read(ad9154_reg::CODEGRPSYNCFLG));
info!("FRAMESYNC: 0x{:02x}", read(ad9154_reg::FRAMESYNCFLG));
info!("GOODCHECKSUM: 0x{:02x}", read(ad9154_reg::GOODCHKSUMFLG));
info!("INITLANESYNC: 0x{:02x}", read(ad9154_reg::INITLANESYNCFLG));
info!("");
info!("DID_REG: 0x{:02x}", read(ad9154_reg::DID_REG));
info!("BID_REG: 0x{:02x}", read(ad9154_reg::BID_REG));
info!("SCR_L_REG: 0x{:02x}", read(ad9154_reg::SCR_L_REG));
info!("F_REG: 0x{:02x}", read(ad9154_reg::F_REG));
info!("K_REG: 0x{:02x}", read(ad9154_reg::K_REG));
info!("M_REG: 0x{:02x}", read(ad9154_reg::M_REG));
info!("CS_N_REG: 0x{:02x}", read(ad9154_reg::CS_N_REG));
info!("NP_REG: 0x{:02x}", read(ad9154_reg::NP_REG));
info!("S_REG: 0x{:02x}", read(ad9154_reg::S_REG));
info!("HD_CF_REG: 0x{:02x}", read(ad9154_reg::HD_CF_REG));
info!("RES1_REG: 0x{:02x}", read(ad9154_reg::RES1_REG));
info!("RES2_REG: 0x{:02x}", read(ad9154_reg::RES2_REG));
info!("LIDx_REG: 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x}",
read(ad9154_reg::LID0_REG),
read(ad9154_reg::LID1_REG),
read(ad9154_reg::LID2_REG),
read(ad9154_reg::LID3_REG),
read(ad9154_reg::LID4_REG),
read(ad9154_reg::LID5_REG),
read(ad9154_reg::LID6_REG),
read(ad9154_reg::LID7_REG));
info!("CHECKSUMx_REG: 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x}",
read(ad9154_reg::CHECKSUM0_REG),
read(ad9154_reg::CHECKSUM1_REG),
read(ad9154_reg::CHECKSUM2_REG),
read(ad9154_reg::CHECKSUM3_REG),
read(ad9154_reg::CHECKSUM4_REG),
read(ad9154_reg::CHECKSUM5_REG),
read(ad9154_reg::CHECKSUM6_REG),
read(ad9154_reg::CHECKSUM7_REG));
info!("COMPSUMx_REG: 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x} 0x{:02x}",
read(ad9154_reg::COMPSUM0_REG),
read(ad9154_reg::COMPSUM1_REG),
read(ad9154_reg::COMPSUM2_REG),
read(ad9154_reg::COMPSUM3_REG),
read(ad9154_reg::COMPSUM4_REG),
read(ad9154_reg::COMPSUM5_REG),
read(ad9154_reg::COMPSUM6_REG),
read(ad9154_reg::COMPSUM7_REG));
info!("BADDISPARITY: 0x{:02x}", read(ad9154_reg::BADDISPARITY));
info!("NITDISPARITY: 0x{:02x}", read(ad9154_reg::NIT_W));
}
pub fn prbs(dacno: u8) -> Result<(), &'static str> {
let mut prbs_errors: u32 = 0;
spi_setup(dacno);
/* follow phy prbs testing (p58 of ad9154 datasheet) */
info!("AD9154-{} running PRBS test...", dacno);
/* step 2: select prbs mode */
write(ad9154_reg::PHY_PRBS_TEST_CTRL,
0b00*ad9154_reg::PHY_PRBS_PAT_SEL);
/* step 3: enable test for all lanes */
write(ad9154_reg::PHY_PRBS_TEST_EN, 0xff);
/* step 4: reset */
write(ad9154_reg::PHY_PRBS_TEST_CTRL,
0b00*ad9154_reg::PHY_PRBS_PAT_SEL |
1*ad9154_reg::PHY_TEST_RESET);
write(ad9154_reg::PHY_PRBS_TEST_CTRL,
0b00*ad9154_reg::PHY_PRBS_PAT_SEL);
/* step 5: prbs threshold */
write(ad9154_reg::PHY_PRBS_TEST_THRESHOLD_LOBITS, 0);
write(ad9154_reg::PHY_PRBS_TEST_THRESHOLD_MIDBITS, 0);
write(ad9154_reg::PHY_PRBS_TEST_THRESHOLD_HIBITS, 0);
/* step 6: start */
write(ad9154_reg::PHY_PRBS_TEST_CTRL,
0b00*ad9154_reg::PHY_PRBS_PAT_SEL);
write(ad9154_reg::PHY_PRBS_TEST_CTRL,
0b00*ad9154_reg::PHY_PRBS_PAT_SEL |
1*ad9154_reg::PHY_TEST_START);
/* step 7: wait 500 ms */
clock::spin_us(500000);
/* step 8 : stop */
write(ad9154_reg::PHY_PRBS_TEST_CTRL,
0b00*ad9154_reg::PHY_PRBS_PAT_SEL);
for i in 0..8 {
/* step 9.a: select src err */
write(ad9154_reg::PHY_PRBS_TEST_CTRL,
i*ad9154_reg::PHY_SRC_ERR_CNT);
/* step 9.b: retrieve number of errors */
let lane_errors = (read(ad9154_reg::PHY_PRBS_TEST_ERRCNT_LOBITS) as u32) |
((read(ad9154_reg::PHY_PRBS_TEST_ERRCNT_MIDBITS) as u32) << 8) |
((read(ad9154_reg::PHY_PRBS_TEST_ERRCNT_HIBITS) as u32) << 16);
if lane_errors > 0 {
warn!(" PRBS errors on lane{}: {:06x}", i, lane_errors);
}
prbs_errors += lane_errors
}
if prbs_errors > 0 {
return Err("PRBS failed")
}
info!(" ...passed");
Ok(())
}
pub fn stpl(dacno: u8, m: u8, s: u8) -> Result<(), &'static str> {
spi_setup(dacno);
info!("AD9154-{} running STPL test...", dacno);
fn prng(seed: u32) -> u32 {
return ((seed + 1)*0x31415979 + 1) & 0xffff;
}
for i in 0..m {
let mut data: u32;
let mut errors: u8 = 0;
for j in 0..s {
/* select converter */
write(ad9154_reg::SHORT_TPL_TEST_0,
0b0*ad9154_reg::SHORT_TPL_TEST_EN |
0b0*ad9154_reg::SHORT_TPL_TEST_RESET |
i*ad9154_reg::SHORT_TPL_DAC_SEL |
j*ad9154_reg::SHORT_TPL_SP_SEL);
/* set expected value */
data = prng(((i as u32) << 8) | (j as u32));
write(ad9154_reg::SHORT_TPL_TEST_1, (data & 0x00ff) as u8);
write(ad9154_reg::SHORT_TPL_TEST_2, ((data & 0xff00) >> 8) as u8);
/* enable stpl */
write(ad9154_reg::SHORT_TPL_TEST_0,
0b1*ad9154_reg::SHORT_TPL_TEST_EN |
0b0*ad9154_reg::SHORT_TPL_TEST_RESET |
i*ad9154_reg::SHORT_TPL_DAC_SEL |
j*ad9154_reg::SHORT_TPL_SP_SEL);
/* reset stpl */
write(ad9154_reg::SHORT_TPL_TEST_0,
0b1*ad9154_reg::SHORT_TPL_TEST_EN |
0b1*ad9154_reg::SHORT_TPL_TEST_RESET |
i*ad9154_reg::SHORT_TPL_DAC_SEL |
j*ad9154_reg::SHORT_TPL_SP_SEL);
/* release reset stpl */
write(ad9154_reg::SHORT_TPL_TEST_0,
0b1*ad9154_reg::SHORT_TPL_TEST_EN |
0b0*ad9154_reg::SHORT_TPL_TEST_RESET |
i*ad9154_reg::SHORT_TPL_DAC_SEL |
j*ad9154_reg::SHORT_TPL_SP_SEL);
errors += read(ad9154_reg::SHORT_TPL_TEST_3);
}
info!(" c{} errors: {}", i, errors);
if errors > 0 {
return Err("STPL failed")
}
}
info!(" ...passed");
Ok(())
}
pub fn sync(dacno: u8) -> Result<bool, &'static str> {
spi_setup(dacno);
write(ad9154_reg::SYNC_CONTROL,
0x1*ad9154_reg::SYNCMODE | 1*ad9154_reg::SYNCENABLE |
1*ad9154_reg::SYNCARM | 1*ad9154_reg::SYNCCLRSTKY);
clock::spin_us(1000); // ensure at least one sysref edge
let sync_status = read(ad9154_reg::SYNC_STATUS);
if sync_status & ad9154_reg::SYNC_BUSY != 0 {
return Err("sync logic busy");
}
if sync_status & ad9154_reg::SYNC_LOCK == 0 {
return Err("no sync lock");
}
if sync_status & ad9154_reg::SYNC_TRIP == 0 {
return Err("no sysref edge");
}
let realign_occured = sync_status & ad9154_reg::SYNC_ROTATE != 0;
Ok(realign_occured)
}

View File

@ -1,826 +0,0 @@
#![allow(dead_code)]
pub const SPI_INTFCONFA : u16 = 0x000;
pub const SOFTRESET : u8 = 1 << 0;
pub const LSBFIRST : u8 = 1 << 1;
pub const ADDRINC : u8 = 1 << 2;
pub const SDOACTIVE : u8 = 1 << 3;
pub const SDOACTIVE_M : u8 = 1 << 4;
pub const ADDRINC_M : u8 = 1 << 5;
pub const LSBFIRST_M : u8 = 1 << 6;
pub const SOFTRESET_M : u8 = 1 << 7;
pub const CHIPTYPE : u16 = 0x003;
pub const PRODIDL : u16 = 0x004;
pub const PRODIDH : u16 = 0x005;
pub const CHIPGRADE : u16 = 0x006;
pub const DEV_REVISION : u8 = 1 << 0;
pub const PROD_GRADE : u8 = 1 << 4;
pub const SPI_PAGEINDX : u16 = 0x008;
pub const PWRCNTRL0 : u16 = 0x011;
pub const PD_DAC3 : u8 = 1 << 3;
pub const PD_DAC2 : u8 = 1 << 4;
pub const PD_DAC1 : u8 = 1 << 5;
pub const PD_DAC0 : u8 = 1 << 6;
pub const PD_BG : u8 = 1 << 7;
pub const TXENMASK1 : u16 = 0x012;
pub const DACA_MASK : u8 = 1 << 6;
pub const DACB_MASK : u8 = 1 << 7;
pub const PWRCNTRL3 : u16 = 0x013;
pub const SPI_TXEN : u8 = 1 << 0;
pub const ENA_SPI_TXEN : u8 = 1 << 1;
pub const SPI_PA_CTRL : u8 = 1 << 2;
pub const ENA_PA_CTRL_FROM_SPI : u8 = 1 << 3;
pub const ENA_PA_CTRL_FROM_BLSM : u8 = 1 << 4;
pub const ENA_PA_CTRL_FROM_TXENSM : u8 = 1 << 5;
pub const ENA_PA_CTRL_FROM_PARROT_ERR : u8 = 1 << 6;
pub const GROUP_DLY : u16 = 0x014;
pub const COARSE_GROUP_DELAY : u8 = 1 << 0;
pub const GROUP_DELAY_RESERVED : u8 = 1 << 4;
pub const IRQEN_STATUSMODE0 : u16 = 0x01f;
pub const IRQEN_SMODE_LANEFIFOERR : u8 = 1 << 1;
pub const IRQEN_SMODE_SERPLLLOCK : u8 = 1 << 2;
pub const IRQEN_SMODE_SERPLLLOST : u8 = 1 << 3;
pub const IRQEN_SMODE_DACPLLLOCK : u8 = 1 << 4;
pub const IRQEN_SMODE_DACPLLLOST : u8 = 1 << 5;
pub const IRQEN_STATUSMODE1 : u16 = 0x020;
pub const IRQEN_SMODE_PRBS0 : u8 = 1 << 0;
pub const IRQEN_SMODE_PRBS1 : u8 = 1 << 1;
pub const IRQEN_SMODE_PRBS2 : u8 = 1 << 2;
pub const IRQEN_SMODE_PRBS3 : u8 = 1 << 3;
pub const IRQEN_STATUSMODE2 : u16 = 0x021;
pub const IRQEN_SMODE_SYNC_TRIP0 : u8 = 1 << 0;
pub const IRQEN_SMODE_SYNC_WLIM0 : u8 = 1 << 1;
pub const IRQEN_SMODE_SYNC_ROTATE0 : u8 = 1 << 2;
pub const IRQEN_SMODE_SYNC_LOCK0 : u8 = 1 << 3;
pub const IRQEN_SMODE_NCO_ALIGN0 : u8 = 1 << 4;
pub const IRQEN_SMODE_BLNKDONE0 : u8 = 1 << 5;
pub const IRQEN_SMODE_PDPERR0 : u8 = 1 << 7;
pub const IRQEN_STATUSMODE3 : u16 = 0x022;
pub const IRQEN_SMODE_SYNC_TRIP1 : u8 = 1 << 0;
pub const IRQEN_SMODE_SYNC_WLIM1 : u8 = 1 << 1;
pub const IRQEN_SMODE_SYNC_ROTATE1 : u8 = 1 << 2;
pub const IRQEN_SMODE_SYNC_LOCK1 : u8 = 1 << 3;
pub const IRQEN_SMODE_NCO_ALIGN1 : u8 = 1 << 4;
pub const IRQEN_SMODE_BLNKDONE1 : u8 = 1 << 5;
pub const IRQEN_SMODE_PDPERR1 : u8 = 1 << 7;
pub const IRQ_STATUS0 : u16 = 0x023;
pub const LANEFIFOERR : u8 = 1 << 1;
pub const SERPLLLOCK : u8 = 1 << 2;
pub const SERPLLLOST : u8 = 1 << 3;
pub const DACPLLLOCK : u8 = 1 << 4;
pub const DACPLLLOST : u8 = 1 << 5;
pub const IRQ_STATUS1 : u16 = 0x024;
pub const PRBS0 : u8 = 1 << 0;
pub const PRBS1 : u8 = 1 << 1;
pub const PRBS2 : u8 = 1 << 2;
pub const PRBS3 : u8 = 1 << 3;
pub const IRQ_STATUS2 : u16 = 0x025;
pub const SYNC_TRIP0 : u8 = 1 << 0;
pub const SYNC_WLIM0 : u8 = 1 << 1;
pub const SYNC_ROTATE0 : u8 = 1 << 2;
pub const SYNC_LOCK0 : u8 = 1 << 3;
pub const NCO_ALIGN0 : u8 = 1 << 4;
pub const BLNKDONE0 : u8 = 1 << 5;
pub const PDPERR0 : u8 = 1 << 7;
pub const IRQ_STATUS3 : u16 = 0x026;
pub const SYNC_TRIP1 : u8 = 1 << 0;
pub const SYNC_WLIM1 : u8 = 1 << 1;
pub const SYNC_ROTATE1 : u8 = 1 << 2;
pub const SYNC_LOCK1 : u8 = 1 << 3;
pub const NCO_ALIGN1 : u8 = 1 << 4;
pub const BLNKDONE1 : u8 = 1 << 5;
pub const PDPERR1 : u8 = 1 << 7;
pub const JESD_CHECKS : u16 = 0x030;
pub const ERR_INTSUPP : u8 = 1 << 0;
pub const ERR_SUBCLASS : u8 = 1 << 1;
pub const ERR_KUNSUPP : u8 = 1 << 2;
pub const ERR_JESDBAD : u8 = 1 << 3;
pub const ERR_WINLIMIT : u8 = 1 << 4;
pub const ERR_DLYOVER : u8 = 1 << 5;
pub const SYNC_ERRWINDOW : u16 = 0x034;
pub const SYNC_LASTERR_L : u16 = 0x038;
pub const SYNC_LASTERR_H : u16 = 0x039;
pub const LASTERROR_H : u8 = 1 << 0;
pub const LASTOVER : u8 = 1 << 6;
pub const LASTUNDER : u8 = 1 << 7;
pub const SYNC_CONTROL : u16 = 0x03a;
pub const SYNCMODE : u8 = 1 << 0;
pub const SYNCCLRLAST : u8 = 1 << 4;
pub const SYNCCLRSTKY : u8 = 1 << 5;
pub const SYNCARM : u8 = 1 << 6;
pub const SYNCENABLE : u8 = 1 << 7;
pub const SYNC_STATUS : u16 = 0x03b;
pub const SYNC_TRIP : u8 = 1 << 0;
pub const SYNC_WLIM : u8 = 1 << 1;
pub const SYNC_ROTATE : u8 = 1 << 2;
pub const SYNC_LOCK : u8 = 1 << 3;
pub const SYNC_BUSY : u8 = 1 << 7;
pub const SYNC_CURRERR_L : u16 = 0x03c;
pub const SYNC_CURRERR_H : u16 = 0x03d;
pub const CURRERROR_H : u8 = 1 << 0;
pub const CURROVER : u8 = 1 << 6;
pub const CURRUNDER : u8 = 1 << 7;
pub const DACGAIN0_I : u16 = 0x040;
pub const DACGAIN1_I : u16 = 0x041;
pub const DACGAIN0_Q : u16 = 0x042;
pub const DACGAIN1_Q : u16 = 0x043;
pub const GROUPDELAY_COMP_I : u16 = 0x044;
pub const GROUPDELAY_COMP_Q : u16 = 0x045;
pub const GROUPDELAY_COMP_BYP : u16 = 0x046;
pub const GROUPCOMP_BYPQ : u8 = 1 << 0;
pub const GROUPCOMP_BYPI : u8 = 1 << 1;
pub const MIX_MODE : u16 = 0x04a;
pub const NCOALIGN_MODE : u16 = 0x050;
pub const NCO_ALIGN_MODE : u8 = 1 << 0;
pub const NCO_ALIGN_FAIL : u8 = 1 << 3;
pub const NCO_ALIGN_PASS : u8 = 1 << 4;
pub const NCO_ALIGN_MTCH : u8 = 1 << 5;
pub const NCO_ALIGN_ARM : u8 = 1 << 7;
pub const NCOKEY_ILSB : u16 = 0x051;
pub const NCOKEY_IMSB : u16 = 0x052;
pub const NCOKEY_QLSB : u16 = 0x053;
pub const NCOKEY_QMSB : u16 = 0x054;
pub const PDP_THRES0 : u16 = 0x060;
pub const PDP_THRES1 : u16 = 0x061;
pub const PDP_AVG_TIME : u16 = 0x062;
pub const PDP_AVG_TIME_ : u8 = 1 << 0;
pub const PA_BUS_SWAP : u8 = 1 << 6;
pub const PDP_ENABLE : u8 = 1 << 7;
pub const PDP_POWER0 : u16 = 0x063;
pub const PDP_POWER1 : u16 = 0x064;
pub const CLKCFG0 : u16 = 0x080;
pub const REF_CLKDIV_EN : u8 = 1 << 0;
pub const RF_SYNC_EN : u8 = 1 << 1;
pub const DUTY_EN : u8 = 1 << 2;
pub const PD_CLK_REC : u8 = 1 << 3;
pub const PD_SERDES_PCLK : u8 = 1 << 4;
pub const PD_CLK_DIG : u8 = 1 << 5;
pub const PD_CLK23 : u8 = 1 << 6;
pub const PD_CLK01 : u8 = 1 << 7;
pub const SYSREF_ACTRL0 : u16 = 0x081;
pub const HYS_CNTRL1 : u8 = 1 << 0;
pub const SYSREF_RISE : u8 = 1 << 2;
pub const HYS_ON : u8 = 1 << 3;
pub const PD_SYSREF_BUFFER : u8 = 1 << 4;
pub const SYSREF_ACTRL1 : u16 = 0x082;
pub const DACPLLCNTRL : u16 = 0x083;
pub const ENABLE_DACPLL : u8 = 1 << 4;
pub const RECAL_DACPLL : u8 = 1 << 7;
pub const DACPLLSTATUS : u16 = 0x084;
pub const DACPLL_LOCK : u8 = 1 << 1;
pub const VCO_CAL_PROGRESS : u8 = 1 << 3;
pub const CP_CAL_VALID : u8 = 1 << 4;
pub const CP_OVERRANGE_L : u8 = 1 << 5;
pub const CP_OVERRANGE_H : u8 = 1 << 6;
pub const DACINTEGERWORD0 : u16 = 0x085;
pub const DACLOOPFILT1 : u16 = 0x087;
pub const LF_C1_WORD : u8 = 1 << 0;
pub const LF_C2_WORD : u8 = 1 << 4;
pub const DACLOOPFILT2 : u16 = 0x088;
pub const LF_C3_WORD : u8 = 1 << 0;
pub const LF_R1_WORD : u8 = 1 << 4;
pub const DACLOOPFILT3 : u16 = 0x089;
pub const LF_R3_WORD : u8 = 1 << 0;
pub const LF_BYPASS_C1 : u8 = 1 << 4;
pub const LF_BYPASS_C2 : u8 = 1 << 5;
pub const LF_BYPASS_R1 : u8 = 1 << 6;
pub const LF_BYPASS_R3 : u8 = 1 << 7;
pub const DACCPCNTRL : u16 = 0x08a;
pub const CP_CURRENT : u8 = 1 << 0;
pub const VT_FORCE : u8 = 1 << 6;
pub const DACLOGENCNTRL : u16 = 0x08b;
pub const LODIVMODE : u8 = 1 << 0;
pub const LO_POWER_MODE : u8 = 1 << 4;
pub const DACLDOCNTRL1 : u16 = 0x08c;
pub const REFDIVMODE : u8 = 1 << 0;
pub const LDO_BYPASS_FLT : u8 = 1 << 6;
pub const LDO_REF_SEL : u8 = 1 << 7;
pub const DACLDOCNTRL2 : u16 = 0x08d;
pub const LDO_VDROP : u8 = 1 << 0;
pub const LDO_SEL : u8 = 1 << 2;
pub const LDO_INRUSH : u8 = 1 << 5;
pub const LDO_BYPASS : u8 = 1 << 7;
pub const DATA_FORMAT : u16 = 0x110;
pub const BINARY_FORMAT : u8 = 1 << 7;
pub const DATAPATH_CTRL : u16 = 0x111;
pub const I_TO_Q : u8 = 1 << 0;
pub const SEL_SIDEBAND : u8 = 1 << 1;
pub const MODULATION_TYPE : u8 = 1 << 2;
pub const PHASE_ADJ_ENABLE : u8 = 1 << 4;
pub const DIG_GAIN_ENABLE : u8 = 1 << 5;
pub const INVSINC_ENABLE : u8 = 1 << 7;
pub const INTERP_MODE : u16 = 0x112;
pub const NCO_FTW_UPDATE : u16 = 0x113;
pub const FTW_UPDATE_REQ : u8 = 1 << 0;
pub const FTW_UPDATE_ACK : u8 = 1 << 1;
pub const FTW0 : u16 = 0x114;
pub const FTW1 : u16 = 0x115;
pub const FTW2 : u16 = 0x116;
pub const FTW3 : u16 = 0x117;
pub const FTW4 : u16 = 0x118;
pub const FTW5 : u16 = 0x119;
pub const NCO_PHASE_OFFSET0 : u16 = 0x11a;
pub const NCO_PHASE_OFFSET1 : u16 = 0x11b;
pub const PHASE_ADJ0 : u16 = 0x11c;
pub const PHASE_ADJ1 : u16 = 0x11d;
pub const TXEN_SM_0 : u16 = 0x11f;
pub const TXEN_SM_EN : u8 = 1 << 0;
pub const GP_PA_CTRL : u8 = 1 << 1;
pub const GP_PA_ON_INVERT : u8 = 1 << 2;
pub const RISE_COUNTERS : u8 = 1 << 4;
pub const FALL_COUNTERS : u8 = 1 << 6;
pub const TXEN_RISE_COUNT_0 : u16 = 0x121;
pub const TXEN_RISE_COUNT_1 : u16 = 0x122;
pub const TXEN_FALL_COUNT_0 : u16 = 0x123;
pub const TXEN_FALL_COUNT_1 : u16 = 0x124;
pub const DEVICE_CONFIG_REG_0 : u16 = 0x12d;
pub const DIE_TEMP_CTRL0 : u16 = 0x12f;
pub const AUXADC_ENABLE : u8 = 1 << 0;
pub const AUXADC_RESERVED : u8 = 1 << 1;
pub const DIE_TEMP0 : u16 = 0x132;
pub const DIE_TEMP1 : u16 = 0x133;
pub const DIE_TEMP_UPDATE : u16 = 0x134;
pub const DC_OFFSET_CTRL : u16 = 0x135;
pub const IPATH_DC_OFFSET_1PART0 : u16 = 0x136;
pub const IPATH_DC_OFFSET_1PART1 : u16 = 0x137;
pub const QPATH_DC_OFFSET_1PART0 : u16 = 0x138;
pub const QPATH_DC_OFFSET_1PART1 : u16 = 0x139;
pub const IPATH_DC_OFFSET_2PART : u16 = 0x13a;
pub const QPATH_DC_OFFSET_2PART : u16 = 0x13b;
pub const IDAC_DIG_GAIN0 : u16 = 0x13c;
pub const IDAC_DIG_GAIN1 : u16 = 0x13d;
pub const QDAC_DIG_GAIN0 : u16 = 0x13e;
pub const QDAC_DIG_GAIN1 : u16 = 0x13f;
pub const GAIN_RAMP_UP_STEP0 : u16 = 0x140;
pub const GAIN_RAMP_UP_STEP1 : u16 = 0x141;
pub const GAIN_RAMP_DOWN_STEP0 : u16 = 0x142;
pub const GAIN_RAMP_DOWN_STEP1 : u16 = 0x143;
pub const DEVICE_CONFIG_REG_1 : u16 = 0x146;
pub const BSM_STAT : u16 = 0x147;
pub const SOFTBLANKRB : u8 = 1 << 6;
pub const PRBS : u16 = 0x14b;
pub const PRBS_EN : u8 = 1 << 0;
pub const PRBS_RESET : u8 = 1 << 1;
pub const PRBS_MODE : u8 = 1 << 2;
pub const PRBS_GOOD_I : u8 = 1 << 6;
pub const PRBS_GOOD_Q : u8 = 1 << 7;
pub const PRBS_ERROR_I : u16 = 0x14c;
pub const PRBS_ERROR_Q : u16 = 0x14d;
pub const DACPLLT0 : u16 = 0x1b0;
pub const LOGEN_PD : u8 = 1 << 1;
pub const LDO_PD : u8 = 1 << 3;
pub const SYNTH_PD : u8 = 1 << 4;
pub const VCO_PD_ALC : u8 = 1 << 5;
pub const VCO_PD_PTAT : u8 = 1 << 6;
pub const VCO_PD_IN : u8 = 1 << 7;
pub const DACPLLT1 : u16 = 0x1b1;
pub const PFD_EDGE : u8 = 1 << 1;
pub const PFD_DELAY : u8 = 1 << 2;
pub const DACPLLT2 : u16 = 0x1b2;
pub const EXT_ALC_WORD : u8 = 1 << 0;
pub const EXT_ALC_WORD_EN : u8 = 1 << 7;
pub const DACPLLT3 : u16 = 0x1b3;
pub const EXT_BAND1 : u8 = 1 << 0;
pub const DACPLLT4 : u16 = 0x1b4;
pub const EXT_BAND2 : u8 = 1 << 0;
pub const EXT_BAND_EN : u8 = 1 << 1;
pub const VCO_CAL_OFFSET : u8 = 1 << 3;
pub const BYP_LOAD_DELAY : u8 = 1 << 7;
pub const DACPLLT5 : u16 = 0x1b5;
pub const DACPLLT6 : u16 = 0x1b6;
pub const DACPLLT7 : u16 = 0x1b7;
pub const DACPLLT8 : u16 = 0x1b8;
pub const DACPLLT9 : u16 = 0x1b9;
pub const DACPLLTA : u16 = 0x1ba;
pub const DACPLLTB : u16 = 0x1bb;
pub const VCO_BIAS_REF : u8 = 1 << 0;
pub const VCO_BIAS_TCF : u8 = 1 << 3;
pub const DACPLLTC : u16 = 0x1bc;
pub const DACPLLTD : u16 = 0x1bd;
pub const DACPLLTE : u16 = 0x1be;
pub const DACPLLTF : u16 = 0x1bf;
pub const DACPLLT10 : u16 = 0x1c0;
pub const DACPLLT11 : u16 = 0x1c1;
pub const DACPLLT15 : u16 = 0x1c2;
pub const DACPLLT16 : u16 = 0x1c3;
pub const DACPLLT17 : u16 = 0x1c4;
pub const DACPLLT18 : u16 = 0x1c5;
pub const MASTER_PD : u16 = 0x200;
pub const PHY_PD : u16 = 0x201;
pub const GENERIC_PD : u16 = 0x203;
pub const PD_SYNCOUT1B : u8 = 1 << 0;
pub const PD_SYNCOUT0B : u8 = 1 << 1;
pub const CDR_RESET : u16 = 0x206;
pub const CDR_OPERATING_MODE_REG_0 : u16 = 0x230;
pub const CDR_OVERSAMP : u8 = 1 << 1;
pub const CDR_RESERVED : u8 = 1 << 2;
pub const ENHALFRATE : u8 = 1 << 5;
pub const EQ_BIAS_REG : u16 = 0x268;
pub const EQ_BIAS_RESERVED : u8 = 1 << 0;
pub const EQ_POWER_MODE : u8 = 1 << 6;
pub const SERDESPLL_ENABLE_CNTRL : u16 = 0x280;
pub const ENABLE_SERDESPLL : u8 = 1 << 0;
pub const RECAL_SERDESPLL : u8 = 1 << 2;
pub const PLL_STATUS : u16 = 0x281;
pub const SERDES_PLL_LOCK_RB : u8 = 1 << 0;
pub const SERDES_CURRENTS_READY_RB : u8 = 1 << 1;
pub const SERDES_VCO_CAL_IN_PROGRESS_RB : u8 = 1 << 2;
pub const SERDES_PLL_CAL_VALID_RB : u8 = 1 << 3;
pub const SERDES_PLL_OVERRANGE_L_RB : u8 = 1 << 4;
pub const SERDES_PLL_OVERRANGE_H_RB : u8 = 1 << 5;
pub const LDO_FILTER_1 : u16 = 0x284;
pub const LDO_FILTER_2 : u16 = 0x285;
pub const LDO_FILTER_3 : u16 = 0x286;
pub const CP_CURRENT_SPI : u16 = 0x287;
pub const SPI_CP_CURRENT : u8 = 1 << 0;
pub const SPI_SERDES_LOGEN_POWER_MODE : u8 = 1 << 6;
pub const REF_CLK_DIVIDER_LDO : u16 = 0x289;
pub const SPI_CDR_OVERSAMP : u8 = 1 << 0;
pub const SPI_LDO_BYPASS_FILT : u8 = 1 << 2;
pub const SPI_LDO_REF_SEL : u8 = 1 << 3;
pub const VCO_LDO : u16 = 0x28a;
pub const PLL_RD_REG : u16 = 0x28b;
pub const SPI_SERDES_LOGEN_PD_CORE : u8 = 1 << 0;
pub const SPI_SERDES_LDO_PD : u8 = 1 << 2;
pub const SPI_SYN_PD : u8 = 1 << 3;
pub const SPI_VCO_PD_ALC : u8 = 1 << 4;
pub const SPI_VCO_PD_PTAT : u8 = 1 << 5;
pub const SPI_VCO_PD : u8 = 1 << 6;
pub const ALC_VARACTOR : u16 = 0x290;
pub const SPI_VCO_VARACTOR : u8 = 1 << 0;
pub const SPI_INIT_ALC_VALUE : u8 = 1 << 4;
pub const VCO_OUTPUT : u16 = 0x291;
pub const SPI_VCO_OUTPUT_LEVEL : u8 = 1 << 0;
pub const SPI_VCO_OUTPUT_RESERVED : u8 = 1 << 4;
pub const CP_CONFIG : u16 = 0x294;
pub const SPI_CP_TEST : u8 = 1 << 0;
pub const SPI_CP_CAL_EN : u8 = 1 << 2;
pub const SPI_CP_FORCE_CALBITS : u8 = 1 << 3;
pub const SPI_CP_OFFSET_OFF : u8 = 1 << 4;
pub const SPI_CP_ENABLE_MACHINE : u8 = 1 << 5;
pub const SPI_CP_DITHER_MODE : u8 = 1 << 6;
pub const SPI_CP_HALF_VCO_CAL_CLK : u8 = 1 << 7;
pub const VCO_BIAS_1 : u16 = 0x296;
pub const SPI_VCO_BIAS_REF : u8 = 1 << 0;
pub const SPI_VCO_BIAS_TCF : u8 = 1 << 3;
pub const VCO_BIAS_2 : u16 = 0x297;
pub const SPI_PRESCALE_BIAS : u8 = 1 << 0;
pub const SPI_LAST_ALC_EN : u8 = 1 << 2;
pub const SPI_PRESCALE_BYPASS_R : u8 = 1 << 3;
pub const SPI_VCO_COMP_BYPASS_BIASR : u8 = 1 << 4;
pub const SPI_VCO_BYPASS_DAC_R : u8 = 1 << 5;
pub const VCO_PD_OVERRIDES : u16 = 0x299;
pub const SPI_VCO_PD_OVERRIDE_VCO_BUF : u8 = 1 << 0;
pub const SPI_VCO_PD_OVERRIDE_CAL_TCF : u8 = 1 << 1;
pub const SPI_VCO_PD_OVERRIDE_VAR_REF_TCF : u8 = 1 << 2;
pub const SPI_VCO_PD_OVERRIDE_VAR_REF : u8 = 1 << 3;
pub const VCO_CAL : u16 = 0x29a;
pub const SPI_FB_CLOCK_ADV : u8 = 1 << 0;
pub const SPI_VCO_CAL_COUNT : u8 = 1 << 2;
pub const SPI_VCO_CAL_ALC_WAIT : u8 = 1 << 4;
pub const SPI_VCO_CAL_EN : u8 = 1 << 7;
pub const CP_LEVEL_DETECT : u16 = 0x29c;
pub const SPI_CP_LEVEL_THRESHOLD_HIGH : u8 = 1 << 0;
pub const SPI_CP_LEVEL_THRESHOLD_LOW : u8 = 1 << 3;
pub const SPI_CP_LEVEL_DET_PD : u8 = 1 << 6;
pub const VCO_VARACTOR_CTRL_0 : u16 = 0x29f;
pub const SPI_VCO_VARACTOR_OFFSET : u8 = 1 << 0;
pub const SPI_VCO_VARACTOR_REF_TCF : u8 = 1 << 4;
pub const VCO_VARACTOR_CTRL_1 : u16 = 0x2a0;
pub const SPI_VCO_VARACTOR_REF : u8 = 1 << 0;
pub const TERM_BLK1_CTRLREG0 : u16 = 0x2a7;
pub const TERM_BLK2_CTRLREG0 : u16 = 0x2ae;
pub const GENERAL_JRX_CTRL_0 : u16 = 0x300;
pub const LINK_EN : u8 = 1 << 0;
pub const LINK_PAGE : u8 = 1 << 2;
pub const LINK_MODE : u8 = 1 << 3;
pub const CHECKSUM_MODE : u8 = 1 << 6;
pub const GENERAL_JRX_CTRL_1 : u16 = 0x301;
pub const DYN_LINK_LATENCY_0 : u16 = 0x302;
pub const DYN_LINK_LATENCY_1 : u16 = 0x303;
pub const LMFC_DELAY_0 : u16 = 0x304;
pub const LMFC_DELAY_1 : u16 = 0x305;
pub const LMFC_VAR_0 : u16 = 0x306;
pub const LMFC_VAR_1 : u16 = 0x307;
pub const XBAR_LN_0_1 : u16 = 0x308;
pub const LOGICAL_LANE0_SRC : u8 = 1 << 0;
pub const LOGICAL_LANE1_SRC : u8 = 1 << 3;
pub const XBAR_LN_2_3 : u16 = 0x309;
pub const LOGICAL_LANE2_SRC : u8 = 1 << 0;
pub const LOGICAL_LANE3_SRC : u8 = 1 << 3;
pub const XBAR_LN_4_5 : u16 = 0x30a;
pub const LOGICAL_LANE4_SRC : u8 = 1 << 0;
pub const LOGICAL_LANE5_SRC : u8 = 1 << 3;
pub const XBAR_LN_6_7 : u16 = 0x30b;
pub const LOGICAL_LANE6_SRC : u8 = 1 << 0;
pub const LOGICAL_LANE7_SRC : u8 = 1 << 3;
pub const FIFO_STATUS_REG_0 : u16 = 0x30c;
pub const FIFO_STATUS_REG_1 : u16 = 0x30d;
pub const SYNCB_GEN_1 : u16 = 0x312;
pub const SYNCB_ERR_DUR : u8 = 1 << 4;
pub const SERDES_SPI_REG : u16 = 0x314;
pub const PHY_PRBS_TEST_EN : u16 = 0x315;
pub const PHY_PRBS_TEST_CTRL : u16 = 0x316;
pub const PHY_TEST_RESET : u8 = 1 << 0;
pub const PHY_TEST_START : u8 = 1 << 1;
pub const PHY_PRBS_PAT_SEL : u8 = 1 << 2;
pub const PHY_SRC_ERR_CNT : u8 = 1 << 4;
pub const PHY_PRBS_TEST_THRESHOLD_LOBITS : u16 = 0x317;
pub const PHY_PRBS_TEST_THRESHOLD_MIDBITS : u16 = 0x318;
pub const PHY_PRBS_TEST_THRESHOLD_HIBITS : u16 = 0x319;
pub const PHY_PRBS_TEST_ERRCNT_LOBITS : u16 = 0x31a;
pub const PHY_PRBS_TEST_ERRCNT_MIDBITS : u16 = 0x31b;
pub const PHY_PRBS_TEST_ERRCNT_HIBITS : u16 = 0x31c;
pub const PHY_PRBS_TEST_STATUS : u16 = 0x31d;
pub const SHORT_TPL_TEST_0 : u16 = 0x32c;
pub const SHORT_TPL_TEST_EN : u8 = 1 << 0;
pub const SHORT_TPL_TEST_RESET : u8 = 1 << 1;
pub const SHORT_TPL_DAC_SEL : u8 = 1 << 2;
pub const SHORT_TPL_SP_SEL : u8 = 1 << 4;
pub const SHORT_TPL_TEST_1 : u16 = 0x32d;
pub const SHORT_TPL_TEST_2 : u16 = 0x32e;
pub const SHORT_TPL_TEST_3 : u16 = 0x32f;
pub const DEVICE_CONFIG_REG_2 : u16 = 0x333;
pub const JESD_BIT_INVERSE_CTRL : u16 = 0x334;
pub const DID_REG : u16 = 0x400;
pub const BID_REG : u16 = 0x401;
pub const BID_RD : u8 = 1 << 0;
pub const ADJCNT_RD : u8 = 1 << 4;
pub const LID0_REG : u16 = 0x402;
pub const LID0_RD : u8 = 1 << 0;
pub const PHADJ_RD : u8 = 1 << 5;
pub const ADJDIR_RD : u8 = 1 << 6;
pub const SCR_L_REG : u16 = 0x403;
pub const L_1_RD : u8 = 1 << 0;
pub const SCR_RD : u8 = 1 << 7;
pub const F_REG : u16 = 0x404;
pub const K_REG : u16 = 0x405;
pub const M_REG : u16 = 0x406;
pub const CS_N_REG : u16 = 0x407;
pub const N_1_RD : u8 = 1 << 0;
pub const CS_RD : u8 = 1 << 6;
pub const NP_REG : u16 = 0x408;
pub const NP_1_RD : u8 = 1 << 0;
pub const SUBCLASSV_RD : u8 = 1 << 5;
pub const S_REG : u16 = 0x409;
pub const S_1_RD : u8 = 1 << 0;
pub const JESDV_RD : u8 = 1 << 5;
pub const HD_CF_REG : u16 = 0x40a;
pub const CF_RD : u8 = 1 << 0;
pub const HD_RD : u8 = 1 << 7;
pub const RES1_REG : u16 = 0x40b;
pub const RES2_REG : u16 = 0x40c;
pub const CHECKSUM0_REG : u16 = 0x40d;
pub const COMPSUM0_REG : u16 = 0x40e;
pub const LID1_REG : u16 = 0x412;
pub const CHECKSUM1_REG : u16 = 0x415;
pub const COMPSUM1_REG : u16 = 0x416;
pub const LID2_REG : u16 = 0x41a;
pub const CHECKSUM2_REG : u16 = 0x41d;
pub const COMPSUM2_REG : u16 = 0x41e;
pub const LID3_REG : u16 = 0x422;
pub const CHECKSUM3_REG : u16 = 0x425;
pub const COMPSUM3_REG : u16 = 0x426;
pub const LID4_REG : u16 = 0x42a;
pub const CHECKSUM4_REG : u16 = 0x42d;
pub const COMPSUM4_REG : u16 = 0x42e;
pub const LID5_REG : u16 = 0x432;
pub const CHECKSUM5_REG : u16 = 0x435;
pub const COMPSUM5_REG : u16 = 0x436;
pub const LID6_REG : u16 = 0x43a;
pub const CHECKSUM6_REG : u16 = 0x43d;
pub const COMPSUM6_REG : u16 = 0x43e;
pub const LID7_REG : u16 = 0x442;
pub const CHECKSUM7_REG : u16 = 0x445;
pub const COMPSUM7_REG : u16 = 0x446;
pub const ILS_DID : u16 = 0x450;
pub const ILS_BID : u16 = 0x451;
pub const BID : u8 = 1 << 0;
pub const ADJCNT : u8 = 1 << 4;
pub const ILS_LID0 : u16 = 0x452;
pub const LID0 : u8 = 1 << 0;
pub const PHADJ : u8 = 1 << 5;
pub const ADJDIR : u8 = 1 << 6;
pub const ILS_SCR_L : u16 = 0x453;
pub const L_1 : u8 = 1 << 0;
pub const SCR : u8 = 1 << 7;
pub const ILS_F : u16 = 0x454;
pub const ILS_K : u16 = 0x455;
pub const ILS_M : u16 = 0x456;
pub const ILS_CS_N : u16 = 0x457;
pub const N_1 : u8 = 1 << 0;
pub const CS : u8 = 1 << 6;
pub const ILS_NP : u16 = 0x458;
pub const NP_1 : u8 = 1 << 0;
pub const SUBCLASSV : u8 = 1 << 5;
pub const ILS_S : u16 = 0x459;
pub const S_1 : u8 = 1 << 0;
pub const JESDV : u8 = 1 << 5;
pub const ILS_HD_CF : u16 = 0x45a;
pub const CF : u8 = 1 << 0;
pub const HD : u8 = 1 << 7;
pub const ILS_RES1 : u16 = 0x45b;
pub const ILS_RES2 : u16 = 0x45c;
pub const ILS_CHECKSUM : u16 = 0x45d;
pub const ERRCNTRMON : u16 = 0x46b;
pub const CNTRSEL : u8 = 1 << 0;
pub const LANESEL : u8 = 1 << 4;
pub const LANEDESKEW : u16 = 0x46c;
pub const BADDISPARITY : u16 = 0x46d;
pub const LANE_ADDR_DIS : u8 = 1 << 0;
pub const RST_ERR_CNTR_DIS : u8 = 1 << 5;
pub const DISABLE_ERR_CNTR_DIS : u8 = 1 << 6;
pub const RST_IRQ_DIS : u8 = 1 << 7;
pub const NIT_W : u16 = 0x46e;
pub const LANE_ADDR_NIT : u8 = 1 << 0;
pub const RST_ERR_CNTR_NIT : u8 = 1 << 5;
pub const DISABLE_ERR_CNTR_NIT : u8 = 1 << 6;
pub const RST_IRQ_NIT : u8 = 1 << 7;
pub const UNEXPECTEDCONTROL_W : u16 = 0x46f;
pub const LANE_ADDR_UCC : u8 = 1 << 0;
pub const RST_ERR_CNTR_UCC : u8 = 1 << 5;
pub const DISABLE_ERR_CNTR_UCC : u8 = 1 << 6;
pub const RST_IRQ_UCC : u8 = 1 << 7;
pub const CODEGRPSYNCFLG : u16 = 0x470;
pub const FRAMESYNCFLG : u16 = 0x471;
pub const GOODCHKSUMFLG : u16 = 0x472;
pub const INITLANESYNCFLG : u16 = 0x473;
pub const CTRLREG1 : u16 = 0x476;
pub const CTRLREG2 : u16 = 0x477;
pub const THRESHOLD_MASK_EN : u8 = 1 << 3;
pub const ILAS_MODE : u8 = 1 << 7;
pub const KVAL : u16 = 0x478;
pub const IRQVECTOR_MASK : u16 = 0x47a;
pub const CODEGRPSYNC_MASK : u8 = 1 << 0;
pub const BADCHECKSUM_MASK : u8 = 1 << 2;
pub const INITIALLANESYNC_MASK : u8 = 1 << 3;
pub const UCC_MASK : u8 = 1 << 5;
pub const NIT_MASK : u8 = 1 << 6;
pub const BADDIS_MASK : u8 = 1 << 7;
pub const SYNCASSERTIONMASK : u16 = 0x47b;
pub const CMM_ENABLE : u8 = 1 << 3;
pub const CMM : u8 = 1 << 4;
pub const UCC_S : u8 = 1 << 5;
pub const NIT_S : u8 = 1 << 6;
pub const BADDIS_S : u8 = 1 << 7;
pub const ERRORTHRES : u16 = 0x47c;
pub const LANEENABLE : u16 = 0x47d;
pub const RAMP_ENA : u16 = 0x47e;
pub const DIG_TEST0 : u16 = 0x520;
pub const DC_TEST_MODE : u8 = 1 << 1;
pub const DC_TEST_VALUEI0 : u16 = 0x521;
pub const DC_TEST_VALUEI1 : u16 = 0x522;
pub const DC_TEST_VALUEQ0 : u16 = 0x523;
pub const DC_TEST_VALUEQ1 : u16 = 0x524;

View File

@ -0,0 +1,219 @@
use board_misoc::{csr, clock, config};
#[cfg(feature = "alloc")]
use alloc::format;
struct SerdesConfig {
pub delay: [u8; 4],
}
impl SerdesConfig {
pub fn as_bytes(&self) -> &[u8] {
unsafe {
core::slice::from_raw_parts(
(self as *const SerdesConfig) as *const u8,
core::mem::size_of::<SerdesConfig>(),
)
}
}
}
fn select_lane(lane_no: u8) {
unsafe {
csr::eem_transceiver::lane_sel_write(lane_no);
}
}
fn apply_delay(tap: u8) {
unsafe {
csr::eem_transceiver::dly_cnt_in_write(tap);
csr::eem_transceiver::dly_ld_write(1);
clock::spin_us(1);
assert!(tap as u8 == csr::eem_transceiver::dly_cnt_out_read());
}
}
fn apply_config(config: &SerdesConfig) {
for lane_no in 0..4 {
select_lane(lane_no as u8);
apply_delay(config.delay[lane_no]);
}
}
unsafe fn assign_delay() -> SerdesConfig {
// Select an appropriate delay for lane 0
select_lane(0);
let read_align = |dly: u8| -> f32 {
apply_delay(dly);
csr::eem_transceiver::counter_reset_write(1);
csr::eem_transceiver::counter_enable_write(1);
clock::spin_us(2000);
csr::eem_transceiver::counter_enable_write(0);
let (high, low) = (
csr::eem_transceiver::counter_high_count_read(),
csr::eem_transceiver::counter_low_count_read(),
);
if csr::eem_transceiver::counter_overflow_read() == 1 {
panic!("Unexpected phase detector counter overflow");
}
low as f32 / (low + high) as f32
};
let mut best_dly = None;
loop {
let mut prev = None;
for curr_dly in 0..32 {
let curr_low_rate = read_align(curr_dly);
if let Some(prev_low_rate) = prev {
// This is potentially a crossover position
if prev_low_rate <= curr_low_rate && curr_low_rate >= 0.5 {
let prev_dev = 0.5 - prev_low_rate;
let curr_dev = curr_low_rate - 0.5;
let selected_idx = if prev_dev < curr_dev {
curr_dly - 1
} else {
curr_dly
};
// The setup setup/hold calibration timing (even with
// tolerance) might be invalid in other lanes due to skew.
// 5 taps is very conservative, generally it is 1 or 2
if selected_idx < 5 {
prev = None;
continue;
} else {
best_dly = Some(selected_idx);
break;
}
}
}
// Only rising slope from <= 0.5 can result in a rising low rate
// crossover at 50%.
if curr_low_rate <= 0.5 {
prev = Some(curr_low_rate);
}
}
if best_dly.is_none() {
error!("setup/hold timing calibration failed, retry in 1s...");
clock::spin_us(1_000_000);
} else {
break;
}
}
let best_dly = best_dly.unwrap();
apply_delay(best_dly);
let mut delay_list = [best_dly; 4];
// Assign delay for other lanes
for lane_no in 1..=3 {
select_lane(lane_no as u8);
let mut min_deviation = 0.5;
let mut min_idx = 0;
for dly_delta in -3..=3 {
let index = (best_dly as isize + dly_delta) as u8;
let low_rate = read_align(index);
// abs() from f32 is not available in core library
let deviation = if low_rate < 0.5 {
0.5 - low_rate
} else {
low_rate - 0.5
};
if deviation < min_deviation {
min_deviation = deviation;
min_idx = index;
}
}
apply_delay(min_idx);
delay_list[lane_no] = min_idx;
}
debug!("setup/hold timing calibration: {:?}", delay_list);
SerdesConfig {
delay: delay_list,
}
}
unsafe fn align_comma() {
loop {
for slip in 1..=10 {
// The soft transceiver has 2 8b10b decoders, which receives lane
// 0/1 and lane 2/3 respectively. The decoder are time-multiplexed
// to decode exactly 1 lane each sysclk cycle.
//
// The decoder decodes lane 0/2 data on odd sysclk cycles, buffer
// on even cycles, and vice versa for lane 1/3. Data/Clock latency
// could change timing. The extend bit flips the decoding timing,
// so lane 0/2 data are decoded on even cycles, and lane 1/3 data
// are decoded on odd cycles.
//
// This is needed because transmitting/receiving a 8b10b character
// takes 2 sysclk cycles. Adjusting bitslip only via ISERDES
// limits the range to 1 cycle. The wordslip bit extends the range
// to 2 sysclk cycles.
csr::eem_transceiver::wordslip_write((slip > 5) as u8);
// Apply a double bitslip since the ISERDES is 2x oversampled.
// Bitslip is used for comma alignment purposes once setup/hold
// timing is met.
csr::eem_transceiver::bitslip_write(1);
csr::eem_transceiver::bitslip_write(1);
clock::spin_us(1);
csr::eem_transceiver::comma_align_reset_write(1);
clock::spin_us(100);
if csr::eem_transceiver::comma_read() == 1 {
debug!("comma alignment completed after {} bitslips", slip);
return;
}
}
error!("comma alignment failed, retrying in 1s...");
clock::spin_us(1_000_000);
}
}
pub fn init() {
for trx_no in 0..csr::CONFIG_EEM_DRTIO_COUNT {
unsafe {
csr::eem_transceiver::transceiver_sel_write(trx_no as u8);
}
let key = format!("eem_drtio_delay{}", trx_no);
config::read(&key, |r| {
match r {
Ok(record) => {
info!("loading calibrated timing values from flash");
unsafe {
apply_config(&*(record.as_ptr() as *const SerdesConfig));
}
},
Err(_) => {
info!("calibrating...");
let config = unsafe { assign_delay() };
config::write(&key, config.as_bytes()).unwrap();
}
}
});
unsafe {
align_comma();
csr::eem_transceiver::rx_ready_write(1);
}
}
}

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