diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 0fd58fda3..45573d386 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -25,9 +25,17 @@ 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. +* 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 + 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/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 363314a9e..cd980bb34 100644 --- a/artiq/applets/simple.py +++ b/artiq/applets/simple.py @@ -11,11 +11,51 @@ from sipyco.pc_rpc import AsyncioClient as RPCClient from sipyco import pyon from sipyco.pipe_ipc import AsyncioChildComm +from artiq.language.scan import ScanObject + logger = logging.getLogger(__name__) -class 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 @@ -37,8 +77,13 @@ 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: + +class AppletRequestRPC(_AppletRequestInterface): def __init__(self, loop, dataset_ctl): self.loop = loop self.dataset_ctl = dataset_ctl @@ -137,6 +182,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, @@ -200,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 = AppletControlRPC(self.loop, dataset_ctl) + self.req = AppletRequestRPC(self.loop, dataset_ctl) else: - self.ctl = AppletControlIPC(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": @@ -316,7 +367,7 @@ class SimpleApplet: try: self.ipc_init() try: - self.ctl_init() + self.req_init() try: self.create_main_widget() self.subscribe() @@ -325,7 +376,7 @@ class SimpleApplet: finally: self.unsubscribe() finally: - self.ctl_close() + self.req_close() finally: self.ipc_close() finally: 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/coredevice/core.py b/artiq/coredevice/core.py index 199d02b25..ecd18a429 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -62,7 +62,11 @@ class Core: def close(self): self.comm.close() - def compile(self, method, args, kwargs, embedding_map, file_output=None): + def compile(self, method, args, kwargs, embedding_map, file_output=None, target=None): + if target is not None: + # NAC3TODO: subkernels + raise NotImplementedError + if not self.analyzed: self.compiler.analyze(core_language._registered_functions, core_language._registered_classes) self.analyzed = True diff --git a/artiq/coredevice/coredevice_generic.schema.json b/artiq/coredevice/coredevice_generic.schema.json index 5403a827a..e05460a64 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", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "efc"] + "enum": ["dio", "dio_spi", "urukul", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"] }, "board": { "type": "string" @@ -563,11 +563,11 @@ "required": ["ports"] } },{ - "title": "EFC", + "title": "Shuttler", "if": { "properties": { "type": { - "const": "efc" + "const": "shuttler" } } }, @@ -580,6 +580,10 @@ }, "minItems": 1, "maxItems": 2 + }, + "drtio_destination": { + "type": "integer", + "default": 4 } }, "required": ["ports"] diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 71b3050f5..c6761e0ba 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -2,23 +2,131 @@ 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 +from artiq.coredevice import spi2 as spi +from artiq.language.units import us + + +@portable +def shuttler_volt_to_mu(volt): + """Return the equivalent DAC code. Valid input range is from -10 to + 10 - LSB. + """ + return round((1 << 16) * (volt / 20.0)) & 0xffff class Config: - kernel_invariants = {"core", "channel", "target_o"} + """Shuttler configuration registers interface. + + The configuration registers control waveform phase auto-clear, and pre-DAC + gain & offset values for calibration with ADC on the Shuttler AFE card. + + To find the calibrated DAC code, the Shuttler Core first multiplies the + output data with pre-DAC gain, then adds the offset. + + .. note:: + The DAC code is capped at 0x7fff and 0x8000. + + :param channel: RTIO channel number of this interface. + :param core_device: Core device name. + """ + kernel_invariants = { + "core", "channel", "target_base", "target_read", + "target_gain", "target_offset", "target_clr" + } def __init__(self, dmgr, channel, core_device="core"): self.core = dmgr.get(core_device) self.channel = channel - self.target_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): + """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"): @@ -28,7 +136,35 @@ class Volt: @kernel def set_waveform(self, a0: TInt32, a1: TInt32, a2: TInt64, a3: TInt64): - pdq_words = [ + """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 16, 32, 48 + and 48 bits in width respectively. See :meth:`shuttler_volt_to_mu` for + machine unit conversion. + + Note: The waveform is not updated to the Shuttler Core until + triggered. See :class:`Trigger` for the update triggering mechanism. + + :param a0: The :math:`a_0` coefficient in machine unit. + :param a1: The :math:`a_1` coefficient in machine unit. + :param a2: The :math:`a_2` coefficient in machine unit. + :param a3: The :math:`a_3` coefficient in machine unit. + """ + coef_words = [ a0, a1, a1 >> 16, @@ -40,12 +176,36 @@ 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)) 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"): @@ -56,7 +216,45 @@ class Dds: @kernel def set_waveform(self, b0: TInt32, b1: TInt32, b2: TInt64, b3: TInt64, c0: TInt32, c1: TInt32, c2: TInt32): - pdq_words = [ + """Set the DDS spline waveform. + + Given `b(t)` and `c(t)` as defined in :class:`Dds`, the coefficients + should be configured by the following formulae. + + .. math:: + T &= 8*10^{-9} + + b_0 &= q_0 + + b_1 &= q_1T + \\frac{q_2T^2}{2} + \\frac{q_3T^3}{6} + + b_2 &= q_2T^2 + q_3T^3 + + b_3 &= q_3T^3 + + c_0 &= r_0 + + c_1 &= r_1T + \\frac{r_2T^2}{2} + + c_2 &= r_2T^2 + + :math:`b_0`, :math:`b_1`, :math:`b_2` and :math:`b_3` are 16, 32, 48 + and 48 bits in width respectively. See :meth:`shuttler_volt_to_mu` for + machine unit conversion. :math:`c_0`, :math:`c_1` and :math:`c_2` are + 16, 32 and 32 bits in width respectively. + + Note: The waveform is not updated to the Shuttler Core until + triggered. See :class:`Trigger` for the update triggering mechanism. + + :param b0: The :math:`b_0` coefficient in machine unit. + :param b1: The :math:`b_1` coefficient in machine unit. + :param b2: The :math:`b_2` coefficient in machine unit. + :param b3: The :math:`b_3` coefficient in machine unit. + :param c0: The :math:`c_0` coefficient in machine unit. + :param c1: The :math:`c_1` coefficient in machine unit. + :param c2: The :math:`c_2` coefficient in machine unit. + """ + coef_words = [ b0, b1, b1 >> 16, @@ -73,12 +271,17 @@ 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)) 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"): @@ -88,4 +291,335 @@ 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) + + +RELAY_SPI_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END | + 0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | + 0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | + 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) + +ADC_SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END | + 0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | + 1*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE | + 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) + +# SPI clock write and read dividers +# CS should assert at least 9.5 ns after clk pulse +SPIT_RELAY_WR = 4 +# 25 ns high/low pulse hold (limiting for write) +SPIT_ADC_WR = 4 +SPIT_ADC_RD = 16 + +# SPI CS line +CS_RELAY = 1 << 0 +CS_LED = 1 << 1 +CS_ADC = 1 << 0 + +# Referenced AD4115 registers +_AD4115_REG_STATUS = 0x00 +_AD4115_REG_ADCMODE = 0x01 +_AD4115_REG_DATA = 0x04 +_AD4115_REG_ID = 0x07 +_AD4115_REG_CH0 = 0x10 +_AD4115_REG_SETUPCON0 = 0x20 + + +class Relay: + """Shuttler AFE relay switches. + + It controls the AFE relay switches and the LEDs. Switch on the relay to + enable AFE output; And off to disable the output. The LEDs indicates the + relay status. + + .. note:: + The relay does not disable ADC measurements. Voltage of any channels + can still be read by the ADC even after switching off the relays. + + :param spi_device: SPI bus device name. + :param core_device: Core device name. + """ + kernel_invariant = {"core", "bus"} + + def __init__(self, dmgr, spi_device, core_device="core"): + self.core = dmgr.get(core_device) + self.bus = dmgr.get(spi_device) + + @kernel + def init(self): + """Initialize SPI device. + + Configures the SPI bus to 16-bits, write-only, simultaneous relay + switches and LED control. + """ + self.bus.set_config_mu( + RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED) + + @kernel + def enable(self, en: TInt32): + """Enable/Disable relay switches of corresponding channels. + + Each bit corresponds to the relay switch of a channel. Asserting a bit + turns on the corresponding relay switch; Deasserting the same bit + turns off the switch instead. + + :param en: Switch enable bits. The MSB corresponds to Channel 15, LSB + corresponds to Channel 0. + """ + self.bus.write(en << 16) + + +class ADC: + """Shuttler AFE ADC (AD4115) driver. + + :param spi_device: SPI bus device name. + :param core_device: Core device name. + """ + kernel_invariant = {"core", "bus"} + + def __init__(self, dmgr, spi_device, core_device="core"): + self.core = dmgr.get(core_device) + self.bus = dmgr.get(spi_device) + + @kernel + def read_id(self) -> TInt32: + """Read the product ID of the ADC. + + The expected return value is 0x38DX, the 4 LSbs are don't cares. + + :return: The read-back product ID. + """ + return self.read16(_AD4115_REG_ID) + + @kernel + def reset(self): + """AD4115 reset procedure. + + This performs a write operation of 96 serial clock cycles with DIN + held at high. It resets the entire device, including the register + contents. + + .. note:: + The datasheet only requires 64 cycles, but reasserting `CS_n` right + after the transfer appears to interrupt the start-up sequence. + """ + self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC) + self.bus.write(0xffffffff) + self.bus.write(0xffffffff) + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC) + self.bus.write(0xffffffff) + + @kernel + def read8(self, addr: TInt32) -> TInt32: + """Read from 8 bit register. + + :param addr: Register address. + :return: Read-back register content. + """ + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + 16, SPIT_ADC_RD, CS_ADC) + self.bus.write((addr | 0x40) << 24) + return self.bus.read() & 0xff + + @kernel + def read16(self, addr: TInt32) -> TInt32: + """Read from 16 bit register. + + :param addr: Register address. + :return: Read-back register content. + """ + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + 24, SPIT_ADC_RD, CS_ADC) + self.bus.write((addr | 0x40) << 24) + return self.bus.read() & 0xffff + + @kernel + def read24(self, addr: TInt32) -> TInt32: + """Read from 24 bit register. + + :param addr: Register address. + :return: Read-back register content. + """ + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + 32, SPIT_ADC_RD, CS_ADC) + self.bus.write((addr | 0x40) << 24) + return self.bus.read() & 0xffffff + + @kernel + def write8(self, addr: TInt32, data: TInt32): + """Write to 8 bit register. + + :param addr: Register address. + :param data: Data to be written. + """ + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END, 16, SPIT_ADC_WR, CS_ADC) + self.bus.write(addr << 24 | (data & 0xff) << 16) + + @kernel + def write16(self, addr: TInt32, data: TInt32): + """Write to 16 bit register. + + :param addr: Register address. + :param data: Data to be written. + """ + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END, 24, SPIT_ADC_WR, CS_ADC) + self.bus.write(addr << 24 | (data & 0xffff) << 8) + + @kernel + def write24(self, addr: TInt32, data: TInt32): + """Write to 24 bit register. + + :param addr: Register address. + :param data: Data to be written. + """ + self.bus.set_config_mu( + ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC) + self.bus.write(addr << 24 | (data & 0xffffff)) + + @kernel + def read_ch(self, channel: TInt32) -> TFloat: + """Sample a Shuttler channel on the AFE. + + It performs a single conversion using profile 0 and setup 0, on the + selected channel. The sample is then recovered and converted to volt. + + :param channel: Shuttler channel to be sampled. + :return: Voltage sample in volt. + """ + # Always configure Profile 0 for single conversion + self.write16(_AD4115_REG_CH0, 0x8000 | ((channel * 2 + 1) << 4)) + self.write16(_AD4115_REG_SETUPCON0, 0x1300) + self.single_conversion() + + delay(100*us) + adc_code = self.read24(_AD4115_REG_DATA) + return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1 + + @kernel + def single_conversion(self): + """Place the ADC in single conversion mode. + + The ADC returns to standby mode after the conversion is complete. + """ + self.write16(_AD4115_REG_ADCMODE, 0x8010) + + @kernel + def standby(self): + """Place the ADC in standby mode and disables power down the clock. + + The ADC can be returned to single conversion mode by calling + :meth:`single_conversion`. + """ + # Selecting internal XO (0b00) also disables clock during standby + self.write16(_AD4115_REG_ADCMODE, 0x8020) + + @kernel + def power_down(self): + """Place the ADC in power-down mode. + + The ADC must be reset before returning to other modes. + + .. note:: + The AD4115 datasheet suggests placing the ADC in standby mode + before power-down. This is to prevent accidental entry into the + power-down mode. + + .. seealso:: + :meth:`standby` + + :meth:`power_up` + + """ + self.write16(_AD4115_REG_ADCMODE, 0x8030) + + @kernel + def power_up(self): + """Exit the ADC power-down mode. + + The ADC should be in power-down mode before calling this method. + + .. seealso:: + :meth:`power_down` + """ + self.reset() + # Although the datasheet claims 500 us reset wait time, only waiting + # for ~500 us can result in DOUT pin stuck in high + delay(2500*us) + + @kernel + def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]): + """Calibrate the Shuttler waveform generator using the ADC on the AFE. + + It finds the average slope rate and average offset by samples, and + compensate by writing the pre-DAC gain and offset registers in the + configuration registers. + + .. note:: + If the pre-calibration slope rate < 1, the calibration procedure + will introduce a pre-DAC gain compensation. However, this may + saturate the pre-DAC voltage code. (See :class:`Config` notes). + Shuttler cannot cover the entire +/- 10 V range in this case. + + .. seealso:: + :meth:`Config.set_gain` + + :meth:`Config.set_offset` + + :param volts: A list of all 16 cubic DC-bias spline. + (See :class:`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 + + measurements = [0.0] * len(samples) + + for ch in range(16): + # Find the average slope rate and offset + for i in range(len(samples)): + self.core.break_realtime() + volts[ch].set_waveform( + shuttler_volt_to_mu(samples[i]), 0, 0, 0) + trigger.trigger(1 << ch) + measurements[i] = self.read_ch(ch) + + # Find the average output slope + slope_sum = 0.0 + for i in range(len(samples) - 1): + slope_sum += (measurements[i+1] - measurements[i])/(samples[i+1] - samples[i]) + slope_avg = slope_sum / (len(samples) - 1) + + gain_code = int32(1 / slope_avg * (2 ** 16)) & 0xffff + + # Scale the measurements by 1/slope, find average offset + offset_sum = 0.0 + for i in range(len(samples)): + offset_sum += (measurements[i] / slope_avg) - samples[i] + offset_avg = offset_sum / len(samples) + + offset_code = shuttler_volt_to_mu(-offset_avg) + + self.core.break_realtime() + config.set_gain(ch, gain_code) + + delay_mu(int64(self.core.ref_multiplier)) + config.set_offset(ch, offset_code) 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): diff --git a/artiq/dashboard/experiments.py b/artiq/dashboard/experiments.py index 8a0da9fd0..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() @@ -159,6 +151,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 +184,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 +658,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/examples/kasli_shuttler/kasli_efc.json b/artiq/examples/kasli_shuttler/kasli_efc.json new file mode 100644 index 000000000..aece1ee72 --- /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": "shuttler", + "ports": [0] + }, + { + "type": "dio", + "ports": [1], + "bank_direction_low": "input", + "bank_direction_high": "output" + } + ] +} diff --git a/artiq/examples/kasli_shuttler/repository/shuttler.py b/artiq/examples/kasli_shuttler/repository/shuttler.py new file mode 100644 index 000000000..22a3a776e --- /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 shuttler_phase_offset(offset_degree): + return round(offset_degree / 360 * (2 ** 16)) + +@portable +def shuttler_freq_mu(freq_mhz): + return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz) + +@portable +def shuttler_chirp_rate_mu(freq_mhz_per_us): + return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2)) + +@portable +def shuttler_freq_sweep(start_f_MHz, end_f_MHz, time_us): + return shuttler_chirp_rate_mu((end_f_MHz - start_f_MHz)/(time_us)) + +@portable +def shuttler_volt_amp_mu(volt): + return shuttler_volt_to_mu(volt) + +@portable +def shuttler_volt_damp_mu(volt_per_us): + return round(float(2) ** 32 * (volt_per_us / 20) / DAC_Fs_MHZ) + +@portable +def shuttler_volt_ddamp_mu(volt_per_us_square): + return round(float(2) ** 48 * (volt_per_us_square / 20) * 2 / (DAC_Fs_MHZ ** 2)) + +@portable +def shuttler_volt_dddamp_mu(volt_per_us_cube): + return round(float(2) ** 48 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3)) + +@portable +def shuttler_dds_amp_mu(volt): + return shuttler_volt_amp_mu(volt / CORDIC_GAIN) + +@portable +def shuttler_dds_damp_mu(volt_per_us): + return shuttler_volt_damp_mu(volt_per_us / CORDIC_GAIN) + +@portable +def shuttler_dds_ddamp_mu(volt_per_us_square): + return shuttler_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN) + +@portable +def shuttler_dds_dddamp_mu(volt_per_us_cube): + return shuttler_volt_dddamp_mu(volt_per_us_cube / CORDIC_GAIN) + +class Shuttler(EnvExperiment): + def build(self): + self.setattr_device("core") + self.setattr_device("core_dma") + self.setattr_device("scheduler") + self.shuttler0_leds = [ self.get_device("shuttler0_led{}".format(i)) for i in range(2) ] + self.setattr_device("shuttler0_config") + self.setattr_device("shuttler0_trigger") + self.shuttler0_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 + def record(self): + with self.core_dma.record("example_waveform"): + self.example_waveform() + + @kernel + def init(self): + self.led() + self.relay_init() + self.adc_init() + self.shuttler_reset() + + @kernel + def run(self): + self.core.reset() + self.core.break_realtime() + self.init() + + self.record() + example_waveform_handle = self.core_dma.get_handle("example_waveform") + + print("Example Waveforms are on OUT0 and OUT1") + self.core.break_realtime() + while not(self.scheduler.check_termination()): + delay(1*s) + self.core_dma.playback_handle(example_waveform_handle) + + @kernel + def shuttler_reset(self): + for i in range(16): + self.shuttler_channel_reset(i) + # To avoid RTIO Underflow + delay(50*us) + + @kernel + def shuttler_channel_reset(self, ch): + self.shuttler0_volt[ch].set_waveform( + a0=0, + a1=0, + a2=0, + a3=0, + ) + self.shuttler0_dds[ch].set_waveform( + b0=0, + b1=0, + b2=0, + b3=0, + c0=0, + c1=0, + c2=0, + ) + self.shuttler0_trigger.trigger(1 << ch) + + @kernel + def example_waveform(self): + # Equation of Output Waveform + # w(t_us) = a(t_us) + b(t_us) * cos(c(t_us)) + # Step 1: + # Enable the Output Relay of OUT0 and OUT1 + # Step 2: Cosine Wave Frequency Sweep from 10kHz to 50kHz in 500us + # OUT0: b(t_us) = 1 + # c(t_us) = 2 * pi * (0.08 * t_us ^ 2 + 0.01 * t_us) + # OUT1: b(t_us) = 1 + # c(t_us) = 2 * pi * (0.05 * t_us) + # Step 3(after 500us): Cosine Wave with 180 Degree Phase Offset + # OUT0: b(t_us) = 1 + # c(t_us) = 2 * pi * (0.05 * t_us) + pi + # OUT1: b(t_us) = 1 + # c(t_us) = 2 * pi * (0.05 * t_us) + # Step 4(after 500us): Cosine Wave with Amplitude Envelop + # OUT0: b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us + # c(t_us) = 2 * pi * (0.05 * t_us) + # OUT1: b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us + # c(t_us) = 0 + # Step 5(after 500us): Sawtooth Wave Modulated with 50kHz Cosine Wave + # OUT0: a(t_us) = 0.01 * t_us - 5 + # b(t_us) = 1 + # c(t_us) = 2 * pi * (0.05 * t_us) + # OUT1: a(t_us) = 0.01 * t_us - 5 + # Step 6(after 1000us): A Combination of Previous Waveforms + # OUT0: a(t_us) = 0.01 * t_us - 5 + # b(t_us) = -0.0001367187 * t_us ^ 2 + 0.06835937 * t_us + # c(t_us) = 2 * pi * (0.08 * t_us ^ 2 + 0.01 * t_us) + # Step 7(after 500us): Mirrored Waveform in Step 6 + # OUT0: a(t_us) = 2.5 + -0.01 * (1000 ^ 2) * t_us + # b(t_us) = 0.0001367187 * t_us ^ 2 - 0.06835937 * t_us + # c(t_us) = 2 * pi * (-0.08 * t_us ^ 2 + 0.05 * t_us) + pi + # Step 8(after 500us): + # Disable Output Relay of OUT0 and OUT1 + # Reset OUT0 and OUT1 + + ## Step 1 ## + self.shuttler0_relay.enable(0b11) + + ## Step 2 ## + start_f_MHz = 0.01 + end_f_MHz = 0.05 + duration_us = 500 + # OUT0 and OUT1 have their frequency and phase aligned at 500us + self.shuttler0_dds[0].set_waveform( + b0=shuttler_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=0, + c1=shuttler_freq_mu(start_f_MHz), + c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us), + ) + self.shuttler0_dds[1].set_waveform( + b0=shuttler_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=0, + c1=shuttler_freq_mu(end_f_MHz), + c2=0, + ) + self.shuttler0_trigger.trigger(0b11) + delay(500*us) + + ## Step 3 ## + # OUT0 and OUT1 has 180 degree phase difference + self.shuttler0_dds[0].set_waveform( + b0=shuttler_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=shuttler_phase_offset(180.0), + c1=shuttler_freq_mu(end_f_MHz), + c2=0, + ) + # Phase and Output Setting of OUT1 is retained + # if the channel is not triggered or config is not cleared + self.shuttler0_trigger.trigger(0b1) + delay(500*us) + + ## Step 4 ## + # b(0) = 0, b(250) = 8.545, b(500) = 0 + self.shuttler0_dds[0].set_waveform( + b0=0, + b1=shuttler_dds_damp_mu(0.06835937), + b2=shuttler_dds_ddamp_mu(-0.0001367187), + b3=0, + c0=0, + c1=shuttler_freq_mu(end_f_MHz), + c2=0, + ) + self.shuttler0_dds[1].set_waveform( + b0=0, + b1=shuttler_dds_damp_mu(0.06835937), + b2=shuttler_dds_ddamp_mu(-0.0001367187), + b3=0, + c0=0, + c1=0, + c2=0, + ) + self.shuttler0_trigger.trigger(0b11) + delay(500*us) + + ## Step 5 ## + self.shuttler0_volt[0].set_waveform( + a0=shuttler_volt_amp_mu(-5.0), + a1=int32(shuttler_volt_damp_mu(0.01)), + a2=0, + a3=0, + ) + self.shuttler0_dds[0].set_waveform( + b0=shuttler_dds_amp_mu(1.0), + b1=0, + b2=0, + b3=0, + c0=0, + c1=shuttler_freq_mu(end_f_MHz), + c2=0, + ) + self.shuttler0_volt[1].set_waveform( + a0=shuttler_volt_amp_mu(-5.0), + a1=int32(shuttler_volt_damp_mu(0.01)), + a2=0, + a3=0, + ) + self.shuttler0_dds[1].set_waveform( + b0=0, + b1=0, + b2=0, + b3=0, + c0=0, + c1=0, + c2=0, + ) + self.shuttler0_trigger.trigger(0b11) + delay(1000*us) + + ## Step 6 ## + self.shuttler0_volt[0].set_waveform( + a0=shuttler_volt_amp_mu(-2.5), + a1=int32(shuttler_volt_damp_mu(0.01)), + a2=0, + a3=0, + ) + self.shuttler0_dds[0].set_waveform( + b0=0, + b1=shuttler_dds_damp_mu(0.06835937), + b2=shuttler_dds_ddamp_mu(-0.0001367187), + b3=0, + c0=0, + c1=shuttler_freq_mu(start_f_MHz), + c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us), + ) + self.shuttler0_trigger.trigger(0b1) + self.shuttler_channel_reset(1) + delay(500*us) + + ## Step 7 ## + self.shuttler0_volt[0].set_waveform( + a0=shuttler_volt_amp_mu(2.5), + a1=int32(shuttler_volt_damp_mu(-0.01)), + a2=0, + a3=0, + ) + self.shuttler0_dds[0].set_waveform( + b0=0, + b1=shuttler_dds_damp_mu(-0.06835937), + b2=shuttler_dds_ddamp_mu(0.0001367187), + b3=0, + c0=shuttler_phase_offset(180.0), + c1=shuttler_freq_mu(end_f_MHz), + c2=shuttler_freq_sweep(end_f_MHz, start_f_MHz, duration_us), + ) + self.shuttler0_trigger.trigger(0b1) + delay(500*us) + + ## Step 8 ## + self.shuttler0_relay.enable(0) + self.shuttler_channel_reset(0) + self.shuttler_channel_reset(1) + + @kernel + def led(self): + for i in range(2): + for j in range(3): + self.shuttler0_leds[i].pulse(.1*s) + delay(.1*s) + + @kernel + def relay_init(self): + self.shuttler0_relay.init() + self.shuttler0_relay.enable(0x0000) + + @kernel + def adc_init(self): + delay_mu(int64(self.core.ref_multiplier)) + self.shuttler0_adc.power_up() + + delay_mu(int64(self.core.ref_multiplier)) + assert self.shuttler0_adc.read_id() >> 4 == 0x038d + + delay_mu(int64(self.core.ref_multiplier)) + # The actual output voltage is limited by the hardware, the calculated calibration gain and offset. + # For example, if the system has a calibration gain of 1.06, then the max output voltage = 10 / 1.06 = 9.43V. + # Setting a value larger than 9.43V will result in overflow. + self.shuttler0_adc.calibrate(self.shuttler0_volt, self.shuttler0_trigger, self.shuttler0_config) 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 + "" diff --git a/artiq/firmware/libboard_artiq/ad9117.rs b/artiq/firmware/libboard_artiq/ad9117.rs index 4c152e976..f3d3f3533 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 @@ -51,12 +53,17 @@ 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); 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)?; 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..3ead188b5 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) @@ -470,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; @@ -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..c524483fc 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: _destination, 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); 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/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index 84ea963c7..caba8b919 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -4,24 +4,27 @@ 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 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) @@ -593,12 +598,89 @@ class PeripheralManager: channel=rtio_offset+i) return 8 + 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"] = {{ + "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", @@ -606,11 +688,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): + # 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)) + + def process(output, primary_description, satellites): drtio_role = primary_description["drtio_role"] if drtio_role not in ("standalone", "master"): @@ -623,9 +712,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": @@ -635,12 +726,21 @@ 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) + print("device_db[\"satellite_cpu_targets\"][{}] = \"{}\"".format(destination, get_cpu_target(description)), 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) + 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) def main(): 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") diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 4fbe34686..dc7261c69 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -716,11 +716,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) ]))), @@ -731,7 +731,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) ]))), @@ -746,4 +746,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 c422615a4..9a20e5ae7 100644 --- a/artiq/gateware/eem_7series.py +++ b/artiq/gateware/eem_7series.py @@ -126,7 +126,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 @@ -134,7 +134,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, @@ -148,7 +148,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 90e4eff68..9728a0a1d 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 @@ -91,20 +93,54 @@ 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) + + # 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) + 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), + ) + + 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 + 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): + def __init__(self, sdm=False): self.clear = Signal() - self.data = Signal(16) + self.data = Signal(14) + self.gain = Signal(16) + self.offset = Signal(16) + + overflow = Signal() + underflow = Signal() ### @@ -113,10 +149,44 @@ class Dac(Module): Dds(self.clear), ] + # Infer signed multiplication + 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) + data_sink = Signal(16) + + if sdm: + self.submodules.sdm = SigmaDeltaModulator(16, 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 timing for the DSP + data_buf.eq(((data_raw * Cat(self.gain, ~self.gain[-1])) + (self.offset << 16))[16:]), + If(overflow, + data_sink.eq(0x7fff), + ).Elif(underflow, + data_sink.eq(0x8000), + ).Else( + data_sink.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])), + ] + + 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 @@ -229,11 +299,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") @@ -250,7 +352,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) @@ -258,13 +360,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, [], [])) @@ -274,9 +386,11 @@ 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) ] 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()) diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index 6312170c9..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"))) @@ -317,6 +319,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 +568,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) diff --git a/artiq/gateware/targets/kasli_generic.py b/artiq/gateware/targets/kasli_generic.py index 3a855933c..2f91de1b1 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 @@ -71,14 +70,13 @@ 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"], 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) @@ -177,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"] diff --git a/artiq/gui/applets.py b/artiq/gui/applets.py index a9e22b420..411075ec6 100644 --- a/artiq/gui/applets.py +++ b/artiq/gui/applets.py @@ -14,17 +14,181 @@ 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): + 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 = [] @@ -50,6 +214,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"]: @@ -83,6 +254,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 +281,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 +291,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 +310,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 +507,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 +520,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() @@ -397,11 +572,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) @@ -447,7 +623,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) @@ -674,12 +850,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) 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() 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): 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/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() 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()) diff --git a/doc/manual/conf.py b/doc/manual/conf.py index 7cb08a3a5..c004b4d85 100644 --- a/doc/manual/conf.py +++ b/doc/manual/conf.py @@ -35,7 +35,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/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index 9782ce274..077be2942 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -137,6 +137,12 @@ DAC/ADC drivers .. automodule:: artiq.coredevice.fastino :members: +:mod:`artiq.coredevice.shuttler` module +++++++++++++++++++++++++++++++++++++++++ + +.. automodule:: artiq.coredevice.shuttler + :members: + Miscellaneous ------------- diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 167291f77..0302ed40c 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) ------------------------------ 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 ************************