From 460cbf4499f4f2c44d4eac0cec029dc2ba710a28 Mon Sep 17 00:00:00 2001 From: David Mak Date: Thu, 14 Sep 2023 11:32:33 +0800 Subject: [PATCH 01/50] docs: Add section on untrusted substituters in Nix Signed-off-by: David Mak --- doc/manual/installing.rst | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 916cfec8c..a231e48ec 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -87,6 +87,41 @@ You can create directories containing each a ``flake.nix`` that correspond to di If your favorite package is not available with Nix, contact us using the helpdesk@ email. +Troubleshooting +^^^^^^^^^^^^^^^ + +"Ignoring untrusted substituter, you are not a trusted user" +"""""""""""""""""""""""""""""""""""""""""""""""""""""""""""" + +If the following message displays when running ``nix shell`` or ``nix develop`` + +:: + + warning: ignoring untrusted substituter 'https://nixbld.m-labs.hk', you are not a trusted user. + Run `man nix.conf` for more information on the `substituters` configuration option. + +and Nix proceeds to build some packages from source, this means that you are using `multi-user mode `_ in Nix, for example when Nix is installed via ``pacman`` in Arch Linux. + +By default, users accessing Nix in multi-user mode are "unprivileged" and cannot use untrusted substituters. To change this, edit ``/etc/nix/nix.conf`` and add the following line (or append to the key if the key already exists): + +:: + + trusted-substituters = https://nixbld.m-labs.hk + +This will add the substituter as a trusted substituter for all users using Nix. + +Alternatively, add the following line: + +:: + + trusted-users = # Replace with the user invoking `nix` + +This will set your user as a trusted user, allowing the use of any untrusted substituters. + +.. warning:: + + Setting users as trusted users will effectively grant root access to those users. See the `Nix documentation `_ for more information. + Installing via MSYS2 (Windows) ------------------------------ From e45dc948e9e7ab9033a92e3bdabbe805985de515 Mon Sep 17 00:00:00 2001 From: David Nadlinger Date: Fri, 15 Sep 2023 00:11:27 +0100 Subject: [PATCH 02/50] setup.py: Add lmdb dependency This has actually been a required dependency since e710d4baddafa87aec6b5c2c04c8586ed6dd7678. --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 93d4d2c35..a38815b22 100755 --- a/setup.py +++ b/setup.py @@ -13,7 +13,7 @@ if sys.version_info[:2] < (3, 7): # Depends on PyQt5, but setuptools cannot check for it. requirements = [ "numpy", "scipy", - "python-dateutil", "prettytable", "h5py", + "python-dateutil", "prettytable", "h5py", "lmdb", "qasync", "pyqtgraph", "pygit2", "llvmlite", "pythonparser", "levenshtein", ] From f01e654b9c2e5408ea4bf077a78374a2d099b8e2 Mon Sep 17 00:00:00 2001 From: Simon Renblad Date: Fri, 15 Sep 2023 11:24:53 +0800 Subject: [PATCH 03/50] gui.entries: fix RangeScan SpinBox size layouts --- artiq/gui/entries.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/artiq/gui/entries.py b/artiq/gui/entries.py index b86bc5c57..8b9bf4788 100644 --- a/artiq/gui/entries.py +++ b/artiq/gui/entries.py @@ -222,9 +222,6 @@ class _RangeScan(LayoutWidget): start = ScientificSpinBox() start.setStyleSheet("QDoubleSpinBox {color:blue}") - start.setMinimumSize(110, 0) - start.setSizePolicy(QtWidgets.QSizePolicy( - QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed)) disable_scroll_wheel(start) self.addWidget(start, 0, 1) @@ -236,13 +233,15 @@ class _RangeScan(LayoutWidget): stop = ScientificSpinBox() stop.setStyleSheet("QDoubleSpinBox {color:red}") - stop.setMinimumSize(110, 0) disable_scroll_wheel(stop) self.addWidget(stop, 2, 1) randomize = QtWidgets.QCheckBox("Randomize") self.addWidget(randomize, 3, 1) + self.layout.setColumnStretch(0, 4) + self.layout.setColumnStretch(1, 1) + apply_properties(start) start.setSigFigs() start.setRelativeStep() From fc74b78a459948b5132cb36281d81dfb0e90c120 Mon Sep 17 00:00:00 2001 From: David Nadlinger Date: Fri, 9 Jun 2023 16:35:28 +0100 Subject: [PATCH 04/50] 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. --- RELEASE_NOTES.rst | 4 ++++ artiq/gui/applets.py | 16 ++++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 0fd58fda3..98f111c2d 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -28,6 +28,10 @@ Highlights: * Distributed DMA is now supported, allowing DMA to be run directly on satellites for corresponding RTIO events, increasing bandwidth in scenarios with heavy satellite usage. * API extensions have been implemented, enabling applets to directly modify datasets. +* Dashboard: + - 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. * Persistent datasets are now stored in a LMDB database for improved performance. PYON databases can be converted with the script below. diff --git a/artiq/gui/applets.py b/artiq/gui/applets.py index a9e22b420..4b3d2b98b 100644 --- a/artiq/gui/applets.py +++ b/artiq/gui/applets.py @@ -397,11 +397,12 @@ class AppletsDock(QtWidgets.QDockWidget): delete_action.setShortcutContext(QtCore.Qt.WidgetShortcut) delete_action.triggered.connect(self.delete) self.table.addAction(delete_action) - close_all_action = QtWidgets.QAction("Close all applets", self.table) - close_all_action.setShortcut("CTRL+ALT+W") - close_all_action.setShortcutContext(QtCore.Qt.ApplicationShortcut) - close_all_action.triggered.connect(self.close_all) - self.table.addAction(close_all_action) + close_nondocked_action = QtWidgets.QAction("Close non-docked applets", self.table) + close_nondocked_action.setShortcut("CTRL+ALT+W") + close_nondocked_action.setShortcutContext(QtCore.Qt.ApplicationShortcut) + close_nondocked_action.triggered.connect(self.close_nondocked) + self.table.addAction(close_nondocked_action) + new_group_action = QtWidgets.QAction("New group", self.table) new_group_action.triggered.connect(partial(self.new_with_parent, self.new_group)) self.table.addAction(new_group_action) @@ -674,12 +675,15 @@ class AppletsDock(QtWidgets.QDockWidget): def restore_state(self, state): self.restore_state_item(state, None) - def close_all(self): + def close_nondocked(self): def walk(wi): for i in range(wi.childCount()): cwi = wi.child(i) if cwi.ty == "applet": if cwi.checkState(0) == QtCore.Qt.Checked: + if cwi.applet_dock is not None: + if not cwi.applet_dock.isFloating(): + continue cwi.setCheckState(0, QtCore.Qt.Unchecked) elif cwi.ty == "group": walk(cwi) From 22ab62324c1c4eb2d810c163d2e02a41045d10fb Mon Sep 17 00:00:00 2001 From: David Nadlinger Date: Sun, 17 Sep 2023 00:48:42 +0100 Subject: [PATCH 05/50] gateware/targets/kasli: Set DRTIO_ROLE in {Master, Satellite}Base These were introduced in 82bd913f63e3, 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. --- artiq/gateware/targets/kasli.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index 6312170c9..ce9944a53 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -317,6 +317,7 @@ class MasterBase(MiniSoC, AMPSoC): self.add_memory_region(memory_name, memory_address | self.shadow_base, 0x800) self.config["HAS_DRTIO"] = None self.config["HAS_DRTIO_ROUTING"] = None + self.config["DRTIO_ROLE"] = "master" rtio_clk_period = 1e9/rtio_clk_freq gtp = self.gt_drtio.gtps[0] @@ -565,6 +566,7 @@ class SatelliteBase(BaseSoC, AMPSoC): self.add_memory_region(memory_name, memory_address | self.shadow_base, 0x800) self.config["HAS_DRTIO"] = None self.config["HAS_DRTIO_ROUTING"] = None + self.config["DRTIO_ROLE"] = "satellite" self.add_csr_group("drtioaux", drtioaux_csr_group) self.add_memory_group("drtioaux_mem", drtioaux_memory_group) self.add_csr_group("drtiorep", drtiorep_csr_group) From 9e5b62a6b1b32ec8755f2ac16fbb2ddb5597098d Mon Sep 17 00:00:00 2001 From: David Nadlinger Date: Sun, 17 Sep 2023 01:40:46 +0100 Subject: [PATCH 06/50] 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. --- artiq/gateware/targets/kasli.py | 2 ++ artiq/gateware/targets/kasli_generic.py | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index ce9944a53..ca3364398 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -70,6 +70,8 @@ class StandaloneBase(MiniSoC, AMPSoC): AMPSoC.__init__(self) add_identifier(self, gateware_identifier_str=gateware_identifier_str) + self.config["DRTIO_ROLE"] = "standalone" + if self.platform.hw_rev == "v2.0": self.submodules.error_led = gpio.GPIOOut(Cat( self.platform.request("error_led"))) diff --git a/artiq/gateware/targets/kasli_generic.py b/artiq/gateware/targets/kasli_generic.py index 3a855933c..ace08826e 100755 --- a/artiq/gateware/targets/kasli_generic.py +++ b/artiq/gateware/targets/kasli_generic.py @@ -22,7 +22,6 @@ class GenericStandalone(StandaloneBase): hw_rev = description["hw_rev"] self.class_name_override = description["variant"] StandaloneBase.__init__(self, hw_rev=hw_rev, **kwargs) - self.config["DRTIO_ROLE"] = description["drtio_role"] self.config["RTIO_FREQUENCY"] = "{:.1f}".format(description["rtio_frequency"]/1e6) if "ext_ref_frequency" in description: self.config["SI5324_EXT_REF"] = None @@ -78,7 +77,6 @@ class GenericMaster(MasterBase): enable_sata=description["enable_sata_drtio"], enable_sys5x=has_drtio_over_eem, **kwargs) - self.config["DRTIO_ROLE"] = description["drtio_role"] if "ext_ref_frequency" in description: self.config["SI5324_EXT_REF"] = None self.config["EXT_REF_FREQUENCY"] = "{:.1f}".format( @@ -123,7 +121,6 @@ class GenericSatellite(SatelliteBase): rtio_clk_freq=description["rtio_frequency"], enable_sata=description["enable_sata_drtio"], **kwargs) - self.config["DRTIO_ROLE"] = description["drtio_role"] if hw_rev == "v1.0": # EEM clock fan-out from Si5324, not MMCX self.comb += self.platform.request("clk_sel").eq(1) From 85abb1da2c7a787116d68b2a0e0a472fc9cf6de4 Mon Sep 17 00:00:00 2001 From: linuswck Date: Tue, 12 Sep 2023 11:54:40 +0800 Subject: [PATCH 07/50] 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 --- artiq/firmware/libboard_artiq/ad9117.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/artiq/firmware/libboard_artiq/ad9117.rs b/artiq/firmware/libboard_artiq/ad9117.rs index 4c152e976..5aafb2ace 100644 --- a/artiq/firmware/libboard_artiq/ad9117.rs +++ b/artiq/firmware/libboard_artiq/ad9117.rs @@ -7,6 +7,8 @@ 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 @@ -57,6 +59,10 @@ pub fn init() -> Result<(), &'static str> { 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)?; From 372008cb66a9705700eba8302c56dc34948c0711 Mon Sep 17 00:00:00 2001 From: linuswck Date: Mon, 18 Sep 2023 12:58:39 +0800 Subject: [PATCH 08/50] Firmware: AD9117 Add check presence of clk comment --- artiq/firmware/libboard_artiq/ad9117.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/artiq/firmware/libboard_artiq/ad9117.rs b/artiq/firmware/libboard_artiq/ad9117.rs index 5aafb2ace..f3d3f3533 100644 --- a/artiq/firmware/libboard_artiq/ad9117.rs +++ b/artiq/firmware/libboard_artiq/ad9117.rs @@ -53,6 +53,7 @@ pub fn init() -> Result<(), &'static str> { 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); From 1f3b2ef645a8915886a43635eb3c75219e6376e5 Mon Sep 17 00:00:00 2001 From: Simon Renblad Date: Mon, 11 Sep 2023 15:56:59 +0800 Subject: [PATCH 09/50] dashboard.datasets: fix numpy objects in CreateEditDialog --- artiq/dashboard/datasets.py | 55 +++++++++++++++++++++++++++++-------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/artiq/dashboard/datasets.py b/artiq/dashboard/datasets.py index 9219058af..97146bb33 100644 --- a/artiq/dashboard/datasets.py +++ b/artiq/dashboard/datasets.py @@ -74,19 +74,20 @@ class CreateEditDialog(QtWidgets.QDialog): self.key = key self.name_widget.setText(key) - self.value_widget.setText(value) + value_edit_string = self.value_to_edit_string(value) if metadata is not None: scale = scale_from_metadata(metadata) - decoded_value = pyon.decode(value) - if scale == 1: - self.value_widget.setText(value) - else: - self.value_widget.setText(pyon.encode(decoded_value / scale)) + 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): @@ -104,11 +105,11 @@ class CreateEditDialog(QtWidgets.QDialog): if precision != "": metadata['precision'] = int(precision) scale = scale_from_metadata(metadata) - value = pyon.decode(value) + value = self.parse_edit_string(value) t = value.dtype if value is np.ndarray else type(value) - is_floating = scale != 1 or np.issubdtype(t, np.floating) - if is_floating: - value = value * scale + 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: @@ -119,7 +120,9 @@ class CreateEditDialog(QtWidgets.QDialog): def dtype(self): txt = self.value_widget.text() try: - result = pyon.decode(txt) + result = self.parse_edit_string(txt) + # ensure only pyon compatible types are permissable + pyon.encode(result) except: pixmap = self.style().standardPixmap( QtWidgets.QStyle.SP_MessageBoxWarning) @@ -129,6 +132,35 @@ class CreateEditDialog(QtWidgets.QDialog): self.data_type.setText(type(result).__name__) self.ok.setEnabled(True) + @staticmethod + def parse_edit_string(s): + if s == "": + raise TypeError + _eval_dict = { + "__builtins__": {}, + "array": np.array, + "null": np.nan, + "inf": np.inf + } + for t_ in pyon._numpy_scalar: + _eval_dict[t_] = eval("np.{}".format(t_), {"np": np}) + return eval(s, _eval_dict, {}) + + @staticmethod + def value_to_edit_string(v): + t = type(v) + r = "" + if isinstance(v, np.generic): + r += t.__name__ + r += "(" + r += repr(v) + r += ")" + elif v is None: + return r + else: + r += repr(v) + return r + class Model(DictSyncTreeSepModel): def __init__(self, init): @@ -209,7 +241,6 @@ class DatasetsDock(QtWidgets.QDockWidget): key = self.table_model.index_to_key(idx) if key is not None: persist, value, metadata = self.table_model.backing_store[key] - value = pyon.encode(value) CreateEditDialog(self, self.dataset_ctl, key, value, metadata, persist).open() def delete_clicked(self): From c02a14ba379bed7475a595f3cf6557ca99d8e078 Mon Sep 17 00:00:00 2001 From: Florian Agbuya Date: Mon, 18 Sep 2023 17:51:44 +0800 Subject: [PATCH 10/50] compiler: fix lit tests numpy.transpose error (#2190) --- artiq/compiler/embedding.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 8eb5cfae2..f695029cf 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -18,6 +18,13 @@ from . import types, builtins, asttyped, math_fns, prelude from .transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer, TypedtreePrinter from .transforms.asttyped_rewriter import LocalExtractor +try: + # From numpy=1.25.0 dispatching for `__array_function__` is done via + # a C wrapper: https://github.com/numpy/numpy/pull/23020 + from numpy.core._multiarray_umath import _ArrayFunctionDispatcher +except ImportError: + _ArrayFunctionDispatcher = None + class SpecializedFunction: def __init__(self, instance_type, host_function): @@ -336,7 +343,9 @@ class ASTSynthesizer: elif inspect.isfunction(value) or inspect.ismethod(value) or \ isinstance(value, pytypes.BuiltinFunctionType) or \ isinstance(value, SpecializedFunction) or \ - isinstance(value, numpy.ufunc): + isinstance(value, numpy.ufunc) or \ + (isinstance(value, _ArrayFunctionDispatcher) if + _ArrayFunctionDispatcher is not None else False): if inspect.ismethod(value): quoted_self = self.quote(value.__self__) function_type = self.quote_function(value.__func__, self.expanded_from) From 1bb7e9ceefb292c3d8e88b463ec598cb9a0f0b7c Mon Sep 17 00:00:00 2001 From: occheung Date: Wed, 13 Sep 2023 16:43:53 -0700 Subject: [PATCH 11/50] shuttler: support pre-DAC gain & offset --- artiq/gateware/shuttler.py | 67 +++++++++++++++++++++++++++++++---- artiq/gateware/targets/efc.py | 34 ++++++++++++++++++ 2 files changed, 95 insertions(+), 6 deletions(-) diff --git a/artiq/gateware/shuttler.py b/artiq/gateware/shuttler.py index 90e4eff68..1eb2221bf 100644 --- a/artiq/gateware/shuttler.py +++ b/artiq/gateware/shuttler.py @@ -100,11 +100,16 @@ class Dac(Module): data (Signal[16]): Output value to be send to the DAC. clear (Signal): Clear accumulated phase offset when loading a new waveform. Input. + gain (Signal[16]): Output value gain. The gain signal represents the + decimal part os the gain in 2's complement. + offset (Signal[16]): Output value offset. i (Endpoint[]): Coefficients of the output lines. """ def __init__(self): self.clear = Signal() self.data = Signal(16) + self.gain = Signal(16) + self.offset = Signal(16) ### @@ -113,8 +118,14 @@ class Dac(Module): Dds(self.clear), ] + # Infer signed multiplication + data_raw = Signal((14, True)) + data_buf = Signal(14) self.sync.rio += [ - self.data.eq(reduce(add, [sub.data for sub in subs])), + data_raw.eq(reduce(add, [sub.data for sub in subs])), + # Extra buffer for better DSP timing + data_buf.eq(((data_raw * Cat(self.gain, ~self.gain[-1])) + (self.offset << 16))[16:]), + self.data.eq(data_buf), ] self.i = [ sub.i for sub in subs ] @@ -229,11 +240,43 @@ class Dds(Module): class Config(Module): def __init__(self): self.clr = Signal(16, reset=0xFFFF) - self.i = Endpoint([("data", 16)]) + self.gain = [ Signal(16) for _ in range(16) ] + self.offset = [ Signal(16) for _ in range(16) ] + + reg_file = Array(self.gain + self.offset + [self.clr]) + self.i = Endpoint([ + ("data", 16), + ("addr", 7), + ]) + self.o = Endpoint([ + ("data", 16), + ]) # This introduces 1 extra latency to everything in config # See the latency/delay attributes in Volt & DDS Endpoints/rtlinks - self.sync.rio += If(self.i.stb, self.clr.eq(self.i.data)) + # + # Gain & offsets are intended for initial calibration only, latency + # is NOT adjusted to match outputs to the DAC interface + # + # Interface address bits mapping: + # 6: Read bit. Assert to read, deassert to write. + # 5: Clear bit. Assert to write clr. clr is write-only. + # 4: Gain/Offset. (De)Assert to access (Gain)Offset registers. + # 0-3: Channel selection for the Gain & Offset registers. + # + # Reading Gain / Offset register generates an RTIOInput event + self.sync.rio += [ + self.o.stb.eq(0), + If(self.i.stb, + If(~self.i.addr[6], + reg_file[self.i.addr[:6]].eq(self.i.data), + ).Else( + # clr register is unreadable, as an optimization + self.o.data.eq(reg_file[self.i.addr[:5]]), + self.o.stb.eq(1), + ) + ), + ] Phy = namedtuple("Phy", "rtlink probes overrides") @@ -258,13 +301,23 @@ class Shuttler(Module, AutoCSR): self.phys = [] self.submodules.cfg = Config() - cfg_rtl_iface = rtlink.Interface(rtlink.OInterface( - data_width=len(self.cfg.i.data), - enable_replace=False)) + cfg_rtl_iface = rtlink.Interface( + rtlink.OInterface( + data_width=len(self.cfg.i.data), + address_width=len(self.cfg.i.addr), + enable_replace=False, + ), + rtlink.IInterface( + data_width=len(self.cfg.o.data), + ), + ) self.comb += [ self.cfg.i.stb.eq(cfg_rtl_iface.o.stb), + self.cfg.i.addr.eq(cfg_rtl_iface.o.address), self.cfg.i.data.eq(cfg_rtl_iface.o.data), + cfg_rtl_iface.i.stb.eq(self.cfg.o.stb), + cfg_rtl_iface.i.data.eq(self.cfg.o.data), ] self.phys.append(Phy(cfg_rtl_iface, [], [])) @@ -277,6 +330,8 @@ class Shuttler(Module, AutoCSR): dac = Dac() self.comb += [ dac.clear.eq(self.cfg.clr[idx]), + dac.gain.eq(self.cfg.gain[idx]), + dac.offset.eq(self.cfg.offset[idx]), self.dac_interface.data[idx // 2][idx % 2].eq(dac.data) ] diff --git a/artiq/gateware/targets/efc.py b/artiq/gateware/targets/efc.py index b88f3f2b5..42a4f031b 100644 --- a/artiq/gateware/targets/efc.py +++ b/artiq/gateware/targets/efc.py @@ -13,6 +13,7 @@ from artiq.gateware.amp import AMPSoC from artiq.gateware import rtio from artiq.gateware.rtio.xilinx_clocking import fix_serdes_timing_path from artiq.gateware.rtio.phy import ttl_simple +from artiq.gateware.rtio.phy import spi2 as rtio_spi from artiq.gateware.drtio.transceiver import eem_serdes from artiq.gateware.drtio.rx_synchronizer import NoRXSynchronizer from artiq.gateware.drtio import * @@ -145,6 +146,20 @@ class Satellite(BaseSoC, AMPSoC): Subsignal('data', Pins('fmc0:HB13_N fmc0:HB12_N fmc0:HB13_P fmc0:HB12_P fmc0:HB15_N fmc0:HB15_P fmc0:HB11_N fmc0:HB09_N fmc0:HB09_P fmc0:HB14_N fmc0:HB14_P fmc0:HB10_N fmc0:HB10_P fmc0:HB11_P')), Subsignal('clk', Pins('fmc0:HB06_CC_P')), IOStandard('LVCMOS18')), + ('afe_ctrl_dir', 0, Pins('fmc0:LA26_N fmc0:HB00_CC_N fmc0:HB17_CC_P'), IOStandard("LVCMOS18")), + ('afe_ctrl_oe_n', 0, Pins('fmc0:HB19_N'), IOStandard("LVCMOS18")), + ('afe_relay', 0, + Subsignal('clk', Pins('fmc0:LA02_N')), + Subsignal('mosi', Pins('fmc0:LA00_CC_N')), + Subsignal('cs_n', Pins('fmc0:LA02_P fmc0:LA01_CC_N')), + IOStandard("LVCMOS18")), + ('afe_adc_spi', 0, + Subsignal('clk', Pins('fmc0:LA29_P')), + Subsignal('mosi', Pins('fmc0:LA29_N')), + Subsignal('miso', Pins('fmc0:LA30_N')), + Subsignal('cs_n', Pins('fmc0:LA28_P')), + IOStandard("LVCMOS18")), + ('afe_adc_error_n', 0, Pins('fmc0:LA28_N'), IOStandard("LVCMOS18")), ] platform.add_extension(shuttler_io) @@ -167,6 +182,25 @@ class Satellite(BaseSoC, AMPSoC): self.csr_devices.append("shuttler") self.rtio_channels.extend(rtio.Channel.from_phy(phy) for phy in self.shuttler.phys) + afe_dir = platform.request("afe_ctrl_dir") + self.comb += afe_dir.eq(0b011) + + afe_oe = platform.request("afe_ctrl_oe_n") + self.comb += afe_oe.eq(0) + + relay_led_phy = rtio_spi.SPIMaster(self.platform.request("afe_relay")) + self.submodules += relay_led_phy + print("SHUTTLER RELAY at RTIO channel 0x{:06x}".format(len(self.rtio_channels))) + self.rtio_channels.append(rtio.Channel.from_phy(relay_led_phy)) + + adc_error_n = platform.request("afe_adc_error_n") + self.comb += adc_error_n.eq(1) + + adc_spi = rtio_spi.SPIMaster(self.platform.request("afe_adc_spi")) + self.submodules += adc_spi + print("SHUTTLER ADC at RTIO channel 0x{:06x}".format(len(self.rtio_channels))) + self.rtio_channels.append(rtio.Channel.from_phy(adc_spi)) + self.config["HAS_RTIO_LOG"] = None self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels) self.rtio_channels.append(rtio.LogChannel()) From 67b6588d95e7a037762b8523b5bca6bf2f5e4723 Mon Sep 17 00:00:00 2001 From: occheung Date: Wed, 13 Sep 2023 16:45:48 -0700 Subject: [PATCH 12/50] shuttler: implement gain & offset register access --- artiq/coredevice/shuttler.py | 37 +++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 71b3050f5..df121f3f2 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -2,20 +2,47 @@ import numpy from artiq.language.core import * from artiq.language.types import * -from artiq.coredevice.rtio import rtio_output +from artiq.coredevice.rtio import rtio_output, rtio_input_data class Config: - kernel_invariants = {"core", "channel", "target_o"} + 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_o = channel << 8 + 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_config(self, config): - rtio_output(self.target_o, config) + def set_clr(self, clr): + rtio_output(self.target_base | self.target_clr, clr) + + @kernel + def set_gain(self, channel, gain): + rtio_output(self.target_base | self.target_gain | channel, gain) + + @kernel + def get_gain(self, channel): + 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): + rtio_output(self.target_base | self.target_offset | channel, offset) + + @kernel + def get_offset(self, channel): + rtio_output(self.target_base | self.target_offset | + self.target_read | channel, 0) + return rtio_input_data(self.channel) class Volt: From eb08c55abe06bd0e3bda020e34fdc000b1e174c5 Mon Sep 17 00:00:00 2001 From: occheung Date: Wed, 13 Sep 2023 16:46:51 -0700 Subject: [PATCH 13/50] shuttler: add AFE drivers --- artiq/coredevice/shuttler.py | 145 +++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index df121f3f2..5c5be0d34 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -3,6 +3,14 @@ import numpy 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): + # TODO: Check arg, raise exception if exceeds shuttler limit + return int(round((1 << 14) * (volt / 20.0))) & 0x3fff class Config: @@ -116,3 +124,140 @@ class Trigger: @kernel def trigger(self, trig_out): 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) + +SPI_CS_RELAY = 1 << 0 +SPI_CS_LED = 1 << 1 +SPI_DIV = 4 + +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) + +ADC_CS = 1 +ADC_SPI_DIV = 32 + + +class Relay: + 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): + self.bus.set_config_mu( + RELAY_SPI_CONFIG, 16, SPI_DIV, SPI_CS_RELAY | SPI_CS_LED) + + @kernel + def set_led(self, leds: TInt32): + self.bus.write(leds << 16) + + +class ADC: + 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: + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + 24, ADC_SPI_DIV, ADC_CS) + self.bus.write(0x47 << 24) + return (self.bus.read() & 0xFFFF) + + @kernel + def read_ch(self, channel: TInt32) -> TFloat: + # Always configure Profile 0 + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END, 24, ADC_SPI_DIV, ADC_CS) + self.bus.write(0x10 << 24 | (0x8000 | ((channel * 2 + 1) << 4)) << 8) + + # Configure Setup 0 + # Input buffer must be enabled to use REF pins correctly + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END, 24, ADC_SPI_DIV, ADC_CS) + self.bus.write((0x20 << 24) | (0x1300 << 8)) + + # Trigger single conversion + self.bus.set_config_mu( + ADC_SPI_CONFIG, 24, ADC_SPI_DIV, ADC_CS) + self.bus.write((0x01 << 24) | (0x8010 << 8)) + + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_INPUT, 16, ADC_SPI_DIV, ADC_CS) + self.bus.write(0x40 << 24) + while self.bus.read() & 0x80: + delay(10*us) + self.bus.write(0x40 << 24) + + delay(10*us) + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + 32, ADC_SPI_DIV, ADC_CS) + self.bus.write(0x44 << 24) + + adc_code = self.bus.read() & 0xFFFFFF + return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1 + + @kernel + def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]): + 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) + delay(1*us) + measurements[i] = self.read_ch(ch ^ 1) + + # Find the average output slope + print(measurements) + 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) + print(slope_avg) + + print("Suitable gain in Shuttler:") + gain_code = int32(1 / slope_avg * (2 ** 16)) & 0xffff + print(gain_code) + + # 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) + print(offset_avg) + + print("Suitable offset in Shuttler:") + offset_code = shuttler_volt_to_mu(-offset_avg) + print(offset_code) + + self.core.break_realtime() + config.set_gain(ch, gain_code) + + delay_mu(int64(self.core.ref_multiplier)) + assert config.get_gain(ch) == gain_code + + self.core.break_realtime() + config.set_offset(ch, offset_code) + + delay_mu(int64(self.core.ref_multiplier)) + assert config.get_offset(ch) == offset_code From 55150ebdbba9906f568c382126ef0ff7dc1c05a7 Mon Sep 17 00:00:00 2001 From: occheung Date: Mon, 18 Sep 2023 19:52:22 -0700 Subject: [PATCH 14/50] shuttler: fix calibration channel target --- artiq/coredevice/shuttler.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 5c5be0d34..34c6afda7 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -224,8 +224,7 @@ class ADC: volts[ch].set_waveform( shuttler_volt_to_mu(samples[i]), 0, 0, 0) trigger.trigger(1 << ch) - delay(1*us) - measurements[i] = self.read_ch(ch ^ 1) + measurements[i] = self.read_ch(ch) # Find the average output slope print(measurements) From e443e06e62baee529b75c419747e39a2932e8d9e Mon Sep 17 00:00:00 2001 From: occheung Date: Mon, 18 Sep 2023 19:52:59 -0700 Subject: [PATCH 15/50] shuttler: remove adc calibrate debug lines --- artiq/coredevice/shuttler.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 34c6afda7..0e33c1dfd 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -227,36 +227,23 @@ class ADC: measurements[i] = self.read_ch(ch) # Find the average output slope - print(measurements) 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) - print(slope_avg) - print("Suitable gain in Shuttler:") gain_code = int32(1 / slope_avg * (2 ** 16)) & 0xffff - print(gain_code) # 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) - print(offset_avg) - print("Suitable offset in Shuttler:") offset_code = shuttler_volt_to_mu(-offset_avg) - print(offset_code) self.core.break_realtime() config.set_gain(ch, gain_code) delay_mu(int64(self.core.ref_multiplier)) - assert config.get_gain(ch) == gain_code - - self.core.break_realtime() config.set_offset(ch, offset_code) - - delay_mu(int64(self.core.ref_multiplier)) - assert config.get_offset(ch) == offset_code From 06426e0ed95baefbf92c3c58f8882fd808dfcf44 Mon Sep 17 00:00:00 2001 From: occheung Date: Mon, 18 Sep 2023 19:53:54 -0700 Subject: [PATCH 16/50] shuttler: impl general reg access --- artiq/coredevice/shuttler.py | 73 +++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 35 deletions(-) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 0e33c1dfd..0f7cffe7f 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -170,44 +170,47 @@ class ADC: @kernel def read_id(self) -> TInt32: - self.bus.set_config_mu( - ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, - 24, ADC_SPI_DIV, ADC_CS) - self.bus.write(0x47 << 24) - return (self.bus.read() & 0xFFFF) - @kernel - def read_ch(self, channel: TInt32) -> TFloat: - # Always configure Profile 0 - self.bus.set_config_mu( - ADC_SPI_CONFIG | spi.SPI_END, 24, ADC_SPI_DIV, ADC_CS) - self.bus.write(0x10 << 24 | (0x8000 | ((channel * 2 + 1) << 4)) << 8) - - # Configure Setup 0 - # Input buffer must be enabled to use REF pins correctly - self.bus.set_config_mu( - ADC_SPI_CONFIG | spi.SPI_END, 24, ADC_SPI_DIV, ADC_CS) - self.bus.write((0x20 << 24) | (0x1300 << 8)) - - # Trigger single conversion - self.bus.set_config_mu( - ADC_SPI_CONFIG, 24, ADC_SPI_DIV, ADC_CS) - self.bus.write((0x01 << 24) | (0x8010 << 8)) - - self.bus.set_config_mu( - ADC_SPI_CONFIG | spi.SPI_INPUT, 16, ADC_SPI_DIV, ADC_CS) - self.bus.write(0x40 << 24) - while self.bus.read() & 0x80: - delay(10*us) - self.bus.write(0x40 << 24) - - delay(10*us) + def read8(self, addr: TInt32) -> TInt32: self.bus.set_config_mu( ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, - 32, ADC_SPI_DIV, ADC_CS) - self.bus.write(0x44 << 24) - - adc_code = self.bus.read() & 0xFFFFFF + 16, SPIT_ADC_RD, CS_ADC) + self.bus.write((addr | 0x40) << 24) + return self.bus.read() & 0xff + + @kernel + def read16(self, addr: TInt32) -> TInt32: + 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: + 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): + 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): + 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): + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC) + self.bus.write(addr << 24 | (data & 0xffffff)) return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1 @kernel From c2d136f669d66d19fc5b03533e90156f85c65339 Mon Sep 17 00:00:00 2001 From: occheung Date: Mon, 18 Sep 2023 19:54:32 -0700 Subject: [PATCH 17/50] shuttler: reorg SPI constants --- artiq/coredevice/shuttler.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 0f7cffe7f..75c970033 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -131,17 +131,30 @@ RELAY_SPI_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END | 0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) -SPI_CS_RELAY = 1 << 0 -SPI_CS_LED = 1 << 1 -SPI_DIV = 4 - 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) -ADC_CS = 1 -ADC_SPI_DIV = 32 +# 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: @@ -154,7 +167,7 @@ class Relay: @kernel def init(self): self.bus.set_config_mu( - RELAY_SPI_CONFIG, 16, SPI_DIV, SPI_CS_RELAY | SPI_CS_LED) + RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED) @kernel def set_led(self, leds: TInt32): From 870020bc9f231724bc741a8cbbe5c27db4c9f756 Mon Sep 17 00:00:00 2001 From: occheung Date: Mon, 18 Sep 2023 19:55:16 -0700 Subject: [PATCH 18/50] adc: use a generous upper bound --- artiq/coredevice/shuttler.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 75c970033..97d0ee6f5 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -224,6 +224,9 @@ class ADC: self.bus.set_config_mu( ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC) self.bus.write(addr << 24 | (data & 0xffffff)) + + delay(100*us) + adc_code = self.read24(_AD4115_REG_DATA) return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1 @kernel From 9e1447d104604615b0b728be2bf294bc3214771b Mon Sep 17 00:00:00 2001 From: occheung Date: Mon, 18 Sep 2023 19:55:47 -0700 Subject: [PATCH 19/50] adc: implement standby & power-down/up --- artiq/coredevice/shuttler.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 97d0ee6f5..3519103d6 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -228,7 +228,23 @@ class ADC: delay(100*us) adc_code = self.read24(_AD4115_REG_DATA) return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1 - + + @kernel + def standby(self): + # Selecting internal XO (0b00) also disables clock during standby + self.write16(_AD4115_REG_ADCMODE, 0x8020) + + @kernel + def power_down(self): + self.write16(_AD4115_REG_ADCMODE, 0x8030) + + @kernel + def exit_power_down(self): + 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]): assert len(volts) == 16 From f2694f25ebea90c3a1fab2cfe52620b98517b789 Mon Sep 17 00:00:00 2001 From: occheung Date: Mon, 18 Sep 2023 19:56:21 -0700 Subject: [PATCH 20/50] re-impl ADC using general access methods --- artiq/coredevice/shuttler.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 3519103d6..60bd00593 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -183,6 +183,20 @@ class ADC: @kernel def read_id(self) -> TInt32: + return self.read16(_AD4115_REG_ID) + + @kernel + def reset(self): + # Hold DIN high for 64 cycles + # However, asserting CS right after the 64 cycles seems 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: self.bus.set_config_mu( @@ -225,6 +239,13 @@ class ADC: 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: + # Always configure Profile 0 for single conversion + self.write16(_AD4115_REG_CH0, 0x8000 | ((channel * 2 + 1) << 4)) + self.write16(_AD4115_REG_SETUPCON0, 0x1300) + self.write16(_AD4115_REG_ADCMODE, 0x8010) + delay(100*us) adc_code = self.read24(_AD4115_REG_DATA) return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1 From 477a7b693cc2b206e98e2d893e3b4cff379d4679 Mon Sep 17 00:00:00 2001 From: occheung Date: Mon, 18 Sep 2023 19:56:41 -0700 Subject: [PATCH 21/50] remove debug for converter --- artiq/coredevice/shuttler.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 60bd00593..43809b2b8 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -9,8 +9,7 @@ from artiq.language.units import us @portable def shuttler_volt_to_mu(volt): - # TODO: Check arg, raise exception if exceeds shuttler limit - return int(round((1 << 14) * (volt / 20.0))) & 0x3fff + return round((1 << 14) * (volt / 20.0)) & 0x3fff class Config: From 5c64eac8d2dfbb2a62661797cddf395b761b5ed6 Mon Sep 17 00:00:00 2001 From: occheung Date: Mon, 18 Sep 2023 20:01:44 -0700 Subject: [PATCH 22/50] relay: fix naming --- artiq/coredevice/shuttler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 43809b2b8..358522eac 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -169,8 +169,8 @@ class Relay: RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED) @kernel - def set_led(self, leds: TInt32): - self.bus.write(leds << 16) + def enable(self, en: TInt32): + self.bus.write(en << 16) class ADC: From a2fbcb8bfd64d5d97ba37b16b501aa99a0fb3bfb Mon Sep 17 00:00:00 2001 From: occheung Date: Mon, 18 Sep 2023 23:19:21 -0700 Subject: [PATCH 23/50] pre-dac gain/offsets: detect overflow & underflow And output maximum / minimum DAC code when over/underflow --- artiq/gateware/shuttler.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/artiq/gateware/shuttler.py b/artiq/gateware/shuttler.py index 1eb2221bf..c6da201e5 100644 --- a/artiq/gateware/shuttler.py +++ b/artiq/gateware/shuttler.py @@ -111,6 +111,9 @@ class Dac(Module): self.gain = Signal(16) self.offset = Signal(16) + overflow = Signal() + underflow = Signal() + ### subs = [ @@ -120,12 +123,25 @@ class Dac(Module): # Infer signed multiplication data_raw = Signal((14, True)) - data_buf = Signal(14) + data_buf = Signal(16) self.sync.rio += [ data_raw.eq(reduce(add, [sub.data for sub in subs])), - # Extra buffer for better DSP timing + # Extra buffer for timing for the DSP data_buf.eq(((data_raw * Cat(self.gain, ~self.gain[-1])) + (self.offset << 16))[16:]), - self.data.eq(data_buf), + If(overflow, + self.data.eq(0x1fff), + ).Elif(underflow, + self.data.eq(0x2000), + ).Else( + self.data.eq(data_buf), + ), + ] + + self.comb += [ + # Overflow condition + overflow.eq(~data_buf[-1] & (data_buf[-2] | data_buf[-3])), + # Underflow condition + underflow.eq(data_buf[-1] & (~data_buf[-2] | ~data_buf[-3])), ] self.i = [ sub.i for sub in subs ] From 40ac2e03abbe15d6407ad0083e1dd16241a6c622 Mon Sep 17 00:00:00 2001 From: Simon Renblad Date: Mon, 18 Sep 2023 16:13:45 +0800 Subject: [PATCH 24/50] set_argument_value in applets --- artiq/applets/simple.py | 15 ++++++++++ artiq/dashboard/experiments.py | 48 ++++++++++++++++++++----------- artiq/frontend/artiq_dashboard.py | 1 + artiq/gui/applets.py | 15 ++++++---- 4 files changed, 58 insertions(+), 21 deletions(-) diff --git a/artiq/applets/simple.py b/artiq/applets/simple.py index 363314a9e..d49223670 100644 --- a/artiq/applets/simple.py +++ b/artiq/applets/simple.py @@ -11,6 +11,8 @@ from sipyco.pc_rpc import AsyncioClient as RPCClient from sipyco import pyon from sipyco.pipe_ipc import AsyncioChildComm +from artiq.language.scan import ScanObject + logger = logging.getLogger(__name__) @@ -37,6 +39,11 @@ class AppletControlIPC: 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 AppletControlRPC: def __init__(self, loop, dataset_ctl): @@ -67,6 +74,8 @@ class AppletControlRPC: mod = {"action": "append", "path": [key, 1], "x": value} self._background(self.dataset_ctl.update, mod) + def set_argument_value(self, expurl, name, value): + raise NotImplementedError class AppletIPCClient(AsyncioChildComm): def set_close_cb(self, close_cb): @@ -137,6 +146,12 @@ class AppletIPCClient(AsyncioChildComm): 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: def __init__(self, main_widget_class, cmd_description=None, diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index 8a0da9fd0..254dd05ab 100644 --- a/artiq/dashboard/experiments.py +++ b/artiq/dashboard/experiments.py @@ -159,6 +159,23 @@ class _ArgumentEditor(QtWidgets.QTreeWidget): self._groups[name] = 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): asyncio.ensure_future(self._recompute_argument(name)) @@ -175,22 +192,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget): state = procdesc_to_entry(procdesc).default_state(procdesc) argument["desc"] = procdesc argument["state"] = state - - # Qt needs a setItemWidget() to handle layout correctly, - # simply replacing the entry inside the LayoutWidget - # results in a bug. - - widgets = self._arg_to_widgets[name] - - widgets["entry"].deleteLater() - widgets["entry"] = procdesc_to_entry(procdesc)(argument) - widgets["disable_other_scans"].setVisible( - isinstance(widgets["entry"], ScanEntry)) - widgets["fix_layout"].deleteLater() - widgets["fix_layout"] = LayoutWidget() - widgets["fix_layout"].addWidget(widgets["entry"]) - self.setItemWidget(widgets["widget_item"], 1, widgets["fix_layout"]) - self.updateGeometries() + self.update_argument(name, argument) def _disable_other_scans(self, current_name): for name, widgets in self._arg_to_widgets.items(): @@ -664,6 +666,20 @@ class ExperimentManager: self.submission_arguments[expurl] = arguments self.argument_ui_names[expurl] = ui_name return arguments + + def set_argument_value(self, expurl, name, value): + try: + argument = self.submission_arguments[expurl][name] + if argument["desc"]["ty"] == "Scannable": + ty = value["ty"] + argument["state"]["selected"] = ty + argument["state"][ty] = value + else: + argument["state"] = value + if expurl in self.open_experiments.keys(): + self.open_experiments[expurl].argeditor.update_argument(name, argument) + except: + logger.warn("Failed to set value for argument \"{}\" in experiment: {}.".format(name, expurl), exc_info=1) def get_submission_arguments(self, expurl): if expurl in self.submission_arguments: diff --git a/artiq/frontend/artiq_dashboard.py b/artiq/frontend/artiq_dashboard.py index 251c2beb3..8bcf9546c 100755 --- a/artiq/frontend/artiq_dashboard.py +++ b/artiq/frontend/artiq_dashboard.py @@ -192,6 +192,7 @@ def main(): d_applets = applets_ccb.AppletsCCBDock(main_window, sub_clients["datasets"], rpc_clients["dataset_db"], + expmgr, extra_substitutes={ "server": args.server, "port_notify": args.port_notify, diff --git a/artiq/gui/applets.py b/artiq/gui/applets.py index 4b3d2b98b..ac1db098d 100644 --- a/artiq/gui/applets.py +++ b/artiq/gui/applets.py @@ -21,10 +21,11 @@ logger = logging.getLogger(__name__) class AppletIPCServer(AsyncioParentComm): - def __init__(self, dataset_sub, dataset_ctl): + def __init__(self, dataset_sub, dataset_ctl, expmgr): AsyncioParentComm.__init__(self) self.dataset_sub = dataset_sub self.dataset_ctl = dataset_ctl + self.expmgr = expmgr self.datasets = set() self.dataset_prefixes = [] @@ -83,6 +84,8 @@ class AppletIPCServer(AsyncioParentComm): await self.dataset_ctl.set(obj["key"], obj["value"], metadata=obj["metadata"], persist=obj["persist"]) elif action == "update_dataset": await self.dataset_ctl.update(obj["mod"]) + elif action == "set_argument_value": + self.expmgr.set_argument_value(obj["expurl"], obj["name"], obj["value"]) else: raise ValueError("unknown action in applet message") except: @@ -108,7 +111,7 @@ class AppletIPCServer(AsyncioParentComm): class _AppletDock(QDockWidgetCloseDetect): - def __init__(self, dataset_sub, dataset_ctl, uid, name, spec, extra_substitutes): + def __init__(self, dataset_sub, dataset_ctl, expmgr, uid, name, spec, extra_substitutes): QDockWidgetCloseDetect.__init__(self, "Applet: " + name) self.setObjectName("applet" + str(uid)) @@ -118,6 +121,7 @@ class _AppletDock(QDockWidgetCloseDetect): self.dataset_sub = dataset_sub self.dataset_ctl = dataset_ctl + self.expmgr = expmgr self.applet_name = name self.spec = spec self.extra_substitutes = extra_substitutes @@ -136,7 +140,7 @@ class _AppletDock(QDockWidgetCloseDetect): return self.starting_stopping = True try: - self.ipc = AppletIPCServer(self.dataset_sub, self.dataset_ctl) + self.ipc = AppletIPCServer(self.dataset_sub, self.dataset_ctl, self.expmgr) env = os.environ.copy() env["PYTHONUNBUFFERED"] = "1" env["ARTIQ_APPLET_EMBED"] = self.ipc.get_address() @@ -333,7 +337,7 @@ class _CompleterDelegate(QtWidgets.QStyledItemDelegate): class AppletsDock(QtWidgets.QDockWidget): - def __init__(self, main_window, dataset_sub, dataset_ctl, extra_substitutes={}, *, loop=None): + def __init__(self, main_window, dataset_sub, dataset_ctl, expmgr, extra_substitutes={}, *, loop=None): """ :param extra_substitutes: Map of extra ``${strings}`` to substitute in applet commands to their respective values. @@ -346,6 +350,7 @@ class AppletsDock(QtWidgets.QDockWidget): self.main_window = main_window self.dataset_sub = dataset_sub self.dataset_ctl = dataset_ctl + self.expmgr = expmgr self.extra_substitutes = extra_substitutes self.applet_uids = set() @@ -448,7 +453,7 @@ class AppletsDock(QtWidgets.QDockWidget): self.table.itemChanged.connect(self.item_changed) def create(self, item, name, spec): - dock = _AppletDock(self.dataset_sub, self.dataset_ctl, item.applet_uid, name, spec, self.extra_substitutes) + dock = _AppletDock(self.dataset_sub, self.dataset_ctl, self.expmgr, item.applet_uid, name, spec, self.extra_substitutes) self.main_window.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock) dock.setFloating(True) asyncio.ensure_future(dock.start(), loop=self._loop) From eb57b3b39397faaf4e247390e40505a9646c064b Mon Sep 17 00:00:00 2001 From: mwojcik Date: Thu, 24 Aug 2023 17:34:35 +0800 Subject: [PATCH 25/50] drtio: async messages become synchronous They are now a reply for DestinationStatusRequest. This prevents gateware errors and lost packets if the receiver is busy. --- artiq/firmware/runtime/analyzer.rs | 18 +-- artiq/firmware/runtime/kern_hwreq.rs | 137 ++++++++++++---------- artiq/firmware/runtime/main.rs | 6 +- artiq/firmware/runtime/moninj.rs | 55 +++++---- artiq/firmware/runtime/rtio_dma.rs | 24 ++-- artiq/firmware/runtime/rtio_mgt.rs | 163 +++++++++++++++------------ artiq/firmware/runtime/session.rs | 11 +- artiq/firmware/satman/main.rs | 68 +++++------ 8 files changed, 262 insertions(+), 220 deletions(-) diff --git a/artiq/firmware/runtime/analyzer.rs b/artiq/firmware/runtime/analyzer.rs index e7c555d76..fa62a535c 100644 --- a/artiq/firmware/runtime/analyzer.rs +++ b/artiq/firmware/runtime/analyzer.rs @@ -52,16 +52,18 @@ pub mod remote_analyzer { pub data: Vec } - pub fn get_data(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, - routing_table: &drtio_routing::RoutingTable, - up_destinations: &Urc>) -> Result { + pub fn get_data(io: &Io, aux_mutex: &Mutex, routing_table: &drtio_routing::RoutingTable, + up_destinations: &Urc> + ) -> Result { // gets data from satellites and returns consolidated data let mut remote_data: Vec = Vec::new(); let mut remote_overflow = false; let mut remote_sent_bytes = 0; let mut remote_total_bytes = 0; - let data_vec = match drtio::analyzer_query(io, aux_mutex, ddma_mutex, routing_table, up_destinations) { + let data_vec = match drtio::analyzer_query( + io, aux_mutex, routing_table, up_destinations + ) { Ok(data_vec) => data_vec, Err(e) => return Err(e) }; @@ -83,7 +85,7 @@ pub mod remote_analyzer { -fn worker(stream: &mut TcpStream, _io: &Io, _aux_mutex: &Mutex, _ddma_mutex: &Mutex, +fn worker(stream: &mut TcpStream, _io: &Io, _aux_mutex: &Mutex, _routing_table: &drtio_routing::RoutingTable, _up_destinations: &Urc> ) -> Result<(), IoError> { @@ -97,7 +99,7 @@ fn worker(stream: &mut TcpStream, _io: &Io, _aux_mutex: &Mutex, _ddma_mutex: &Mu #[cfg(has_drtio)] let remote = remote_analyzer::get_data( - _io, _aux_mutex, _ddma_mutex, _routing_table, _up_destinations); + _io, _aux_mutex, _routing_table, _up_destinations); #[cfg(has_drtio)] let (header, remote_data) = match remote { Ok(remote) => (Header { @@ -144,7 +146,7 @@ fn worker(stream: &mut TcpStream, _io: &Io, _aux_mutex: &Mutex, _ddma_mutex: &Mu Ok(()) } -pub fn thread(io: Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, +pub fn thread(io: Io, aux_mutex: &Mutex, routing_table: &Urc>, up_destinations: &Urc>) { let listener = TcpListener::new(&io, 65535); @@ -159,7 +161,7 @@ pub fn thread(io: Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, disarm(); let routing_table = routing_table.borrow(); - match worker(&mut stream, &io, aux_mutex, ddma_mutex, &routing_table, up_destinations) { + match worker(&mut stream, &io, aux_mutex, &routing_table, up_destinations) { Ok(()) => (), Err(err) => error!("analyzer aborted: {}", err) } diff --git a/artiq/firmware/runtime/kern_hwreq.rs b/artiq/firmware/runtime/kern_hwreq.rs index b178d8e29..7fd0b379c 100644 --- a/artiq/firmware/runtime/kern_hwreq.rs +++ b/artiq/firmware/runtime/kern_hwreq.rs @@ -14,11 +14,14 @@ mod remote_i2c { use rtio_mgt::drtio; use sched::{Io, Mutex}; - pub fn start(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, destination: u8, busno: u8) -> Result<(), &'static str> { - let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::I2cStartRequest { - destination: destination, - busno: busno - }); + pub fn start(io: &Io, aux_mutex: &Mutex, + linkno: u8, destination: u8, busno: u8 + ) -> Result<(), &'static str> { + let reply = drtio::aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::I2cStartRequest { + destination: destination, + busno: busno + }); match reply { Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => { if succeeded { Ok(()) } else { Err("i2c basic reply error") } @@ -34,11 +37,14 @@ mod remote_i2c { } } - pub fn restart(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, destination: u8, busno: u8) -> Result<(), &'static str> { - let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::I2cRestartRequest { - destination: destination, - busno: busno - }); + pub fn restart(io: &Io, aux_mutex: &Mutex, + linkno: u8, destination: u8, busno: u8 + ) -> Result<(), &'static str> { + let reply = drtio::aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::I2cRestartRequest { + destination: destination, + busno: busno + }); match reply { Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => { if succeeded { Ok(()) } else { Err("i2c basic reply error") } @@ -54,11 +60,14 @@ mod remote_i2c { } } - pub fn stop(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, destination: u8, busno: u8) -> Result<(), &'static str> { - let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::I2cStopRequest { - destination: destination, - busno: busno - }); + pub fn stop(io: &Io, aux_mutex: &Mutex, + linkno: u8, destination: u8, busno: u8 + ) -> Result<(), &'static str> { + let reply = drtio::aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::I2cStopRequest { + destination: destination, + busno: busno + }); match reply { Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => { if succeeded { Ok(()) } else { Err("i2c basic reply error") } @@ -74,12 +83,15 @@ mod remote_i2c { } } - pub fn write(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, destination: u8, busno: u8, data: u8) -> Result { - let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::I2cWriteRequest { - destination: destination, - busno: busno, - data: data - }); + pub fn write(io: &Io, aux_mutex: &Mutex, + linkno: u8, destination: u8, busno: u8, data: u8 + ) -> Result { + let reply = drtio::aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::I2cWriteRequest { + destination: destination, + busno: busno, + data: data + }); match reply { Ok(drtioaux::Packet::I2cWriteReply { succeeded, ack }) => { if succeeded { Ok(ack) } else { Err("i2c write reply error") } @@ -95,12 +107,15 @@ mod remote_i2c { } } - pub fn read(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, destination: u8, busno: u8, ack: bool) -> Result { - let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::I2cReadRequest { - destination: destination, - busno: busno, - ack: ack - }); + pub fn read(io: &Io, aux_mutex: &Mutex, + linkno: u8, destination: u8, busno: u8, ack: bool + ) -> Result { + let reply = drtio::aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::I2cReadRequest { + destination: destination, + busno: busno, + ack: ack + }); match reply { Ok(drtioaux::Packet::I2cReadReply { succeeded, data }) => { if succeeded { Ok(data) } else { Err("i2c read reply error") } @@ -116,13 +131,16 @@ mod remote_i2c { } } - pub fn switch_select(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, destination: u8, busno: u8, address: u8, mask: u8) -> Result<(), &'static str> { - let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::I2cSwitchSelectRequest { - destination: destination, - busno: busno, - address: address, - mask: mask, - }); + pub fn switch_select(io: &Io, aux_mutex: &Mutex, + linkno: u8, destination: u8, busno: u8, address: u8, mask: u8 + ) -> Result<(), &'static str> { + let reply = drtio::aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::I2cSwitchSelectRequest { + destination: destination, + busno: busno, + address: address, + mask: mask, + }); match reply { Ok(drtioaux::Packet::I2cBasicReply { succeeded }) => { if succeeded { Ok(()) } else { Err("i2c basic reply error") } @@ -145,8 +163,10 @@ mod remote_spi { use rtio_mgt::drtio; use sched::{Io, Mutex}; - pub fn set_config(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8) -> Result<(), ()> { - let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::SpiSetConfigRequest { + pub fn set_config(io: &Io, aux_mutex: &Mutex, + linkno: u8, destination: u8, busno: u8, flags: u8, length: u8, div: u8, cs: u8 + ) -> Result<(), ()> { + let reply = drtio::aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::SpiSetConfigRequest { destination: destination, busno: busno, flags: flags, @@ -169,8 +189,10 @@ mod remote_spi { } } - pub fn write(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, destination: u8, busno: u8, data: u32) -> Result<(), ()> { - let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::SpiWriteRequest { + pub fn write(io: &Io, aux_mutex: &Mutex, + linkno: u8, destination: u8, busno: u8, data: u32 + ) -> Result<(), ()> { + let reply = drtio::aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::SpiWriteRequest { destination: destination, busno: busno, data: data @@ -190,11 +212,13 @@ mod remote_spi { } } - pub fn read(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, destination: u8, busno: u8) -> Result { - let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::SpiReadRequest { - destination: destination, - busno: busno - }); + pub fn read(io: &Io, aux_mutex: &Mutex, linkno: u8, destination: u8, busno: u8 + ) -> Result { + let reply = drtio::aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::SpiReadRequest { + destination: destination, + busno: busno + }); match reply { Ok(drtioaux::Packet::SpiReadReply { succeeded, data }) => { if succeeded { Ok(data) } else { Err(()) } @@ -214,7 +238,7 @@ mod remote_spi { #[cfg(has_drtio)] macro_rules! dispatch { - ($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $mod_local:ident, $mod_remote:ident, $routing_table:ident, $busno:expr, $func:ident $(, $param:expr)*) => {{ + ($io:ident, $aux_mutex:ident, $mod_local:ident, $mod_remote:ident, $routing_table:ident, $busno:expr, $func:ident $(, $param:expr)*) => {{ let destination = ($busno >> 16) as u8; let busno = $busno as u8; let hop = $routing_table.0[destination as usize][0]; @@ -222,28 +246,27 @@ macro_rules! dispatch { $mod_local::$func(busno, $($param, )*) } else { let linkno = hop - 1; - $mod_remote::$func($io, $aux_mutex, $ddma_mutex, linkno, destination, busno, $($param, )*) + $mod_remote::$func($io, $aux_mutex, linkno, destination, busno, $($param, )*) } }} } #[cfg(not(has_drtio))] macro_rules! dispatch { - ($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $mod_local:ident, $mod_remote:ident, $routing_table:ident, $busno:expr, $func:ident $(, $param:expr)*) => {{ + ($io:ident, $aux_mutex:ident, $mod_local:ident, $mod_remote:ident, $routing_table:ident, $busno:expr, $func:ident $(, $param:expr)*) => {{ let busno = $busno as u8; $mod_local::$func(busno, $($param, )*) }} } pub fn process_kern_hwreq(io: &Io, aux_mutex: &Mutex, - ddma_mutex: &Mutex, _routing_table: &drtio_routing::RoutingTable, _up_destinations: &Urc>, request: &kern::Message) -> Result> { match request { &kern::RtioInitRequest => { info!("resetting RTIO"); - rtio_mgt::reset(io, aux_mutex, ddma_mutex); + rtio_mgt::reset(io, aux_mutex); kern_acknowledge() } @@ -259,47 +282,47 @@ pub fn process_kern_hwreq(io: &Io, aux_mutex: &Mutex, } &kern::I2cStartRequest { busno } => { - let succeeded = dispatch!(io, aux_mutex, ddma_mutex, local_i2c, remote_i2c, _routing_table, busno, start).is_ok(); + let succeeded = dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno, start).is_ok(); kern_send(io, &kern::I2cBasicReply { succeeded: succeeded }) } &kern::I2cRestartRequest { busno } => { - let succeeded = dispatch!(io, aux_mutex, ddma_mutex, local_i2c, remote_i2c, _routing_table, busno, restart).is_ok(); + let succeeded = dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno, restart).is_ok(); kern_send(io, &kern::I2cBasicReply { succeeded: succeeded }) } &kern::I2cStopRequest { busno } => { - let succeeded = dispatch!(io, aux_mutex, ddma_mutex, local_i2c, remote_i2c, _routing_table, busno, stop).is_ok(); + let succeeded = dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno, stop).is_ok(); kern_send(io, &kern::I2cBasicReply { succeeded: succeeded }) } &kern::I2cWriteRequest { busno, data } => { - match dispatch!(io, aux_mutex, ddma_mutex, local_i2c, remote_i2c, _routing_table, busno, write, data) { + match dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno, write, data) { Ok(ack) => kern_send(io, &kern::I2cWriteReply { succeeded: true, ack: ack }), Err(_) => kern_send(io, &kern::I2cWriteReply { succeeded: false, ack: false }) } } &kern::I2cReadRequest { busno, ack } => { - match dispatch!(io, aux_mutex, ddma_mutex, local_i2c, remote_i2c, _routing_table, busno, read, ack) { + match dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno, read, ack) { Ok(data) => kern_send(io, &kern::I2cReadReply { succeeded: true, data: data }), Err(_) => kern_send(io, &kern::I2cReadReply { succeeded: false, data: 0xff }) } } &kern::I2cSwitchSelectRequest { busno, address, mask } => { - let succeeded = dispatch!(io, aux_mutex, ddma_mutex, local_i2c, remote_i2c, _routing_table, busno, + let succeeded = dispatch!(io, aux_mutex, local_i2c, remote_i2c, _routing_table, busno, switch_select, address, mask).is_ok(); kern_send(io, &kern::I2cBasicReply { succeeded: succeeded }) } &kern::SpiSetConfigRequest { busno, flags, length, div, cs } => { - let succeeded = dispatch!(io, aux_mutex, ddma_mutex, local_spi, remote_spi, _routing_table, busno, + let succeeded = dispatch!(io, aux_mutex, local_spi, remote_spi, _routing_table, busno, set_config, flags, length, div, cs).is_ok(); kern_send(io, &kern::SpiBasicReply { succeeded: succeeded }) }, &kern::SpiWriteRequest { busno, data } => { - let succeeded = dispatch!(io, aux_mutex, ddma_mutex, local_spi, remote_spi, _routing_table, busno, + let succeeded = dispatch!(io, aux_mutex, local_spi, remote_spi, _routing_table, busno, write, data).is_ok(); kern_send(io, &kern::SpiBasicReply { succeeded: succeeded }) } &kern::SpiReadRequest { busno } => { - match dispatch!(io, aux_mutex, ddma_mutex, local_spi, remote_spi, _routing_table, busno, read) { + match dispatch!(io, aux_mutex, local_spi, remote_spi, _routing_table, busno, read) { Ok(data) => kern_send(io, &kern::SpiReadReply { succeeded: true, data: data }), Err(_) => kern_send(io, &kern::SpiReadReply { succeeded: false, data: 0 }) } diff --git a/artiq/firmware/runtime/main.rs b/artiq/firmware/runtime/main.rs index 94cfd23c5..08367375a 100644 --- a/artiq/firmware/runtime/main.rs +++ b/artiq/firmware/runtime/main.rs @@ -210,17 +210,15 @@ fn startup() { #[cfg(any(has_rtio_moninj, has_drtio))] { let aux_mutex = aux_mutex.clone(); - let ddma_mutex = ddma_mutex.clone(); let drtio_routing_table = drtio_routing_table.clone(); - io.spawn(4096, move |io| { moninj::thread(io, &aux_mutex, &ddma_mutex, &drtio_routing_table) }); + io.spawn(4096, move |io| { moninj::thread(io, &aux_mutex, &drtio_routing_table) }); } #[cfg(has_rtio_analyzer)] { let aux_mutex = aux_mutex.clone(); let drtio_routing_table = drtio_routing_table.clone(); let up_destinations = up_destinations.clone(); - let ddma_mutex = ddma_mutex.clone(); - io.spawn(8192, move |io| { analyzer::thread(io, &aux_mutex, &ddma_mutex, &drtio_routing_table, &up_destinations) }); + io.spawn(8192, move |io| { analyzer::thread(io, &aux_mutex, &drtio_routing_table, &up_destinations) }); } #[cfg(has_grabber)] diff --git a/artiq/firmware/runtime/moninj.rs b/artiq/firmware/runtime/moninj.rs index 88f6c6aaf..1ef6b4215 100644 --- a/artiq/firmware/runtime/moninj.rs +++ b/artiq/firmware/runtime/moninj.rs @@ -53,12 +53,14 @@ mod remote_moninj { use rtio_mgt::drtio; use sched::{Io, Mutex}; - pub fn read_probe(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, destination: u8, channel: u16, probe: u8) -> u64 { - let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::MonitorRequest { - destination: destination, - channel: channel, - probe: probe - }); + pub fn read_probe(io: &Io, aux_mutex: &Mutex, linkno: u8, + destination: u8, channel: u16, probe: u8) -> u64 { + let reply = drtio::aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::MonitorRequest { + destination: destination, + channel: channel, + probe: probe + }); match reply { Ok(drtioaux::Packet::MonitorReply { value }) => return value, Ok(packet) => error!("received unexpected aux packet: {:?}", packet), @@ -67,7 +69,8 @@ mod remote_moninj { 0 } - pub fn inject(io: &Io, aux_mutex: &Mutex, _ddma_mutex: &Mutex, linkno: u8, destination: u8, channel: u16, overrd: u8, value: u8) { + pub fn inject(io: &Io, aux_mutex: &Mutex, linkno: u8, + destination: u8, channel: u16, overrd: u8, value: u8) { let _lock = aux_mutex.lock(io).unwrap(); drtioaux::send(linkno, &drtioaux::Packet::InjectionRequest { destination: destination, @@ -77,12 +80,14 @@ mod remote_moninj { }).unwrap(); } - pub fn read_injection_status(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, destination: u8, channel: u16, overrd: u8) -> u8 { - let reply = drtio::aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::InjectionStatusRequest { - destination: destination, - channel: channel, - overrd: overrd - }); + pub fn read_injection_status(io: &Io, aux_mutex: &Mutex, linkno: u8, + destination: u8, channel: u16, overrd: u8) -> u8 { + let reply = drtio::aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::InjectionStatusRequest { + destination: destination, + channel: channel, + overrd: overrd + }); match reply { Ok(drtioaux::Packet::InjectionStatusReply { value }) => return value, Ok(packet) => error!("received unexpected aux packet: {:?}", packet), @@ -94,7 +99,7 @@ mod remote_moninj { #[cfg(has_drtio)] macro_rules! dispatch { - ($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $routing_table:ident, $channel:expr, $func:ident $(, $param:expr)*) => {{ + ($io:ident, $aux_mutex:ident, $routing_table:ident, $channel:expr, $func:ident $(, $param:expr)*) => {{ let destination = ($channel >> 16) as u8; let channel = $channel as u16; let hop = $routing_table.0[destination as usize][0]; @@ -102,21 +107,21 @@ macro_rules! dispatch { local_moninj::$func(channel, $($param, )*) } else { let linkno = hop - 1; - remote_moninj::$func($io, $aux_mutex, $ddma_mutex, linkno, destination, channel, $($param, )*) + remote_moninj::$func($io, $aux_mutex, linkno, destination, channel, $($param, )*) } }} } #[cfg(not(has_drtio))] macro_rules! dispatch { - ($io:ident, $aux_mutex:ident, $ddma_mutex:ident, $routing_table:ident, $channel:expr, $func:ident $(, $param:expr)*) => {{ + ($io:ident, $aux_mutex:ident, $routing_table:ident, $channel:expr, $func:ident $(, $param:expr)*) => {{ let channel = $channel as u16; local_moninj::$func(channel, $($param, )*) }} } -fn connection_worker(io: &Io, _aux_mutex: &Mutex, _ddma_mutex: &Mutex, _routing_table: &drtio_routing::RoutingTable, - mut stream: &mut TcpStream) -> Result<(), Error> { +fn connection_worker(io: &Io, _aux_mutex: &Mutex, _routing_table: &drtio_routing::RoutingTable, + mut stream: &mut TcpStream) -> Result<(), Error> { let mut probe_watch_list = BTreeMap::new(); let mut inject_watch_list = BTreeMap::new(); let mut next_check = 0; @@ -144,9 +149,10 @@ fn connection_worker(io: &Io, _aux_mutex: &Mutex, _ddma_mutex: &Mutex, _routing_ let _ = inject_watch_list.remove(&(channel, overrd)); } }, - HostMessage::Inject { channel, overrd, value } => dispatch!(io, _aux_mutex, _ddma_mutex, _routing_table, channel, inject, overrd, value), + HostMessage::Inject { channel, overrd, value } => dispatch!( + io, _aux_mutex, _routing_table, channel, inject, overrd, value), HostMessage::GetInjectionStatus { channel, overrd } => { - let value = dispatch!(io, _aux_mutex, _ddma_mutex, _routing_table, channel, read_injection_status, overrd); + let value = dispatch!(io, _aux_mutex, _routing_table, channel, read_injection_status, overrd); let reply = DeviceMessage::InjectionStatus { channel: channel, overrd: overrd, @@ -163,7 +169,7 @@ fn connection_worker(io: &Io, _aux_mutex: &Mutex, _ddma_mutex: &Mutex, _routing_ if clock::get_ms() > next_check { for (&(channel, probe), previous) in probe_watch_list.iter_mut() { - let current = dispatch!(io, _aux_mutex, _ddma_mutex, _routing_table, channel, read_probe, probe); + let current = dispatch!(io, _aux_mutex, _routing_table, channel, read_probe, probe); if previous.is_none() || previous.unwrap() != current { let message = DeviceMessage::MonitorStatus { channel: channel, @@ -178,7 +184,7 @@ fn connection_worker(io: &Io, _aux_mutex: &Mutex, _ddma_mutex: &Mutex, _routing_ } } for (&(channel, overrd), previous) in inject_watch_list.iter_mut() { - let current = dispatch!(io, _aux_mutex, _ddma_mutex, _routing_table, channel, read_injection_status, overrd); + let current = dispatch!(io, _aux_mutex, _routing_table, channel, read_injection_status, overrd); if previous.is_none() || previous.unwrap() != current { let message = DeviceMessage::InjectionStatus { channel: channel, @@ -199,19 +205,18 @@ fn connection_worker(io: &Io, _aux_mutex: &Mutex, _ddma_mutex: &Mutex, _routing_ } } -pub fn thread(io: Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, routing_table: &Urc>) { +pub fn thread(io: Io, aux_mutex: &Mutex, routing_table: &Urc>) { let listener = TcpListener::new(&io, 2047); listener.listen(1383).expect("moninj: cannot listen"); loop { let aux_mutex = aux_mutex.clone(); - let ddma_mutex = ddma_mutex.clone(); let routing_table = routing_table.clone(); let stream = listener.accept().expect("moninj: cannot accept").into_handle(); io.spawn(16384, move |io| { let routing_table = routing_table.borrow(); let mut stream = TcpStream::from_handle(&io, stream); - match connection_worker(&io, &aux_mutex, &ddma_mutex, &routing_table, &mut stream) { + match connection_worker(&io, &aux_mutex, &routing_table, &mut stream) { Ok(()) => {}, Err(err) => error!("moninj aborted: {}", err) } diff --git a/artiq/firmware/runtime/rtio_dma.rs b/artiq/firmware/runtime/rtio_dma.rs index c057edaeb..ce0fd4088 100644 --- a/artiq/firmware/runtime/rtio_dma.rs +++ b/artiq/firmware/runtime/rtio_dma.rs @@ -91,12 +91,12 @@ pub mod remote_dma { Ok(playback_state) } - pub fn erase(io: &Io, aux_mutex: &Mutex, routing_table: &RoutingTable, - ddma_mutex: &Mutex, id: u32) { + pub fn erase(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, + routing_table: &RoutingTable, id: u32) { let _lock = ddma_mutex.lock(io).unwrap(); let destinations = unsafe { TRACES.get(&id).unwrap() }; for destination in destinations.keys() { - match drtio::ddma_send_erase(io, aux_mutex, ddma_mutex, routing_table, id, *destination) { + match drtio::ddma_send_erase(io, aux_mutex, routing_table, id, *destination) { Ok(_) => (), Err(e) => error!("Error erasing trace on DMA: {}", e) } @@ -104,12 +104,12 @@ pub mod remote_dma { unsafe { TRACES.remove(&id); } } - pub fn upload_traces(io: &Io, aux_mutex: &Mutex, routing_table: &RoutingTable, - ddma_mutex: &Mutex, id: u32) { + pub fn upload_traces(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, + routing_table: &RoutingTable, id: u32) { let _lock = ddma_mutex.lock(io); let traces = unsafe { TRACES.get_mut(&id).unwrap() }; for (destination, mut trace) in traces { - match drtio::ddma_upload_trace(io, aux_mutex, ddma_mutex, routing_table, id, *destination, trace.get_trace()) + match drtio::ddma_upload_trace(io, aux_mutex, routing_table, id, *destination, trace.get_trace()) { Ok(_) => trace.state = RemoteState::Loaded, Err(e) => error!("Error adding DMA trace on destination {}: {}", destination, e) @@ -117,8 +117,8 @@ pub mod remote_dma { } } - pub fn playback(io: &Io, aux_mutex: &Mutex, routing_table: &RoutingTable, - ddma_mutex: &Mutex, id: u32, timestamp: u64) { + pub fn playback(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, + routing_table: &RoutingTable, id: u32, timestamp: u64) { // triggers playback on satellites let destinations = unsafe { let _lock = ddma_mutex.lock(io).unwrap(); @@ -133,7 +133,7 @@ pub mod remote_dma { continue; } } - match drtio::ddma_send_playback(io, aux_mutex, ddma_mutex, routing_table, id, *destination, timestamp) { + match drtio::ddma_send_playback(io, aux_mutex, routing_table, id, *destination, timestamp) { Ok(_) => (), Err(e) => error!("Error during remote DMA playback: {}", e) } @@ -152,15 +152,15 @@ pub mod remote_dma { }; } - pub fn destination_changed(io: &Io, aux_mutex: &Mutex, routing_table: &RoutingTable, - ddma_mutex: &Mutex, destination: u8, up: bool) { + pub fn destination_changed(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, + routing_table: &RoutingTable, destination: u8, up: bool) { // update state of the destination, resend traces if it's up let _lock = ddma_mutex.lock(io).unwrap(); let traces_iter = unsafe { TRACES.iter_mut() }; for (id, dest_traces) in traces_iter { if let Some(trace) = dest_traces.get_mut(&destination) { if up { - match drtio::ddma_upload_trace(io, aux_mutex, ddma_mutex, routing_table, *id, destination, trace.get_trace()) + match drtio::ddma_upload_trace(io, aux_mutex, routing_table, *id, destination, trace.get_trace()) { Ok(_) => trace.state = RemoteState::Loaded, Err(e) => error!("Error adding DMA trace on destination {}: {}", destination, e) diff --git a/artiq/firmware/runtime/rtio_mgt.rs b/artiq/firmware/runtime/rtio_mgt.rs index de1b3332c..0eb1679fc 100644 --- a/artiq/firmware/runtime/rtio_mgt.rs +++ b/artiq/firmware/runtime/rtio_mgt.rs @@ -61,23 +61,27 @@ pub mod drtio { } } - pub fn aux_transact(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, - linkno: u8, request: &drtioaux::Packet ) -> Result { - let _lock = aux_mutex.lock(io).unwrap(); - drtioaux::send(linkno, request).unwrap(); - loop { - let reply = recv_aux_timeout(io, linkno, 200); - match reply { - Ok(drtioaux::Packet::DmaPlaybackStatus { id, destination, error, channel, timestamp }) => { - remote_dma::playback_done(io, ddma_mutex, id, destination, error, channel, timestamp); - }, - Ok(packet) => return Ok(packet), - Err(e) => return Err(e) - } + fn process_async_packets(io: &Io, ddma_mutex: &Mutex, packet: drtioaux::Packet + ) -> Option { + // returns None if an async packet has been consumed + match packet { + drtioaux::Packet::DmaPlaybackStatus { id, destination, error, channel, timestamp } => { + remote_dma::playback_done(io, ddma_mutex, id, destination, error, channel, timestamp); + None + }, + other => Some(other) } } - fn ping_remote(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8) -> u32 { + pub fn aux_transact(io: &Io, aux_mutex: &Mutex, linkno: u8, request: &drtioaux::Packet + ) -> Result { + let _lock = aux_mutex.lock(io).unwrap(); + drtioaux::send(linkno, request).unwrap(); + let reply = recv_aux_timeout(io, linkno, 200)?; + Ok(reply) + } + + fn ping_remote(io: &Io, aux_mutex: &Mutex, linkno: u8) -> u32 { let mut count = 0; loop { if !link_rx_up(linkno) { @@ -87,7 +91,7 @@ pub mod drtio { if count > 100 { return 0; } - let reply = aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::EchoRequest); + let reply = aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::EchoRequest); match reply { Ok(drtioaux::Packet::EchoReply) => { // make sure receive buffer is drained @@ -123,10 +127,10 @@ pub mod drtio { } } - fn load_routing_table(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, + fn load_routing_table(io: &Io, aux_mutex: &Mutex, linkno: u8, routing_table: &drtio_routing::RoutingTable) -> Result<(), &'static str> { for i in 0..drtio_routing::DEST_COUNT { - let reply = aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::RoutingSetPath { + let reply = aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::RoutingSetPath { destination: i as u8, hops: routing_table.0[i] })?; @@ -137,10 +141,12 @@ pub mod drtio { Ok(()) } - fn set_rank(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, linkno: u8, rank: u8) -> Result<(), &'static str> { - let reply = aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::RoutingSetRank { - rank: rank - })?; + fn set_rank(io: &Io, aux_mutex: &Mutex, + linkno: u8, rank: u8) -> Result<(), &'static str> { + let reply = aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::RoutingSetRank { + rank: rank + })?; if reply != drtioaux::Packet::RoutingAck { return Err("unexpected reply"); } @@ -229,45 +235,60 @@ pub mod drtio { let linkno = hop - 1; if destination_up(up_destinations, destination) { if up_links[linkno as usize] { - let reply = aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::DestinationStatusRequest { - destination: destination - }); - match reply { - Ok(drtioaux::Packet::DestinationDownReply) => { - destination_set_up(routing_table, up_destinations, destination, false); - remote_dma::destination_changed(io, aux_mutex, routing_table, ddma_mutex, destination, false); + loop { + let reply = aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::DestinationStatusRequest { + destination: destination + }); + if let Ok(reply) = reply { + let reply = process_async_packets(io, ddma_mutex, reply); + match reply { + Some(drtioaux::Packet::DestinationDownReply) => { + destination_set_up(routing_table, up_destinations, destination, false); + remote_dma::destination_changed(io, aux_mutex, ddma_mutex, routing_table, destination, false); + } + Some(drtioaux::Packet::DestinationOkReply) => (), + Some(drtioaux::Packet::DestinationSequenceErrorReply { channel }) => { + error!("[DEST#{}] RTIO sequence error involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(channel as u32)); + unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_SEQUENCE_ERROR }; + } + Some(drtioaux::Packet::DestinationCollisionReply { channel }) => { + error!("[DEST#{}] RTIO collision involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(channel as u32)); + unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_COLLISION }; + } + Some(drtioaux::Packet::DestinationBusyReply { channel }) => { + error!("[DEST#{}] RTIO busy error involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(channel as u32)); + unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_BUSY }; + } + Some(packet) => error!("[DEST#{}] received unexpected aux packet: {:?}", destination, packet), + None => { + // continue asking until we get Destination...Reply or error out + // wait a bit not to overwhelm the receiver causing gateway errors + io.sleep(10).unwrap(); + continue; + } + } + } else { + error!("[DEST#{}] communication failed ({})", destination, reply.unwrap_err()); } - Ok(drtioaux::Packet::DestinationOkReply) => (), - Ok(drtioaux::Packet::DestinationSequenceErrorReply { channel }) => { - error!("[DEST#{}] RTIO sequence error involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(channel as u32)); - unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_SEQUENCE_ERROR }; - } - Ok(drtioaux::Packet::DestinationCollisionReply { channel }) => { - error!("[DEST#{}] RTIO collision involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(channel as u32)); - unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_COLLISION }; - } - Ok(drtioaux::Packet::DestinationBusyReply { channel }) => { - error!("[DEST#{}] RTIO busy error involving channel 0x{:04x}:{}", destination, channel, resolve_channel_name(channel as u32)); - unsafe { SEEN_ASYNC_ERRORS |= ASYNC_ERROR_BUSY }; - } - Ok(packet) => error!("[DEST#{}] received unexpected aux packet: {:?}", destination, packet), - Err(e) => error!("[DEST#{}] communication failed ({})", destination, e) + break; } } else { destination_set_up(routing_table, up_destinations, destination, false); - remote_dma::destination_changed(io, aux_mutex, routing_table, ddma_mutex, destination, false); + remote_dma::destination_changed(io, aux_mutex, ddma_mutex, routing_table, destination, false); } } else { if up_links[linkno as usize] { - let reply = aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::DestinationStatusRequest { - destination: destination - }); + let reply = aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::DestinationStatusRequest { + destination: destination + }); match reply { Ok(drtioaux::Packet::DestinationDownReply) => (), Ok(drtioaux::Packet::DestinationOkReply) => { destination_set_up(routing_table, up_destinations, destination, true); init_buffer_space(destination as u8, linkno); - remote_dma::destination_changed(io, aux_mutex, routing_table, ddma_mutex, destination, true); + remote_dma::destination_changed(io, aux_mutex, ddma_mutex, routing_table, destination, true); }, Ok(packet) => error!("[DEST#{}] received unexpected aux packet: {:?}", destination, packet), Err(e) => error!("[DEST#{}] communication failed ({})", destination, e) @@ -299,17 +320,17 @@ pub mod drtio { /* link was previously down */ if link_rx_up(linkno) { info!("[LINK#{}] link RX became up, pinging", linkno); - let ping_count = ping_remote(&io, aux_mutex, ddma_mutex, linkno); + let ping_count = ping_remote(&io, aux_mutex, linkno); if ping_count > 0 { info!("[LINK#{}] remote replied after {} packets", linkno, ping_count); up_links[linkno as usize] = true; if let Err(e) = sync_tsc(&io, aux_mutex, linkno) { error!("[LINK#{}] failed to sync TSC ({})", linkno, e); } - if let Err(e) = load_routing_table(&io, aux_mutex, ddma_mutex, linkno, routing_table) { + if let Err(e) = load_routing_table(&io, aux_mutex, linkno, routing_table) { error!("[LINK#{}] failed to load routing table ({})", linkno, e); } - if let Err(e) = set_rank(&io, aux_mutex, ddma_mutex, linkno, 1) { + if let Err(e) = set_rank(&io, aux_mutex, linkno, 1) { error!("[LINK#{}] failed to set rank ({})", linkno, e); } info!("[LINK#{}] link initialization completed", linkno); @@ -324,7 +345,7 @@ pub mod drtio { } } - pub fn reset(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex) { + pub fn reset(io: &Io, aux_mutex: &Mutex) { for linkno in 0..csr::DRTIO.len() { unsafe { (csr::DRTIO[linkno].reset_write)(1); @@ -340,7 +361,7 @@ pub mod drtio { for linkno in 0..csr::DRTIO.len() { let linkno = linkno as u8; if link_rx_up(linkno) { - let reply = aux_transact(io, aux_mutex, ddma_mutex, linkno, + let reply = aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::ResetRequest); match reply { Ok(drtioaux::Packet::ResetAck) => (), @@ -351,8 +372,7 @@ pub mod drtio { } } - pub fn ddma_upload_trace(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, - routing_table: &drtio_routing::RoutingTable, + pub fn ddma_upload_trace(io: &Io, aux_mutex: &Mutex, routing_table: &drtio_routing::RoutingTable, id: u32, destination: u8, trace: &Vec) -> Result<(), &'static str> { let linkno = routing_table.0[destination as usize][0] - 1; let mut i = 0; @@ -362,7 +382,7 @@ pub mod drtio { let last = i + len == trace.len(); trace_slice[..len].clone_from_slice(&trace[i..i+len]); i += len; - let reply = aux_transact(io, aux_mutex, ddma_mutex, linkno, + let reply = aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::DmaAddTraceRequest { id: id, destination: destination, last: last, length: len as u16, trace: trace_slice}); match reply { @@ -376,12 +396,10 @@ pub mod drtio { Ok(()) } - - pub fn ddma_send_erase(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, - routing_table: &drtio_routing::RoutingTable, + pub fn ddma_send_erase(io: &Io, aux_mutex: &Mutex, routing_table: &drtio_routing::RoutingTable, id: u32, destination: u8) -> Result<(), &'static str> { let linkno = routing_table.0[destination as usize][0] - 1; - let reply = aux_transact(io, aux_mutex, ddma_mutex, linkno, + let reply = aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::DmaRemoveTraceRequest { id: id, destination: destination }); match reply { Ok(drtioaux::Packet::DmaRemoveTraceReply { succeeded: true }) => Ok(()), @@ -391,12 +409,11 @@ pub mod drtio { } } - pub fn ddma_send_playback(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, - routing_table: &drtio_routing::RoutingTable, + pub fn ddma_send_playback(io: &Io, aux_mutex: &Mutex, routing_table: &drtio_routing::RoutingTable, id: u32, destination: u8, timestamp: u64) -> Result<(), &'static str> { let linkno = routing_table.0[destination as usize][0] - 1; - let reply = aux_transact(io, aux_mutex, ddma_mutex, linkno, &drtioaux::Packet::DmaPlaybackRequest{ - id: id, destination: destination, timestamp: timestamp }); + let reply = aux_transact(io, aux_mutex, linkno, + &drtioaux::Packet::DmaPlaybackRequest{ id: id, destination: destination, timestamp: timestamp }); match reply { Ok(drtioaux::Packet::DmaPlaybackReply { succeeded: true }) => return Ok(()), Ok(drtioaux::Packet::DmaPlaybackReply { succeeded: false }) => @@ -407,11 +424,10 @@ pub mod drtio { } #[cfg(has_rtio_analyzer)] - fn analyzer_get_data(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, - routing_table: &drtio_routing::RoutingTable, destination: u8 - ) -> Result { + fn analyzer_get_data(io: &Io, aux_mutex: &Mutex, routing_table: &drtio_routing::RoutingTable, + destination: u8) -> Result { let linkno = routing_table.0[destination as usize][0] - 1; - let reply = aux_transact(io, aux_mutex, ddma_mutex, linkno, + let reply = aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::AnalyzerHeaderRequest { destination: destination }); let (sent, total, overflow) = match reply { Ok(drtioaux::Packet::AnalyzerHeader { @@ -425,7 +441,7 @@ pub mod drtio { if sent > 0 { let mut last_packet = false; while !last_packet { - let reply = aux_transact(io, aux_mutex, ddma_mutex, linkno, + let reply = aux_transact(io, aux_mutex, linkno, &drtioaux::Packet::AnalyzerDataRequest { destination: destination }); match reply { Ok(drtioaux::Packet::AnalyzerData { last, length, data }) => { @@ -447,14 +463,13 @@ pub mod drtio { } #[cfg(has_rtio_analyzer)] - pub fn analyzer_query(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex, - routing_table: &drtio_routing::RoutingTable, + pub fn analyzer_query(io: &Io, aux_mutex: &Mutex, routing_table: &drtio_routing::RoutingTable, up_destinations: &Urc> ) -> Result, &'static str> { let mut remote_buffers: Vec = Vec::new(); for i in 1..drtio_routing::DEST_COUNT { if destination_up(up_destinations, i as u8) { - remote_buffers.push(analyzer_get_data(io, aux_mutex, ddma_mutex, routing_table, i as u8)?); + remote_buffers.push(analyzer_get_data(io, aux_mutex, routing_table, i as u8)?); } } Ok(remote_buffers) @@ -542,9 +557,9 @@ pub fn startup(io: &Io, aux_mutex: &Mutex, io.spawn(4096, async_error_thread); } -pub fn reset(io: &Io, aux_mutex: &Mutex, ddma_mutex: &Mutex) { +pub fn reset(io: &Io, aux_mutex: &Mutex) { unsafe { csr::rtio_core::reset_write(1); } - drtio::reset(io, aux_mutex, ddma_mutex) + drtio::reset(io, aux_mutex) } diff --git a/artiq/firmware/runtime/session.rs b/artiq/firmware/runtime/session.rs index 95b3ca28f..64e015038 100644 --- a/artiq/firmware/runtime/session.rs +++ b/artiq/firmware/runtime/session.rs @@ -349,7 +349,7 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex, kern_recv_dotrace(request); - if kern_hwreq::process_kern_hwreq(io, aux_mutex, ddma_mutex, routing_table, up_destinations, request)? { + if kern_hwreq::process_kern_hwreq(io, aux_mutex, routing_table, up_destinations, request)? { return Ok(false) } @@ -373,7 +373,7 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex, if let Some(_id) = session.congress.dma_manager.record_start(name) { // replace the record #[cfg(has_drtio)] - remote_dma::erase(io, aux_mutex, routing_table, ddma_mutex, _id); + remote_dma::erase(io, aux_mutex, ddma_mutex, routing_table, _id); } kern_acknowledge() } @@ -385,7 +385,7 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex, let _id = session.congress.dma_manager.record_stop(duration, enable_ddma, io, ddma_mutex); #[cfg(has_drtio)] if enable_ddma { - remote_dma::upload_traces(io, aux_mutex, routing_table, ddma_mutex, _id); + remote_dma::upload_traces(io, aux_mutex, ddma_mutex, routing_table, _id); } cache::flush_l2_cache(); kern_acknowledge() @@ -393,7 +393,7 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex, &kern::DmaEraseRequest { name } => { #[cfg(has_drtio)] if let Some(id) = session.congress.dma_manager.get_id(name) { - remote_dma::erase(io, aux_mutex, routing_table, ddma_mutex, *id); + remote_dma::erase(io, aux_mutex, ddma_mutex, routing_table, *id); } session.congress.dma_manager.erase(name); kern_acknowledge() @@ -416,7 +416,7 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex, } &kern::DmaStartRemoteRequest { id: _id, timestamp: _timestamp } => { #[cfg(has_drtio)] - remote_dma::playback(io, aux_mutex, routing_table, ddma_mutex, _id as u32, _timestamp as u64); + remote_dma::playback(io, aux_mutex, ddma_mutex, routing_table, _id as u32, _timestamp as u64); kern_acknowledge() } &kern::DmaAwaitRemoteRequest { id: _id } => { @@ -510,7 +510,6 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex, } } } - request => unexpected!("unexpected request {:?} from kernel CPU", request) }.and(Ok(false)) }) diff --git a/artiq/firmware/satman/main.rs b/artiq/firmware/satman/main.rs index 6eb1c7d9d..8226cbc33 100644 --- a/artiq/firmware/satman/main.rs +++ b/artiq/firmware/satman/main.rs @@ -109,37 +109,44 @@ fn process_aux_packet(_manager: &mut DmaManager, analyzer: &mut Analyzer, _repea let hop = 0; if hop == 0 { - let errors; - unsafe { - errors = csr::drtiosat::rtio_error_read(); - } - if errors & 1 != 0 { - let channel; + // async messages + if let Some(status) = _manager.check_state() { + info!("playback done, error: {}, channel: {}, timestamp: {}", status.error, status.channel, status.timestamp); + drtioaux::send(0, &drtioaux::Packet::DmaPlaybackStatus { + destination: *_rank, id: status.id, error: status.error, channel: status.channel, timestamp: status.timestamp })?; + } else { + let errors; unsafe { - channel = csr::drtiosat::sequence_error_channel_read(); - csr::drtiosat::rtio_error_write(1); + errors = csr::drtiosat::rtio_error_read(); } - drtioaux::send(0, - &drtioaux::Packet::DestinationSequenceErrorReply { channel })?; - } else if errors & 2 != 0 { - let channel; - unsafe { - channel = csr::drtiosat::collision_channel_read(); - csr::drtiosat::rtio_error_write(2); + if errors & 1 != 0 { + let channel; + unsafe { + channel = csr::drtiosat::sequence_error_channel_read(); + csr::drtiosat::rtio_error_write(1); + } + drtioaux::send(0, + &drtioaux::Packet::DestinationSequenceErrorReply { channel })?; + } else if errors & 2 != 0 { + let channel; + unsafe { + channel = csr::drtiosat::collision_channel_read(); + csr::drtiosat::rtio_error_write(2); + } + drtioaux::send(0, + &drtioaux::Packet::DestinationCollisionReply { channel })?; + } else if errors & 4 != 0 { + let channel; + unsafe { + channel = csr::drtiosat::busy_channel_read(); + csr::drtiosat::rtio_error_write(4); + } + drtioaux::send(0, + &drtioaux::Packet::DestinationBusyReply { channel })?; } - drtioaux::send(0, - &drtioaux::Packet::DestinationCollisionReply { channel })?; - } else if errors & 4 != 0 { - let channel; - unsafe { - channel = csr::drtiosat::busy_channel_read(); - csr::drtiosat::rtio_error_write(4); + else { + drtioaux::send(0, &drtioaux::Packet::DestinationOkReply)?; } - drtioaux::send(0, - &drtioaux::Packet::DestinationBusyReply { channel })?; - } - else { - drtioaux::send(0, &drtioaux::Packet::DestinationOkReply)?; } } @@ -643,13 +650,6 @@ pub extern fn main() -> i32 { error!("aux packet error: {}", e); } } - if let Some(status) = dma_manager.check_state() { - info!("playback done, error: {}, channel: {}, timestamp: {}", status.error, status.channel, status.timestamp); - if let Err(e) = drtioaux::send(0, &drtioaux::Packet::DmaPlaybackStatus { - destination: rank, id: status.id, error: status.error, channel: status.channel, timestamp: status.timestamp }) { - error!("error sending DMA playback status: {}", e); - } - } } drtiosat_reset_phy(true); From af77885dfcaa7693c92539ad44738a414cac8010 Mon Sep 17 00:00:00 2001 From: mwojcik Date: Fri, 22 Sep 2023 09:46:40 +0800 Subject: [PATCH 26/50] rtio_mgt: fix drtio reset on standalone --- artiq/firmware/runtime/rtio_mgt.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/firmware/runtime/rtio_mgt.rs b/artiq/firmware/runtime/rtio_mgt.rs index 0eb1679fc..3ead188b5 100644 --- a/artiq/firmware/runtime/rtio_mgt.rs +++ b/artiq/firmware/runtime/rtio_mgt.rs @@ -485,7 +485,7 @@ pub mod drtio { _routing_table: &Urc>, _up_destinations: &Urc>, _ddma_mutex: &Mutex) {} - pub fn reset(_io: &Io, _aux_mutex: &Mutex, _ddma_mutex: &Mutex) {} + pub fn reset(_io: &Io, _aux_mutex: &Mutex) {} } static mut SEEN_ASYNC_ERRORS: u8 = 0; From 36b3678853e2a5667d1a458b11f5f176be39d933 Mon Sep 17 00:00:00 2001 From: mwojcik Date: Fri, 22 Sep 2023 10:25:37 +0800 Subject: [PATCH 27/50] satman: fix ddma reporting wrong destination --- artiq/firmware/satman/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/artiq/firmware/satman/main.rs b/artiq/firmware/satman/main.rs index 8226cbc33..c524483fc 100644 --- a/artiq/firmware/satman/main.rs +++ b/artiq/firmware/satman/main.rs @@ -113,7 +113,7 @@ fn process_aux_packet(_manager: &mut DmaManager, analyzer: &mut Analyzer, _repea if let Some(status) = _manager.check_state() { info!("playback done, error: {}, channel: {}, timestamp: {}", status.error, status.channel, status.timestamp); drtioaux::send(0, &drtioaux::Packet::DmaPlaybackStatus { - destination: *_rank, id: status.id, error: status.error, channel: status.channel, timestamp: status.timestamp })?; + destination: _destination, id: status.id, error: status.error, channel: status.channel, timestamp: status.timestamp })?; } else { let errors; unsafe { From ab8247b3d7e9b815b210b579b2194b621bf860e7 Mon Sep 17 00:00:00 2001 From: linuswck Date: Mon, 11 Sep 2023 17:28:33 +0800 Subject: [PATCH 28/50] 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 --- artiq/examples/kasli_shuttler/device_db.py | 330 ++++++++++++++++++ .../kasli_shuttler/repository/shuttler.py | 330 ++++++++++++++++++ 2 files changed, 660 insertions(+) create mode 100644 artiq/examples/kasli_shuttler/device_db.py create mode 100644 artiq/examples/kasli_shuttler/repository/shuttler.py diff --git a/artiq/examples/kasli_shuttler/device_db.py b/artiq/examples/kasli_shuttler/device_db.py new file mode 100644 index 000000000..b87f8dc63 --- /dev/null +++ b/artiq/examples/kasli_shuttler/device_db.py @@ -0,0 +1,330 @@ +core_addr = "192.168.1.73" + +device_db = { + "core": { + "type": "local", + "module": "artiq.coredevice.core", + "class": "Core", + "arguments": {"host": core_addr, "ref_period": 1e-09, "target": "rv32g"}, + }, + "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" + }, + + "i2c_switch0": { + "type": "local", + "module": "artiq.coredevice.i2c", + "class": "I2CSwitch", + "arguments": {"address": 0xe0} + }, + "i2c_switch1": { + "type": "local", + "module": "artiq.coredevice.i2c", + "class": "I2CSwitch", + "arguments": {"address": 0xe2} + }, +} + +device_db["efc_led0"] = { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 0x040000}, +} + +device_db["efc_led1"] = { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 0x040001}, +} + +device_db["pdq_config"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Config", + "arguments": {"channel": 0x040002}, +} + +device_db["pdq_trigger"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Trigger", + "arguments": {"channel": 0x040003}, +} + +device_db["pdq0_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040004}, +} + +device_db["pdq0_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040005}, +} + +device_db["pdq1_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040006}, +} + +device_db["pdq1_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040007}, +} + +device_db["pdq2_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040008}, +} + +device_db["pdq2_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040009}, +} + +device_db["pdq3_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04000A}, +} + +device_db["pdq3_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04000B}, +} + +device_db["pdq4_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04000C}, +} + +device_db["pdq4_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04000D}, +} + +device_db["pdq5_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04000E}, +} + +device_db["pdq5_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04000F}, +} + +device_db["pdq6_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040010}, +} + +device_db["pdq6_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040011}, +} + +device_db["pdq7_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040012}, +} + +device_db["pdq7_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040013}, +} + +device_db["pdq8_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040014}, +} + +device_db["pdq8_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040015}, +} + +device_db["pdq9_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040016}, +} + +device_db["pdq9_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040017}, +} + +device_db["pdq10_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040018}, +} + +device_db["pdq10_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040019}, +} + +device_db["pdq11_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04001A}, +} + +device_db["pdq11_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04001B}, +} + +device_db["pdq12_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04001C}, +} + +device_db["pdq12_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04001D}, +} + +device_db["pdq13_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x04001E}, +} + +device_db["pdq13_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x04001F}, +} + +device_db["pdq14_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040020}, +} + +device_db["pdq14_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040021}, +} + +device_db["pdq15_volt"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {"channel": 0x040022}, +} + +device_db["pdq15_dds"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {"channel": 0x040023}, +} + +device_db["spi_afe_relay"] = { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 0x040024} +} + +device_db["afe_relay"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Relay", + "arguments": { + "spi_device": "spi_afe_relay", + } +} + +device_db["spi_afe_adc"] = { + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {"channel": 0x040025} +} + +device_db["afe_adc"] = { + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "ADC", + "arguments": { + "spi_device": "spi_afe_adc", + } +} diff --git a/artiq/examples/kasli_shuttler/repository/shuttler.py b/artiq/examples/kasli_shuttler/repository/shuttler.py new file mode 100644 index 000000000..70e01d4f9 --- /dev/null +++ b/artiq/examples/kasli_shuttler/repository/shuttler.py @@ -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 pdq_phase_offset(offset_degree): + return round(offset_degree / 360 * (2 ** 16)) + +@portable +def pdq_freq_mu(freq_mhz): + return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz) + +@portable +def pdq_chirp_rate_mu(freq_mhz_per_us): + return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2)) + +@portable +def pdq_freq_sweep(start_f_MHz, end_f_MHz, time_us): + return pdq_chirp_rate_mu((end_f_MHz - start_f_MHz)/(time_us)) + +@portable +def pdq_volt_amp_mu(volt): + return shuttler_volt_to_mu(volt) + +@portable +def pdq_volt_damp_mu(volt_per_us): + return round(float(2) ** 30 * (volt_per_us / 20) / DAC_Fs_MHZ) + +@portable +def pdq_volt_ddamp_mu(volt_per_us_square): + return round(float(2) ** 46 * (volt_per_us_square / 20) * 2 / (DAC_Fs_MHZ ** 2)) + +@portable +def pdq_volt_dddamp_mu(volt_per_us_cube): + return round(float(2) ** 46 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3)) + +@portable +def pdq_dds_amp_mu(volt): + return pdq_volt_amp_mu(volt / CORDIC_GAIN) + +@portable +def pdq_dds_damp_mu(volt_per_us): + return pdq_volt_damp_mu(volt_per_us / CORDIC_GAIN) + +@portable +def pdq_dds_ddamp_mu(volt_per_us_square): + return pdq_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN) + +@portable +def pdq_dds_dddamp_mu(volt_per_us_cube): + return pdq_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.leds = [ self.get_device("efc_led{}".format(i)) for i in range(2) ] + self.setattr_device("pdq_config") + self.setattr_device("pdq_trigger") + self.pdq_volt = [ self.get_device("pdq{}_volt".format(i)) for i in range(16) ] + self.pdq_dds = [ self.get_device("pdq{}_dds".format(i)) for i in range(16) ] + self.setattr_device("afe_relay") + self.setattr_device("afe_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.pdq_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 pdq_reset(self): + for i in range(16): + self.pdq_channel_reset(i) + # To avoid RTIO Underflow + delay(50*us) + + @kernel + def pdq_channel_reset(self, ch): + self.pdq_volt[ch].set_waveform( + a0=0, + a1=0, + a2=0, + a3=0, + ) + self.pdq_dds[ch].set_waveform( + b0=0, + b1=0, + b2=0, + b3=0, + c0=0, + c1=0, + c2=0, + ) + self.pdq_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.afe_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.pdq_dds[0].set_waveform( + b0=pdq_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=0, + c1=pdq_freq_mu(start_f_MHz), + c2=pdq_freq_sweep(start_f_MHz, end_f_MHz, duration_us), + ) + self.pdq_dds[1].set_waveform( + b0=pdq_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=0, + c1=pdq_freq_mu(end_f_MHz), + c2=0, + ) + self.pdq_trigger.trigger(0b11) + delay(500*us) + + ## Step 3 ## + # OUT0 and OUT1 has 180 degree phase difference + self.pdq_dds[0].set_waveform( + b0=pdq_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=pdq_phase_offset(180.0), + c1=pdq_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.pdq_trigger.trigger(0b1) + delay(500*us) + + ## Step 4 ## + # b(0) = 0, b(250) = 8.545, b(500) = 0 + self.pdq_dds[0].set_waveform( + b0=0, + b1=pdq_dds_damp_mu(0.06835937), + b2=pdq_dds_ddamp_mu(-0.0001367187), + b3=0, + c0=0, + c1=pdq_freq_mu(end_f_MHz), + c2=0, + ) + self.pdq_dds[1].set_waveform( + b0=0, + b1=pdq_dds_damp_mu(0.06835937), + b2=pdq_dds_ddamp_mu(-0.0001367187), + b3=0, + c0=0, + c1=0, + c2=0, + ) + self.pdq_trigger.trigger(0b11) + delay(500*us) + + ## Step 5 ## + self.pdq_volt[0].set_waveform( + a0=pdq_volt_amp_mu(-5.0), + a1=int32(pdq_volt_damp_mu(0.01)), + a2=0, + a3=0, + ) + self.pdq_dds[0].set_waveform( + b0=pdq_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=0, + c1=pdq_freq_mu(end_f_MHz), + c2=0, + ) + self.pdq_volt[1].set_waveform( + a0=pdq_volt_amp_mu(-5.0), + a1=int32(pdq_volt_damp_mu(0.01)), + a2=0, + a3=0, + ) + self.pdq_dds[1].set_waveform( + b0=0, + b1=0, + b2=0, + b3=0, + c0=0, + c1=0, + c2=0, + ) + self.pdq_trigger.trigger(0b11) + delay(1000*us) + + ## Step 6 ## + self.pdq_volt[0].set_waveform( + a0=pdq_volt_amp_mu(-2.5), + a1=int32(pdq_volt_damp_mu(0.01)), + a2=0, + a3=0, + ) + self.pdq_dds[0].set_waveform( + b0=0, + b1=pdq_dds_damp_mu(0.06835937), + b2=pdq_dds_ddamp_mu(-0.0001367187), + b3=0, + c0=0, + c1=pdq_freq_mu(start_f_MHz), + c2=pdq_freq_sweep(start_f_MHz, end_f_MHz, duration_us), + ) + self.pdq_trigger.trigger(0b1) + self.pdq_channel_reset(1) + delay(500*us) + + ## Step 7 ## + self.pdq_volt[0].set_waveform( + a0=pdq_volt_amp_mu(2.5), + a1=int32(pdq_volt_damp_mu(-0.01)), + a2=0, + a3=0, + ) + self.pdq_dds[0].set_waveform( + b0=0, + b1=pdq_dds_damp_mu(-0.06835937), + b2=pdq_dds_ddamp_mu(0.0001367187), + b3=0, + c0=pdq_phase_offset(180.0), + c1=pdq_freq_mu(end_f_MHz), + c2=pdq_freq_sweep(end_f_MHz, start_f_MHz, duration_us), + ) + self.pdq_trigger.trigger(0b1) + delay(500*us) + + ## Step 8 ## + self.afe_relay.enable(0) + self.pdq_channel_reset(0) + self.pdq_channel_reset(1) + + @kernel + def led(self): + for i in range(2): + for j in range(3): + self.leds[i].pulse(.1*s) + delay(.1*s) + + @kernel + def relay_init(self): + self.afe_relay.init() + self.afe_relay.enable(0x0000) + + @kernel + def adc_init(self): + delay_mu(int64(self.core.ref_multiplier)) + self.afe_adc.power_up() + + delay_mu(int64(self.core.ref_multiplier)) + assert self.afe_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.afe_adc.calibrate(self.pdq_volt, self.pdq_trigger, self.pdq_config) From 73ab71f44392c4da63da05be4d9ca4d4ffa1d126 Mon Sep 17 00:00:00 2001 From: occheung <54844539+occheung@users.noreply.github.com> Date: Mon, 25 Sep 2023 02:47:47 -0700 Subject: [PATCH 29/50] shuttler: add documentation --- artiq/coredevice/shuttler.py | 331 +++++++++++++++++++++++++- doc/manual/core_drivers_reference.rst | 6 + 2 files changed, 332 insertions(+), 5 deletions(-) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 358522eac..722b95f2a 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -9,10 +9,27 @@ 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 << 14) * (volt / 20.0)) & 0x3fff 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 0x1fff and 0x2000. + + :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" @@ -29,30 +46,87 @@ class Config: @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 Volt: + """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"): @@ -62,6 +136,34 @@ class Volt: @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:`Volt`, 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 14, 30, 46 + and 46 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. + """ pdq_words = [ a0, a1, @@ -80,6 +182,30 @@ class Volt: 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"): @@ -90,6 +216,44 @@ class Dds: @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 14, 30, 46 + and 46 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. + """ pdq_words = [ b0, b1, @@ -113,6 +277,11 @@ class Dds: 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"): @@ -122,6 +291,16 @@ class Trigger: @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:`Volt` 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) @@ -157,6 +336,19 @@ _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"): @@ -165,15 +357,34 @@ class Relay: @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"): @@ -182,13 +393,26 @@ class ADC: @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): - # Hold DIN high for 64 cycles - # However, asserting CS right after the 64 cycles seems to interrupt - # the start-up sequence. + """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 reaserting `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) @@ -198,6 +422,11 @@ class ADC: @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) @@ -206,6 +435,11 @@ class ADC: @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) @@ -214,6 +448,11 @@ class ADC: @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) @@ -222,44 +461,102 @@ class ADC: @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.write16(_AD4115_REG_ADCMODE, 0x8010) + 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 exit_power_down(self): + 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 @@ -267,6 +564,30 @@ class ADC: @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:`Volt`) + :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 diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index ca81ff2ed..5de40c63f 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -143,6 +143,12 @@ DAC/ADC drivers .. automodule:: artiq.coredevice.fastino :members: +:mod:`artiq.coredevice.shuttler` module +++++++++++++++++++++++++++++++++++++++++ + +.. automodule:: artiq.coredevice.shuttler + :members: + Miscellaneous ------------- From b52f253dbdc3bf80eb0aa6343f018c2527da683e Mon Sep 17 00:00:00 2001 From: occheung <54844539+occheung@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:02:49 -0700 Subject: [PATCH 30/50] 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 --- .../gateware/drtio/transceiver/eem_serdes.py | 104 +++++++++++------- 1 file changed, 65 insertions(+), 39 deletions(-) diff --git a/artiq/gateware/drtio/transceiver/eem_serdes.py b/artiq/gateware/drtio/transceiver/eem_serdes.py index 4092cb202..75b4f3389 100644 --- a/artiq/gateware/drtio/transceiver/eem_serdes.py +++ b/artiq/gateware/drtio/transceiver/eem_serdes.py @@ -1,4 +1,5 @@ from migen import * +from migen.genlib.resetsync import AsyncResetSynchronizer from misoc.interconnect.csr import * from misoc.cores.code_8b10b import SingleEncoder, Decoder from artiq.gateware.drtio.core import TransceiverInterface, ChannelInterface @@ -396,53 +397,78 @@ class SerdesSingle(Module): class OOBReset(Module): - def __init__(self, iserdes_o): - ce_counter = Signal(13) - activity_ce = Signal() - transition_ce = Signal() + def __init__(self, platform, iserdes_o): + self.clock_domains.cd_clk100 = ClockDomain() + self.specials += [ + Instance("BUFR", + i_I=ClockSignal("clk200"), + o_O=ClockSignal("clk100"), + p_BUFR_DIVIDE="2"), + AsyncResetSynchronizer(self.cd_clk100, ResetSignal("clk200")), + ] - self.sync.clk200 += Cat(ce_counter, activity_ce).eq(ce_counter + 1) - self.comb += transition_ce.eq(ce_counter[0]) - - idle_low_meta = Signal() - idle_high_meta = Signal() idle_low = Signal() idle_high = Signal() - idle = Signal() self.rst = Signal(reset=1) - # Detect the lack of transitions (idle) within 2 clk200 cycles - self.specials += [ - Instance("FDCE", p_INIT=1, i_D=1, i_CLR=iserdes_o, - i_CE=transition_ce, i_C=ClockSignal("clk200"), o_Q=idle_low_meta, - attr={"async_reg", "ars_ff1"}), - Instance("FDCE", p_INIT=1, i_D=idle_low_meta, i_CLR=0, - i_CE=transition_ce, i_C=ClockSignal("clk200"), o_Q=idle_low, - attr={"async_reg", "ars_ff2"}), + # Detect the lack of transitions (idle) within a clk100 cycle + for idle, source in [ + (idle_low, iserdes_o), (idle_high, ~iserdes_o)]: + idle_meta = Signal() + ff_pair = [ff1, ff2] = [ + Instance("FDCE", p_INIT=1, i_D=1, i_CLR=source, + i_CE=1, i_C=ClockSignal("clk100"), o_Q=idle_meta, + attr={"async_reg"}), + Instance("FDCE", p_INIT=1, i_D=idle_meta, i_CLR=0, + i_CE=1, i_C=ClockSignal("clk100"), o_Q=idle, + attr={"async_reg"}), + ] + self.specials += ff_pair - Instance("FDCE", p_INIT=1, i_D=1, i_CLR=~iserdes_o, - i_CE=transition_ce, i_C=ClockSignal("clk200"), o_Q=idle_high_meta, - attr={"async_reg", "ars_ff1"}), - Instance("FDCE", p_INIT=1, i_D=idle_high_meta, i_CLR=0, - i_CE=transition_ce, i_C=ClockSignal("clk200"), o_Q=idle_high, - attr={"async_reg", "ars_ff2"}), - ] + platform.add_platform_command( + "set_false_path -quiet -to {ff1}/CLR", ff1=ff1) + # Capture transition detected by FF1/Q in FF2/D + platform.add_platform_command( + "set_max_delay 2 -quiet " + "-from {ff1}/Q -to {ff2}/D", ff1=ff1, ff2=ff2) - # Detect activity for the last 2**13 clk200 cycles - # The 2**13 cycles are fully partitioned into 2**12 time segments of 2 - # cycles in duration. If there exists 2-cycle time segment without - # signal level transition, rst is asserted. - self.sync.clk200 += [ - If(activity_ce, - idle.eq(0), - self.rst.eq(idle), - ), + # Detect activity for the last 2**15 clk100 cycles + self.submodules.fsm = fsm = ClockDomainsRenamer("clk100")( + FSM(reset_state="WAIT_TRANSITION")) + counter = Signal(15, reset=0x7FFF) + + # Keep sysclk reset asserted until transition is detected for a + # continuous 2**15 clk100 cycles + fsm.act("WAIT_TRANSITION", + self.rst.eq(1), If(idle_low | idle_high, - idle.eq(1), - self.rst.eq(1), - ), - ] + NextValue(counter, 0x7FFF), + ).Else( + If(counter == 0, + NextState("WAIT_NO_TRANSITION"), + NextValue(counter, 0x7FFF), + ).Else( + NextValue(counter, counter - 1), + ) + ) + ) + + # Reassert sysclk reset if there are no transition for the last 2**15 + # clk100 cycles. + fsm.act("WAIT_NO_TRANSITION", + self.rst.eq(0), + If(idle_low | idle_high, + If(counter == 0, + NextState("WAIT_TRANSITION"), + NextValue(counter, 0x7FFF), + ).Else( + NextValue(counter, counter - 1), + ) + ).Else( + NextValue(counter, 0x7FFF), + ) + ) class EEMSerdes(Module, TransceiverInterface, AutoCSR): @@ -526,7 +552,7 @@ class EEMSerdes(Module, TransceiverInterface, AutoCSR): self.submodules += serdes_list - self.submodules.oob_reset = OOBReset(serdes_list[0].rx_serdes.o[0]) + self.submodules.oob_reset = OOBReset(platform, serdes_list[0].rx_serdes.o[0]) self.rst = self.oob_reset.rst self.rst.attr.add("no_retiming") From b7b8f0efa2506c00fb12391ea454f3f1451b7e80 Mon Sep 17 00:00:00 2001 From: occheung <54844539+occheung@users.noreply.github.com> Date: Mon, 25 Sep 2023 18:44:21 -0700 Subject: [PATCH 31/50] 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 --- .../coredevice/coredevice_generic.schema.json | 4 + artiq/frontend/artiq_ddb_template.py | 104 +++++++++++++++++- 2 files changed, 103 insertions(+), 5 deletions(-) diff --git a/artiq/coredevice/coredevice_generic.schema.json b/artiq/coredevice/coredevice_generic.schema.json index 9e1fb885b..925a37b49 100644 --- a/artiq/coredevice/coredevice_generic.schema.json +++ b/artiq/coredevice/coredevice_generic.schema.json @@ -628,6 +628,10 @@ }, "minItems": 1, "maxItems": 2 + }, + "drtio_destination": { + "type": "integer", + "default": 4 } }, "required": ["ports"] diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index 15dcbf642..a7398a37a 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -4,7 +4,7 @@ import argparse import sys import textwrap from collections import defaultdict -from itertools import count +from itertools import count, filterfalse from artiq import __version__ as artiq_version from artiq.coredevice import jsondesc @@ -621,12 +621,90 @@ class PeripheralManager: channel=rtio_offset+i) return 8 + def process_efc(self, efc_peripheral): + efc_name = self.get_name("efc") + rtio_offset = efc_peripheral["drtio_destination"] << 16 + rtio_offset += self.add_board_leds(rtio_offset, board_name=efc_name) + + shuttler_name = self.get_name("shuttler") + channel = count(0) + self.gen(""" + device_db["{name}_config"] = {{ + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Config", + "arguments": {{"channel": 0x{channel:06x}}}, + }}""", + name=shuttler_name, + channel=rtio_offset + next(channel)) + self.gen(""" + device_db["{name}_trigger"] = {{ + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Trigger", + "arguments": {{"channel": 0x{channel:06x}}}, + }}""", + name=shuttler_name, + channel=rtio_offset + next(channel)) + for i in range(16): + self.gen(""" + device_db["{name}_volt{ch}"] = {{ + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Volt", + "arguments": {{"channel": 0x{channel:06x}}}, + }}""", + name=shuttler_name, + ch=i, + channel=rtio_offset + next(channel)) + self.gen(""" + device_db["{name}_dds{ch}"] = {{ + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "Dds", + "arguments": {{"channel": 0x{channel:06x}}}, + }}""", + name=shuttler_name, + ch=i, + channel=rtio_offset + next(channel)) + + device_class_names = ["Relay", "ADC"] + for i, device_name in enumerate(device_class_names): + spi_name = "{name}_spi{ch}".format( + name=shuttler_name, + ch=i) + self.gen(""" + device_db["{spi}"] = {{ + "type": "local", + "module": "artiq.coredevice.spi2", + "class": "SPIMaster", + "arguments": {{"channel": 0x{channel:06x}}}, + }}""", + spi=spi_name, + channel=rtio_offset + next(channel)) + self.gen(""" + device_db["{name}_{device}"] = {{ + "type": "local", + "module": "artiq.coredevice.shuttler", + "class": "{dev_class}", + "arguments": {{"spi_device": "{spi}"}}, + }}""", + name=shuttler_name, + device=device_name.lower(), + dev_class=device_name, + spi=spi_name) + return 0 + def process(self, rtio_offset, peripheral): processor = getattr(self, "process_"+str(peripheral["type"])) return processor(rtio_offset, peripheral) - def add_board_leds(self, rtio_offset): + def add_board_leds(self, rtio_offset, board_name=None): for i in range(2): + if board_name is None: + led_name = self.get_name("led") + else: + led_name = self.get_name("{}_led".format(board_name)) self.gen(""" device_db["{name}"] = {{ "type": "local", @@ -634,11 +712,18 @@ class PeripheralManager: "class": "TTLOut", "arguments": {{"channel": 0x{channel:06x}}} }}""", - name=self.get_name("led"), + name=led_name, channel=rtio_offset+i) return 2 +def split_drtio_eem(peripherals): + # EFC is the only peripheral that uses DRTIO-over-EEM at this moment + drtio_eem_filter = lambda peripheral: peripheral["type"] == "efc" + return filterfalse(drtio_eem_filter, peripherals), \ + list(filter(drtio_eem_filter, peripherals)) + + def process(output, primary_description, satellites): drtio_role = primary_description["drtio_role"] if drtio_role not in ("standalone", "master"): @@ -651,9 +736,11 @@ def process(output, primary_description, satellites): pm = PeripheralManager(output, primary_description) + local_peripherals, drtio_peripherals = split_drtio_eem(primary_description["peripherals"]) + print("# {} peripherals".format(drtio_role), file=output) rtio_offset = 0 - for peripheral in primary_description["peripherals"]: + for peripheral in local_peripherals: n_channels = pm.process(rtio_offset, peripheral) rtio_offset += n_channels if drtio_role == "standalone": @@ -663,12 +750,19 @@ def process(output, primary_description, satellites): for destination, description in satellites: if description["drtio_role"] != "satellite": raise ValueError("Invalid DRTIO role for satellite at destination {}".format(destination)) + peripherals, satellite_drtio_peripherals = split_drtio_eem(description["peripherals"]) + drtio_peripherals.extend(satellite_drtio_peripherals) print("# DEST#{} peripherals".format(destination), file=output) rtio_offset = destination << 16 - for peripheral in description["peripherals"]: + for peripheral in peripherals: n_channels = pm.process(rtio_offset, peripheral) rtio_offset += n_channels + + for peripheral in drtio_peripherals: + print("# DEST#{} peripherals".format(peripheral["drtio_destination"]), file=output) + processor = getattr(pm, "process_"+str(peripheral["type"])) + processor(peripheral) def main(): From a61bbf561872cd723e2915653a7f31358542ffb0 Mon Sep 17 00:00:00 2001 From: linuswck Date: Tue, 26 Sep 2023 11:07:40 +0800 Subject: [PATCH 32/50] Shuttler: Replace ddb with json for the example --- artiq/examples/kasli_shuttler/device_db.py | 330 ------------------- artiq/examples/kasli_shuttler/kasli_efc.json | 18 + 2 files changed, 18 insertions(+), 330 deletions(-) delete mode 100644 artiq/examples/kasli_shuttler/device_db.py create mode 100644 artiq/examples/kasli_shuttler/kasli_efc.json diff --git a/artiq/examples/kasli_shuttler/device_db.py b/artiq/examples/kasli_shuttler/device_db.py deleted file mode 100644 index b87f8dc63..000000000 --- a/artiq/examples/kasli_shuttler/device_db.py +++ /dev/null @@ -1,330 +0,0 @@ -core_addr = "192.168.1.73" - -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {"host": core_addr, "ref_period": 1e-09, "target": "rv32g"}, - }, - "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" - }, - - "i2c_switch0": { - "type": "local", - "module": "artiq.coredevice.i2c", - "class": "I2CSwitch", - "arguments": {"address": 0xe0} - }, - "i2c_switch1": { - "type": "local", - "module": "artiq.coredevice.i2c", - "class": "I2CSwitch", - "arguments": {"address": 0xe2} - }, -} - -device_db["efc_led0"] = { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 0x040000}, -} - -device_db["efc_led1"] = { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 0x040001}, -} - -device_db["pdq_config"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Config", - "arguments": {"channel": 0x040002}, -} - -device_db["pdq_trigger"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Trigger", - "arguments": {"channel": 0x040003}, -} - -device_db["pdq0_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x040004}, -} - -device_db["pdq0_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x040005}, -} - -device_db["pdq1_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x040006}, -} - -device_db["pdq1_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x040007}, -} - -device_db["pdq2_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x040008}, -} - -device_db["pdq2_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x040009}, -} - -device_db["pdq3_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x04000A}, -} - -device_db["pdq3_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x04000B}, -} - -device_db["pdq4_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x04000C}, -} - -device_db["pdq4_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x04000D}, -} - -device_db["pdq5_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x04000E}, -} - -device_db["pdq5_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x04000F}, -} - -device_db["pdq6_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x040010}, -} - -device_db["pdq6_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x040011}, -} - -device_db["pdq7_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x040012}, -} - -device_db["pdq7_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x040013}, -} - -device_db["pdq8_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x040014}, -} - -device_db["pdq8_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x040015}, -} - -device_db["pdq9_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x040016}, -} - -device_db["pdq9_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x040017}, -} - -device_db["pdq10_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x040018}, -} - -device_db["pdq10_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x040019}, -} - -device_db["pdq11_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x04001A}, -} - -device_db["pdq11_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x04001B}, -} - -device_db["pdq12_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x04001C}, -} - -device_db["pdq12_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x04001D}, -} - -device_db["pdq13_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x04001E}, -} - -device_db["pdq13_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x04001F}, -} - -device_db["pdq14_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x040020}, -} - -device_db["pdq14_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x040021}, -} - -device_db["pdq15_volt"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Volt", - "arguments": {"channel": 0x040022}, -} - -device_db["pdq15_dds"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Dds", - "arguments": {"channel": 0x040023}, -} - -device_db["spi_afe_relay"] = { - "type": "local", - "module": "artiq.coredevice.spi2", - "class": "SPIMaster", - "arguments": {"channel": 0x040024} -} - -device_db["afe_relay"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "Relay", - "arguments": { - "spi_device": "spi_afe_relay", - } -} - -device_db["spi_afe_adc"] = { - "type": "local", - "module": "artiq.coredevice.spi2", - "class": "SPIMaster", - "arguments": {"channel": 0x040025} -} - -device_db["afe_adc"] = { - "type": "local", - "module": "artiq.coredevice.shuttler", - "class": "ADC", - "arguments": { - "spi_device": "spi_afe_adc", - } -} diff --git a/artiq/examples/kasli_shuttler/kasli_efc.json b/artiq/examples/kasli_shuttler/kasli_efc.json new file mode 100644 index 000000000..0a8f77a2a --- /dev/null +++ b/artiq/examples/kasli_shuttler/kasli_efc.json @@ -0,0 +1,18 @@ +{ + "target": "kasli", + "variant": "master", + "hw_rev": "v2.0", + "base": "master", + "peripherals": [ + { + "type": "efc", + "ports": [0] + }, + { + "type": "dio", + "ports": [1], + "bank_direction_low": "input", + "bank_direction_high": "output" + } + ] +} From eedac7cf71913c6e79574dd2ce5821ee2ac4ff9a Mon Sep 17 00:00:00 2001 From: linuswck Date: Tue, 26 Sep 2023 11:08:16 +0800 Subject: [PATCH 33/50] Shuttler: Patch ddb entries in the example code --- .../kasli_shuttler/repository/shuttler.py | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/artiq/examples/kasli_shuttler/repository/shuttler.py b/artiq/examples/kasli_shuttler/repository/shuttler.py index 70e01d4f9..18c02a879 100644 --- a/artiq/examples/kasli_shuttler/repository/shuttler.py +++ b/artiq/examples/kasli_shuttler/repository/shuttler.py @@ -57,13 +57,13 @@ class Shuttler(EnvExperiment): self.setattr_device("core") self.setattr_device("core_dma") self.setattr_device("scheduler") - self.leds = [ self.get_device("efc_led{}".format(i)) for i in range(2) ] - self.setattr_device("pdq_config") - self.setattr_device("pdq_trigger") - self.pdq_volt = [ self.get_device("pdq{}_volt".format(i)) for i in range(16) ] - self.pdq_dds = [ self.get_device("pdq{}_dds".format(i)) for i in range(16) ] - self.setattr_device("afe_relay") - self.setattr_device("afe_adc") + self.efc0_leds = [ self.get_device("efc0_led{}".format(i)) for i in range(2) ] + self.setattr_device("shuttler0_config") + self.setattr_device("shuttler0_trigger") + self.shuttler0_volt = [ self.get_device("shuttler0_volt{}".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 @@ -102,13 +102,13 @@ class Shuttler(EnvExperiment): @kernel def pdq_channel_reset(self, ch): - self.pdq_volt[ch].set_waveform( + self.shuttler0_volt[ch].set_waveform( a0=0, a1=0, a2=0, a3=0, ) - self.pdq_dds[ch].set_waveform( + self.shuttler0_dds[ch].set_waveform( b0=0, b1=0, b2=0, @@ -117,7 +117,7 @@ class Shuttler(EnvExperiment): c1=0, c2=0, ) - self.pdq_trigger.trigger(1 << ch) + self.shuttler0_trigger.trigger(1 << ch) @kernel def example_waveform(self): @@ -158,14 +158,14 @@ class Shuttler(EnvExperiment): # Reset OUT0 and OUT1 ## Step 1 ## - self.afe_relay.enable(0b11) + 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.pdq_dds[0].set_waveform( + self.shuttler0_dds[0].set_waveform( b0=pdq_dds_amp_mu(1.0), b1=0, b2=0, @@ -174,7 +174,7 @@ class Shuttler(EnvExperiment): c1=pdq_freq_mu(start_f_MHz), c2=pdq_freq_sweep(start_f_MHz, end_f_MHz, duration_us), ) - self.pdq_dds[1].set_waveform( + self.shuttler0_dds[1].set_waveform( b0=pdq_dds_amp_mu(1.0), b1=0, b2=0, @@ -183,12 +183,12 @@ class Shuttler(EnvExperiment): c1=pdq_freq_mu(end_f_MHz), c2=0, ) - self.pdq_trigger.trigger(0b11) + self.shuttler0_trigger.trigger(0b11) delay(500*us) ## Step 3 ## # OUT0 and OUT1 has 180 degree phase difference - self.pdq_dds[0].set_waveform( + self.shuttler0_dds[0].set_waveform( b0=pdq_dds_amp_mu(1.0), b1=0, b2=0, @@ -199,12 +199,12 @@ class Shuttler(EnvExperiment): ) # Phase and Output Setting of OUT1 is retained # if the channel is not triggered or config is not cleared - self.pdq_trigger.trigger(0b1) + self.shuttler0_trigger.trigger(0b1) delay(500*us) ## Step 4 ## # b(0) = 0, b(250) = 8.545, b(500) = 0 - self.pdq_dds[0].set_waveform( + self.shuttler0_dds[0].set_waveform( b0=0, b1=pdq_dds_damp_mu(0.06835937), b2=pdq_dds_ddamp_mu(-0.0001367187), @@ -213,7 +213,7 @@ class Shuttler(EnvExperiment): c1=pdq_freq_mu(end_f_MHz), c2=0, ) - self.pdq_dds[1].set_waveform( + self.shuttler0_dds[1].set_waveform( b0=0, b1=pdq_dds_damp_mu(0.06835937), b2=pdq_dds_ddamp_mu(-0.0001367187), @@ -222,17 +222,17 @@ class Shuttler(EnvExperiment): c1=0, c2=0, ) - self.pdq_trigger.trigger(0b11) + self.shuttler0_trigger.trigger(0b11) delay(500*us) ## Step 5 ## - self.pdq_volt[0].set_waveform( + self.shuttler0_volt[0].set_waveform( a0=pdq_volt_amp_mu(-5.0), a1=int32(pdq_volt_damp_mu(0.01)), a2=0, a3=0, ) - self.pdq_dds[0].set_waveform( + self.shuttler0_dds[0].set_waveform( b0=pdq_dds_amp_mu(1.0), b1=0, b2=0, @@ -241,13 +241,13 @@ class Shuttler(EnvExperiment): c1=pdq_freq_mu(end_f_MHz), c2=0, ) - self.pdq_volt[1].set_waveform( + self.shuttler0_volt[1].set_waveform( a0=pdq_volt_amp_mu(-5.0), a1=int32(pdq_volt_damp_mu(0.01)), a2=0, a3=0, ) - self.pdq_dds[1].set_waveform( + self.shuttler0_dds[1].set_waveform( b0=0, b1=0, b2=0, @@ -256,17 +256,17 @@ class Shuttler(EnvExperiment): c1=0, c2=0, ) - self.pdq_trigger.trigger(0b11) + self.shuttler0_trigger.trigger(0b11) delay(1000*us) ## Step 6 ## - self.pdq_volt[0].set_waveform( + self.shuttler0_volt[0].set_waveform( a0=pdq_volt_amp_mu(-2.5), a1=int32(pdq_volt_damp_mu(0.01)), a2=0, a3=0, ) - self.pdq_dds[0].set_waveform( + self.shuttler0_dds[0].set_waveform( b0=0, b1=pdq_dds_damp_mu(0.06835937), b2=pdq_dds_ddamp_mu(-0.0001367187), @@ -275,18 +275,18 @@ class Shuttler(EnvExperiment): c1=pdq_freq_mu(start_f_MHz), c2=pdq_freq_sweep(start_f_MHz, end_f_MHz, duration_us), ) - self.pdq_trigger.trigger(0b1) + self.shuttler0_trigger.trigger(0b1) self.pdq_channel_reset(1) delay(500*us) ## Step 7 ## - self.pdq_volt[0].set_waveform( + self.shuttler0_volt[0].set_waveform( a0=pdq_volt_amp_mu(2.5), a1=int32(pdq_volt_damp_mu(-0.01)), a2=0, a3=0, ) - self.pdq_dds[0].set_waveform( + self.shuttler0_dds[0].set_waveform( b0=0, b1=pdq_dds_damp_mu(-0.06835937), b2=pdq_dds_ddamp_mu(0.0001367187), @@ -295,11 +295,11 @@ class Shuttler(EnvExperiment): c1=pdq_freq_mu(end_f_MHz), c2=pdq_freq_sweep(end_f_MHz, start_f_MHz, duration_us), ) - self.pdq_trigger.trigger(0b1) + self.shuttler0_trigger.trigger(0b1) delay(500*us) ## Step 8 ## - self.afe_relay.enable(0) + self.shuttler0_relay.enable(0) self.pdq_channel_reset(0) self.pdq_channel_reset(1) @@ -307,24 +307,24 @@ class Shuttler(EnvExperiment): def led(self): for i in range(2): for j in range(3): - self.leds[i].pulse(.1*s) + self.efc0_leds[i].pulse(.1*s) delay(.1*s) @kernel def relay_init(self): - self.afe_relay.init() - self.afe_relay.enable(0x0000) + self.shuttler0_relay.init() + self.shuttler0_relay.enable(0x0000) @kernel def adc_init(self): delay_mu(int64(self.core.ref_multiplier)) - self.afe_adc.power_up() + self.shuttler0_adc.power_up() delay_mu(int64(self.core.ref_multiplier)) - assert self.afe_adc.read_id() >> 4 == 0x038d + 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.afe_adc.calibrate(self.pdq_volt, self.pdq_trigger, self.pdq_config) + self.shuttler0_adc.calibrate(self.shuttler0_volt, self.shuttler0_trigger, self.shuttler0_config) From 892b0eaca21d13bc6fc3d60775eed567f5edd047 Mon Sep 17 00:00:00 2001 From: David Nadlinger Date: Tue, 26 Sep 2023 23:31:21 +0100 Subject: [PATCH 34/50] compiler: Fix crash on multiple types with the same name The original fix in 21574bdfa91223f2aedf6f7d3a568f450f750516 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. --- artiq/compiler/embedding.py | 46 ++++++++++++------- artiq/test/lit/embedding/class_same_name.py | 51 +++++++++++++++++++++ 2 files changed, 81 insertions(+), 16 deletions(-) create mode 100644 artiq/test/lit/embedding/class_same_name.py diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index f695029cf..d1b276766 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -52,7 +52,14 @@ class EmbeddingMap: self.object_forward_map = {} self.object_reverse_map = {} self.module_map = {} + + # type_map connects the host Python `type` to the pair of associated + # `(TInstance, TConstructor)`s. The `used_…_names` sets cache the + # respective `.name`s for O(1) collision avoidance. self.type_map = {} + self.used_instance_type_names = set() + self.used_constructor_type_names = set() + self.function_map = {} self.str_forward_map = {} self.str_reverse_map = {} @@ -98,16 +105,6 @@ class EmbeddingMap: # Types def store_type(self, host_type, instance_type, constructor_type): - self._rename_type(instance_type) - self.type_map[host_type] = (instance_type, constructor_type) - - def retrieve_type(self, host_type): - return self.type_map[host_type] - - def has_type(self, host_type): - return host_type in self.type_map - - def _rename_type(self, new_instance_type): # Generally, user-defined types that have exact same name (which is to say, classes # defined inside functions) do not pose a problem to the compiler. The two places which # cannot handle this are: @@ -116,12 +113,29 @@ class EmbeddingMap: # Since handling #2 requires renaming on ARTIQ side anyway, it's more straightforward # to do it once when embedding (since non-embedded code cannot define classes in # functions). Also, easier to debug. - n = 0 - for host_type in self.type_map: - instance_type, constructor_type = self.type_map[host_type] - if instance_type.name == new_instance_type.name: - n += 1 - new_instance_type.name = "{}.{}".format(new_instance_type.name, n) + suffix = 0 + new_instance_name = instance_type.name + new_constructor_name = constructor_type.name + while True: + if (new_instance_name not in self.used_instance_type_names + and new_constructor_name not in self.used_constructor_type_names): + break + suffix += 1 + new_instance_name = f"{instance_type.name}.{suffix}" + new_constructor_name = f"{constructor_type.name}.{suffix}" + + self.used_instance_type_names.add(new_instance_name) + instance_type.name = new_instance_name + self.used_constructor_type_names.add(new_constructor_name) + constructor_type.name = new_constructor_name + + self.type_map[host_type] = (instance_type, constructor_type) + + def retrieve_type(self, host_type): + return self.type_map[host_type] + + def has_type(self, host_type): + return host_type in self.type_map def attribute_count(self): count = 0 diff --git a/artiq/test/lit/embedding/class_same_name.py b/artiq/test/lit/embedding/class_same_name.py new file mode 100644 index 000000000..46cf7c16d --- /dev/null +++ b/artiq/test/lit/embedding/class_same_name.py @@ -0,0 +1,51 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s + +from artiq.language.core import * + + +class InnerA: + def __init__(self, val): + self.val = val + + @kernel + def run_once(self): + return self.val + + +class InnerB: + def __init__(self, val): + self.val = val + + @kernel + def run_once(self): + return self.val + + +def make_runner(InnerCls, val): + class Runner: + def __init__(self): + self.inner = InnerCls(val) + + @kernel + def run_once(self): + return self.inner.run_once() + + return Runner() + + +class Parent: + def __init__(self): + self.a = make_runner(InnerA, 1) + self.b = make_runner(InnerB, 42.0) + + @kernel + def run_once(self): + return self.a.run_once() + self.b.run_once() + + +parent = Parent() + + +@kernel +def entrypoint(): + parent.run_once() From 586d97c6cb25832b40891ca3cdb252d9d8658502 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Mon, 21 Aug 2023 11:02:08 +0100 Subject: [PATCH 35/50] 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 --- artiq/compiler/embedding.py | 2 +- artiq/test/lit/embedding/mixed_tuple.py | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 artiq/test/lit/embedding/mixed_tuple.py diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index d1b276766..502b364b9 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -564,7 +564,7 @@ class StitchingASTTypedRewriter(ASTTypedRewriter): node = asttyped.QuotedFunctionDefT( typing_env=extractor.typing_env, globals_in_scope=extractor.global_, signature_type=types.TVar(), return_type=types.TVar(), - name=node.name, args=node.args, returns=node.returns, + name=node.name, args=node.args, returns=None, body=node.body, decorator_list=node.decorator_list, keyword_loc=node.keyword_loc, name_loc=node.name_loc, arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs, diff --git a/artiq/test/lit/embedding/mixed_tuple.py b/artiq/test/lit/embedding/mixed_tuple.py new file mode 100644 index 000000000..aaf947149 --- /dev/null +++ b/artiq/test/lit/embedding/mixed_tuple.py @@ -0,0 +1,16 @@ +# RUN: %python -m artiq.compiler.testbench.embedding %s + +from artiq.language.core import * +from artiq.language.types import * + +@kernel +def consume_tuple(x: TTuple([TInt32, TBool])): + print(x) + +@kernel +def return_tuple() -> TTuple([TInt32, TBool]): + return (123, False) + +@kernel +def entrypoint(): + consume_tuple(return_tuple()) From 6eb81494c52255ee7586e659ae07d8d6f2fb1982 Mon Sep 17 00:00:00 2001 From: Jonathan Coates Date: Fri, 22 Sep 2023 11:01:40 +0100 Subject: [PATCH 36/50] 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 --- RELEASE_NOTES.rst | 2 + artiq/compiler/embedding.py | 87 ++++++++++++++++--- artiq/test/lit/embedding/annotation_py.py | 34 ++++++++ .../lit/embedding/error_specialized_annot.py | 6 +- 4 files changed, 112 insertions(+), 17 deletions(-) create mode 100644 artiq/test/lit/embedding/annotation_py.py diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 98f111c2d..ea94f5651 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -25,6 +25,8 @@ Highlights: support legacy installations, but may be removed in a future release. * Added channel names to RTIO errors. * Full Python 3.10 support. +* Python's built-in types (such as `float`, or `List[...]`) can now be used in type annotations on + kernel functions. * Distributed DMA is now supported, allowing DMA to be run directly on satellites for corresponding RTIO events, increasing bandwidth in scenarios with heavy satellite usage. * API extensions have been implemented, enabling applets to directly modify datasets. diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 502b364b9..040fc80ee 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -5,6 +5,7 @@ the references to the host objects and translates the functions annotated as ``@kernel`` when they are referenced. """ +import typing import os, re, linecache, inspect, textwrap, types as pytypes, numpy from collections import OrderedDict, defaultdict @@ -1071,9 +1072,6 @@ class Stitcher: return function_node def _extract_annot(self, function, annot, kind, call_loc, fn_kind): - if annot is None: - annot = builtins.TNone() - if isinstance(function, SpecializedFunction): host_function = function.host_function else: @@ -1087,9 +1085,20 @@ class Stitcher: if isinstance(embedded_function, str): embedded_function = host_function + return self._to_artiq_type( + annot, + function=function, + kind=kind, + eval_in_scope=lambda x: eval(x, embedded_function.__globals__), + call_loc=call_loc, + fn_kind=fn_kind) + + def _to_artiq_type( + self, annot, *, function, kind: str, eval_in_scope, call_loc: str, fn_kind: str + ) -> types.Type: if isinstance(annot, str): try: - annot = eval(annot, embedded_function.__globals__) + annot = eval_in_scope(annot) except Exception: diag = diagnostic.Diagnostic( "error", @@ -1099,18 +1108,68 @@ class Stitcher: notes=self._call_site_note(call_loc, fn_kind)) self.engine.process(diag) - if not isinstance(annot, types.Type): - diag = diagnostic.Diagnostic("error", - "type annotation for {kind}, '{annot}', is not an ARTIQ type", - {"kind": kind, "annot": repr(annot)}, - self._function_loc(function), - notes=self._call_site_note(call_loc, fn_kind)) - self.engine.process(diag) - - return types.TVar() - else: + if isinstance(annot, types.Type): return annot + # Convert built-in Python types to ARTIQ ones. + if annot is None: + return builtins.TNone() + elif annot is numpy.int64: + return builtins.TInt64() + elif annot is numpy.int32: + return builtins.TInt32() + elif annot is float: + return builtins.TFloat() + elif annot is bool: + return builtins.TBool() + elif annot is str: + return builtins.TStr() + elif annot is bytes: + return builtins.TBytes() + elif annot is bytearray: + return builtins.TByteArray() + + # Convert generic Python types to ARTIQ ones. + generic_ty = typing.get_origin(annot) + if generic_ty is not None: + type_args = typing.get_args(annot) + artiq_args = [ + self._to_artiq_type( + x, + function=function, + kind=kind, + eval_in_scope=eval_in_scope, + call_loc=call_loc, + fn_kind=fn_kind) + for x in type_args + ] + + if generic_ty is list and len(artiq_args) == 1: + return builtins.TList(artiq_args[0]) + elif generic_ty is tuple: + return types.TTuple(artiq_args) + + # Otherwise report an unknown type and just use a fresh tyvar. + + if annot is int: + message = ( + "type annotation for {kind}, 'int' cannot be used as an ARTIQ type. " + "Use numpy's int32 or int64 instead." + ) + ty = builtins.TInt() + else: + message = "type annotation for {kind}, '{annot}', is not an ARTIQ type" + ty = types.TVar() + + diag = diagnostic.Diagnostic("error", + message, + {"kind": kind, "annot": repr(annot)}, + self._function_loc(function), + notes=self._call_site_note(call_loc, fn_kind)) + self.engine.process(diag) + + return ty + def _quote_syscall(self, function, loc): signature = inspect.signature(function) diff --git a/artiq/test/lit/embedding/annotation_py.py b/artiq/test/lit/embedding/annotation_py.py new file mode 100644 index 000000000..c790b6914 --- /dev/null +++ b/artiq/test/lit/embedding/annotation_py.py @@ -0,0 +1,34 @@ +# RUN: env ARTIQ_DUMP_LLVM=%t %python -m artiq.compiler.testbench.embedding +compile %s +# RUN: OutputCheck %s --file-to-check=%t.ll + +from typing import List, Tuple + +import numpy as np + +from artiq.language.core import * +from artiq.language.types import * + +# CHECK-L: i64 @_Z13testbench.foozz(i64 %ARG.x, { i1, i32 } %ARG.y) + +@kernel +def foo(x: np.int64, y: np.int32 = 1) -> np.int64: + print(x + y) + return x + y + +# CHECK-L: void @_Z13testbench.barzz() +@kernel +def bar(x: np.int32) -> None: + print(x) + +# CHECK-L: @_Z21testbench.unpack_listzz({ i1, i64 }* nocapture writeonly sret({ i1, i64 }) %.1, { i64*, i32 }* %ARG.xs) +@kernel +def unpack_list(xs: List[np.int64]) -> Tuple[bool, np.int64]: + print(xs) + return (len(xs) == 1, xs[0]) + +@kernel +def entrypoint(): + print(foo(0, 2)) + print(foo(1, 3)) + bar(3) + print(unpack_list([1, 2, 3])) diff --git a/artiq/test/lit/embedding/error_specialized_annot.py b/artiq/test/lit/embedding/error_specialized_annot.py index 2f5955043..5d901d0d4 100644 --- a/artiq/test/lit/embedding/error_specialized_annot.py +++ b/artiq/test/lit/embedding/error_specialized_annot.py @@ -4,14 +4,14 @@ from artiq.experiment import * class c(): -# CHECK-L: ${LINE:+2}: error: type annotation for argument 'x', '', is not an ARTIQ type +# CHECK-L: ${LINE:+2}: error: type annotation for argument 'x', '', is not an ARTIQ type @kernel - def hello(self, x: float): + def hello(self, x: list): pass @kernel def run(self): - self.hello(2) + self.hello([]) i = c() @kernel From ab0d4c41c3caf4f7fc579a21c31037755ebc1a7c Mon Sep 17 00:00:00 2001 From: linuswck Date: Tue, 26 Sep 2023 15:09:29 +0800 Subject: [PATCH 37/50] Shuttler: pdq, efc->shuttler pdq_words->coef_words --- .../coredevice/coredevice_generic.schema.json | 6 +- artiq/coredevice/shuttler.py | 12 +- artiq/examples/kasli_shuttler/kasli_efc.json | 2 +- .../kasli_shuttler/repository/shuttler.py | 116 +++++++++--------- artiq/frontend/artiq_ddb_template.py | 13 +- artiq/gateware/eem.py | 8 +- artiq/gateware/eem_7series.py | 6 +- artiq/gateware/shuttler.py | 2 + artiq/gateware/targets/kasli_generic.py | 8 +- 9 files changed, 87 insertions(+), 86 deletions(-) diff --git a/artiq/coredevice/coredevice_generic.schema.json b/artiq/coredevice/coredevice_generic.schema.json index 925a37b49..f12f1d9b0 100644 --- a/artiq/coredevice/coredevice_generic.schema.json +++ b/artiq/coredevice/coredevice_generic.schema.json @@ -142,7 +142,7 @@ "properties": { "type": { "type": "string", - "enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "efc"] + "enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"] }, "board": { "type": "string" @@ -611,11 +611,11 @@ "required": ["ports"] } },{ - "title": "EFC", + "title": "Shuttler", "if": { "properties": { "type": { - "const": "efc" + "const": "shuttler" } } }, diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 722b95f2a..07c416dd3 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -164,7 +164,7 @@ class Volt: :param a2: The :math:`a_2` coefficient in machine unit. :param a3: The :math:`a_3` coefficient in machine unit. """ - pdq_words = [ + coef_words = [ a0, a1, a1 >> 16, @@ -176,8 +176,8 @@ class Volt: (a3 >> 32) & 0xFFFF, ] - for i in range(len(pdq_words)): - rtio_output(self.target_o | i, pdq_words[i]) + for i in range(len(coef_words)): + rtio_output(self.target_o | i, coef_words[i]) delay_mu(int64(self.core.ref_multiplier)) @@ -254,7 +254,7 @@ class Dds: :param c1: The :math:`c_1` coefficient in machine unit. :param c2: The :math:`c_2` coefficient in machine unit. """ - pdq_words = [ + coef_words = [ b0, b1, b1 >> 16, @@ -271,8 +271,8 @@ class Dds: c2 >> 16, ] - for i in range(len(pdq_words)): - rtio_output(self.target_o | i, pdq_words[i]) + for i in range(len(coef_words)): + rtio_output(self.target_o | i, coef_words[i]) delay_mu(int64(self.core.ref_multiplier)) diff --git a/artiq/examples/kasli_shuttler/kasli_efc.json b/artiq/examples/kasli_shuttler/kasli_efc.json index 0a8f77a2a..aece1ee72 100644 --- a/artiq/examples/kasli_shuttler/kasli_efc.json +++ b/artiq/examples/kasli_shuttler/kasli_efc.json @@ -5,7 +5,7 @@ "base": "master", "peripherals": [ { - "type": "efc", + "type": "shuttler", "ports": [0] }, { diff --git a/artiq/examples/kasli_shuttler/repository/shuttler.py b/artiq/examples/kasli_shuttler/repository/shuttler.py index 18c02a879..85db3b9bd 100644 --- a/artiq/examples/kasli_shuttler/repository/shuttler.py +++ b/artiq/examples/kasli_shuttler/repository/shuttler.py @@ -5,59 +5,59 @@ DAC_Fs_MHZ = 125 CORDIC_GAIN = 1.64676 @portable -def pdq_phase_offset(offset_degree): +def shuttler_phase_offset(offset_degree): return round(offset_degree / 360 * (2 ** 16)) @portable -def pdq_freq_mu(freq_mhz): +def shuttler_freq_mu(freq_mhz): return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz) @portable -def pdq_chirp_rate_mu(freq_mhz_per_us): +def shuttler_chirp_rate_mu(freq_mhz_per_us): return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2)) @portable -def pdq_freq_sweep(start_f_MHz, end_f_MHz, time_us): - return pdq_chirp_rate_mu((end_f_MHz - start_f_MHz)/(time_us)) +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 pdq_volt_amp_mu(volt): +def shuttler_volt_amp_mu(volt): return shuttler_volt_to_mu(volt) @portable -def pdq_volt_damp_mu(volt_per_us): +def shuttler_volt_damp_mu(volt_per_us): return round(float(2) ** 30 * (volt_per_us / 20) / DAC_Fs_MHZ) @portable -def pdq_volt_ddamp_mu(volt_per_us_square): +def shuttler_volt_ddamp_mu(volt_per_us_square): return round(float(2) ** 46 * (volt_per_us_square / 20) * 2 / (DAC_Fs_MHZ ** 2)) @portable -def pdq_volt_dddamp_mu(volt_per_us_cube): +def shuttler_volt_dddamp_mu(volt_per_us_cube): return round(float(2) ** 46 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3)) @portable -def pdq_dds_amp_mu(volt): - return pdq_volt_amp_mu(volt / CORDIC_GAIN) +def shuttler_dds_amp_mu(volt): + return shuttler_volt_amp_mu(volt / CORDIC_GAIN) @portable -def pdq_dds_damp_mu(volt_per_us): - return pdq_volt_damp_mu(volt_per_us / CORDIC_GAIN) +def shuttler_dds_damp_mu(volt_per_us): + return shuttler_volt_damp_mu(volt_per_us / CORDIC_GAIN) @portable -def pdq_dds_ddamp_mu(volt_per_us_square): - return pdq_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN) +def shuttler_dds_ddamp_mu(volt_per_us_square): + return shuttler_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN) @portable -def pdq_dds_dddamp_mu(volt_per_us_cube): - return pdq_volt_dddamp_mu(volt_per_us_cube / CORDIC_GAIN) +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.efc0_leds = [ self.get_device("efc0_led{}".format(i)) for i in range(2) ] + self.shuttler0_leds = [ self.get_device("shuttler0_led{}".format(i)) for i in range(2) ] self.setattr_device("shuttler0_config") self.setattr_device("shuttler0_trigger") self.shuttler0_volt = [ self.get_device("shuttler0_volt{}".format(i)) for i in range(16) ] @@ -76,7 +76,7 @@ class Shuttler(EnvExperiment): self.led() self.relay_init() self.adc_init() - self.pdq_reset() + self.shuttler_reset() @kernel def run(self): @@ -94,14 +94,14 @@ class Shuttler(EnvExperiment): self.core_dma.playback_handle(example_waveform_handle) @kernel - def pdq_reset(self): + def shuttler_reset(self): for i in range(16): - self.pdq_channel_reset(i) + self.shuttler_channel_reset(i) # To avoid RTIO Underflow delay(50*us) @kernel - def pdq_channel_reset(self, ch): + def shuttler_channel_reset(self, ch): self.shuttler0_volt[ch].set_waveform( a0=0, a1=0, @@ -166,21 +166,21 @@ class Shuttler(EnvExperiment): duration_us = 500 # OUT0 and OUT1 have their frequency and phase aligned at 500us self.shuttler0_dds[0].set_waveform( - b0=pdq_dds_amp_mu(1.0), + b0=shuttler_dds_amp_mu(1.0), b1=0, b2=0, b3=0, c0=0, - c1=pdq_freq_mu(start_f_MHz), - c2=pdq_freq_sweep(start_f_MHz, end_f_MHz, duration_us), + 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=pdq_dds_amp_mu(1.0), + b0=shuttler_dds_amp_mu(1.0), b1=0, b2=0, b3=0, c0=0, - c1=pdq_freq_mu(end_f_MHz), + c1=shuttler_freq_mu(end_f_MHz), c2=0, ) self.shuttler0_trigger.trigger(0b11) @@ -189,12 +189,12 @@ class Shuttler(EnvExperiment): ## Step 3 ## # OUT0 and OUT1 has 180 degree phase difference self.shuttler0_dds[0].set_waveform( - b0=pdq_dds_amp_mu(1.0), + b0=shuttler_dds_amp_mu(1.0), b1=0, b2=0, b3=0, - c0=pdq_phase_offset(180.0), - c1=pdq_freq_mu(end_f_MHz), + c0=shuttler_phase_offset(180.0), + c1=shuttler_freq_mu(end_f_MHz), c2=0, ) # Phase and Output Setting of OUT1 is retained @@ -206,17 +206,17 @@ class Shuttler(EnvExperiment): # b(0) = 0, b(250) = 8.545, b(500) = 0 self.shuttler0_dds[0].set_waveform( b0=0, - b1=pdq_dds_damp_mu(0.06835937), - b2=pdq_dds_ddamp_mu(-0.0001367187), + b1=shuttler_dds_damp_mu(0.06835937), + b2=shuttler_dds_ddamp_mu(-0.0001367187), b3=0, c0=0, - c1=pdq_freq_mu(end_f_MHz), + c1=shuttler_freq_mu(end_f_MHz), c2=0, ) self.shuttler0_dds[1].set_waveform( b0=0, - b1=pdq_dds_damp_mu(0.06835937), - b2=pdq_dds_ddamp_mu(-0.0001367187), + b1=shuttler_dds_damp_mu(0.06835937), + b2=shuttler_dds_ddamp_mu(-0.0001367187), b3=0, c0=0, c1=0, @@ -227,23 +227,23 @@ class Shuttler(EnvExperiment): ## Step 5 ## self.shuttler0_volt[0].set_waveform( - a0=pdq_volt_amp_mu(-5.0), - a1=int32(pdq_volt_damp_mu(0.01)), + 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=pdq_dds_amp_mu(1.0), + b0=shuttler_dds_amp_mu(1.0), b1=0, b2=0, b3=0, c0=0, - c1=pdq_freq_mu(end_f_MHz), + c1=shuttler_freq_mu(end_f_MHz), c2=0, ) self.shuttler0_volt[1].set_waveform( - a0=pdq_volt_amp_mu(-5.0), - a1=int32(pdq_volt_damp_mu(0.01)), + a0=shuttler_volt_amp_mu(-5.0), + a1=int32(shuttler_volt_damp_mu(0.01)), a2=0, a3=0, ) @@ -261,53 +261,53 @@ class Shuttler(EnvExperiment): ## Step 6 ## self.shuttler0_volt[0].set_waveform( - a0=pdq_volt_amp_mu(-2.5), - a1=int32(pdq_volt_damp_mu(0.01)), + 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=pdq_dds_damp_mu(0.06835937), - b2=pdq_dds_ddamp_mu(-0.0001367187), + b1=shuttler_dds_damp_mu(0.06835937), + b2=shuttler_dds_ddamp_mu(-0.0001367187), b3=0, c0=0, - c1=pdq_freq_mu(start_f_MHz), - c2=pdq_freq_sweep(start_f_MHz, end_f_MHz, duration_us), + 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.pdq_channel_reset(1) + self.shuttler_channel_reset(1) delay(500*us) ## Step 7 ## self.shuttler0_volt[0].set_waveform( - a0=pdq_volt_amp_mu(2.5), - a1=int32(pdq_volt_damp_mu(-0.01)), + 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=pdq_dds_damp_mu(-0.06835937), - b2=pdq_dds_ddamp_mu(0.0001367187), + b1=shuttler_dds_damp_mu(-0.06835937), + b2=shuttler_dds_ddamp_mu(0.0001367187), b3=0, - c0=pdq_phase_offset(180.0), - c1=pdq_freq_mu(end_f_MHz), - c2=pdq_freq_sweep(end_f_MHz, start_f_MHz, duration_us), + 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.pdq_channel_reset(0) - self.pdq_channel_reset(1) + 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.efc0_leds[i].pulse(.1*s) + self.shuttler0_leds[i].pulse(.1*s) delay(.1*s) @kernel diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index a7398a37a..60166ddce 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -621,12 +621,11 @@ class PeripheralManager: channel=rtio_offset+i) return 8 - def process_efc(self, efc_peripheral): - efc_name = self.get_name("efc") - rtio_offset = efc_peripheral["drtio_destination"] << 16 - rtio_offset += self.add_board_leds(rtio_offset, board_name=efc_name) - + def process_shuttler(self, shuttler_peripheral): shuttler_name = self.get_name("shuttler") + rtio_offset = shuttler_peripheral["drtio_destination"] << 16 + rtio_offset += self.add_board_leds(rtio_offset, board_name=shuttler_name) + channel = count(0) self.gen(""" device_db["{name}_config"] = {{ @@ -718,8 +717,8 @@ class PeripheralManager: def split_drtio_eem(peripherals): - # EFC is the only peripheral that uses DRTIO-over-EEM at this moment - drtio_eem_filter = lambda peripheral: peripheral["type"] == "efc" + # Shuttler is the only peripheral that uses DRTIO-over-EEM at this moment + drtio_eem_filter = lambda peripheral: peripheral["type"] == "shuttler" return filterfalse(drtio_eem_filter, peripherals), \ list(filter(drtio_eem_filter, peripherals)) diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index e683946c5..71f5bb0fe 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -763,11 +763,11 @@ class HVAmp(_EEM): target.rtio_channels.append(rtio.Channel.from_phy(phy)) -class EFC(_EEM): +class Shuttler(_EEM): @staticmethod def io(eem, iostandard=default_iostandard): # Master: Pair 0~3 data IN, 4~7 OUT - data_in = ("efc{}_drtio_rx".format(eem), 0, + data_in = ("shuttler{}_drtio_rx".format(eem), 0, Subsignal("p", Pins("{} {} {} {}".format(*[ _eem_pin(eem, i, "p") for i in range(4) ]))), @@ -778,7 +778,7 @@ class EFC(_EEM): Misc("DIFF_TERM=TRUE"), ) - data_out = ("efc{}_drtio_tx".format(eem), 0, + data_out = ("shuttler{}_drtio_tx".format(eem), 0, Subsignal("p", Pins("{} {} {} {}".format(*[ _eem_pin(eem, i, "p") for i in range(4, 8) ]))), @@ -793,4 +793,4 @@ class EFC(_EEM): @classmethod def add_std(cls, target, eem, eem_aux, iostandard=default_iostandard): cls.add_extension(target, eem, is_drtio_over_eem=True, iostandard=iostandard) - target.eem_drtio_channels.append((target.platform.request("efc{}_drtio_rx".format(eem), 0), target.platform.request("efc{}_drtio_tx".format(eem), 0))) + target.eem_drtio_channels.append((target.platform.request("shuttler{}_drtio_rx".format(eem), 0), target.platform.request("shuttler{}_drtio_tx".format(eem), 0))) diff --git a/artiq/gateware/eem_7series.py b/artiq/gateware/eem_7series.py index 8c7f16c21..c98d11fad 100644 --- a/artiq/gateware/eem_7series.py +++ b/artiq/gateware/eem_7series.py @@ -133,7 +133,7 @@ def peripheral_hvamp(module, peripheral, **kwargs): eem.HVAmp.add_std(module, peripheral["ports"][0], ttl_simple.Output, **kwargs) -def peripheral_efc(module, peripheral, **kwargs): +def peripheral_shuttler(module, peripheral, **kwargs): if len(peripheral["ports"]) == 1: port = peripheral["ports"][0] port_aux = None @@ -141,7 +141,7 @@ def peripheral_efc(module, peripheral, **kwargs): port, port_aux = peripheral["ports"] else: raise ValueError("wrong number of ports") - eem.EFC.add_std(module, port, port_aux) + eem.Shuttler.add_std(module, port, port_aux) peripheral_processors = { "dio": peripheral_dio, @@ -156,7 +156,7 @@ peripheral_processors = { "fastino": peripheral_fastino, "phaser": peripheral_phaser, "hvamp": peripheral_hvamp, - "efc": peripheral_efc, + "shuttler": peripheral_shuttler, } diff --git a/artiq/gateware/shuttler.py b/artiq/gateware/shuttler.py index c6da201e5..93a1ac7d2 100644 --- a/artiq/gateware/shuttler.py +++ b/artiq/gateware/shuttler.py @@ -1,5 +1,7 @@ # Copyright 2013-2017 Robert Jordens # +# shuttler is developed based on pdq. +# # pdq is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or diff --git a/artiq/gateware/targets/kasli_generic.py b/artiq/gateware/targets/kasli_generic.py index ace08826e..2f91de1b1 100755 --- a/artiq/gateware/targets/kasli_generic.py +++ b/artiq/gateware/targets/kasli_generic.py @@ -70,7 +70,7 @@ class GenericMaster(MasterBase): if hw_rev is None: hw_rev = description["hw_rev"] self.class_name_override = description["variant"] - has_drtio_over_eem = any(peripheral["type"] == "efc" for peripheral in description["peripherals"]) + has_drtio_over_eem = any(peripheral["type"] == "shuttler" for peripheral in description["peripherals"]) MasterBase.__init__(self, hw_rev=hw_rev, rtio_clk_freq=description["rtio_frequency"], @@ -174,9 +174,9 @@ def main(): else: raise ValueError("Invalid DRTIO role") - has_efc = any(peripheral["type"] == "efc" for peripheral in description["peripherals"]) - if has_efc and (description["drtio_role"] == "standalone"): - raise ValueError("EFC requires DRTIO, please switch role to master") + has_shuttler = any(peripheral["type"] == "shuttler" for peripheral in description["peripherals"]) + if has_shuttler and (description["drtio_role"] == "standalone"): + raise ValueError("Shuttler requires DRTIO, please switch role to master") soc = cls(description, gateware_identifier_str=args.gateware_identifier_str, **soc_kasli_argdict(args)) args.variant = description["variant"] From 0c1b5728725b85c27a3f7ccf79d2ca58fc1caff2 Mon Sep 17 00:00:00 2001 From: linuswck Date: Tue, 26 Sep 2023 15:12:29 +0800 Subject: [PATCH 38/50] Shuttler: Correct spelling and grammar in docs --- artiq/coredevice/shuttler.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 07c416dd3..5ad6e4e71 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -21,7 +21,7 @@ class Config: 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 + To find the calibrated DAC code, the Shuttler Core first multiplies the output data with pre-DAC gain, then adds the offset. .. note:: @@ -49,7 +49,7 @@ class Config: """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 + 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. @@ -156,7 +156,7 @@ class Volt: and 46 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 + 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. @@ -243,7 +243,7 @@ class Dds: 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 + 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. @@ -291,11 +291,11 @@ class Trigger: @kernel def trigger(self, trig_out): - """Triggers coefficient update of (a) Shuttler core channel(s). + """Triggers coefficient update of (a) Shuttler Core channel(s). Each bit corresponds to a Shuttler waveform generator core. Setting `trig_out` bits commits the pending coefficient update (from - `set_waveform` in :class:`Volt` and :class:`Dds`) to the Shuttler core + `set_waveform` in :class:`Volt` and :class:`Dds`) to the Shuttler Core synchronously. :param trig_out: Coefficient update trigger bits. The MSB corresponds @@ -410,7 +410,7 @@ class ADC: contents. .. note:: - The datasheet only requires 64 cycles, but reaserting `CS_n` right + The datasheet only requires 64 cycles, but reasserting `CS_n` right after the transfer appears to interrupt the start-up sequence. """ self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC) @@ -584,7 +584,7 @@ class ADC: :param volts: A list of all 16 cubic DC-bias spline. (See :class:`Volt`) :param trigger: The Shuttler spline coefficient update trigger. - :param config: The Shuttler core configuration registers. + :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. """ From fcf6c90ba2414502abdda264a4e4ecdba2ad9aee Mon Sep 17 00:00:00 2001 From: mwojcik Date: Wed, 27 Sep 2023 17:15:13 +0800 Subject: [PATCH 39/50] ddb_template: support different satellite targets --- artiq/frontend/artiq_ddb_template.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index 60166ddce..50f9eb76a 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -11,17 +11,20 @@ from artiq.coredevice import jsondesc from artiq.coredevice.phaser import PHASER_GW_MIQRO, PHASER_GW_BASE -def process_header(output, description): +def get_cpu_target(description): + if description.get("type", None) == "shuttler": + return "rv32g" if description["target"] == "kasli": if description["hw_rev"] in ("v1.0", "v1.1"): - cpu_target = "rv32ima" + return "rv32ima" else: - cpu_target = "rv32g" + return "rv32g" elif description["target"] == "kasli_soc": - cpu_target = "cortexa9" + return "cortexa9" else: raise NotImplementedError +def process_header(output, description): print(textwrap.dedent(""" # Autogenerated for the {variant} variant core_addr = "{core_addr}" @@ -57,6 +60,8 @@ def process_header(output, description): "class": "CoreDMA" }}, + "satellite_cpu_targets": {{}}, + "i2c_switch0": {{ "type": "local", "module": "artiq.coredevice.i2c", @@ -74,7 +79,7 @@ def process_header(output, description): variant=description["variant"], core_addr=description["core_addr"], ref_period=1/(8*description["rtio_frequency"]), - cpu_target=cpu_target), + cpu_target=get_cpu_target(description)), file=output) @@ -753,6 +758,7 @@ def process(output, primary_description, satellites): drtio_peripherals.extend(satellite_drtio_peripherals) print("# DEST#{} peripherals".format(destination), file=output) + print("device_db[\"satellite_cpu_targets\"][{}] = \"{}\"".format(destination, get_cpu_target(description)), file=output) rtio_offset = destination << 16 for peripheral in peripherals: n_channels = pm.process(rtio_offset, peripheral) @@ -760,6 +766,7 @@ def process(output, primary_description, satellites): for peripheral in drtio_peripherals: print("# DEST#{} peripherals".format(peripheral["drtio_destination"]), file=output) + print("device_db[\"satellite_cpu_targets\"][{}] = \"{}\"".format(peripheral["drtio_destination"], get_cpu_target(peripheral)), file=output) processor = getattr(pm, "process_"+str(peripheral["type"])) processor(peripheral) From 0e8aa339797fb17fa560eb531457c075da6a004e Mon Sep 17 00:00:00 2001 From: mwojcik Date: Wed, 27 Sep 2023 17:15:48 +0800 Subject: [PATCH 40/50] core: separate master target from compilation --- artiq/coredevice/core.py | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index 406a7c961..928d7eb17 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -53,6 +53,17 @@ def rtio_get_counter() -> TInt64: raise NotImplementedError("syscall not simulated") +def get_target_cls(target): + if target == "rv32g": + return RV32GTarget + elif target == "rv32ima": + return RV32IMATarget + elif target == "cortexa9": + return CortexA9Target + else: + raise ValueError("Unsupported target") + + class Core: """Core device driver. @@ -75,14 +86,7 @@ class Core: def __init__(self, dmgr, host, ref_period, ref_multiplier=8, target="rv32g"): self.ref_period = ref_period self.ref_multiplier = ref_multiplier - if target == "rv32g": - self.target_cls = RV32GTarget - elif target == "rv32ima": - self.target_cls = RV32IMATarget - elif target == "cortexa9": - self.target_cls = CortexA9Target - else: - raise ValueError("Unsupported target") + self.target_cls = get_target_cls(target) self.coarse_ref_period = ref_period*ref_multiplier if host is None: self.comm = CommKernelDummy() @@ -98,7 +102,8 @@ class Core: self.comm.close() def compile(self, function, args, kwargs, set_result=None, - attribute_writeback=True, print_as_rpc=True): + attribute_writeback=True, print_as_rpc=True, + target=None): try: engine = _DiagnosticEngine(all_errors_are_fatal=True) @@ -110,7 +115,7 @@ class Core: module = Module(stitcher, ref_period=self.ref_period, attribute_writeback=attribute_writeback) - target = self.target_cls() + target = target if target is not None else self.target_cls() library = target.compile_and_link([module]) stripped_library = target.strip(library) From bafb85a27428ef1da47dbcf4d14daf8b63ab8655 Mon Sep 17 00:00:00 2001 From: Simon Renblad Date: Mon, 25 Sep 2023 15:25:49 +0800 Subject: [PATCH 41/50] custom_applet: change constructor, data_changed signatures --- artiq/examples/no_hardware/repository/custom_applet.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/artiq/examples/no_hardware/repository/custom_applet.py b/artiq/examples/no_hardware/repository/custom_applet.py index 1f197ffbb..0bc124325 100644 --- a/artiq/examples/no_hardware/repository/custom_applet.py +++ b/artiq/examples/no_hardware/repository/custom_applet.py @@ -4,13 +4,13 @@ from artiq.applets.simple import SimpleApplet class DemoWidget(QtWidgets.QLabel): - def __init__(self, args): + def __init__(self, args, ctl): QtWidgets.QLabel.__init__(self) self.dataset_name = args.dataset - def data_changed(self, data, mods): + def data_changed(self, value, metadata, persist, mods): try: - n = str(data[self.dataset_name][1]) + n = str(value[self.dataset_name]) except (KeyError, ValueError, TypeError): n = "---" n = "" + n + "" From a772dee1cc318c631c6693707cb9243e8cf72e71 Mon Sep 17 00:00:00 2001 From: occheung Date: Wed, 27 Sep 2023 17:11:12 -0700 Subject: [PATCH 42/50] shuttler: change 0th order accumulator width It now truncates the LSBs instead of the MSBs. --- artiq/coredevice/shuttler.py | 12 ++++++------ artiq/examples/kasli_shuttler/repository/shuttler.py | 6 +++--- artiq/gateware/shuttler.py | 12 +++++++----- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 5ad6e4e71..c6761e0ba 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -12,7 +12,7 @@ def shuttler_volt_to_mu(volt): """Return the equivalent DAC code. Valid input range is from -10 to 10 - LSB. """ - return round((1 << 14) * (volt / 20.0)) & 0x3fff + return round((1 << 16) * (volt / 20.0)) & 0xffff class Config: @@ -25,7 +25,7 @@ class Config: output data with pre-DAC gain, then adds the offset. .. note:: - The DAC code is capped at 0x1fff and 0x2000. + The DAC code is capped at 0x7fff and 0x8000. :param channel: RTIO channel number of this interface. :param core_device: Core device name. @@ -152,8 +152,8 @@ class Volt: a_3 &= p_3T^3 - :math:`a_0`, :math:`a_1`, :math:`a_2` and :math:`a_3` are 14, 30, 46 - and 46 bits in width respectively. See :meth:`shuttler_volt_to_mu` for + :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 @@ -238,8 +238,8 @@ class Dds: c_2 &= r_2T^2 - :math:`b_0`, :math:`b_1`, :math:`b_2` and :math:`b_3` are 14, 30, 46 - and 46 bits in width respectively. See :meth:`shuttler_volt_to_mu` for + :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. diff --git a/artiq/examples/kasli_shuttler/repository/shuttler.py b/artiq/examples/kasli_shuttler/repository/shuttler.py index 85db3b9bd..22a3a776e 100644 --- a/artiq/examples/kasli_shuttler/repository/shuttler.py +++ b/artiq/examples/kasli_shuttler/repository/shuttler.py @@ -26,15 +26,15 @@ def shuttler_volt_amp_mu(volt): @portable def shuttler_volt_damp_mu(volt_per_us): - return round(float(2) ** 30 * (volt_per_us / 20) / DAC_Fs_MHZ) + 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) ** 46 * (volt_per_us_square / 20) * 2 / (DAC_Fs_MHZ ** 2)) + 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) ** 46 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3)) + return round(float(2) ** 48 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3)) @portable def shuttler_dds_amp_mu(volt): diff --git a/artiq/gateware/shuttler.py b/artiq/gateware/shuttler.py index 93a1ac7d2..ceebb1043 100644 --- a/artiq/gateware/shuttler.py +++ b/artiq/gateware/shuttler.py @@ -124,16 +124,18 @@ class Dac(Module): ] # Infer signed multiplication - data_raw = Signal((14, True)) - data_buf = Signal(16) + data_raw = Signal((16, True)) + # Buffer data should have 2 more bits than the desired output width + # It is to perform overflow/underflow detection + data_buf = Signal(18) self.sync.rio += [ data_raw.eq(reduce(add, [sub.data for sub in subs])), # Extra buffer for timing for the DSP data_buf.eq(((data_raw * Cat(self.gain, ~self.gain[-1])) + (self.offset << 16))[16:]), If(overflow, - self.data.eq(0x1fff), + self.data.eq(0x7fff), ).Elif(underflow, - self.data.eq(0x2000), + self.data.eq(0x8000), ).Else( self.data.eq(data_buf), ), @@ -350,7 +352,7 @@ class Shuttler(Module, AutoCSR): dac.clear.eq(self.cfg.clr[idx]), dac.gain.eq(self.cfg.gain[idx]), dac.offset.eq(self.cfg.offset[idx]), - self.dac_interface.data[idx // 2][idx % 2].eq(dac.data) + self.dac_interface.data[idx // 2][idx % 2].eq(dac.data[2:]) ] for i in dac.i: From 2eb89cb1682bd338cb29992516c5a2e38802ac5f Mon Sep 17 00:00:00 2001 From: David Nadlinger Date: Sat, 30 Sep 2023 00:14:30 +0100 Subject: [PATCH 43/50] 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. --- artiq/gui/applets.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/artiq/gui/applets.py b/artiq/gui/applets.py index ac1db098d..c52d3a9d8 100644 --- a/artiq/gui/applets.py +++ b/artiq/gui/applets.py @@ -51,6 +51,13 @@ class AppletIPCServer(AsyncioParentComm): def _on_mod(self, mod): if mod["action"] == "init": + if not (self.datasets or self.dataset_prefixes): + # The dataset db connection just came online, and an applet is + # running but did not call `subscribe` yet (e.g. because the + # dashboard was just restarted and a previously enabled applet + # is being re-opened). We will later synthesize an "init" `mod` + # message once the applet actually subscribes. + return mod = self._synthesize_init(mod["struct"]) else: if mod["path"]: From 0e8fa8933f21227c7c8c1d9812bcf7f502769ebf Mon Sep 17 00:00:00 2001 From: occheung Date: Fri, 29 Sep 2023 20:39:34 -0700 Subject: [PATCH 44/50] shuttler: init sigma-delta modulator --- artiq/gateware/shuttler.py | 54 +++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/artiq/gateware/shuttler.py b/artiq/gateware/shuttler.py index ceebb1043..9d772cd72 100644 --- a/artiq/gateware/shuttler.py +++ b/artiq/gateware/shuttler.py @@ -93,13 +93,36 @@ class DacInterface(Module, AutoCSR): p_DDR_CLK_EDGE="SAME_EDGE") for bit in range(bit_width)] + +class SigmaDeltaModulator(Module): + """First order Sigma-Delta modulator.""" + def __init__(self, x_width, y_width): + self.x = Signal(x_width) + self.y = Signal(y_width) + + # The SDM cannot represent any sample >0x7ffc with pulse modulation + # Allowing pulse modulation on values >0x7ffc may overflow the + # accumulator, so the DAC code becomes 0x2000 -> -10.V. + x_capped = Signal(x_width) + self.comb += If((self.x & 0xfffc) == 0x7ffc, + x_capped.eq(0x7ffc), + ).Else( + x_capped.eq(self.x), + ) + + acc = Signal(x_width) + + self.comb += self.y.eq(acc[x_width-y_width:]) + self.sync.rio += acc.eq(x_capped - Cat(Replicate(0, x_width-y_width), self.y) + acc) + + class Dac(Module): """Output module. Holds the two output line executors. Attributes: - data (Signal[16]): Output value to be send to the DAC. + data (Signal[14]): Output value to be send to the DAC. clear (Signal): Clear accumulated phase offset when loading a new waveform. Input. gain (Signal[16]): Output value gain. The gain signal represents the @@ -107,9 +130,9 @@ class Dac(Module): offset (Signal[16]): Output value offset. i (Endpoint[]): Coefficients of the output lines. """ - def __init__(self): + def __init__(self, sdm=False): self.clear = Signal() - self.data = Signal(16) + self.data = Signal(14) self.gain = Signal(16) self.offset = Signal(16) @@ -128,16 +151,21 @@ class Dac(Module): # Buffer data should have 2 more bits than the desired output width # It is to perform overflow/underflow detection data_buf = Signal(18) + data_sink = Signal(16) + + if sdm: + self.submodules.sdm = SigmaDeltaModulator(16, 14) + self.sync.rio += [ data_raw.eq(reduce(add, [sub.data for sub in subs])), # Extra buffer for timing for the DSP data_buf.eq(((data_raw * Cat(self.gain, ~self.gain[-1])) + (self.offset << 16))[16:]), If(overflow, - self.data.eq(0x7fff), + data_sink.eq(0x7fff), ).Elif(underflow, - self.data.eq(0x8000), + data_sink.eq(0x8000), ).Else( - self.data.eq(data_buf), + data_sink.eq(data_buf), ), ] @@ -148,6 +176,14 @@ class Dac(Module): underflow.eq(data_buf[-1] & (~data_buf[-2] | ~data_buf[-3])), ] + if sdm: + self.comb += [ + self.sdm.x.eq(data_sink), + self.data.eq(self.sdm.y), + ] + else: + self.comb += self.data.eq(data_sink[2:]) + self.i = [ sub.i for sub in subs ] self.submodules += subs @@ -313,7 +349,7 @@ class Shuttler(Module, AutoCSR): Attributes: phys (list): List of Endpoints. """ - def __init__(self, pads): + def __init__(self, pads, sdm=False): NUM_OF_DACS = 16 self.submodules.dac_interface = DacInterface(pads) @@ -347,12 +383,12 @@ class Shuttler(Module, AutoCSR): self.phys.append(Phy(trigger_iface, [], [])) for idx in range(NUM_OF_DACS): - dac = Dac() + dac = Dac(sdm=sdm) self.comb += [ dac.clear.eq(self.cfg.clr[idx]), dac.gain.eq(self.cfg.gain[idx]), dac.offset.eq(self.cfg.offset[idx]), - self.dac_interface.data[idx // 2][idx % 2].eq(dac.data[2:]) + self.dac_interface.data[idx // 2][idx % 2].eq(dac.data) ] for i in dac.i: From 13271cea64302b4a3bd6e7ecef358cd52897ac61 Mon Sep 17 00:00:00 2001 From: Simon Renblad Date: Wed, 4 Oct 2023 13:35:01 +0800 Subject: [PATCH 45/50] gui: remove copies of _WheelFilter and refactor with parameter --- artiq/browser/experiments.py | 14 +++----------- artiq/dashboard/experiments.py | 14 +++----------- artiq/gui/tools.py | 14 ++++++++++---- 3 files changed, 16 insertions(+), 26 deletions(-) diff --git a/artiq/browser/experiments.py b/artiq/browser/experiments.py index 23a271eb9..c34ea5832 100644 --- a/artiq/browser/experiments.py +++ b/artiq/browser/experiments.py @@ -10,22 +10,14 @@ import h5py from sipyco import pyon from artiq import __artiq_dir__ as artiq_dir -from artiq.gui.tools import LayoutWidget, log_level_to_name, get_open_file_name +from artiq.gui.tools import (LayoutWidget, WheelFilter, + log_level_to_name, get_open_file_name) from artiq.gui.entries import procdesc_to_entry from artiq.master.worker import Worker, log_worker_exception logger = logging.getLogger(__name__) -class _WheelFilter(QtCore.QObject): - def eventFilter(self, obj, event): - if (event.type() == QtCore.QEvent.Wheel and - event.modifiers() != QtCore.Qt.NoModifier): - event.ignore() - return True - return False - - class _ArgumentEditor(QtWidgets.QTreeWidget): def __init__(self, dock): QtWidgets.QTreeWidget.__init__(self) @@ -46,7 +38,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget): self.setStyleSheet("QTreeWidget {background: " + self.palette().midlight().color().name() + " ;}") - self.viewport().installEventFilter(_WheelFilter(self.viewport())) + self.viewport().installEventFilter(WheelFilter(self.viewport(), True)) self._groups = dict() self._arg_to_widgets = dict() diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index 254dd05ab..74c3496a9 100644 --- a/artiq/dashboard/experiments.py +++ b/artiq/dashboard/experiments.py @@ -11,7 +11,8 @@ from sipyco import pyon from artiq.gui.entries import procdesc_to_entry, ScanEntry 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) logger = logging.getLogger(__name__) @@ -23,15 +24,6 @@ logger = logging.getLogger(__name__) # 2. file:@ -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): def __init__(self, manager, dock, expurl): self.manager = manager @@ -55,7 +47,7 @@ class _ArgumentEditor(QtWidgets.QTreeWidget): self.setStyleSheet("QTreeWidget {background: " + self.palette().midlight().color().name() + " ;}") - self.viewport().installEventFilter(_WheelFilter(self.viewport())) + self.viewport().installEventFilter(WheelFilter(self.viewport(), True)) self._groups = dict() self._arg_to_widgets = dict() diff --git a/artiq/gui/tools.py b/artiq/gui/tools.py index bc681bb77..441da9b81 100644 --- a/artiq/gui/tools.py +++ b/artiq/gui/tools.py @@ -16,10 +16,16 @@ def log_level_to_name(level): return "DEBUG" -class _WheelFilter(QtCore.QObject): +class WheelFilter(QtCore.QObject): + def __init__(self, parent, ignore_with_modifier=False): + super().__init__(parent) + self.ignore_with_modifier = ignore_with_modifier + def eventFilter(self, obj, event): - if (event.type() == QtCore.QEvent.Wheel and - event.modifiers() == QtCore.Qt.NoModifier): + if event.type() != QtCore.QEvent.Wheel: + return False + has_modifier = event.modifiers() != QtCore.Qt.NoModifier + if has_modifier == self.ignore_with_modifier: event.ignore() return True return False @@ -27,7 +33,7 @@ class _WheelFilter(QtCore.QObject): def disable_scroll_wheel(widget): widget.setFocusPolicy(QtCore.Qt.StrongFocus) - widget.installEventFilter(_WheelFilter(widget)) + widget.installEventFilter(WheelFilter(widget)) class QDockWidgetCloseDetect(QtWidgets.QDockWidget): From 4f3e58db529dde83e4a5319db79d9f9e54827210 Mon Sep 17 00:00:00 2001 From: Simon Renblad Date: Wed, 13 Sep 2023 11:41:26 +0800 Subject: [PATCH 46/50] gui.applets: add EntryArea --- artiq/gui/applets.py | 165 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 164 insertions(+), 1 deletion(-) diff --git a/artiq/gui/applets.py b/artiq/gui/applets.py index c52d3a9d8..411075ec6 100644 --- a/artiq/gui/applets.py +++ b/artiq/gui/applets.py @@ -14,11 +14,174 @@ from sipyco.pipe_ipc import AsyncioParentComm from sipyco.logging_tools import LogParser from sipyco import pyon -from artiq.gui.tools import QDockWidgetCloseDetect, LayoutWidget +from artiq.gui.entries import procdesc_to_entry +from artiq.gui.tools import (QDockWidgetCloseDetect, LayoutWidget, + WheelFilter) logger = logging.getLogger(__name__) +class EntryArea(QtWidgets.QTreeWidget): + def __init__(self): + QtWidgets.QTreeWidget.__init__(self) + self.setColumnCount(3) + self.header().setStretchLastSection(False) + if hasattr(self.header(), "setSectionResizeMode"): + set_resize_mode = self.header().setSectionResizeMode + else: + set_resize_mode = self.header().setResizeMode + set_resize_mode(0, QtWidgets.QHeaderView.ResizeToContents) + set_resize_mode(1, QtWidgets.QHeaderView.Stretch) + self.header().setVisible(False) + self.setSelectionMode(self.NoSelection) + self.setHorizontalScrollMode(self.ScrollPerPixel) + self.setVerticalScrollMode(self.ScrollPerPixel) + + self.setStyleSheet("QTreeWidget {background: " + + self.palette().midlight().color().name() + " ;}") + + self.viewport().installEventFilter(WheelFilter(self.viewport(), True)) + + self._groups = dict() + self._arg_to_widgets = dict() + self._arguments = dict() + + self.gradient = QtGui.QLinearGradient( + 0, 0, 0, QtGui.QFontMetrics(self.font()).lineSpacing()*2.5) + self.gradient.setColorAt(0, self.palette().base().color()) + self.gradient.setColorAt(1, self.palette().midlight().color()) + + reset_all_button = QtWidgets.QPushButton("Restore defaults") + reset_all_button.setToolTip("Reset all to default values") + reset_all_button.setIcon( + QtWidgets.QApplication.style().standardIcon( + QtWidgets.QStyle.SP_BrowserReload)) + reset_all_button.clicked.connect(self.reset_all) + buttons = LayoutWidget() + buttons.layout.setColumnStretch(0, 1) + buttons.layout.setColumnStretch(1, 0) + buttons.layout.setColumnStretch(2, 1) + buttons.addWidget(reset_all_button, 0, 1) + self.bottom_item = QtWidgets.QTreeWidgetItem() + self.addTopLevelItem(self.bottom_item) + self.setItemWidget(self.bottom_item, 1, buttons) + self.bottom_item.setHidden(True) + + def setattr_argument(self, name, proc, group=None, tooltip=None): + argument = dict() + desc = proc.describe() + argument["desc"] = desc + argument["group"] = group + argument["tooltip"] = tooltip + self._arguments[name] = argument + widgets = dict() + self._arg_to_widgets[name] = widgets + entry_class = procdesc_to_entry(argument["desc"]) + argument["state"] = entry_class.default_state(argument["desc"]) + entry = entry_class(argument) + widget_item = QtWidgets.QTreeWidgetItem([name]) + if argument["tooltip"]: + widget_item.setToolTip(0, argument["tooltip"]) + widgets["entry"] = entry + widgets["widget_item"] = widget_item + + if len(self._arguments) > 1: + self.bottom_item.setHidden(False) + + for col in range(3): + widget_item.setBackground(col, self.gradient) + font = widget_item.font(0) + font.setBold(True) + widget_item.setFont(0, font) + + if argument["group"] is None: + self.insertTopLevelItem(self.indexFromItem(self.bottom_item).row(), widget_item) + else: + self._get_group(argument["group"]).addChild(widget_item) + self.bottom_item.setHidden(False) + fix_layout = LayoutWidget() + widgets["fix_layout"] = fix_layout + fix_layout.addWidget(entry) + self.setItemWidget(widget_item, 1, fix_layout) + + reset_value = QtWidgets.QToolButton() + reset_value.setToolTip("Reset to default value") + reset_value.setIcon( + QtWidgets.QApplication.style().standardIcon( + QtWidgets.QStyle.SP_BrowserReload)) + reset_value.clicked.connect(partial(self.reset_value, name)) + + tool_buttons = LayoutWidget() + tool_buttons.addWidget(reset_value, 0) + self.setItemWidget(widget_item, 2, tool_buttons) + + def _get_group(self, name): + if name in self._groups: + return self._groups[name] + group = QtWidgets.QTreeWidgetItem([name]) + for col in range(3): + group.setBackground(col, self.palette().mid()) + group.setForeground(col, self.palette().brightText()) + font = group.font(col) + font.setBold(True) + group.setFont(col, font) + self.insertTopLevelItem(self.indexFromItem(self.bottom_item).row(), group) + self._groups[name] = group + return group + + def __getattr__(self, name): + return self.get_value(name) + + def get_value(self, name): + entry = self._arg_to_widgets[name]["entry"] + argument = self._arguments[name] + return entry.state_to_value(argument["state"]) + + def set_value(self, name, value): + ty = self._arguments[name]["desc"]["ty"] + if ty == "Scannable": + desc = value.describe() + self._arguments[name]["state"][desc["ty"]] = desc + self._arguments[name]["state"]["selected"] = desc["ty"] + else: + self._arguments[name]["state"] = value + self.update_value(name) + + def get_values(self): + d = dict() + for name in self._arguments.keys(): + d[name] = self.get_value(name) + return d + + def set_values(self, values): + for name, value in values.items(): + self.set_value(name, value) + + def update_value(self, name): + widgets = self._arg_to_widgets[name] + argument = self._arguments[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["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 reset_value(self, name): + procdesc = self._arguments[name]["desc"] + self._arguments[name]["state"] = procdesc_to_entry(procdesc).default_state(procdesc) + self.update_value(name) + + def reset_all(self): + for name in self._arguments.keys(): + self.reset_value(name) + class AppletIPCServer(AsyncioParentComm): def __init__(self, dataset_sub, dataset_ctl, expmgr): From 43926574da1f2423e059487b0eb6c0c254c2dd2b Mon Sep 17 00:00:00 2001 From: occheung Date: Wed, 4 Oct 2023 16:29:55 -0700 Subject: [PATCH 47/50] shuttler: remove sdm constants --- artiq/gateware/shuttler.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/artiq/gateware/shuttler.py b/artiq/gateware/shuttler.py index 9d772cd72..9728a0a1d 100644 --- a/artiq/gateware/shuttler.py +++ b/artiq/gateware/shuttler.py @@ -100,12 +100,15 @@ class SigmaDeltaModulator(Module): self.x = Signal(x_width) self.y = Signal(y_width) - # The SDM cannot represent any sample >0x7ffc with pulse modulation - # Allowing pulse modulation on values >0x7ffc may overflow the - # accumulator, so the DAC code becomes 0x2000 -> -10.V. + # SDM can at most output the max DAC code `Replicate(1, y_width-1)`, + # which represents the sample of value + # `Replicate(1, y_width-1) << (x_width-y_width)`. + # + # If the input sample exceeds such limit, SDM may overflow. x_capped = Signal(x_width) - self.comb += If((self.x & 0xfffc) == 0x7ffc, - x_capped.eq(0x7ffc), + max_dac_code = Replicate(1, (y_width-1)) + self.comb += If(self.x[x_width-y_width:] == max_dac_code, + x_capped.eq(Cat(Replicate(0, x_width-y_width), max_dac_code)), ).Else( x_capped.eq(self.x), ) From da9f7cb58a10d8d88887886bf265b3d0a4bc0c6f Mon Sep 17 00:00:00 2001 From: Simon Renblad Date: Thu, 21 Sep 2023 17:44:52 +0800 Subject: [PATCH 48/50] applet extensions documentation --- artiq/applets/simple.py | 48 ++++++++++++++++++--- doc/manual/conf.py | 2 +- doc/manual/management_system.rst | 71 ++++++++++++++++++++++++++++++++ 3 files changed, 114 insertions(+), 7 deletions(-) diff --git a/artiq/applets/simple.py b/artiq/applets/simple.py index d49223670..580991982 100644 --- a/artiq/applets/simple.py +++ b/artiq/applets/simple.py @@ -17,7 +17,45 @@ from artiq.language.scan import ScanObject logger = logging.getLogger(__name__) -class AppletControlIPC: +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 @@ -45,7 +83,7 @@ class AppletControlIPC: self.ipc.set_argument_value(expurl, name, value) -class AppletControlRPC: +class AppletRequestRPC(_AppletRequestInterface): def __init__(self, loop, dataset_ctl): self.loop = loop self.dataset_ctl = dataset_ctl @@ -74,8 +112,6 @@ class AppletControlRPC: mod = {"action": "append", "path": [key, 1], "x": value} self._background(self.dataset_ctl.update, mod) - def set_argument_value(self, expurl, name, value): - raise NotImplementedError class AppletIPCClient(AsyncioChildComm): def set_close_cb(self, close_cb): @@ -220,9 +256,9 @@ class SimpleApplet: dataset_ctl = RPCClient() self.loop.run_until_complete(dataset_ctl.connect_rpc( self.args.server, self.args.port_control, "master_dataset_db")) - self.ctl = AppletControlRPC(self.loop, dataset_ctl) + self.ctl = AppletRequestRPC(self.loop, dataset_ctl) else: - self.ctl = AppletControlIPC(self.ipc) + self.ctl = AppletRequestIPC(self.ipc) def ctl_close(self): if self.embed is None: diff --git a/doc/manual/conf.py b/doc/manual/conf.py index df603b7b2..7e06ea680 100644 --- a/doc/manual/conf.py +++ b/doc/manual/conf.py @@ -41,7 +41,7 @@ mock_modules = ["artiq.gui.waitingspinnerwidget", "sipyco", "sipyco.pc_rpc", "sipyco.sync_struct", "sipyco.asyncio_tools", "sipyco.logging_tools", "sipyco.broadcast", "sipyco.packed_exceptions", - "sipyco.keepalive"] + "sipyco.keepalive", "sipyco.pipe_ipc"] for module in mock_modules: sys.modules[module] = Mock() diff --git a/doc/manual/management_system.rst b/doc/manual/management_system.rst index aafbb0361..abb5c2d17 100644 --- a/doc/manual/management_system.rst +++ b/doc/manual/management_system.rst @@ -143,6 +143,77 @@ CCBs are used by experiments to configure applets in the dashboard, for example .. autoclass:: artiq.dashboard.applets_ccb.AppletsCCBDock :members: +Applet request interfaces +************************* + +Applet request interfaces allow applets to perform actions on the master database and set arguments in the dashboard. Applets may inherit from the ``artiq.applets.simple.SimpleApplet`` and call the methods defined below through the `req` attribute. + +Embedded applets should use `AppletRequestIPC` while standalone applets use `AppletRequestRPC`. `SimpleApplet` automatically chooses the correct interface on initialization. + +.. autoclass:: artiq.applets.simple._AppletRequestInterface + :members: + + +Applet entry area +***************** + +Extensions are provided to enable the use of argument widgets in applets through the `EntryArea` class. + +Below is a simple example code snippet using the `EntryArea` class: :: + + # Create the experiment area + entry_area = EntryArea() + + # Create a new widget + entry_area.setattr_argument("bl", BooleanValue(True)) + + # Get the value of the widget (output: True) + print(entry_area.bl) + + # Set the value + entry_area.set_value("bl", False) + + # False + print(entry_area.bl) + +The `EntryArea` object can then be added to a layout and integrated with the applet GUI. Multiple `EntryArea` objects can be used in a single applet. + +.. class:: artiq.gui.applets.EntryArea + + .. method:: setattr_argument(name, proc, group=None, tooltip=None) + + Sets an argument as attribute. The names of the argument and of the + attribute are the same. + + :param name: Argument name + :param proc: Argument processor, for example ``NumberValue`` + :param group: Used to group together arguments in the GUI under a common category + :param tooltip: Tooltip displayed when hovering over the entry widget + + .. method:: get_value(name) + + Get the value of an entry widget. + + :param name: Argument name + + .. method:: get_values() + + Get all values in the ``EntryArea`` as a dictionary. Names are stored as keys, and argument values as values. + + .. method:: set_value(name, value) + + Set the value of an entry widget. The change is temporary and will reset to default when the reset button is clicked. + + :param name: Argument name + :param value: Object representing the new 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. + + .. method:: set_values(values) + + Set multiple values from a dictionary input. Calls ``set_value()`` for each key-value pair. + + :param values: Dictionary with names as keys and new argument values as values. + Front-end tool reference ************************ From bb7caacb5f15ded97cc9fae67e69b039f6482249 Mon Sep 17 00:00:00 2001 From: Simon Renblad Date: Thu, 5 Oct 2023 11:55:39 +0800 Subject: [PATCH 49/50] RELEASE_NOTES: applet API extensions --- RELEASE_NOTES.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index ea94f5651..45573d386 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -29,7 +29,9 @@ Highlights: kernel functions. * Distributed DMA is now supported, allowing DMA to be run directly on satellites for corresponding RTIO events, increasing bandwidth in scenarios with heavy satellite usage. -* API extensions have been implemented, enabling applets to directly modify datasets. +* Applet Request Interfaces have been implemented, enabling applets to directly modify datasets + and temporarily set arguments in the dashboard. +* EntryArea widget has been implemented, allowing argument entry widgets to be used in applets. * Dashboard: - The "Close all applets" command (shortcut: Ctrl-Alt-W) now ignores docked applets, making it a convenient way to clean up after exploratory work without destroying a From 47fc640f755b7bbf9e82b0876fcf6450e56645e9 Mon Sep 17 00:00:00 2001 From: Simon Renblad Date: Thu, 5 Oct 2023 12:28:46 +0800 Subject: [PATCH 50/50] applets: rename 'ctl' attribute to 'req' --- artiq/applets/big_number.py | 6 +++--- artiq/applets/image.py | 2 +- artiq/applets/plot_hist.py | 2 +- artiq/applets/plot_xy.py | 2 +- artiq/applets/plot_xy_hist.py | 2 +- artiq/applets/progress_bar.py | 2 +- artiq/applets/simple.py | 16 ++++++++-------- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/artiq/applets/big_number.py b/artiq/applets/big_number.py index a085ce8e4..56458f507 100755 --- a/artiq/applets/big_number.py +++ b/artiq/applets/big_number.py @@ -22,10 +22,10 @@ class QCancellableLineEdit(QtWidgets.QLineEdit): class NumberWidget(QtWidgets.QStackedWidget): - def __init__(self, args, ctl): + def __init__(self, args, req): QtWidgets.QStackedWidget.__init__(self) self.dataset_name = args.dataset - self.ctl = ctl + self.req = req self.lcd_widget = QResponsiveLCDNumber() self.lcd_widget.setDigitCount(args.digit_count) @@ -55,7 +55,7 @@ class NumberWidget(QtWidgets.QStackedWidget): def confirm_edit(self): value = float(self.edit_widget.text()) - self.ctl.set_dataset(self.dataset_name, value) + self.req.set_dataset(self.dataset_name, value) self.setCurrentWidget(self.lcd_widget) def cancel_edit(self): diff --git a/artiq/applets/image.py b/artiq/applets/image.py index 36af01cab..75cecd05a 100755 --- a/artiq/applets/image.py +++ b/artiq/applets/image.py @@ -7,7 +7,7 @@ from artiq.applets.simple import SimpleApplet class Image(pyqtgraph.ImageView): - def __init__(self, args, ctl): + def __init__(self, args, req): pyqtgraph.ImageView.__init__(self) self.args = args diff --git a/artiq/applets/plot_hist.py b/artiq/applets/plot_hist.py index 3be604930..5d386cc6c 100755 --- a/artiq/applets/plot_hist.py +++ b/artiq/applets/plot_hist.py @@ -8,7 +8,7 @@ from artiq.applets.simple import TitleApplet class HistogramPlot(pyqtgraph.PlotWidget): - def __init__(self, args, ctl): + def __init__(self, args, req): pyqtgraph.PlotWidget.__init__(self) self.args = args self.timer = QTimer() diff --git a/artiq/applets/plot_xy.py b/artiq/applets/plot_xy.py index 3838ce345..d7d67803b 100755 --- a/artiq/applets/plot_xy.py +++ b/artiq/applets/plot_xy.py @@ -9,7 +9,7 @@ from artiq.applets.simple import TitleApplet class XYPlot(pyqtgraph.PlotWidget): - def __init__(self, args, ctl): + def __init__(self, args, req): pyqtgraph.PlotWidget.__init__(self) self.args = args self.timer = QTimer() diff --git a/artiq/applets/plot_xy_hist.py b/artiq/applets/plot_xy_hist.py index 7d42aeacc..f757b520d 100755 --- a/artiq/applets/plot_xy_hist.py +++ b/artiq/applets/plot_xy_hist.py @@ -22,7 +22,7 @@ def _compute_ys(histogram_bins, histograms_counts): # pyqtgraph.GraphicsWindow fails to behave like a regular Qt widget # and breaks embedding. Do not use as top widget. class XYHistPlot(QtWidgets.QSplitter): - def __init__(self, args, ctl): + def __init__(self, args, req): QtWidgets.QSplitter.__init__(self) self.resize(1000, 600) self.setWindowTitle("XY/Histogram") diff --git a/artiq/applets/progress_bar.py b/artiq/applets/progress_bar.py index b25d380d8..9ea7db836 100644 --- a/artiq/applets/progress_bar.py +++ b/artiq/applets/progress_bar.py @@ -6,7 +6,7 @@ from artiq.applets.simple import SimpleApplet class ProgressWidget(QtWidgets.QProgressBar): - def __init__(self, args, ctl): + def __init__(self, args, req): QtWidgets.QProgressBar.__init__(self) self.setMinimum(args.min) self.setMaximum(args.max) diff --git a/artiq/applets/simple.py b/artiq/applets/simple.py index 580991982..cd980bb34 100644 --- a/artiq/applets/simple.py +++ b/artiq/applets/simple.py @@ -251,21 +251,21 @@ class SimpleApplet: if self.embed is not None: self.ipc.close() - def ctl_init(self): + 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.ctl = AppletRequestRPC(self.loop, dataset_ctl) + self.req = AppletRequestRPC(self.loop, dataset_ctl) else: - self.ctl = AppletRequestIPC(self.ipc) + self.req = AppletRequestIPC(self.ipc) - def ctl_close(self): + def req_close(self): if self.embed is None: - self.ctl.dataset_ctl.close_rpc() + self.req.dataset_ctl.close_rpc() def create_main_widget(self): - self.main_widget = self.main_widget_class(self.args, self.ctl) + self.main_widget = self.main_widget_class(self.args, self.req) if self.embed is not None: self.ipc.set_close_cb(self.main_widget.close) if os.name == "nt": @@ -367,7 +367,7 @@ class SimpleApplet: try: self.ipc_init() try: - self.ctl_init() + self.req_init() try: self.create_main_widget() self.subscribe() @@ -376,7 +376,7 @@ class SimpleApplet: finally: self.unsubscribe() finally: - self.ctl_close() + self.req_close() finally: self.ipc_close() finally: