diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 8fa7d0336..88100ddf5 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -7,8 +7,7 @@ Reporting Issues/Bugs Thanks for `reporting issues to ARTIQ `_! You can also discuss issues and -ask questions on IRC (the `#m-labs channel on freenode -`_), the `Mattermost chat +ask questions on IRC (the #m-labs channel on OFTC), the `Mattermost chat `_, or on the `forum `_. The best bug reports are those which contain sufficient information. With diff --git a/MANIFEST.in b/MANIFEST.in index 52fac08ba..27aa85a93 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -4,3 +4,4 @@ include artiq/gui/logo*.svg include versioneer.py include artiq/_version.py include artiq/coredevice/coredevice_generic.schema.json +include artiq/compiler/kernel.ld diff --git a/README.rst b/README.rst index eda230b13..537745b9a 100644 --- a/README.rst +++ b/README.rst @@ -20,7 +20,7 @@ Like any open source software ARTIQ can equally be built and installed directly ARTIQ is supported by M-Labs and developed openly. Components, features, fixes, improvements, and extensions are often `funded `_ by and developed for the partnering research groups. -Core technologies employed include `Python `_, `Migen `_, `Migen-AXI `_, `Rust `_, `MiSoC `_/`mor1kx `_, `LLVM `_/`llvmlite `_, and `Qt5 `_. +Core technologies employed include `Python `_, `Migen `_, `Migen-AXI `_, `Rust `_, `MiSoC `_/`VexRiscv `_, `LLVM `_/`llvmlite `_, and `Qt5 `_. Website: https://m-labs.hk/artiq diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index c0cd659a5..75b1d3347 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -7,23 +7,48 @@ ARTIQ-7 ------- Highlights: + +* Support for Kasli-SoC, a new EEM carrier based on a Zynq SoC, enabling much faster kernel execution. +* Softcore targets now use the RISC-V architecture (VexRiscv) instead of OR1K (mor1kx). * WRPLL -* ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912 +* Compiler: + - Supports kernel decorator with paths. + - Faster compilation for large arrays/lists. * Phaser: - Improved documentation - - Expose the DAC coarse mixer and sif_sync + - Expose the DAC coarse mixer and ``sif_sync`` - Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs. + - Add helpers to align Phaser updates to the RTIO timeline (``get_next_frame_mu()``) +* ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912 +* On Kasli, the number of FIFO lanes in the scalable events dispatcher (SED) can now be configured in + the JSON hardware description file. +* New hardware support: + - HVAMP_8CH 8 channel HV amplifier for Fastino / Zotino +* ``artiq_ddb_template`` generates edge-counter keys that start with the key of the corresponding + TTL device (e.g. ``"ttl_0_counter"`` for the edge counter on TTL device``"ttl_0"``) +* ``artiq_master`` now has an ``--experiment-subdir`` option to scan only a subdirectory of the + repository when building the list of experiments. +* The configuration entry ``rtio_clock`` supports multiple clocking settings, deprecating the usage + of compile-time options. +* DRTIO: added support for 100MHz clock. +* Previously detected RTIO async errors are reported to the host after each kernel terminates and a + warning is logged. The warning is additional to the one already printed in the core device log upon + detection of the error. * HDF5 options can now be passed when creating datasets with ``set_dataset``. This allows in particular to use transparent compression filters as follows: ``set_dataset(name, value, hdf5_options={"compression": "gzip"})``. - Breaking changes: + * Updated Phaser-Upconverter default frequency 2.875 GHz. The new default uses the target PFD frequency of the hardware design. -* `Phaser.init()` now disables all Kasli-oscillators. This avoids full power RF output being +* ``Phaser.init()`` now disables all Kasli-oscillators. This avoids full power RF output being generated for some configurations. * Phaser: fixed coarse mixer frequency configuration +* Mirny: Added extra delays in ``ADF5356.sync()``. This avoids the need of an extra delay before + calling `ADF5356.init()`. +* DRTIO: Changed message alignment from 32-bits to 64-bits. +* The deprecated ``set_dataset(..., save=...)`` is no longer supported. * The internal dataset representation was changed to support tracking HDF5 options like e.g. a compression method. This requires changes to code reading the dataset persistence file (``dataset_db.pyon``) and to custom applets. @@ -37,7 +62,7 @@ Highlights: * New hardware support: - Phaser, a quad channel 1GS/s RF generator card with dual IQ upconverter and dual 5MS/s ADC and FPGA. - - Zynq SoC core devices, enabling kernels to run on 1 GHz CPU core with a floating-point + - Zynq SoC core device (ZC706), enabling kernels to run on 1 GHz CPU core with a floating-point unit for faster computations. This currently requires an external repository (https://git.m-labs.hk/m-labs/artiq-zynq). - Mirny 4-channel wide-band PLL/VCO-based microwave frequency synthesiser @@ -93,6 +118,9 @@ Breaking changes: * ``quamash`` has been replaced with ``qasync``. * Protocols are updated to use device endian. * Analyzer dump format includes a byte for device endianness. +* To support variable numbers of Urukul cards in the future, the + ``artiq.coredevice.suservo.SUServo`` constructor now accepts two device name lists, + ``cpld_devices`` and ``dds_devices``, rather than four individual arguments. * Experiment classes with underscore-prefixed names are now ignored when ``artiq_client`` determines which experiment to submit (consistent with ``artiq_run``). diff --git a/artiq/applets/plot_hist.py b/artiq/applets/plot_hist.py index 44a4d242e..298246a5c 100755 --- a/artiq/applets/plot_hist.py +++ b/artiq/applets/plot_hist.py @@ -1,6 +1,7 @@ #!/usr/bin/env python3 -import PyQt5 # make sure pyqtgraph imports Qt5 +import PyQt5 # make sure pyqtgraph imports Qt5 +from PyQt5.QtCore import QTimer import pyqtgraph from artiq.applets.simple import TitleApplet @@ -10,6 +11,9 @@ class HistogramPlot(pyqtgraph.PlotWidget): def __init__(self, args): pyqtgraph.PlotWidget.__init__(self) self.args = args + self.timer = QTimer() + self.timer.setSingleShot(True) + self.timer.timeout.connect(self.length_warning) def data_changed(self, data, mods, title): try: @@ -24,10 +28,20 @@ class HistogramPlot(pyqtgraph.PlotWidget): x = list(range(len(y)+1)) if len(y) and len(x) == len(y) + 1: + self.timer.stop() self.clear() self.plot(x, y, stepMode=True, fillLevel=0, brush=(0, 0, 255, 150)) self.setTitle(title) + else: + if not self.timer.isActive(): + self.timer.start(1000) + + def length_warning(self): + self.clear() + text = "⚠️ dataset lengths mismatch:\n"\ + "There should be one more bin boundaries than there are Y values" + self.addItem(pyqtgraph.TextItem(text)) def main(): diff --git a/artiq/applets/plot_xy.py b/artiq/applets/plot_xy.py index ae387cb75..32bf95b50 100755 --- a/artiq/applets/plot_xy.py +++ b/artiq/applets/plot_xy.py @@ -2,6 +2,7 @@ import numpy as np import PyQt5 # make sure pyqtgraph imports Qt5 +from PyQt5.QtCore import QTimer import pyqtgraph from artiq.applets.simple import TitleApplet @@ -12,6 +13,12 @@ class XYPlot(pyqtgraph.PlotWidget): def __init__(self, args): pyqtgraph.PlotWidget.__init__(self) self.args = args + self.timer = QTimer() + self.timer.setSingleShot(True) + self.timer.timeout.connect(self.length_warning) + self.mismatch = {'X values': False, + 'Error bars': False, + 'Fit values': False} def data_changed(self, data, mods, title): try: @@ -25,17 +32,29 @@ class XYPlot(pyqtgraph.PlotWidget): fit = data.get(self.args.fit, empty_dataset())["value"] if not len(y) or len(y) != len(x): - return + self.mismatch['X values'] = True + else: + self.mismatch['X values'] = False if error is not None and hasattr(error, "__len__"): if not len(error): error = None elif len(error) != len(y): - return + self.mismatch['Error bars'] = True + else: + self.mismatch['Error bars'] = False if fit is not None: if not len(fit): fit = None elif len(fit) != len(y): - return + self.mismatch['Fit values'] = True + else: + self.mismatch['Fit values'] = False + if not any(self.mismatch.values()): + self.timer.stop() + else: + if not self.timer.isActive(): + self.timer.start(1000) + return self.clear() self.plot(x, y, pen=None, symbol="x") @@ -51,6 +70,13 @@ class XYPlot(pyqtgraph.PlotWidget): xi = np.argsort(x) self.plot(x[xi], fit[xi]) + def length_warning(self): + self.clear() + text = "⚠️ dataset lengths mismatch:\n" + errors = ', '.join([k for k, v in self.mismatch.items() if v]) + text = ' '.join([errors, "should have the same length as Y values"]) + self.addItem(pyqtgraph.TextItem(text)) + def main(): applet = TitleApplet(XYPlot) diff --git a/artiq/applets/plot_xy_hist.py b/artiq/applets/plot_xy_hist.py index a3a35b4ad..01eab5421 100755 --- a/artiq/applets/plot_xy_hist.py +++ b/artiq/applets/plot_xy_hist.py @@ -2,6 +2,7 @@ import numpy as np from PyQt5 import QtWidgets +from PyQt5.QtCore import QTimer import pyqtgraph from artiq.applets.simple import SimpleApplet @@ -37,6 +38,10 @@ class XYHistPlot(QtWidgets.QSplitter): self.hist_plot_data = None self.args = args + self.timer = QTimer() + self.timer.setSingleShot(True) + self.timer.timeout.connect(self.length_warning) + self.mismatch = {'bins': False, 'xs': False} def _set_full_data(self, xs, histogram_bins, histograms_counts): self.xy_plot.clear() @@ -59,9 +64,9 @@ class XYHistPlot(QtWidgets.QSplitter): point.histogram_index = index point.histogram_counts = counts - self.hist_plot_data = self.hist_plot.plot( - stepMode=True, fillLevel=0, - brush=(0, 0, 255, 150)) + text = "click on a data point at the left\n"\ + "to see the corresponding histogram" + self.hist_plot.addItem(pyqtgraph.TextItem(text)) def _set_partial_data(self, xs, histograms_counts): ys = _compute_ys(self.histogram_bins, histograms_counts) @@ -87,8 +92,17 @@ class XYHistPlot(QtWidgets.QSplitter): else: self.arrow.setPos(position) self.selected_index = spot_item.histogram_index - self.hist_plot_data.setData(x=self.histogram_bins, - y=spot_item.histogram_counts) + + if self.hist_plot_data is None: + self.hist_plot.clear() + self.hist_plot_data = self.hist_plot.plot( + x=self.histogram_bins, + y=spot_item.histogram_counts, + stepMode=True, fillLevel=0, + brush=(0, 0, 255, 150)) + else: + self.hist_plot_data.setData(x=self.histogram_bins, + y=spot_item.histogram_counts) def _can_use_partial(self, mods): if self.hist_plot_data is None: @@ -117,11 +131,41 @@ class XYHistPlot(QtWidgets.QSplitter): histograms_counts = data[self.args.histograms_counts]["value"] except KeyError: return + if len(xs) != histograms_counts.shape[0]: + self.mismatch['xs'] = True + else: + self.mismatch['xs'] = False + if histograms_counts.shape[1] != len(histogram_bins) - 1: + self.mismatch['bins'] = True + else: + self.mismatch['bins'] = False + if any(self.mismatch.values()): + if not self.timer.isActive(): + self.timer.start(1000) + return + else: + self.timer.stop() if self._can_use_partial(mods): self._set_partial_data(xs, histograms_counts) else: self._set_full_data(xs, histogram_bins, histograms_counts) + def length_warning(self): + self.xy_plot.clear() + self.hist_plot.clear() + text = "⚠️ dataset lengths mismatch:\n\n" + if self.mismatch['bins']: + text = ''.join([text, + "bin boundaries should have the same length\n" + "as the first dimension of histogram counts."]) + if self.mismatch['bins'] and self.mismatch['xs']: + text = ''.join([text, '\n\n']) + if self.mismatch['xs']: + text = ''.join([text, + "point abscissas should have the same length\n" + "as the second dimension of histogram counts."]) + self.xy_plot.addItem(pyqtgraph.TextItem(text)) + def main(): applet = SimpleApplet(XYHistPlot) diff --git a/artiq/build_soc.py b/artiq/build_soc.py index e3a1f3360..bdf652948 100644 --- a/artiq/build_soc.py +++ b/artiq/build_soc.py @@ -2,6 +2,7 @@ import os import subprocess from migen import * +from migen.build.platforms.sinara import kasli from misoc.interconnect.csr import * from misoc.integration.builder import * @@ -57,11 +58,17 @@ def build_artiq_soc(soc, argdict): builder = Builder(soc, **argdict) builder.software_packages = [] builder.add_software_package("bootloader", os.path.join(firmware_dir, "bootloader")) + is_kasli_v1 = isinstance(soc.platform, kasli.Platform) and soc.platform.hw_rev in ("v1.0", "v1.1") if isinstance(soc, AMPSoC): - builder.add_software_package("libm") - builder.add_software_package("libprintf") - builder.add_software_package("libunwind") - builder.add_software_package("ksupport", os.path.join(firmware_dir, "ksupport")) + kernel_cpu_type = "vexriscv" if is_kasli_v1 else "vexriscv-g" + builder.add_software_package("libm", cpu_type=kernel_cpu_type) + builder.add_software_package("libprintf", cpu_type=kernel_cpu_type) + builder.add_software_package("libunwind", cpu_type=kernel_cpu_type) + builder.add_software_package("ksupport", os.path.join(firmware_dir, "ksupport"), cpu_type=kernel_cpu_type) + # Generate unwinder for soft float target (ARTIQ runtime) + # If the kernel lacks FPU, then the runtime unwinder is already generated + if not is_kasli_v1: + builder.add_software_package("libunwind") builder.add_software_package("runtime", os.path.join(firmware_dir, "runtime")) else: # Assume DRTIO satellite. diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index fe197d81b..89866c7c8 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -5,7 +5,7 @@ the references to the host objects and translates the functions annotated as ``@kernel`` when they are referenced. """ -import sys, os, re, linecache, inspect, textwrap, types as pytypes, numpy +import os, re, linecache, inspect, textwrap, types as pytypes, numpy from collections import OrderedDict, defaultdict from pythonparser import ast, algorithm, source, diagnostic, parse_buffer @@ -156,6 +156,67 @@ class ASTSynthesizer: return source.Range(self.source_buffer, range_from, range_to, expanded_from=self.expanded_from) + def fast_quote_list(self, value): + elts = [None] * len(value) + is_T = False + if len(value) > 0: + v = value[0] + is_T = True + if isinstance(v, int): + T = int + elif isinstance(v, float): + T = float + elif isinstance(v, numpy.int32): + T = numpy.int32 + elif isinstance(v, numpy.int64): + T = numpy.int64 + else: + is_T = False + if is_T: + for v in value: + if not isinstance(v, T): + is_T = False + break + if is_T: + is_int = T != float + if T == int: + typ = builtins.TInt() + elif T == float: + typ = builtins.TFloat() + elif T == numpy.int32: + typ = builtins.TInt32() + elif T == numpy.int64: + typ = builtins.TInt64() + else: + assert False + text = [repr(elt) for elt in value] + start = len(self.source) + self.source += ", ".join(text) + if is_int: + for i, (v, t) in enumerate(zip(value, text)): + l = len(t) + elts[i] = asttyped.NumT( + n=int(v), ctx=None, type=typ, + loc=source.Range( + self.source_buffer, start, start + l, + expanded_from=self.expanded_from)) + start += l + 2 + else: + for i, (v, t) in enumerate(zip(value, text)): + l = len(t) + elts[i] = asttyped.NumT( + n=v, ctx=None, type=typ, + loc=source.Range( + self.source_buffer, start, start + l, + expanded_from=self.expanded_from)) + start += l + 2 + else: + for index, elt in enumerate(value): + elts[index] = self.quote(elt) + if index < len(value) - 1: + self._add(", ") + return elts + def quote(self, value): """Construct an AST fragment equal to `value`.""" if value is None: @@ -217,21 +278,14 @@ class ASTSynthesizer: return asttyped.QuoteT(value=value, type=builtins.TByteArray(), loc=loc) elif isinstance(value, list): begin_loc = self._add("[") - elts = [] - for index, elt in enumerate(value): - elts.append(self.quote(elt)) - if index < len(value) - 1: - self._add(", ") + elts = self.fast_quote_list(value) end_loc = self._add("]") return asttyped.ListT(elts=elts, ctx=None, type=builtins.TList(), begin_loc=begin_loc, end_loc=end_loc, loc=begin_loc.join(end_loc)) elif isinstance(value, tuple): begin_loc = self._add("(") - elts = [] - for index, elt in enumerate(value): - elts.append(self.quote(elt)) - self._add(", ") + elts = self.fast_quote_list(value) end_loc = self._add(")") return asttyped.TupleT(elts=elts, ctx=None, type=types.TTuple([e.type for e in elts]), @@ -683,6 +737,7 @@ class Stitcher: self.embedding_map = EmbeddingMap() self.value_map = defaultdict(lambda: []) + self.definitely_changed = False def stitch_call(self, function, args, kwargs, callback=None): # We synthesize source code for the initial call so that @@ -703,13 +758,19 @@ class Stitcher: old_attr_count = None while True: inferencer.visit(self.typedtree) - typedtree_hash = typedtree_hasher.visit(self.typedtree) - attr_count = self.embedding_map.attribute_count() + if self.definitely_changed: + changed = True + self.definitely_changed = False + else: + typedtree_hash = typedtree_hasher.visit(self.typedtree) + attr_count = self.embedding_map.attribute_count() + changed = old_attr_count != attr_count or \ + old_typedtree_hash != typedtree_hash + old_typedtree_hash = typedtree_hash + old_attr_count = attr_count - if old_typedtree_hash == typedtree_hash and old_attr_count == attr_count: + if not changed: break - old_typedtree_hash = typedtree_hash - old_attr_count = attr_count # After we've discovered every referenced attribute, check if any kernel_invariant # specifications refers to ones we didn't encounter. @@ -837,6 +898,9 @@ class Stitcher: return types.TVar() def _quote_embedded_function(self, function, flags): + # we are now parsing new functions... definitely changed the type + self.definitely_changed = True + if isinstance(function, SpecializedFunction): host_function = function.host_function else: @@ -903,13 +967,11 @@ class Stitcher: # Parse. source_buffer = source.Buffer(source_code, filename, first_line) - lexer = source_lexer.Lexer(source_buffer, version=sys.version_info[0:2], - diagnostic_engine=self.engine) + lexer = source_lexer.Lexer(source_buffer, version=(3, 6), diagnostic_engine=self.engine) lexer.indent = [(initial_indent, source.Range(source_buffer, 0, len(initial_whitespace)), initial_whitespace)] - parser = source_parser.Parser(lexer, version=sys.version_info[0:2], - diagnostic_engine=self.engine) + parser = source_parser.Parser(lexer, version=(3, 6), diagnostic_engine=self.engine) function_node = parser.file_input().body[0] # Mangle the name, since we put everything into a single module. @@ -949,6 +1011,31 @@ class Stitcher: if annot is None: annot = builtins.TNone() + if isinstance(function, SpecializedFunction): + host_function = function.host_function + else: + host_function = function + + if hasattr(host_function, 'artiq_embedded'): + embedded_function = host_function.artiq_embedded.function + else: + embedded_function = host_function + + if isinstance(embedded_function, str): + embedded_function = host_function + + if isinstance(annot, str): + try: + annot = eval(annot, embedded_function.__globals__) + except Exception: + diag = diagnostic.Diagnostic( + "error", + "type annotation for {kind}, {annot}, cannot be evaluated", + {"kind": kind, "annot": repr(annot)}, + self._function_loc(function), + notes=self._call_site_note(call_loc, fn_kind)) + self.engine.process(diag) + if not isinstance(annot, types.Type): diag = diagnostic.Diagnostic("error", "type annotation for {kind}, '{annot}', is not an ARTIQ type", diff --git a/artiq/compiler/kernel.ld b/artiq/compiler/kernel.ld new file mode 100644 index 000000000..fafbea2af --- /dev/null +++ b/artiq/compiler/kernel.ld @@ -0,0 +1,56 @@ +/* Force ld to make the ELF header as loadable. */ +PHDRS +{ + headers PT_LOAD FILEHDR PHDRS ; + text PT_LOAD ; + data PT_LOAD ; + dynamic PT_DYNAMIC ; + eh_frame PT_GNU_EH_FRAME ; +} + +SECTIONS +{ + /* Push back .text section enough so that ld.lld not complain */ + . = SIZEOF_HEADERS; + + .text : + { + *(.text .text.*) + } : text + + .rodata : + { + *(.rodata .rodata.*) + } + + .eh_frame : + { + KEEP(*(.eh_frame)) + } : text + + .eh_frame_hdr : + { + KEEP(*(.eh_frame_hdr)) + } : text : eh_frame + + .data : + { + *(.data) + } : data + + .dynamic : + { + *(.dynamic) + } : data : dynamic + + .bss (NOLOAD) : ALIGN(4) + { + __bss_start = .; + *(.sbss .sbss.* .bss .bss.*); + . = ALIGN(4); + _end = .; + } + + . = ALIGN(0x1000); + _sstack_guard = .; +} diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py index 9ebc7907d..dc5b68be8 100644 --- a/artiq/compiler/targets.py +++ b/artiq/compiler/targets.py @@ -1,6 +1,6 @@ import os, sys, tempfile, subprocess, io from artiq.compiler import types, ir -from llvmlite_artiq import ir as ll, binding as llvm +from llvmlite import ir as ll, binding as llvm llvm.initialize() llvm.initialize_all_targets() @@ -28,8 +28,10 @@ class RunTool: for argument in self._pattern: cmdline.append(argument.format(**self._tempnames)) + # https://bugs.python.org/issue17023 + windows = os.name == "nt" process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE, - universal_newlines=True) + universal_newlines=True, shell=windows) stdout, stderr = process.communicate() if process.returncode != 0: raise Exception("{} invocation failed: {}". @@ -67,7 +69,7 @@ class Target: generated by the ARTIQ compiler will be deployed. :var triple: (string) - LLVM target triple, e.g. ``"or1k"`` + LLVM target triple, e.g. ``"riscv32"`` :var data_layout: (string) LLVM target data layout, e.g. ``"E-m:e-p:32:32-i64:32-f64:32-v64:32-v128:32-a:0:32-n32"`` :var features: (list of string) @@ -75,9 +77,6 @@ class Target: :var print_function: (string) Name of a formatted print functions (with the signature of ``printf``) provided by the target, e.g. ``"printf"``. - :var little_endian: (boolean) - Whether the code will be executed on a little-endian machine. This cannot be always - determined from data_layout due to JIT. :var now_pinning: (boolean) Whether the target implements the now-pinning RTIO optimization. """ @@ -85,7 +84,6 @@ class Target: data_layout = "" features = [] print_function = "printf" - little_endian = False now_pinning = True tool_ld = "ld.lld" @@ -100,7 +98,8 @@ class Target: lltarget = llvm.Target.from_triple(self.triple) llmachine = lltarget.create_target_machine( features=",".join(["+{}".format(f) for f in self.features]), - reloc="pic", codemodel="default") + reloc="pic", codemodel="default", + abiname="ilp32d" if isinstance(self, RV32GTarget) else "") llmachine.set_asm_verbosity(True) return llmachine @@ -182,6 +181,7 @@ class Target: def link(self, objects): """Link the relocatable objects into a shared library for this target.""" with RunTool([self.tool_ld, "-shared", "--eh-frame-hdr"] + + ["-T" + os.path.join(os.path.dirname(__file__), "kernel.ld")] + ["{{obj{}}}".format(index) for index in range(len(objects))] + ["-x"] + ["-o", "{output}"], @@ -252,32 +252,39 @@ class NativeTarget(Target): super().__init__() self.triple = llvm.get_default_triple() host_data_layout = str(llvm.targets.Target.from_default_triple().create_target_machine().target_data) - assert host_data_layout[0] in "eE" - self.little_endian = host_data_layout[0] == "e" -class OR1KTarget(Target): - triple = "or1k-linux" - data_layout = "E-m:e-p:32:32-i8:8:8-i16:16:16-i64:32:32-" \ - "f64:32:32-v64:32:32-v128:32:32-a0:0:32-n32" - features = ["mul", "div", "ffl1", "cmov", "addc"] +class RV32IMATarget(Target): + triple = "riscv32-unknown-linux" + data_layout = "e-m:e-p:32:32-i64:64-n32-S128" + features = ["m", "a"] print_function = "core_log" - little_endian = False now_pinning = True - tool_ld = "or1k-linux-ld" - tool_strip = "or1k-linux-strip" - tool_addr2line = "or1k-linux-addr2line" - tool_cxxfilt = "or1k-linux-c++filt" + tool_ld = "ld.lld" + tool_strip = "llvm-strip" + tool_addr2line = "llvm-addr2line" + tool_cxxfilt = "llvm-cxxfilt" + +class RV32GTarget(Target): + triple = "riscv32-unknown-linux" + data_layout = "e-m:e-p:32:32-i64:64-n32-S128" + features = ["m", "a", "f", "d"] + print_function = "core_log" + now_pinning = True + + tool_ld = "ld.lld" + tool_strip = "llvm-strip" + tool_addr2line = "llvm-addr2line" + tool_cxxfilt = "llvm-cxxfilt" class CortexA9Target(Target): triple = "armv7-unknown-linux-gnueabihf" data_layout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" features = ["dsp", "fp16", "neon", "vfp3"] print_function = "core_log" - little_endian = True now_pinning = False - tool_ld = "armv7-unknown-linux-gnueabihf-ld" - tool_strip = "armv7-unknown-linux-gnueabihf-strip" - tool_addr2line = "armv7-unknown-linux-gnueabihf-addr2line" - tool_cxxfilt = "armv7-unknown-linux-gnueabihf-c++filt" + tool_ld = "ld.lld" + tool_strip = "llvm-strip" + tool_addr2line = "llvm-addr2line" + tool_cxxfilt = "llvm-cxxfilt" diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py index 309073404..67a43c008 100644 --- a/artiq/compiler/testbench/jit.py +++ b/artiq/compiler/testbench/jit.py @@ -1,6 +1,6 @@ import os, sys, fileinput, ctypes from pythonparser import diagnostic -from llvmlite_artiq import binding as llvm +from llvmlite import binding as llvm from ..module import Module, Source from ..targets import NativeTarget diff --git a/artiq/compiler/testbench/llvmgen.py b/artiq/compiler/testbench/llvmgen.py index ed318b897..6bcf031c9 100644 --- a/artiq/compiler/testbench/llvmgen.py +++ b/artiq/compiler/testbench/llvmgen.py @@ -1,6 +1,6 @@ import sys, fileinput from pythonparser import diagnostic -from llvmlite_artiq import ir as ll +from llvmlite import ir as ll from ..module import Module, Source from ..targets import NativeTarget diff --git a/artiq/compiler/testbench/perf.py b/artiq/compiler/testbench/perf.py index cee4e3c2a..2d70bcf84 100644 --- a/artiq/compiler/testbench/perf.py +++ b/artiq/compiler/testbench/perf.py @@ -1,7 +1,7 @@ import sys, os from pythonparser import diagnostic from ..module import Module, Source -from ..targets import OR1KTarget +from ..targets import RV32GTarget from . import benchmark def main(): @@ -30,7 +30,7 @@ def main(): benchmark(lambda: Module(source), "ARTIQ transforms and validators") - benchmark(lambda: OR1KTarget().compile_and_link([module]), + benchmark(lambda: RV32GTarget().compile_and_link([module]), "LLVM optimization and linking") if __name__ == "__main__": diff --git a/artiq/compiler/testbench/perf_embedding.py b/artiq/compiler/testbench/perf_embedding.py index 41f09cb04..75267cb5b 100644 --- a/artiq/compiler/testbench/perf_embedding.py +++ b/artiq/compiler/testbench/perf_embedding.py @@ -5,7 +5,7 @@ from ...master.databases import DeviceDB, DatasetDB from ...master.worker_db import DeviceManager, DatasetManager from ..module import Module from ..embedding import Stitcher -from ..targets import OR1KTarget +from ..targets import RV32GTarget from . import benchmark @@ -45,7 +45,7 @@ def main(): stitcher = embed() module = Module(stitcher) - target = OR1KTarget() + target = RV32GTarget() llvm_ir = target.compile(module) elf_obj = target.assemble(llvm_ir) elf_shlib = target.link([elf_obj]) diff --git a/artiq/compiler/testbench/shlib.py b/artiq/compiler/testbench/shlib.py index 47209a979..0e2317a5c 100644 --- a/artiq/compiler/testbench/shlib.py +++ b/artiq/compiler/testbench/shlib.py @@ -1,7 +1,7 @@ import sys, os from pythonparser import diagnostic from ..module import Module, Source -from ..targets import OR1KTarget +from ..targets import RV32GTarget def main(): if not len(sys.argv) > 1: @@ -20,7 +20,7 @@ def main(): for filename in sys.argv[1:]: modules.append(Module(Source.from_filename(filename, engine=engine))) - llobj = OR1KTarget().compile_and_link(modules) + llobj = RV32GTarget().compile_and_link(modules) basename, ext = os.path.splitext(sys.argv[-1]) with open(basename + ".so", "wb") as f: diff --git a/artiq/compiler/transforms/inferencer.py b/artiq/compiler/transforms/inferencer.py index 7d6d065b7..fdc268d4f 100644 --- a/artiq/compiler/transforms/inferencer.py +++ b/artiq/compiler/transforms/inferencer.py @@ -6,6 +6,7 @@ from collections import OrderedDict from pythonparser import algorithm, diagnostic, ast from .. import asttyped, types, builtins from .typedtree_printer import TypedtreePrinter +from artiq.experiment import kernel def is_nested_empty_list(node): @@ -1662,7 +1663,14 @@ class Inferencer(algorithm.Visitor): def visit_FunctionDefT(self, node): for index, decorator in enumerate(node.decorator_list): - if types.is_builtin(decorator.type, "kernel") or \ + def eval_attr(attr): + if isinstance(attr.value, asttyped.QuoteT): + return getattr(attr.value.value, attr.attr) + return getattr(eval_attr(attr.value), attr.attr) + if isinstance(decorator, asttyped.AttributeT): + decorator = eval_attr(decorator) + if id(decorator) == id(kernel) or \ + types.is_builtin(decorator.type, "kernel") or \ isinstance(decorator, asttyped.CallT) and \ types.is_builtin(decorator.func.type, "kernel"): continue diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 49bbebc57..5f61db822 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -6,10 +6,11 @@ into LLVM intermediate representation. import os, re, types as pytypes, numpy from collections import defaultdict from pythonparser import ast, diagnostic -from llvmlite_artiq import ir as ll, binding as llvm +from llvmlite import ir as ll, binding as llvm from ...language import core as language_core from .. import types, builtins, ir from ..embedding import SpecializedFunction +from artiq.compiler.targets import RV32GTarget llvoid = ll.VoidType() @@ -37,93 +38,6 @@ def memoize(generator): return result return memoized -class DebugInfoEmitter: - def __init__(self, llmodule): - self.llmodule = llmodule - self.llcompileunit = None - self.cache = {} - - llident = self.llmodule.add_named_metadata('llvm.ident') - llident.add(self.emit_metadata(["ARTIQ"])) - - llflags = self.llmodule.add_named_metadata('llvm.module.flags') - llflags.add(self.emit_metadata([2, "Debug Info Version", 3])) - llflags.add(self.emit_metadata([2, "Dwarf Version", 4])) - - def emit_metadata(self, operands): - def map_operand(operand): - if operand is None: - return ll.Constant(llmetadata, None) - elif isinstance(operand, str): - return ll.MetaDataString(self.llmodule, operand) - elif isinstance(operand, int): - return ll.Constant(lli32, operand) - elif isinstance(operand, (list, tuple)): - return self.emit_metadata(operand) - else: - assert isinstance(operand, ll.NamedValue) - return operand - return self.llmodule.add_metadata(list(map(map_operand, operands))) - - def emit_debug_info(self, kind, operands, is_distinct=False): - return self.llmodule.add_debug_info(kind, operands, is_distinct) - - @memoize - def emit_file(self, source_buffer): - source_dir, source_file = os.path.split(source_buffer.name) - return self.emit_debug_info("DIFile", { - "filename": source_file, - "directory": source_dir, - }) - - @memoize - def emit_compile_unit(self, source_buffer): - return self.emit_debug_info("DICompileUnit", { - "language": ll.DIToken("DW_LANG_Python"), - "file": self.emit_file(source_buffer), - "producer": "ARTIQ", - "runtimeVersion": 0, - "emissionKind": 2, # full=1, lines only=2 - }, is_distinct=True) - - @memoize - def emit_subroutine_type(self, typ): - return self.emit_debug_info("DISubroutineType", { - "types": self.emit_metadata([None]) - }) - - @memoize - def emit_subprogram(self, func, llfunc): - source_buffer = func.loc.source_buffer - - if self.llcompileunit is None: - self.llcompileunit = self.emit_compile_unit(source_buffer) - llcompileunits = self.llmodule.add_named_metadata('llvm.dbg.cu') - llcompileunits.add(self.llcompileunit) - - display_name = "{}{}".format(func.name, types.TypePrinter().name(func.type)) - return self.emit_debug_info("DISubprogram", { - "name": func.name, - "linkageName": llfunc.name, - "type": self.emit_subroutine_type(func.type), - "file": self.emit_file(source_buffer), - "line": func.loc.line(), - "unit": self.llcompileunit, - "scope": self.emit_file(source_buffer), - "scopeLine": func.loc.line(), - "isLocal": func.is_internal, - "isDefinition": True, - "variables": self.emit_metadata([]) - }, is_distinct=True) - - @memoize - def emit_loc(self, loc, scope): - return self.emit_debug_info("DILocation", { - "line": loc.line(), - "column": loc.column(), - "scope": scope - }) - class ABILayoutInfo: """Caches DataLayout size/alignment lookup results. @@ -170,16 +84,8 @@ class LLVMIRGenerator: self.llmap = {} self.llobject_map = {} self.phis = [] - self.debug_info_emitter = DebugInfoEmitter(self.llmodule) self.empty_metadata = self.llmodule.add_metadata([]) - self.tbaa_tree = self.llmodule.add_metadata([ - ll.MetaDataString(self.llmodule, "ARTIQ TBAA") - ]) - self.tbaa_nowrite_call = self.llmodule.add_metadata([ - ll.MetaDataString(self.llmodule, "ref-only function call"), - self.tbaa_tree, - ll.Constant(lli64, 1) - ]) + self.quote_fail_msg = None def needs_sret(self, lltyp, may_be_large=True): if isinstance(lltyp, ll.VoidType): @@ -658,10 +564,6 @@ class LLVMIRGenerator: self.llbuilder = ll.IRBuilder() llblock_map = {} - if not func.is_generated: - lldisubprogram = self.debug_info_emitter.emit_subprogram(func, self.llfunction) - self.llfunction.set_metadata('dbg', lldisubprogram) - # First, map arguments. if self.has_sret(func.type): llactualargs = self.llfunction.args[1:] @@ -681,10 +583,6 @@ class LLVMIRGenerator: for block in func.basic_blocks: self.llbuilder.position_at_end(self.llmap[block]) for insn in block.instructions: - if insn.loc is not None and not func.is_generated: - self.llbuilder.debug_metadata = \ - self.debug_info_emitter.emit_loc(insn.loc, lldisubprogram) - llinsn = getattr(self, "process_" + type(insn).__name__)(insn) assert llinsn is not None self.llmap[insn] = llinsn @@ -1198,19 +1096,30 @@ class LLVMIRGenerator: return self.map(insn.operands[0]) elif insn.op == "now_mu": if self.target.now_pinning: - return self.llbuilder.load(self.llbuiltin("now"), name=insn.name) + # Word swap now.old as CPU is little endian + # Most significant word is stored in lower address (see generated csr.rs) + csr_offset = 2 if isinstance(self.target, RV32GTarget) else 1 + + llnow_hiptr = self.llbuilder.bitcast(self.llbuiltin("now"), lli32.as_pointer()) + llnow_loptr = self.llbuilder.gep(llnow_hiptr, [self.llindex(csr_offset)]) + llnow_hi = self.llbuilder.load(llnow_hiptr, name="now.hi") + llnow_lo = self.llbuilder.load(llnow_loptr, name="now.lo") + llzext_hi = self.llbuilder.zext(llnow_hi, lli64) + llshifted_hi = self.llbuilder.shl(llzext_hi, ll.Constant(lli64, 32)) + llzext_lo = self.llbuilder.zext(llnow_lo, lli64) + return self.llbuilder.or_(llshifted_hi, llzext_lo) else: return self.llbuilder.call(self.llbuiltin("now_mu"), []) elif insn.op == "at_mu": time, = insn.operands lltime = self.map(time) if self.target.now_pinning: + csr_offset = 2 if isinstance(self.target, RV32GTarget) else 1 + lltime_hi = self.llbuilder.trunc(self.llbuilder.lshr(lltime, ll.Constant(lli64, 32)), lli32) lltime_lo = self.llbuilder.trunc(lltime, lli32) llnow_hiptr = self.llbuilder.bitcast(self.llbuiltin("now"), lli32.as_pointer()) - llnow_loptr = self.llbuilder.gep(llnow_hiptr, [self.llindex(1)]) - if self.target.little_endian: - lltime_hi, lltime_lo = lltime_lo, lltime_hi + llnow_loptr = self.llbuilder.gep(llnow_hiptr, [self.llindex(csr_offset)]) llstore_hi = self.llbuilder.store_atomic(lltime_hi, llnow_hiptr, ordering="seq_cst", align=4) llstore_lo = self.llbuilder.store_atomic(lltime_lo, llnow_loptr, ordering="seq_cst", align=4) return llstore_lo @@ -1220,15 +1129,22 @@ class LLVMIRGenerator: interval, = insn.operands llinterval = self.map(interval) if self.target.now_pinning: - llnowptr = self.llbuiltin("now") - llnow = self.llbuilder.load(llnowptr, name="now.old") + # Word swap now.old as CPU is little endian + # Most significant word is stored in lower address (see generated csr.rs) + csr_offset = 2 if isinstance(self.target, RV32GTarget) else 1 + + llnow_hiptr = self.llbuilder.bitcast(self.llbuiltin("now"), lli32.as_pointer()) + llnow_loptr = self.llbuilder.gep(llnow_hiptr, [self.llindex(csr_offset)]) + llnow_hi = self.llbuilder.load(llnow_hiptr, name="now.hi") + llnow_lo = self.llbuilder.load(llnow_loptr, name="now.lo") + llzext_hi = self.llbuilder.zext(llnow_hi, lli64) + llshifted_hi = self.llbuilder.shl(llzext_hi, ll.Constant(lli64, 32)) + llzext_lo = self.llbuilder.zext(llnow_lo, lli64) + llnow = self.llbuilder.or_(llshifted_hi, llzext_lo) + lladjusted = self.llbuilder.add(llnow, llinterval, name="now.new") lladjusted_hi = self.llbuilder.trunc(self.llbuilder.lshr(lladjusted, ll.Constant(lli64, 32)), lli32) lladjusted_lo = self.llbuilder.trunc(lladjusted, lli32) - llnow_hiptr = self.llbuilder.bitcast(llnowptr, lli32.as_pointer()) - llnow_loptr = self.llbuilder.gep(llnow_hiptr, [self.llindex(1)]) - if self.target.little_endian: - lladjusted_hi, lladjusted_lo = lladjusted_lo, lladjusted_hi llstore_hi = self.llbuilder.store_atomic(lladjusted_hi, llnow_hiptr, ordering="seq_cst", align=4) llstore_lo = self.llbuilder.store_atomic(lladjusted_lo, llnow_loptr, ordering="seq_cst", align=4) return llstore_lo @@ -1258,26 +1174,32 @@ class LLVMIRGenerator: else: llfun = self.map(insn.static_target_function) llenv = self.llbuilder.extract_value(llclosure, 0, name="env.fun") - return llfun, [llenv] + list(llargs) + return llfun, [llenv] + list(llargs), {} def _prepare_ffi_call(self, insn): llargs = [] - byvals = [] + llarg_attrs = {} for i, arg in enumerate(insn.arguments()): llarg = self.map(arg) if isinstance(llarg.type, (ll.LiteralStructType, ll.IdentifiedStructType)): llslot = self.llbuilder.alloca(llarg.type) self.llbuilder.store(llarg, llslot) llargs.append(llslot) - byvals.append(i) + llarg_attrs[i] = "byval" else: llargs.append(llarg) + llretty = self.llty_of_type(insn.type, for_return=True) + is_sret = self.needs_sret(llretty) + if is_sret: + llarg_attrs = {i + 1: a for (i, a) in llarg_attrs.items()} + llarg_attrs[0] = "sret" + llfunname = insn.target_function().type.name llfun = self.llmodule.globals.get(llfunname) if llfun is None: - llretty = self.llty_of_type(insn.type, for_return=True) - if self.needs_sret(llretty): + # Function has not been declared in the current LLVM module, do it now. + if is_sret: llfunty = ll.FunctionType(llvoid, [llretty.as_pointer()] + [llarg.type for llarg in llargs]) else: @@ -1285,15 +1207,14 @@ class LLVMIRGenerator: llfun = ll.Function(self.llmodule, llfunty, insn.target_function().type.name) - if self.needs_sret(llretty): - llfun.args[0].add_attribute('sret') - byvals = [i + 1 for i in byvals] - for i in byvals: - llfun.args[i].add_attribute('byval') + for idx, attr in llarg_attrs.items(): + llfun.args[idx].add_attribute(attr) if 'nounwind' in insn.target_function().type.flags: llfun.attributes.add('nounwind') + if 'nowrite' in insn.target_function().type.flags: + llfun.attributes.add('inaccessiblememonly') - return llfun, list(llargs) + return llfun, list(llargs), llarg_attrs def _build_rpc(self, fun_loc, fun_type, args, llnormalblock, llunwindblock): llservice = ll.Constant(lli32, fun_type.service) @@ -1429,31 +1350,27 @@ class LLVMIRGenerator: insn.arguments(), llnormalblock=None, llunwindblock=None) elif types.is_external_function(functiontyp): - llfun, llargs = self._prepare_ffi_call(insn) + llfun, llargs, llarg_attrs = self._prepare_ffi_call(insn) else: - llfun, llargs = self._prepare_closure_call(insn) + llfun, llargs, llarg_attrs = self._prepare_closure_call(insn) if self.has_sret(functiontyp): llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), []) llresultslot = self.llbuilder.alloca(llfun.type.pointee.args[0].pointee) - llcall = self.llbuilder.call(llfun, [llresultslot] + llargs) + self.llbuilder.call(llfun, [llresultslot] + llargs, arg_attrs=llarg_attrs) llresult = self.llbuilder.load(llresultslot) self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr]) else: - llcall = llresult = self.llbuilder.call(llfun, llargs, name=insn.name) + llresult = self.llbuilder.call(llfun, llargs, name=insn.name, + arg_attrs=llarg_attrs) if isinstance(llresult.type, ll.VoidType): # We have NoneType-returning functions return void, but None is # {} elsewhere. llresult = ll.Constant(llunit, []) - # Never add TBAA nowrite metadata to a functon with sret! - # This leads to miscompilations. - if types.is_external_function(functiontyp) and 'nowrite' in functiontyp.flags: - llcall.set_metadata('tbaa', self.tbaa_nowrite_call) - return llresult def process_Invoke(self, insn): @@ -1466,16 +1383,17 @@ class LLVMIRGenerator: insn.arguments(), llnormalblock, llunwindblock) elif types.is_external_function(functiontyp): - llfun, llargs = self._prepare_ffi_call(insn) + llfun, llargs, llarg_attrs = self._prepare_ffi_call(insn) else: - llfun, llargs = self._prepare_closure_call(insn) + llfun, llargs, llarg_attrs = self._prepare_closure_call(insn) if self.has_sret(functiontyp): llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), []) llresultslot = self.llbuilder.alloca(llfun.type.pointee.args[0].pointee) llcall = self.llbuilder.invoke(llfun, [llresultslot] + llargs, - llnormalblock, llunwindblock, name=insn.name) + llnormalblock, llunwindblock, name=insn.name, + arg_attrs=llarg_attrs) self.llbuilder.position_at_start(llnormalblock) llresult = self.llbuilder.load(llresultslot) @@ -1483,7 +1401,7 @@ class LLVMIRGenerator: self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr]) else: llcall = self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock, - name=insn.name) + name=insn.name, arg_attrs=llarg_attrs) llresult = llcall # The !tbaa metadata is not legal to use with the invoke instruction, @@ -1492,8 +1410,24 @@ class LLVMIRGenerator: return llresult def _quote_listish_to_llglobal(self, value, elt_type, path, kind_name): - llelts = [self._quote(value[i], elt_type, lambda: path() + [str(i)]) - for i in range(len(value))] + fail_msg = "at " + ".".join(path()) + if len(value) > 0: + if builtins.is_int(elt_type): + int_typ = (int, numpy.int32, numpy.int64) + for v in value: + assert isinstance(v, int_typ), fail_msg + llty = self.llty_of_type(elt_type) + llelts = [ll.Constant(llty, int(v)) for v in value] + elif builtins.is_float(elt_type): + for v in value: + assert isinstance(v, float), fail_msg + llty = self.llty_of_type(elt_type) + llelts = [ll.Constant(llty, v) for v in value] + else: + llelts = [self._quote(value[i], elt_type, lambda: path() + [str(i)]) + for i in range(len(value))] + else: + llelts = [] lleltsary = ll.Constant(ll.ArrayType(self.llty_of_type(elt_type), len(llelts)), list(llelts)) name = self.llmodule.scope.deduplicate("quoted.{}".format(kind_name)) @@ -1502,60 +1436,63 @@ class LLVMIRGenerator: llglobal.linkage = "private" return llglobal.bitcast(lleltsary.type.element.as_pointer()) + def _quote_attributes(self, value, typ, path, value_id, llty): + llglobal = None + llfields = [] + emit_as_constant = True + for attr in typ.attributes: + if attr == "__objectid__": + objectid = self.embedding_map.store_object(value) + llfields.append(ll.Constant(lli32, objectid)) + + assert llglobal is None + if types.is_constructor(typ): + llglobal = self.get_class(typ) + else: + llglobal = ll.GlobalVariable(self.llmodule, llty.pointee, + name="O.{}".format(objectid)) + + self.llobject_map[value_id] = llglobal + else: + attrvalue = getattr(value, attr) + is_class_function = (types.is_constructor(typ) and + types.is_function(typ.attributes[attr]) and + not types.is_external_function(typ.attributes[attr])) + if is_class_function: + attrvalue = self.embedding_map.specialize_function(typ.instance, attrvalue) + if not (types.is_instance(typ) and attr in typ.constant_attributes): + emit_as_constant = False + llattrvalue = self._quote(attrvalue, typ.attributes[attr], + lambda: path() + [attr]) + llfields.append(llattrvalue) + if is_class_function: + llclosureptr = self.get_global_closure_ptr(typ, attr) + llclosureptr.initializer = llattrvalue + + llglobal.global_constant = emit_as_constant + llglobal.initializer = ll.Constant(llty.pointee, llfields) + llglobal.linkage = "private" + return llglobal + def _quote(self, value, typ, path): value_id = id(value) if value_id in self.llobject_map: return self.llobject_map[value_id] llty = self.llty_of_type(typ) - def _quote_attributes(): - llglobal = None - llfields = [] - emit_as_constant = True - for attr in typ.attributes: - if attr == "__objectid__": - objectid = self.embedding_map.store_object(value) - llfields.append(ll.Constant(lli32, objectid)) + fail_msg = self.quote_fail_msg + if fail_msg == None: + self.quote_fail_msg = fail_msg = "at " + ".".join(path()) - assert llglobal is None - if types.is_constructor(typ): - llglobal = self.get_class(typ) - else: - llglobal = ll.GlobalVariable(self.llmodule, llty.pointee, - name="O.{}".format(objectid)) - - self.llobject_map[value_id] = llglobal - else: - attrvalue = getattr(value, attr) - is_class_function = (types.is_constructor(typ) and - types.is_function(typ.attributes[attr]) and - not types.is_external_function(typ.attributes[attr])) - if is_class_function: - attrvalue = self.embedding_map.specialize_function(typ.instance, attrvalue) - if not (types.is_instance(typ) and attr in typ.constant_attributes): - emit_as_constant = False - llattrvalue = self._quote(attrvalue, typ.attributes[attr], - lambda: path() + [attr]) - llfields.append(llattrvalue) - if is_class_function: - llclosureptr = self.get_global_closure_ptr(typ, attr) - llclosureptr.initializer = llattrvalue - - llglobal.global_constant = emit_as_constant - llglobal.initializer = ll.Constant(llty.pointee, llfields) - llglobal.linkage = "private" - return llglobal - - fail_msg = "at " + ".".join(path()) if types.is_constructor(typ) or types.is_instance(typ): if types.is_instance(typ): # Make sure the class functions are quoted, as this has the side effect of # initializing the global closures. self._quote(type(value), typ.constructor, lambda: path() + ['__class__']) - return _quote_attributes() + return self._quote_attributes(value, typ, path, value_id, llty) elif types.is_module(typ): - return _quote_attributes() + return self._quote_attributes(value, typ, path, value_id, llty) elif builtins.is_none(typ): assert value is None, fail_msg return ll.Constant.literal_struct([]) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index e7b68a3a4..1d9336b4d 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -3,6 +3,7 @@ The :mod:`types` module contains the classes describing the types in :mod:`asttyped`. """ +import builtins import string from collections import OrderedDict from . import iodelay @@ -55,40 +56,39 @@ class TVar(Type): def __init__(self): self.parent = self + self.rank = 0 def find(self): - if self.parent is self: + parent = self.parent + if parent is self: return self else: # The recursive find() invocation is turned into a loop # because paths resulting from unification of large arrays # can easily cause a stack overflow. root = self - while root.__class__ == TVar: - if root is root.parent: - break - else: - root = root.parent - - # path compression - iter = self - while iter.__class__ == TVar: - if iter is root: - break - else: - iter, iter.parent = iter.parent, root - - return root + while parent.__class__ == TVar and root is not parent: + _, parent = root, root.parent = parent, parent.parent + return root.parent def unify(self, other): if other is self: return - other = other.find() - - if self.parent is self: - self.parent = other + x = other.find() + y = self.find() + if x is y: + return + if y.__class__ == TVar: + if x.__class__ == TVar: + if x.rank < y.rank: + x, y = y, x + y.parent = x + if x.rank == y.rank: + x.rank += 1 + else: + y.parent = x else: - self.find().unify(other) + y.unify(x) def fold(self, accum, fn): if self.parent is self: @@ -97,6 +97,8 @@ class TVar(Type): return self.find().fold(accum, fn) def __repr__(self): + if getattr(builtins, "__in_sphinx__", False): + return str(self) if self.parent is self: return "" % id(self) else: @@ -143,6 +145,8 @@ class TMono(Type): return fn(accum, self) def __repr__(self): + if getattr(builtins, "__in_sphinx__", False): + return str(self) return "artiq.compiler.types.TMono(%s, %s)" % (repr(self.name), repr(self.params)) def __getitem__(self, param): @@ -191,6 +195,8 @@ class TTuple(Type): return fn(accum, self) def __repr__(self): + if getattr(builtins, "__in_sphinx__", False): + return str(self) return "artiq.compiler.types.TTuple(%s)" % repr(self.elts) def __eq__(self, other): @@ -269,6 +275,8 @@ class TFunction(Type): return fn(accum, self) def __repr__(self): + if getattr(builtins, "__in_sphinx__", False): + return str(self) return "artiq.compiler.types.TFunction({}, {}, {})".format( repr(self.args), repr(self.optargs), repr(self.ret)) @@ -296,7 +304,7 @@ class TExternalFunction(TFunction): mangling rules). :ivar flags: (set of str) function flags. Flag ``nounwind`` means the function never raises an exception. - Flag ``nowrite`` means the function never writes any memory + Flag ``nowrite`` means the function never accesses any memory that the ARTIQ Python code can observe. :ivar broadcast_across_arrays: (bool) If True, the function is transparently applied element-wise when called @@ -362,6 +370,8 @@ class TRPC(Type): return fn(accum, self) def __repr__(self): + if getattr(builtins, "__in_sphinx__", False): + return str(self) return "artiq.compiler.types.TRPC({})".format(repr(self.ret)) def __eq__(self, other): @@ -399,6 +409,8 @@ class TBuiltin(Type): return fn(accum, self) def __repr__(self): + if getattr(builtins, "__in_sphinx__", False): + return str(self) return "artiq.compiler.types.{}({})".format(type(self).__name__, repr(self.name)) def __eq__(self, other): @@ -459,6 +471,8 @@ class TInstance(TMono): self.constant_attributes = set() def __repr__(self): + if getattr(builtins, "__in_sphinx__", False): + return str(self) return "artiq.compiler.types.TInstance({}, {})".format( repr(self.name), repr(self.attributes)) @@ -474,6 +488,8 @@ class TModule(TMono): self.constant_attributes = set() def __repr__(self): + if getattr(builtins, "__in_sphinx__", False): + return str(self) return "artiq.compiler.types.TModule({}, {})".format( repr(self.name), repr(self.attributes)) @@ -513,6 +529,8 @@ class TValue(Type): return fn(accum, self) def __repr__(self): + if getattr(builtins, "__in_sphinx__", False): + return str(self) return "artiq.compiler.types.TValue(%s)" % repr(self.value) def __eq__(self, other): @@ -571,6 +589,8 @@ class TDelay(Type): return not (self == other) def __repr__(self): + if getattr(builtins, "__in_sphinx__", False): + return str(self) if self.duration is None: return "<{}.TIndeterminateDelay>".format(__name__) elif self.cause is None: diff --git a/artiq/coredevice/ad53xx.py b/artiq/coredevice/ad53xx.py index 3445555d9..9a2b8eb2e 100644 --- a/artiq/coredevice/ad53xx.py +++ b/artiq/coredevice/ad53xx.py @@ -127,9 +127,9 @@ class AD53xx: transactions (default: 1) :param div_write: SPI clock divider for write operations (default: 4, 50MHz max SPI clock with {t_high, t_low} >=8ns) - :param div_read: SPI clock divider for read operations (default: 8, not - optimized for speed, but cf data sheet t22: 25ns min SCLK edge to SDO - valid) + :param div_read: SPI clock divider for read operations (default: 16, not + optimized for speed; datasheet says t22: 25ns min SCLK edge to SDO + valid, and suggests the SPI speed for reads should be <=20 MHz) :param vref: DAC reference voltage (default: 5.) :param offset_dacs: Initial register value for the two offset DACs, device dependent and must be set correctly for correct voltage to mu diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index f0cb8e54e..e93d3202b 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -324,7 +324,7 @@ class AD9910: self.bus.write(data_low) @kernel - def write_ram(self, data: TList(int32)): + def write_ram(self, data: TList(TInt32)): """Write data to RAM. The profile to write to and the step, start, and end address @@ -345,7 +345,7 @@ class AD9910: self.bus.write(data[len(data) - 1]) @kernel - def read_ram(self, data: TList(int32)): + def read_ram(self, data: TList(TInt32)): """Read data from RAM. The profile to read from and the step, start, and end address @@ -374,18 +374,25 @@ class AD9910: data[(n - preload) + i] = self.bus.read() @kernel - def set_cfr1(self, power_down: TInt32 = 0b0000, + def set_cfr1(self, + power_down: TInt32 = 0b0000, phase_autoclear: TInt32 = 0, - drg_load_lrr: TInt32 = 0, drg_autoclear: TInt32 = 0, - internal_profile: TInt32 = 0, ram_destination: TInt32 = 0, - ram_enable: TInt32 = 0, manual_osk_external: TInt32 = 0, - osk_enable: TInt32 = 0, select_auto_osk: TInt32 = 0): + drg_load_lrr: TInt32 = 0, + drg_autoclear: TInt32 = 0, + phase_clear: TInt32 = 0, + internal_profile: TInt32 = 0, + ram_destination: TInt32 = 0, + ram_enable: TInt32 = 0, + manual_osk_external: TInt32 = 0, + osk_enable: TInt32 = 0, + select_auto_osk: TInt32 = 0): """Set CFR1. See the AD9910 datasheet for parameter meanings. This method does not pulse IO_UPDATE. :param power_down: Power down bits. :param phase_autoclear: Autoclear phase accumulator. + :param phase_clear: Asynchronous, static reset of the phase accumulator. :param drg_load_lrr: Load digital ramp generator LRR. :param drg_autoclear: Autoclear digital ramp generator. :param internal_profile: Internal profile control. @@ -405,11 +412,41 @@ class AD9910: (drg_load_lrr << 15) | (drg_autoclear << 14) | (phase_autoclear << 13) | + (phase_clear << 11) | (osk_enable << 9) | (select_auto_osk << 8) | (power_down << 4) | 2) # SDIO input only, MSB first + @kernel + def set_cfr2(self, + asf_profile_enable: TInt32 = 1, + drg_enable: TInt32 = 0, + effective_ftw: TInt32 = 1, + sync_validation_disable: TInt32 = 0, + matched_latency_enable: TInt32 = 0): + """Set CFR2. See the AD9910 datasheet for parameter meanings. + + This method does not pulse IO_UPDATE. + + :param asf_profile_enable: Enable amplitude scale from single tone profiles. + :param drg_enable: Digital ramp enable. + :param effective_ftw: Read effective FTW. + :param sync_validation_disable: Disable the SYNC_SMP_ERR pin indicating + (active high) detection of a synchronization pulse sampling error. + :param matched_latency_enable: Simultaneous application of amplitude, + phase, and frequency changes to the DDS arrive at the output + + * matched_latency_enable = 0: in the order listed + * matched_latency_enable = 1: simultaneously. + """ + self.write32(_AD9910_REG_CFR2, + (asf_profile_enable << 24) | + (drg_enable << 19) | + (effective_ftw << 16) | + (matched_latency_enable << 7) | + (sync_validation_disable << 5)) + @kernel def init(self, blind: TBool = False): """Initialize and configure the DDS. @@ -442,7 +479,7 @@ class AD9910: # enable amplitude scale from profiles # read effective FTW # sync timing validation disable (enabled later) - self.write32(_AD9910_REG_CFR2, 0x01010020) + self.set_cfr2(sync_validation_disable=1) self.cpld.io_update.pulse(1 * us) cfr3 = (0x0807c000 | (self.pll_vco << 24) | (self.pll_cp << 19) | (self.pll_en << 8) | @@ -465,7 +502,7 @@ class AD9910: if i >= 100 - 1: raise ValueError("PLL lock timeout") delay(10 * us) # slack - if self.sync_data.sync_delay_seed >= 0: + if self.sync_data.sync_delay_seed >= 0 and not blind: self.tune_sync_delay(self.sync_data.sync_delay_seed) delay(1 * ms) @@ -481,7 +518,7 @@ class AD9910: @kernel def set_mu(self, ftw: TInt32, pow_: TInt32 = 0, asf: TInt32 = 0x3fff, phase_mode: TInt32 = _PHASE_MODE_DEFAULT, - ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 0): + ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 0) -> TInt32: """Set profile 0 data in machine units. This uses machine units (FTW, POW, ASF). The frequency tuning word @@ -786,7 +823,7 @@ class AD9910: @kernel def set(self, frequency: TFloat, phase: TFloat = 0.0, amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT, - ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 0): + ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 0) -> TFloat: """Set profile 0 data in SI units. .. seealso:: :meth:`set_mu` @@ -875,20 +912,26 @@ class AD9910: self.cpld.cfg_sw(self.chip_select - 4, state) @kernel - def set_sync(self, in_delay: TInt32, window: TInt32): + def set_sync(self, + in_delay: TInt32, + window: TInt32, + en_sync_gen: TInt32 = 0): """Set the relevant parameters in the multi device synchronization register. See the AD9910 datasheet for details. The SYNC clock generator preset value is set to zero, and the SYNC_OUT generator is - disabled. + disabled by default. :param in_delay: SYNC_IN delay tap (0-31) in steps of ~75ps :param window: Symmetric SYNC_IN validation window (0-15) in steps of ~75ps for both hold and setup margin. + :param en_sync_gen: Whether to enable the DDS-internal sync generator + (SYNC_OUT, cf. sync_sel == 1). Should be left off for the normal + use case, where the SYNC clock is supplied by the core device. """ self.write32(_AD9910_REG_SYNC, (window << 28) | # SYNC S/H validation delay (1 << 27) | # SYNC receiver enable - (0 << 26) | # SYNC generator disable + (en_sync_gen << 26) | # SYNC generator enable (0 << 25) | # SYNC generator SYS rising edge (0 << 18) | # SYNC preset (0 << 11) | # SYNC output delay @@ -904,9 +947,10 @@ class AD9910: Also modifies CFR2. """ - self.write32(_AD9910_REG_CFR2, 0x01010020) # clear SMP_ERR + self.set_cfr2(sync_validation_disable=1) # clear SMP_ERR self.cpld.io_update.pulse(1 * us) - self.write32(_AD9910_REG_CFR2, 0x01010000) # enable SMP_ERR + delay(10 * us) # slack + self.set_cfr2(sync_validation_disable=0) # enable SMP_ERR self.cpld.io_update.pulse(1 * us) @kernel @@ -984,7 +1028,7 @@ class AD9910: # set up DRG self.set_cfr1(drg_load_lrr=1, drg_autoclear=1) # DRG -> FTW, DRG enable - self.write32(_AD9910_REG_CFR2, 0x01090000) + self.set_cfr2(drg_enable=1) # no limits self.write64(_AD9910_REG_RAMP_LIMIT, -1, 0) # DRCTL=0, dt=1 t_SYNC_CLK @@ -1005,7 +1049,7 @@ class AD9910: ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW delay(100 * us) # slack # disable DRG - self.write32(_AD9910_REG_CFR2, 0x01010000) + self.set_cfr2(drg_enable=0) self.cpld.io_update.pulse_mu(8) return ftw & 1 diff --git a/artiq/coredevice/ad9912.py b/artiq/coredevice/ad9912.py index b214b9496..cb018c103 100644 --- a/artiq/coredevice/ad9912.py +++ b/artiq/coredevice/ad9912.py @@ -156,7 +156,7 @@ class AD9912: return self.cpld.get_channel_att(self.chip_select - 4) @kernel - def set_mu(self, ftw: TInt64, pow_: TInt32): + def set_mu(self, ftw: TInt64, pow_: TInt32 = 0): """Set profile 0 data in machine units. After the SPI transfer, the shared IO update pin is pulsed to diff --git a/artiq/coredevice/adf5356.py b/artiq/coredevice/adf5356.py index dae2c97cb..845067541 100644 --- a/artiq/coredevice/adf5356.py +++ b/artiq/coredevice/adf5356.py @@ -236,6 +236,7 @@ class ADF5356: Write all registers to the device. Attempts to lock the PLL. """ f_pfd = self.f_pfd() + delay(200 * us) # Slack if f_pfd <= 75.0 * MHz: for i in range(13, 0, -1): @@ -249,6 +250,7 @@ class ADF5356: n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb) = calculate_pll( self.f_vco(), f_pfd >> 1 ) + delay(200 * us) # Slack self.write( 13 diff --git a/artiq/coredevice/cache.py b/artiq/coredevice/cache.py index 7b01e96b9..6c222bb6f 100644 --- a/artiq/coredevice/cache.py +++ b/artiq/coredevice/cache.py @@ -2,11 +2,11 @@ from artiq.language.core import * from artiq.language.types import * -@syscall(flags={"nounwind", "nowrite"}) +@syscall(flags={"nounwind"}) def cache_get(key: TStr) -> TList(TInt32): raise NotImplementedError("syscall not simulated") -@syscall(flags={"nowrite"}) +@syscall def cache_put(key: TStr, value: TList(TInt32)) -> TNone: raise NotImplementedError("syscall not simulated") diff --git a/artiq/coredevice/comm_kernel.py b/artiq/coredevice/comm_kernel.py index cdb54a118..1b0111c49 100644 --- a/artiq/coredevice/comm_kernel.py +++ b/artiq/coredevice/comm_kernel.py @@ -621,6 +621,7 @@ class CommKernel: function = self._read_string() backtrace = [self._read_int32() for _ in range(self._read_int32())] + self._process_async_error() traceback = list(reversed(symbolizer(backtrace))) + \ [(filename, line, column, *demangler([function]), None)] @@ -635,6 +636,16 @@ class CommKernel: python_exn.artiq_core_exception = core_exn raise python_exn + def _process_async_error(self): + errors = self._read_int8() + if errors > 0: + map_name = lambda y, z: [f"{y}(s)"] if z else [] + errors = map_name("collision", errors & 2 ** 0) + \ + map_name("busy error", errors & 2 ** 1) + \ + map_name("sequence error", errors & 2 ** 2) + logger.warning(f"{(', '.join(errors[:-1]) + ' and ') if len(errors) > 1 else ''}{errors[-1]} " + f"reported during kernel execution") + def serve(self, embedding_map, symbolizer, demangler): while True: self._read_header() @@ -646,4 +657,5 @@ class CommKernel: raise exceptions.ClockFailure else: self._read_expect(Reply.KernelFinished) + self._process_async_error() return diff --git a/artiq/coredevice/comm_mgmt.py b/artiq/coredevice/comm_mgmt.py index 79fde1a81..7d8ef6f38 100644 --- a/artiq/coredevice/comm_mgmt.py +++ b/artiq/coredevice/comm_mgmt.py @@ -20,11 +20,6 @@ class Request(Enum): ConfigRemove = 14 ConfigErase = 15 - StartProfiler = 9 - StopProfiler = 10 - GetProfile = 11 - - Hotswap = 4 Reboot = 5 DebugAllocator = 8 @@ -39,8 +34,6 @@ class Reply(Enum): ConfigData = 7 - Profile = 5 - RebootImminent = 3 @@ -190,45 +183,6 @@ class CommMgmt: self._write_header(Request.ConfigErase) self._read_expect(Reply.Success) - def start_profiler(self, interval, edges_size, hits_size): - self._write_header(Request.StartProfiler) - self._write_int32(interval) - self._write_int32(edges_size) - self._write_int32(hits_size) - self._read_expect(Reply.Success) - - def stop_profiler(self): - self._write_header(Request.StopProfiler) - self._read_expect(Reply.Success) - - def stop_profiler(self): - self._write_header(Request.StopProfiler) - self._read_expect(Reply.Success) - - def get_profile(self): - self._write_header(Request.GetProfile) - self._read_expect(Reply.Profile) - - hits = {} - for _ in range(self._read_int32()): - addr = self._read_int32() - count = self._read_int32() - hits[addr] = count - - edges = {} - for _ in range(self._read_int32()): - caller = self._read_int32() - callee = self._read_int32() - count = self._read_int32() - edges[(caller, callee)] = count - - return hits, edges - - def hotswap(self, firmware): - self._write_header(Request.Hotswap) - self._write_bytes(firmware) - self._read_expect(Reply.RebootImminent) - def reboot(self): self._write_header(Request.Reboot) self._read_expect(Reply.RebootImminent) diff --git a/artiq/coredevice/comm_moninj.py b/artiq/coredevice/comm_moninj.py index 0c3a577dc..392ad701a 100644 --- a/artiq/coredevice/comm_moninj.py +++ b/artiq/coredevice/comm_moninj.py @@ -82,12 +82,12 @@ class CommMonInj: if not ty: return if ty == b"\x00": - payload = await self._reader.read(9) + payload = await self._reader.readexactly(9) channel, probe, value = struct.unpack( self.endian + "lbl", payload) self.monitor_cb(channel, probe, value) elif ty == b"\x01": - payload = await self._reader.read(6) + payload = await self._reader.readexactly(6) channel, override, value = struct.unpack( self.endian + "lbb", payload) self.injection_status_cb(channel, override, value) diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index d150df596..529c42f98 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -11,7 +11,7 @@ from artiq.language.units import * from artiq.compiler.module import Module from artiq.compiler.embedding import Stitcher -from artiq.compiler.targets import OR1KTarget, CortexA9Target +from artiq.compiler.targets import RV32IMATarget, RV32GTarget, CortexA9Target from artiq.coredevice.comm_kernel import CommKernel, CommKernelDummy # Import for side effects (creating the exception classes). @@ -71,11 +71,13 @@ class Core: "core", "ref_period", "coarse_ref_period", "ref_multiplier", } - def __init__(self, dmgr, host, ref_period, ref_multiplier=8, target="or1k"): + def __init__(self, dmgr, host, ref_period, ref_multiplier=8, target="rv32g"): self.ref_period = ref_period self.ref_multiplier = ref_multiplier - if target == "or1k": - self.target_cls = OR1KTarget + if target == "rv32g": + self.target_cls = RV32GTarget + elif target == "rv32ima": + self.target_cls = RV32IMATarget elif target == "cortexa9": self.target_cls = CortexA9Target else: diff --git a/artiq/coredevice/coredevice_generic.schema.json b/artiq/coredevice/coredevice_generic.schema.json index 47319fe3e..274d7e2aa 100644 --- a/artiq/coredevice/coredevice_generic.schema.json +++ b/artiq/coredevice/coredevice_generic.schema.json @@ -64,6 +64,13 @@ "type": "boolean", "default": false }, + "sed_lanes": { + "type": "number", + "minimum": 1, + "maximum": 32, + "default": 8, + "description": "Number of FIFOs in the SED, must be a power of 2" + }, "peripherals": { "type": "array", "items": { @@ -127,7 +134,7 @@ "properties": { "type": { "type": "string", - "enum": ["dio", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser"] + "enum": ["dio", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp"] }, "board": { "type": "string" @@ -455,6 +462,28 @@ }, "required": ["ports"] } + }, { + "title": "HVAmp", + "if": { + "properties": { + "type": { + "const": "hvamp" + } + } + }, + "then": { + "properties": { + "ports": { + "type": "array", + "items": { + "type": "integer" + }, + "minItems": 1, + "maxItems": 1 + } + }, + "required": ["ports"] + } }] } } diff --git a/artiq/coredevice/fastino.py b/artiq/coredevice/fastino.py index 26051e2a9..ede875863 100644 --- a/artiq/coredevice/fastino.py +++ b/artiq/coredevice/fastino.py @@ -3,7 +3,7 @@ streaming DAC. """ from numpy import int32 -from artiq.language.core import kernel, portable, delay +from artiq.language.core import kernel, portable, delay, delay_mu from artiq.coredevice.rtio import (rtio_output, rtio_output_wide, rtio_input_data) from artiq.language.units import us @@ -191,3 +191,82 @@ class Fastino: green LED. """ self.write(0x23, leds) + + @kernel + def set_continuous(self, channel_mask): + """Enable continuous DAC updates on channels regardless of new data + being submitted. + """ + self.write(0x25, channel_mask) + + @kernel + def stage_cic_mu(self, rate_mantissa, rate_exponent, gain_exponent): + """Stage machine unit CIC interpolator configuration. + """ + if rate_mantissa < 0 or rate_mantissa >= 1 << 6: + raise ValueError("rate_mantissa out of bounds") + if rate_exponent < 0 or rate_exponent >= 1 << 4: + raise ValueError("rate_exponent out of bounds") + if gain_exponent < 0 or gain_exponent >= 1 << 6: + raise ValueError("gain_exponent out of bounds") + config = rate_mantissa | (rate_exponent << 6) | (gain_exponent << 10) + self.write(0x26, config) + + @kernel + def stage_cic(self, rate) -> TInt32: + """Compute and stage interpolator configuration. + + This method approximates the desired interpolation rate using a 10 bit + floating point representation (6 bit mantissa, 4 bit exponent) and + then determines an optimal interpolation gain compensation exponent + to avoid clipping. Gains for rates that are powers of two are accurately + compensated. Other rates lead to overall less than unity gain (but more + than 0.5 gain). + + The overall gain including gain compensation is + `actual_rate**order/2**ceil(log2(actual_rate**order))` + where `order = 3`. + + Returns the actual interpolation rate. + """ + if rate <= 0 or rate > 1 << 16: + raise ValueError("rate out of bounds") + rate_mantissa = rate + rate_exponent = 0 + while rate_mantissa > 1 << 6: + rate_exponent += 1 + rate_mantissa >>= 1 + order = 3 + gain = 1 + for i in range(order): + gain *= rate_mantissa + gain_exponent = 0 + while gain > 1 << gain_exponent: + gain_exponent += 1 + gain_exponent += order*rate_exponent + assert gain_exponent <= order*16 + self.stage_cic_mu(rate_mantissa - 1, rate_exponent, gain_exponent) + return rate_mantissa << rate_exponent + + @kernel + def apply_cic(self, channel_mask): + """Apply the staged interpolator configuration on the specified channels. + + Each Fastino channel includes a fourth order (cubic) CIC interpolator with + variable rate change and variable output gain compensation (see + :meth:`stage_cic`). + + Channels using non-unity interpolation rate should have + continous DAC updates enabled (see :meth:`set_continuous`) unless + their output is supposed to be constant. + + This method resets and settles the affected interpolators. There will be + no output updates for the next `order = 3` input samples. + Affected channels will only accept one input sample per input sample + period. This method synchronizes the input sample period to the current + frame on the affected channels. + + If application of new interpolator settings results in a change of the + overall gain, there will be a corresponding output step. + """ + self.write(0x27, channel_mask) diff --git a/artiq/coredevice/mirny.py b/artiq/coredevice/mirny.py index a29f57aeb..ddcdd1931 100644 --- a/artiq/coredevice/mirny.py +++ b/artiq/coredevice/mirny.py @@ -40,9 +40,8 @@ class Mirny: :param refclk: Reference clock (SMA, MMCX or on-board 100 MHz oscillator) frequency in Hz :param clk_sel: Reference clock selection. - valid options are: "XO" - onboard crystal oscillator - "SMA" - front-panel SMA connector - "MMCX" - internal MMCX connector + Valid options are: "XO" - onboard crystal oscillator; + "SMA" - front-panel SMA connector; "MMCX" - internal MMCX connector. Passing an integer writes it as ``clk_sel`` in the CPLD's register 1. The effect depends on the hardware revision. :param core_device: Core device name (default: "core") diff --git a/artiq/coredevice/pcu.py b/artiq/coredevice/pcu.py deleted file mode 100644 index 6dd1ff150..000000000 --- a/artiq/coredevice/pcu.py +++ /dev/null @@ -1,77 +0,0 @@ -from .spr import mtspr, mfspr -from artiq.language.core import kernel - - -_MAX_SPRS_PER_GRP_BITS = 11 -_SPRGROUP_PC = 7 << _MAX_SPRS_PER_GRP_BITS -_SPR_PCMR_CP = 0x00000001 # Counter present -_SPR_PCMR_CISM = 0x00000004 # Count in supervisor mode -_SPR_PCMR_CIUM = 0x00000008 # Count in user mode -_SPR_PCMR_LA = 0x00000010 # Load access event -_SPR_PCMR_SA = 0x00000020 # Store access event -_SPR_PCMR_IF = 0x00000040 # Instruction fetch event -_SPR_PCMR_DCM = 0x00000080 # Data cache miss event -_SPR_PCMR_ICM = 0x00000100 # Insn cache miss event -_SPR_PCMR_IFS = 0x00000200 # Insn fetch stall event -_SPR_PCMR_LSUS = 0x00000400 # LSU stall event -_SPR_PCMR_BS = 0x00000800 # Branch stall event -_SPR_PCMR_DTLBM = 0x00001000 # DTLB miss event -_SPR_PCMR_ITLBM = 0x00002000 # ITLB miss event -_SPR_PCMR_DDS = 0x00004000 # Data dependency stall event -_SPR_PCMR_WPE = 0x03ff8000 # Watchpoint events - - -@kernel(flags={"nowrite", "nounwind"}) -def _PCCR(n): - return _SPRGROUP_PC + n - - -@kernel(flags={"nowrite", "nounwind"}) -def _PCMR(n): - return _SPRGROUP_PC + 8 + n - - -class CorePCU: - """Core device performance counter unit (PCU) access""" - def __init__(self, dmgr, core_device="core"): - self.core = dmgr.get(core_device) - - @kernel - def start(self): - """ - Configure and clear the kernel CPU performance counters. - - The eight counters are configured to count the following events: - * Load or store - * Instruction fetch - * Data cache miss - * Instruction cache miss - * Instruction fetch stall - * Load-store-unit stall - * Branch stall - * Data dependency stall - """ - for i in range(8): - if not mfspr(_PCMR(i)) & _SPR_PCMR_CP: - raise ValueError("counter not present") - mtspr(_PCMR(i), 0) - mtspr(_PCCR(i), 0) - mtspr(_PCMR(0), _SPR_PCMR_CISM | _SPR_PCMR_LA | _SPR_PCMR_SA) - mtspr(_PCMR(1), _SPR_PCMR_CISM | _SPR_PCMR_IF) - mtspr(_PCMR(2), _SPR_PCMR_CISM | _SPR_PCMR_DCM) - mtspr(_PCMR(3), _SPR_PCMR_CISM | _SPR_PCMR_ICM) - mtspr(_PCMR(4), _SPR_PCMR_CISM | _SPR_PCMR_IFS) - mtspr(_PCMR(5), _SPR_PCMR_CISM | _SPR_PCMR_LSUS) - mtspr(_PCMR(6), _SPR_PCMR_CISM | _SPR_PCMR_BS) - mtspr(_PCMR(7), _SPR_PCMR_CISM | _SPR_PCMR_DDS) - - @kernel - def get(self, r): - """ - Read the performance counters and store the counts in the - array provided. - - :param list[int] r: array to store the counter values - """ - for i in range(8): - r[i] = mfspr(_PCCR(i)) diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index c8c3c0938..5f7cd7108 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1,7 +1,7 @@ from numpy import int32, int64 from artiq.language.core import kernel, delay_mu, delay -from artiq.coredevice.rtio import rtio_output, rtio_input_data +from artiq.coredevice.rtio import rtio_output, rtio_input_data, rtio_input_timestamp from artiq.language.units import us, ns, ms, MHz from artiq.language.types import TInt32 from artiq.coredevice.dac34h84 import DAC34H84 @@ -92,7 +92,8 @@ class Phaser: The latency/group delay from the RTIO events setting :class:`PhaserOscillator` or :class:`PhaserChannel` DUC parameters all the way to the DAC outputs is deterministic. This enables deterministic - absolute phase with respect to other RTIO input and output events. + absolute phase with respect to other RTIO input and output events + (see `get_next_frame_mu()`). The four analog DAC outputs are passed through anti-aliasing filters. @@ -160,6 +161,7 @@ class Phaser: # self.core.seconds_to_mu(10*8*4*ns) # unfortunately this returns 319 assert self.core.ref_period == 1*ns self.t_frame = 10*8*4 + self.frame_tstamp = int64(0) self.clk_sel = clk_sel self.tune_fifo_offset = tune_fifo_offset self.sync_dly = sync_dly @@ -188,7 +190,7 @@ class Phaser: gw_rev = self.read8(PHASER_ADDR_GW_REV) if debug: - print(gw_rev) + print("gw_rev:", gw_rev) self.core.break_realtime() delay(.1*ms) # slack @@ -197,6 +199,12 @@ class Phaser: raise ValueError("large number of frame CRC errors") delay(.1*ms) # slack + # determine the origin for frame-aligned timestamps + self.measure_frame_timestamp() + if self.frame_tstamp < 0: + raise ValueError("frame timestamp measurement timed out") + delay(.1*ms) + # reset self.set_cfg(dac_resetb=0, dac_sleep=1, dac_txena=0, trf0_ps=1, trf1_ps=1, @@ -262,7 +270,7 @@ class Phaser: if self.tune_fifo_offset: fifo_offset = self.dac_tune_fifo_offset() if debug: - print(fifo_offset) + print("fifo_offset:", fifo_offset) self.core.break_realtime() # self.dac_write(0x20, 0x0000) # stop fifo sync @@ -274,7 +282,7 @@ class Phaser: delay(.1*ms) # slack if alarms & ~0x0040: # ignore PLL alarms (see DS) if debug: - print(alarms) + print("alarms:", alarms) self.core.break_realtime() # ignore alarms else: @@ -468,6 +476,27 @@ class Phaser: """ return self.read8(PHASER_ADDR_CRC_ERR) + @kernel + def measure_frame_timestamp(self): + """Measure the timestamp of an arbitrary frame and store it in `self.frame_tstamp`. + + To be used as reference for aligning updates to the FastLink frames. + See `get_next_frame_mu()`. + """ + rtio_output(self.channel_base << 8, 0) # read any register + self.frame_tstamp = rtio_input_timestamp(now_mu() + 4 * self.t_frame, self.channel_base) + delay(100 * us) + + @kernel + def get_next_frame_mu(self): + """Return the timestamp of the frame strictly after `now_mu()`. + + Register updates (DUC, DAC, TRF, etc.) scheduled at this timestamp and multiples + of `self.t_frame` later will have deterministic latency to output. + """ + n = int64((now_mu() - self.frame_tstamp) / self.t_frame) + return self.frame_tstamp + (n + 1) * self.t_frame + @kernel def set_sync_dly(self, dly): """Set SYNC delay. @@ -860,7 +889,7 @@ class PhaserChannel: By default, the new NCO phase applies on completion of the SPI transfer. This also causes a staged NCO frequency to be applied. - Different triggers for applying nco settings may be configured through + Different triggers for applying NCO settings may be configured through the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see `__init__()`). @@ -878,7 +907,7 @@ class PhaserChannel: By default, the new NCO phase applies on completion of the SPI transfer. This also causes a staged NCO frequency to be applied. - Different triggers for applying nco settings may be configured through + Different triggers for applying NCO settings may be configured through the `syncsel_mixerxx` fields in the `dac` configuration dictionary (see `__init__()`). @@ -1015,7 +1044,7 @@ class PhaserOscillator: """Phaser IQ channel oscillator (NCO/DDS). .. note:: Latencies between oscillators within a channel and between - oscillator paramters (amplitude and phase/frequency) are deterministic + oscillator parameters (amplitude and phase/frequency) are deterministic (with respect to the 25 MS/s sample clock) but not matched. """ kernel_invariants = {"channel", "base_addr"} diff --git a/artiq/coredevice/profiler.py b/artiq/coredevice/profiler.py deleted file mode 100644 index 5ed431915..000000000 --- a/artiq/coredevice/profiler.py +++ /dev/null @@ -1,92 +0,0 @@ -from collections import defaultdict -import subprocess - - -class Symbolizer: - def __init__(self, binary, triple, demangle=True): - cmdline = [ - triple + "-addr2line", "--exe=" + binary, - "--addresses", "--functions", "--inlines" - ] - if demangle: - cmdline.append("--demangle=rust") - self._addr2line = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, - universal_newlines=True) - - def symbolize(self, addr): - self._addr2line.stdin.write("0x{:08x}\n0\n".format(addr)) - self._addr2line.stdin.flush() - self._addr2line.stdout.readline() # 0x[addr] - - result = [] - while True: - function = self._addr2line.stdout.readline().rstrip() - - # check for end marker - if function == "0x00000000": # 0x00000000 - self._addr2line.stdout.readline() # ?? - self._addr2line.stdout.readline() # ??:0 - return result - - file, line = self._addr2line.stdout.readline().rstrip().split(":") - - result.append((function, file, line, addr)) - - -class CallgrindWriter: - def __init__(self, output, binary, triple, compression=True, demangle=True): - self._output = output - self._binary = binary - self._current = defaultdict(lambda: None) - self._ids = defaultdict(lambda: {}) - self._compression = compression - self._symbolizer = Symbolizer(binary, triple, demangle=demangle) - - def _write(self, fmt, *args, **kwargs): - self._output.write(fmt.format(*args, **kwargs)) - self._output.write("\n") - - def _spec(self, spec, value): - if self._current[spec] == value: - return - self._current[spec] = value - - if not self._compression or value == "??": - self._write("{}={}", spec, value) - return - - spec_ids = self._ids[spec] - if value in spec_ids: - self._write("{}=({})", spec, spec_ids[value]) - else: - spec_ids[value] = len(spec_ids) + 1 - self._write("{}=({}) {}", spec, spec_ids[value], value) - - def header(self): - self._write("# callgrind format") - self._write("version: 1") - self._write("creator: ARTIQ") - self._write("positions: instr line") - self._write("events: Hits") - self._write("") - self._spec("ob", self._binary) - self._spec("cob", self._binary) - - def hit(self, addr, count): - for function, file, line, addr in self._symbolizer.symbolize(addr): - self._spec("fl", file) - self._spec("fn", function) - self._write("0x{:08x} {} {}", addr, line, count) - - def edge(self, caller, callee, count): - edges = self._symbolizer.symbolize(callee) + self._symbolizer.symbolize(caller) - for (callee, caller) in zip(edges, edges[1:]): - function, file, line, addr = callee - self._spec("cfl", file) - self._spec("cfn", function) - self._write("calls={} 0x{:08x} {}", count, addr, line) - - function, file, line, addr = caller - self._spec("fl", file) - self._spec("fn", function) - self._write("0x{:08x} {} {}", addr, line, count) diff --git a/artiq/coredevice/spr.py b/artiq/coredevice/spr.py deleted file mode 100644 index 4f968d794..000000000 --- a/artiq/coredevice/spr.py +++ /dev/null @@ -1,12 +0,0 @@ -from artiq.language.core import syscall -from artiq.language.types import TInt32, TNone - - -@syscall(flags={"nounwind", "nowrite"}) -def mfspr(spr: TInt32) -> TInt32: - raise NotImplementedError("syscall not simulated") - - -@syscall(flags={"nowrite", "nowrite"}) -def mtspr(spr: TInt32, value: TInt32) -> TNone: - raise NotImplementedError("syscall not simulated") diff --git a/artiq/coredevice/suservo.py b/artiq/coredevice/suservo.py index 932adf35b..1d0a72dad 100644 --- a/artiq/coredevice/suservo.py +++ b/artiq/coredevice/suservo.py @@ -57,32 +57,26 @@ class SUServo: :param channel: RTIO channel number :param pgia_device: Name of the Sampler PGIA gain setting SPI bus - :param cpld0_device: Name of the first Urukul CPLD SPI bus - :param cpld1_device: Name of the second Urukul CPLD SPI bus - :param dds0_device: Name of the AD9910 device for the DDS on the first - Urukul - :param dds1_device: Name of the AD9910 device for the DDS on the second - Urukul + :param cpld_devices: Names of the Urukul CPLD SPI buses + :param dds_devices: Names of the AD9910 devices :param gains: Initial value for PGIA gains shift register (default: 0x0000). Knowledge of this state is not transferred between experiments. :param core_device: Core device name """ - kernel_invariants = {"channel", "core", "pgia", "cpld0", "cpld1", - "dds0", "dds1", "ref_period_mu"} + kernel_invariants = {"channel", "core", "pgia", "cplds", "ddses", + "ref_period_mu"} def __init__(self, dmgr, channel, pgia_device, - cpld0_device, cpld1_device, - dds0_device, dds1_device, + cpld_devices, dds_devices, gains=0x0000, core_device="core"): self.core = dmgr.get(core_device) self.pgia = dmgr.get(pgia_device) self.pgia.update_xfer_duration_mu(div=4, length=16) - self.dds0 = dmgr.get(dds0_device) - self.dds1 = dmgr.get(dds1_device) - self.cpld0 = dmgr.get(cpld0_device) - self.cpld1 = dmgr.get(cpld1_device) + assert len(dds_devices) == len(cpld_devices) + self.ddses = [dmgr.get(dds) for dds in dds_devices] + self.cplds = [dmgr.get(cpld) for cpld in cpld_devices] self.channel = channel self.gains = gains self.ref_period_mu = self.core.seconds_to_mu( @@ -109,17 +103,15 @@ class SUServo: sampler.SPI_CONFIG | spi.SPI_END, 16, 4, sampler.SPI_CS_PGIA) - self.cpld0.init(blind=True) - cfg0 = self.cpld0.cfg_reg - self.cpld0.cfg_write(cfg0 | (0xf << urukul.CFG_MASK_NU)) - self.dds0.init(blind=True) - self.cpld0.cfg_write(cfg0) + for i in range(len(self.cplds)): + cpld = self.cplds[i] + dds = self.ddses[i] - self.cpld1.init(blind=True) - cfg1 = self.cpld1.cfg_reg - self.cpld1.cfg_write(cfg1 | (0xf << urukul.CFG_MASK_NU)) - self.dds1.init(blind=True) - self.cpld1.cfg_write(cfg1) + cpld.init(blind=True) + prev_cpld_cfg = cpld.cfg_reg + cpld.cfg_write(prev_cpld_cfg | (0xf << urukul.CFG_MASK_NU)) + dds.init(blind=True) + cpld.cfg_write(prev_cpld_cfg) @kernel def write(self, addr, value): @@ -257,9 +249,11 @@ class Channel: self.servo = dmgr.get(servo_device) self.core = self.servo.core self.channel = channel - # FIXME: this assumes the mem channel is right after the control - # channels - self.servo_channel = self.channel + 8 - self.servo.channel + # This assumes the mem channel is right after the control channels + # Make sure this is always the case in eem.py + self.servo_channel = (self.channel + 4 * len(self.servo.cplds) - + self.servo.channel) + self.dds = self.servo.ddses[self.servo_channel // 4] @kernel def set(self, en_out, en_iir=0, profile=0): @@ -311,12 +305,8 @@ class Channel: see :meth:`dds_offset_to_mu` :param phase: DDS phase in turns """ - if self.servo_channel < 4: - dds = self.servo.dds0 - else: - dds = self.servo.dds1 - ftw = dds.frequency_to_ftw(frequency) - pow_ = dds.turns_to_pow(phase) + ftw = self.dds.frequency_to_ftw(frequency) + pow_ = self.dds.turns_to_pow(phase) offs = self.dds_offset_to_mu(offset) self.set_dds_mu(profile, ftw, offs, pow_) diff --git a/artiq/coredevice/zotino.py b/artiq/coredevice/zotino.py index e56842be5..9052f589a 100644 --- a/artiq/coredevice/zotino.py +++ b/artiq/coredevice/zotino.py @@ -27,15 +27,15 @@ class Zotino(AD53xx): :param clr_device: CLR RTIO TTLOut channel name. :param div_write: SPI clock divider for write operations (default: 4, 50MHz max SPI clock) - :param div_read: SPI clock divider for read operations (default: 8, not - optimized for speed, but cf data sheet t22: 25ns min SCLK edge to SDO - valid) + :param div_read: SPI clock divider for read operations (default: 16, not + optimized for speed; datasheet says t22: 25ns min SCLK edge to SDO + valid, and suggests the SPI speed for reads should be <=20 MHz) :param vref: DAC reference voltage (default: 5.) :param core_device: Core device name (default: "core") """ def __init__(self, dmgr, spi_device, ldac_device=None, clr_device=None, - div_write=4, div_read=8, vref=5., core="core"): + div_write=4, div_read=16, vref=5., core="core"): AD53xx.__init__(self, dmgr=dmgr, spi_device=spi_device, ldac_device=ldac_device, clr_device=clr_device, chip_select=_SPI_CS_DAC, div_write=div_write, diff --git a/artiq/dashboard/datasets.py b/artiq/dashboard/datasets.py index e0c3a7981..23fc995cb 100644 --- a/artiq/dashboard/datasets.py +++ b/artiq/dashboard/datasets.py @@ -3,8 +3,9 @@ import logging import numpy as np from PyQt5 import QtCore, QtWidgets +from sipyco import pyon -from artiq.tools import short_format +from artiq.tools import short_format, exc_to_warning from artiq.gui.tools import LayoutWidget, QRecursiveFilterProxyModel from artiq.gui.models import DictSyncTreeSepModel from artiq.gui.scientific_spinbox import ScientificSpinBox @@ -82,6 +83,68 @@ class StringEditor(Editor): return self.edit_widget.text() +class Creator(QtWidgets.QDialog): + def __init__(self, parent, dataset_ctl): + QtWidgets.QDialog.__init__(self, parent=parent) + self.dataset_ctl = dataset_ctl + + self.setWindowTitle("Create dataset") + grid = QtWidgets.QGridLayout() + grid.setRowMinimumHeight(1, 40) + grid.setColumnMinimumWidth(2, 60) + self.setLayout(grid) + + grid.addWidget(QtWidgets.QLabel("Name:"), 0, 0) + self.name_widget = QtWidgets.QLineEdit() + grid.addWidget(self.name_widget, 0, 1) + + grid.addWidget(QtWidgets.QLabel("Value:"), 1, 0) + self.value_widget = QtWidgets.QLineEdit() + self.value_widget.setPlaceholderText('PYON (Python)') + grid.addWidget(self.value_widget, 1, 1) + self.data_type = QtWidgets.QLabel("data type") + grid.addWidget(self.data_type, 1, 2) + self.value_widget.textChanged.connect(self.dtype) + + grid.addWidget(QtWidgets.QLabel("Persist:"), 2, 0) + self.box_widget = QtWidgets.QCheckBox() + grid.addWidget(self.box_widget, 2, 1) + + self.ok = QtWidgets.QPushButton('&Ok') + self.ok.setEnabled(False) + self.cancel = QtWidgets.QPushButton('&Cancel') + self.buttons = QtWidgets.QDialogButtonBox(self) + self.buttons.addButton( + self.ok, QtWidgets.QDialogButtonBox.AcceptRole) + self.buttons.addButton( + self.cancel, QtWidgets.QDialogButtonBox.RejectRole) + grid.setRowStretch(3, 1) + grid.addWidget(self.buttons, 4, 0, 1, 3) + self.buttons.accepted.connect(self.accept) + self.buttons.rejected.connect(self.reject) + + def accept(self): + key = self.name_widget.text() + value = self.value_widget.text() + persist = self.box_widget.isChecked() + asyncio.ensure_future(exc_to_warning(self.dataset_ctl.set( + key, pyon.decode(value), persist))) + QtWidgets.QDialog.accept(self) + + def dtype(self): + txt = self.value_widget.text() + try: + result = pyon.decode(txt) + except: + pixmap = self.style().standardPixmap( + QtWidgets.QStyle.SP_MessageBoxWarning) + self.data_type.setPixmap(pixmap) + self.ok.setEnabled(False) + else: + self.data_type.setText(type(result).__name__) + self.ok.setEnabled(True) + + class Model(DictSyncTreeSepModel): def __init__(self, init): DictSyncTreeSepModel.__init__( @@ -120,6 +183,11 @@ class DatasetsDock(QtWidgets.QDockWidget): grid.addWidget(self.table, 1, 0) self.table.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) + create_action = QtWidgets.QAction("New dataset", self.table) + create_action.triggered.connect(self.create_clicked) + create_action.setShortcut("CTRL+N") + create_action.setShortcutContext(QtCore.Qt.WidgetShortcut) + self.table.addAction(create_action) edit_action = QtWidgets.QAction("Edit dataset", self.table) edit_action.triggered.connect(self.edit_clicked) edit_action.setShortcut("RETURN") @@ -146,6 +214,9 @@ class DatasetsDock(QtWidgets.QDockWidget): self.table_model_filter.setSourceModel(self.table_model) self.table.setModel(self.table_model_filter) + def create_clicked(self): + Creator(self, self.dataset_ctl).open() + def edit_clicked(self): idx = self.table.selectedIndexes() if idx: diff --git a/artiq/examples/kasli_suservo/device_db.py b/artiq/examples/kasli_suservo/device_db.py index d33bfb280..fdb85dc47 100644 --- a/artiq/examples/kasli_suservo/device_db.py +++ b/artiq/examples/kasli_suservo/device_db.py @@ -191,10 +191,8 @@ device_db = { "arguments": { "channel": 24, "pgia_device": "spi_sampler0_pgia", - "cpld0_device": "urukul0_cpld", - "cpld1_device": "urukul1_cpld", - "dds0_device": "urukul0_dds", - "dds1_device": "urukul1_dds" + "cpld_devices": ["urukul0_cpld", "urukul1_cpld"], + "dds_devices": ["urukul0_dds", "urukul1_dds"], } }, diff --git a/artiq/firmware/Cargo.lock b/artiq/firmware/Cargo.lock index 2874347c8..77d262538 100644 --- a/artiq/firmware/Cargo.lock +++ b/artiq/firmware/Cargo.lock @@ -1,56 +1,84 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + [[package]] name = "alloc_list" version = "0.0.0" [[package]] -name = "bitflags" -version = "1.0.3" +name = "bare-metal" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bit_field" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb6dd1c2376d2e096796e234a70e17e94cc2d5d54ff8ce42b28cef1d0d359a4" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "board_artiq" version = "0.0.0" dependencies = [ - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "board_misoc 0.0.0", - "build_misoc 0.0.0", - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "io 0.0.0", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "proto_artiq 0.0.0", + "bitflags", + "board_misoc", + "build_misoc", + "byteorder", + "crc", + "failure", + "failure_derive", + "io", + "log", + "proto_artiq", ] [[package]] name = "board_misoc" version = "0.0.0" dependencies = [ - "build_misoc 0.0.0", - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "smoltcp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "build_misoc", + "byteorder", + "cc", + "log", + "riscv", + "smoltcp", ] [[package]] name = "bootloader" version = "0.0.0" dependencies = [ - "board_misoc 0.0.0", - "build_misoc 0.0.0", - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)", - "smoltcp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "board_misoc", + "build_misoc", + "byteorder", + "crc", + "riscv", + "smoltcp", ] [[package]] name = "build_const" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7" [[package]] name = "build_misoc" @@ -58,31 +86,48 @@ version = "0.0.0" [[package]] name = "byteorder" -version = "1.2.3" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "cc" -version = "1.0.15" +version = "1.0.70" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26a6ce4b6a484fa3edb70f7efa6fc430fd2b87285fe8b84304fd0936faa0dc0" [[package]] name = "cfg-if" -version = "0.1.3" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "compiler_builtins" +version = "0.1.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3748f82c7d366a0b4950257d19db685d4958d2fa27c6d164a3f069fec42b748b" [[package]] name = "crc" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" dependencies = [ - "build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "build_const", ] [[package]] name = "cslice" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f8cb7306107e4b10e64994de6d3274bd08996a7c1322a27b86482392f96be0a" [[package]] name = "dyld" @@ -92,202 +137,290 @@ version = "0.0.0" name = "eh" version = "0.0.0" dependencies = [ - "cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "compiler_builtins", + "cslice", + "libc 0.1.0", + "unwind", ] [[package]] name = "failure" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82" [[package]] name = "failure_derive" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b" dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", - "synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "quote", + "syn", + "synstructure", ] [[package]] name = "fringe" -version = "1.1.0" -source = "git+https://github.com/m-labs/libfringe?rev=b8a6d8f#b8a6d8f68df0edaa3d67d9f3b7b62af9d3bb64a5" +version = "1.2.1" +source = "git+https://git.m-labs.hk/M-Labs/libfringe.git?rev=3ecbe5#3ecbe53f7644b18ee46ebd5b2ca12c9cbceec43a" dependencies = [ - "libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.101", ] [[package]] name = "io" version = "0.0.0" dependencies = [ - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", + "failure", + "failure_derive", ] [[package]] name = "ksupport" version = "0.0.0" dependencies = [ - "board_artiq 0.0.0", - "board_misoc 0.0.0", - "build_misoc 0.0.0", - "cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "dyld 0.0.0", - "eh 0.0.0", - "io 0.0.0", - "proto_artiq 0.0.0", + "board_artiq", + "board_misoc", + "build_misoc", + "cslice", + "dyld", + "eh", + "io", + "libc 0.1.0", + "proto_artiq", + "riscv", + "unwind", ] [[package]] -name = "libc" -version = "0.2.40" +name = "lazy_static" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.1.0" + +[[package]] +name = "libc" +version = "0.2.101" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cb00336871be5ed2c8ed44b60ae9959dc5b9f08539422ed43f09e34ecaeba21" [[package]] name = "log" -version = "0.4.1" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "cfg-if 1.0.0", ] [[package]] name = "log_buffer" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f033173c9486b7fe97a79c895c0a3483ae395ab6744c985d10078950e2492419" [[package]] name = "logger_artiq" version = "0.0.0" dependencies = [ - "board_misoc 0.0.0", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "log_buffer 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", + "board_misoc", + "log", + "log_buffer", ] [[package]] name = "managed" -version = "0.7.0" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c75de51135344a4f8ed3cfe2720dc27736f7711989703a0b43aadf3753c55577" + +[[package]] +name = "memchr" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" [[package]] name = "proto_artiq" version = "0.0.0" dependencies = [ - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "dyld 0.0.0", - "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "io 0.0.0", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "byteorder", + "cslice", + "dyld", + "failure", + "failure_derive", + "io", + "log", ] [[package]] name = "quote" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" + +[[package]] +name = "regex" +version = "1.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b" + +[[package]] +name = "riscv" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2f0b705d428e9d0f78e2bb73093887ee58a83c9688de3faedbb4c0631c4618e" +dependencies = [ + "bare-metal", + "bit_field", + "riscv-target", +] + +[[package]] +name = "riscv-target" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88aa938cda42a0cf62a20cfe8d139ff1af20c2e681212b5b34adb5a58333f222" +dependencies = [ + "lazy_static", + "regex", +] [[package]] name = "runtime" version = "0.0.0" dependencies = [ - "alloc_list 0.0.0", - "board_artiq 0.0.0", - "board_misoc 0.0.0", - "build_misoc 0.0.0", - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", - "eh 0.0.0", - "failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "fringe 1.1.0 (git+https://github.com/m-labs/libfringe?rev=b8a6d8f)", - "io 0.0.0", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", - "logger_artiq 0.0.0", - "managed 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", - "proto_artiq 0.0.0", - "smoltcp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", - "unwind_backtrace 0.0.0", + "alloc_list", + "board_artiq", + "board_misoc", + "build_misoc", + "byteorder", + "cslice", + "eh", + "failure", + "failure_derive", + "fringe", + "io", + "log", + "logger_artiq", + "managed", + "proto_artiq", + "riscv", + "smoltcp", + "unwind_backtrace", +] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", ] [[package]] name = "satman" version = "0.0.0" dependencies = [ - "board_artiq 0.0.0", - "board_misoc 0.0.0", - "build_misoc 0.0.0", - "log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "board_artiq", + "board_misoc", + "build_misoc", + "log", + "riscv", ] +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + [[package]] name = "smoltcp" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fe46639fd2ec79eadf8fe719f237a7a0bd4dac5d957f1ca5bbdbc1c3c39e53a" dependencies = [ - "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", - "byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)", - "managed 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags", + "byteorder", + "managed", ] [[package]] name = "syn" version = "0.11.11" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "quote", + "synom", + "unicode-xid", ] [[package]] name = "synom" version = "0.11.3" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" dependencies = [ - "unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid", ] [[package]] name = "synstructure" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" dependencies = [ - "quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)", - "syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)", + "quote", + "syn", ] [[package]] name = "unicode-xid" version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" + +[[package]] +name = "unwind" +version = "0.0.0" +dependencies = [ + "cfg-if 0.1.10", + "libc 0.1.0", +] [[package]] name = "unwind_backtrace" version = "0.0.0" - -[metadata] -"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" -"checksum build_const 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" -"checksum byteorder 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "74c0b906e9446b0a2e4f760cdb3fa4b2c48cdc6db8766a845c54b6ff063fd2e9" -"checksum cc 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0ebb87d1116151416c0cf66a0e3fb6430cccd120fd6300794b4dfaa050ac40ba" -"checksum cfg-if 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "405216fd8fe65f718daa7102ea808a946b6ce40c742998fbfd3463645552de18" -"checksum crc 1.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d663548de7f5cca343f1e0a48d14dcfb0e9eb4e079ec58883b7251539fa10aeb" -"checksum cslice 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0f8cb7306107e4b10e64994de6d3274bd08996a7c1322a27b86482392f96be0a" -"checksum failure 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "934799b6c1de475a012a02dab0ace1ace43789ee4b99bcfbf1a2e3e8ced5de82" -"checksum failure_derive 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c7cdda555bb90c9bb67a3b670a0f42de8e73f5981524123ad8578aafec8ddb8b" -"checksum fringe 1.1.0 (git+https://github.com/m-labs/libfringe?rev=b8a6d8f)" = "" -"checksum libc 0.2.40 (registry+https://github.com/rust-lang/crates.io-index)" = "6fd41f331ac7c5b8ac259b8bf82c75c0fb2e469bbf37d2becbba9a6a2221965b" -"checksum log 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "89f010e843f2b1a31dbd316b3b8d443758bc634bed37aabade59c686d644e0a2" -"checksum log_buffer 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f033173c9486b7fe97a79c895c0a3483ae395ab6744c985d10078950e2492419" -"checksum managed 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba6713e624266d7600e9feae51b1926c6a6a6bebb18ec5a8e11a5f1d5661baba" -"checksum quote 0.3.15 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e920b65c65f10b2ae65c831a81a073a89edd28c7cce89475bff467ab4167a" -"checksum smoltcp 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0fe46639fd2ec79eadf8fe719f237a7a0bd4dac5d957f1ca5bbdbc1c3c39e53a" -"checksum syn 0.11.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d3b891b9015c88c576343b9b3e41c2c11a51c219ef067b264bd9c8aa9b441dad" -"checksum synom 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a393066ed9010ebaed60b9eafa373d4b1baac186dd7e008555b0f702b51945b6" -"checksum synstructure 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3a761d12e6d8dcb4dcf952a7a89b475e3a9d69e4a69307e01a470977642914bd" -"checksum unicode-xid 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f860d7d29cf02cb2f3f359fd35991af3d30bac52c57d265a3c461074cb4dc" +dependencies = [ + "libc 0.1.0", + "unwind", +] diff --git a/artiq/firmware/bootloader/Cargo.toml b/artiq/firmware/bootloader/Cargo.toml index e89902308..5c2a54ba5 100644 --- a/artiq/firmware/bootloader/Cargo.toml +++ b/artiq/firmware/bootloader/Cargo.toml @@ -17,3 +17,4 @@ byteorder = { version = "1.0", default-features = false } crc = { version = "1.7", default-features = false } board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp"] } smoltcp = { version = "0.6.0", default-features = false, features = ["ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp"] } +riscv = { version = "0.6.0", features = ["inline-asm"] } diff --git a/artiq/firmware/bootloader/Makefile b/artiq/firmware/bootloader/Makefile index ee70ff22f..82baef3eb 100644 --- a/artiq/firmware/bootloader/Makefile +++ b/artiq/firmware/bootloader/Makefile @@ -3,15 +3,19 @@ include $(MISOC_DIRECTORY)/software/common.mak RUSTFLAGS += -Cpanic=abort +export XBUILD_SYSROOT_PATH=$(BUILDINC_DIRECTORY)/../sysroot + all:: bootloader.bin .PHONY: $(RUSTOUT)/libbootloader.a $(RUSTOUT)/libbootloader.a: - $(cargo) --manifest-path $(BOOTLOADER_DIRECTORY)/Cargo.toml + $(cargo) --target-dir ./cargo \ + --manifest-path $(BOOTLOADER_DIRECTORY)/Cargo.toml \ + --target $(BOOTLOADER_DIRECTORY)/../$(CARGO_TRIPLE).json bootloader.elf: $(RUSTOUT)/libbootloader.a $(link) -T $(BOOTLOADER_DIRECTORY)/bootloader.ld %.bin: %.elf $(objcopy) -O binary - $(MSCIMG) $@ + $(MSCIMG) $@ --little diff --git a/artiq/firmware/bootloader/bootloader.ld b/artiq/firmware/bootloader/bootloader.ld index 22aea8cb6..3837f3408 100644 --- a/artiq/firmware/bootloader/bootloader.ld +++ b/artiq/firmware/bootloader/bootloader.ld @@ -15,12 +15,10 @@ SECTIONS *(.text .text.*) } > rom - /* - * The compiler_builtins crate includes some GOTPC relocations, which require a GOT symbol, - * but don't actually need a GOT. This really ought to be fixed on rustc level, but I'm afraid - * it will add further complications to our build system that aren't pulling their weight. - */ - _GLOBAL_OFFSET_TABLE_ = .; + .eh_frame : + { + *(.eh_frame.*) + } > rom .rodata : { @@ -29,25 +27,25 @@ SECTIONS _end = .; } > rom - .crc ALIGN(4) : + .crc (NOLOAD) : ALIGN(4) { _crc = .; . += 4; - } + } > rom - .bss : + .bss (NOLOAD) : { _fbss = .; - *(.bss .bss.*) + *(.sbss .sbss.* .bss .bss.*); . = ALIGN(4); _ebss = .; } > sram - .stack : + .stack (NOLOAD) : ALIGN(16) { /* Ensure we have a certain amount of space available for stack. */ /*. = ORIGIN(sram) + LENGTH(sram) - 0x1a00; */ - . = ORIGIN(sram) + LENGTH(sram) - 4; + . = ORIGIN(sram) + LENGTH(sram) - 16; _fstack = .; } > sram } diff --git a/artiq/firmware/bootloader/main.rs b/artiq/firmware/bootloader/main.rs index ad6479486..9a41bdb4b 100644 --- a/artiq/firmware/bootloader/main.rs +++ b/artiq/firmware/bootloader/main.rs @@ -1,21 +1,23 @@ #![no_std] -#![feature(panic_implementation, panic_info_message)] +#![feature(panic_info_message)] extern crate crc; extern crate byteorder; extern crate smoltcp; #[macro_use] extern crate board_misoc; +extern crate riscv; -use core::{ptr, slice}; +use core::{ptr, slice, convert::TryFrom}; use crc::crc32; -use byteorder::{ByteOrder, BigEndian}; +use byteorder::{ByteOrder, LittleEndian}; use board_misoc::{ident, cache, sdram, config, boot, mem as board_mem}; #[cfg(has_slave_fpga_cfg)] use board_misoc::slave_fpga; #[cfg(has_ethmac)] use board_misoc::{clock, ethmac, net_settings}; use board_misoc::uart_console::Console; +use riscv::register::{mcause, mepc, mtval}; fn check_integrity() -> bool { extern { @@ -45,7 +47,7 @@ fn memory_test(total: &mut usize, wrong: &mut usize) -> bool { MEMORY[$index:expr] = $data:expr } ) => ({ - $prepare; + $prepare for $i in $range { unsafe { ptr::write_volatile(MEMORY.offset($index as isize), $data) }; *total += 1; @@ -54,7 +56,7 @@ fn memory_test(total: &mut usize, wrong: &mut usize) -> bool { cache::flush_cpu_dcache(); cache::flush_l2_cache(); - $prepare; + $prepare for $i in $range { if unsafe { ptr::read_volatile(MEMORY.offset($index as isize)) } != $data { *wrong += 1; @@ -119,8 +121,8 @@ fn load_slave_fpga() { const GATEWARE: *mut u8 = board_misoc::csr::CONFIG_SLAVE_FPGA_GATEWARE as *mut u8; let header = unsafe { slice::from_raw_parts(GATEWARE, 8) }; - let magic = BigEndian::read_u32(&header[0..]); - let length = BigEndian::read_u32(&header[4..]) as usize; + let magic = LittleEndian::read_u32(&header[0..]); + let length = LittleEndian::read_u32(&header[4..]) as usize; println!(" magic: 0x{:08x}, length: 0x{:08x}", magic, length); if magic != 0x5352544d { println!(" ...Error: bad magic"); @@ -155,8 +157,8 @@ fn flash_boot() { println!("Booting from flash..."); let header = unsafe { slice::from_raw_parts(FIRMWARE, 8) }; - let length = BigEndian::read_u32(&header[0..]) as usize; - let expected_crc = BigEndian::read_u32(&header[4..]); + let length = LittleEndian::read_u32(&header[0..]) as usize; + let expected_crc = LittleEndian::read_u32(&header[4..]); if length == 0 || length == 0xffffffff { println!("No firmware present"); @@ -517,8 +519,11 @@ pub extern fn main() -> i32 { } #[no_mangle] -pub extern fn exception(vect: u32, _regs: *const u32, pc: u32, ea: u32) { - panic!("exception {} at PC {:#08x}, EA {:#08x}", vect, pc, ea) +pub extern fn exception(_regs: *const u32) { + let pc = mepc::read(); + let cause = mcause::read().cause(); + let mtval = mtval::read(); + panic!("{:?} at PC {:#08x}, trap value {:#08x}", cause, u32::try_from(pc).unwrap(), mtval); } #[no_mangle] @@ -528,7 +533,7 @@ pub extern fn abort() { } #[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647} -#[panic_implementation] +#[panic_handler] pub fn panic_fmt(info: &core::panic::PanicInfo) -> ! { #[cfg(has_error_led)] unsafe { diff --git a/artiq/firmware/cargosha256.nix b/artiq/firmware/cargosha256.nix deleted file mode 100644 index 2bf5725cf..000000000 --- a/artiq/firmware/cargosha256.nix +++ /dev/null @@ -1 +0,0 @@ -"0ml6j4sxqrayqk25xkrikwg713mahfqa60nrx1jhrj8c2h3p07yk" diff --git a/artiq/firmware/ksupport/Cargo.toml b/artiq/firmware/ksupport/Cargo.toml index ba53a7c9a..a3c038f2e 100644 --- a/artiq/firmware/ksupport/Cargo.toml +++ b/artiq/firmware/ksupport/Cargo.toml @@ -20,3 +20,6 @@ dyld = { path = "../libdyld" } board_misoc = { path = "../libboard_misoc" } board_artiq = { path = "../libboard_artiq" } proto_artiq = { path = "../libproto_artiq" } +riscv = { version = "0.6.0", features = ["inline-asm"] } +libc = { path = "../libc" } +unwind = { path = "../libunwind" } diff --git a/artiq/firmware/ksupport/Makefile b/artiq/firmware/ksupport/Makefile index 297bad7bd..e1e3e48fd 100644 --- a/artiq/firmware/ksupport/Makefile +++ b/artiq/firmware/ksupport/Makefile @@ -7,21 +7,26 @@ CFLAGS += \ -I$(MISOC_DIRECTORY)/software/include/dyld LDFLAGS += --eh-frame-hdr \ + --nmagic \ -L../libm \ -L../libprintf \ -L../libunwind RUSTFLAGS += -Cpanic=unwind +export XBUILD_SYSROOT_PATH=$(BUILDINC_DIRECTORY)/../sysroot + all:: ksupport.elf .PHONY: $(RUSTOUT)/libksupport.a $(RUSTOUT)/libksupport.a: - $(cargo) --manifest-path $(KSUPPORT_DIRECTORY)/Cargo.toml + $(cargo) --target-dir ./cargo \ + --manifest-path $(KSUPPORT_DIRECTORY)/Cargo.toml \ + --target $(KSUPPORT_DIRECTORY)/../$(CARGO_TRIPLE).json ksupport.elf: $(RUSTOUT)/libksupport.a glue.o $(link) -T $(KSUPPORT_DIRECTORY)/ksupport.ld \ - -lunwind-elf -lprintf-float -lm + -lunwind-$(CPU)-elf -lprintf-float -lm %.o: $(KSUPPORT_DIRECTORY)/%.c $(compile) diff --git a/artiq/firmware/ksupport/api.rs b/artiq/firmware/ksupport/api.rs index 84e69dad5..7b219562c 100644 --- a/artiq/firmware/ksupport/api.rs +++ b/artiq/firmware/ksupport/api.rs @@ -131,9 +131,6 @@ static mut API: &'static [(&'static str, *const ())] = &[ api!(cache_get = ::cache_get), api!(cache_put = ::cache_put), - api!(mfspr = ::board_misoc::spr::mfspr), - api!(mtspr = ::board_misoc::spr::mtspr), - /* direct syscalls */ api!(rtio_init = ::rtio::init), api!(rtio_get_destination_status = ::rtio::get_destination_status), diff --git a/artiq/firmware/ksupport/eh_artiq.rs b/artiq/firmware/ksupport/eh_artiq.rs index 8bfc171a0..86473e2ba 100644 --- a/artiq/firmware/ksupport/eh_artiq.rs +++ b/artiq/firmware/ksupport/eh_artiq.rs @@ -8,14 +8,14 @@ // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. -#![allow(private_no_mangle_fns, non_camel_case_types)] +#![allow(non_camel_case_types)] use core::{ptr, mem}; use cslice::CSlice; use unwind as uw; use libc::{c_int, c_void}; -use eh::dwarf::{self, EHAction}; +use eh::dwarf::{self, EHAction, EHContext}; type _Unwind_Stop_Fn = extern "C" fn(version: c_int, actions: uw::_Unwind_Action, @@ -58,8 +58,8 @@ struct ExceptionInfo { #[cfg(target_arch = "x86_64")] const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX -#[cfg(any(target_arch = "or1k"))] -const UNWIND_DATA_REG: (i32, i32) = (3, 4); // R3, R4 +#[cfg(any(target_arch = "riscv32"))] +const UNWIND_DATA_REG: (i32, i32) = (10, 11); // X10, X11 #[export_name="__artiq_personality"] pub extern fn personality(version: c_int, @@ -74,13 +74,25 @@ pub extern fn personality(version: c_int, } let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8; - let ip = uw::_Unwind_GetIP(context) - 1; - let func_start = uw::_Unwind_GetRegionStart(context); + let mut ip_before_instr: c_int = 0; + let ip = uw::_Unwind_GetIPInfo(context, &mut ip_before_instr); + let eh_context = EHContext { + // The return address points 1 byte past the call instruction, + // which could be in the next IP range in LSDA range table. + ip: if ip_before_instr != 0 { ip } else { ip - 1 }, + func_start: uw::_Unwind_GetRegionStart(context), + get_text_start: &|| uw::_Unwind_GetTextRelBase(context), + get_data_start: &|| uw::_Unwind_GetDataRelBase(context), + }; let exception_info = &mut *(uw_exception as *mut ExceptionInfo); let exception = &exception_info.exception.unwrap(); - let eh_action = dwarf::find_eh_action(lsda, func_start, ip, exception.name); + let eh_action = match dwarf::find_eh_action(lsda, &eh_context) { + Ok(action) => action, + Err(_) => return uw::_URC_FATAL_PHASE1_ERROR, + }; + if actions as u32 & uw::_UA_SEARCH_PHASE as u32 != 0 { match eh_action { EHAction::None | diff --git a/artiq/firmware/ksupport/ksupport.ld b/artiq/firmware/ksupport/ksupport.ld index b139833f3..23e84a781 100644 --- a/artiq/firmware/ksupport/ksupport.ld +++ b/artiq/firmware/ksupport/ksupport.ld @@ -15,7 +15,7 @@ MEMORY { } /* Kernel stack is at the end of main RAM. */ -_fstack = ORIGIN(main_ram) + LENGTH(main_ram) - 4; +_fstack = ORIGIN(main_ram) + LENGTH(main_ram) - 16; /* Force ld to make the ELF header as loadable. */ PHDRS @@ -53,22 +53,22 @@ SECTIONS .eh_frame : { KEEP(*(.eh_frame)) - } :text + } > ksupport :text .eh_frame_hdr : { KEEP(*(.eh_frame_hdr)) - } :text :eh_frame + } > ksupport :text :eh_frame .data : { *(.data .data.*) } - .bss : + .bss (NOLOAD) : ALIGN(4) { _fbss = .; - *(.bss .bss.*) + *(.sbss .sbss.* .bss .bss.*); . = ALIGN(4); _ebss = .; } diff --git a/artiq/firmware/ksupport/lib.rs b/artiq/firmware/ksupport/lib.rs index 5e42fd100..d915d7e81 100644 --- a/artiq/firmware/ksupport/lib.rs +++ b/artiq/firmware/ksupport/lib.rs @@ -1,5 +1,5 @@ -#![feature(lang_items, asm, panic_unwind, libc, unwind_attributes, - panic_implementation, panic_info_message, nll)] +#![feature(lang_items, llvm_asm, panic_unwind, libc, unwind_attributes, + panic_info_message, nll)] #![no_std] extern crate libc; @@ -12,8 +12,9 @@ extern crate dyld; extern crate board_misoc; extern crate board_artiq; extern crate proto_artiq; +extern crate riscv; -use core::{mem, ptr, slice, str}; +use core::{mem, ptr, slice, str, convert::TryFrom}; use cslice::{CSlice, AsCSlice}; use io::Cursor; use dyld::Library; @@ -22,6 +23,7 @@ use proto_artiq::{kernel_proto, rpc_proto}; use kernel_proto::*; #[cfg(has_rtio_dma)] use board_misoc::csr; +use riscv::register::{mcause, mepc, mtval}; fn send(request: &Message) { unsafe { mailbox::send(request as *const _ as usize) } @@ -49,7 +51,7 @@ macro_rules! recv { } #[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647} -#[panic_implementation] +#[panic_handler] pub fn panic_fmt(info: &core::panic::PanicInfo) -> ! { if let Some(location) = info.location() { send(&Log(format_args!("panic at {}:{}:{}", @@ -120,7 +122,7 @@ pub extern fn send_to_rtio_log(text: CSlice) { } #[unwind(aborts)] -extern fn rpc_send(service: u32, tag: CSlice, data: *const *const ()) { +extern fn rpc_send(service: u32, tag: &CSlice, data: *const *const ()) { while !rpc_queue::empty() {} send(&RpcSend { async: false, @@ -131,7 +133,7 @@ extern fn rpc_send(service: u32, tag: CSlice, data: *const *const ()) { } #[unwind(aborts)] -extern fn rpc_send_async(service: u32, tag: CSlice, data: *const *const ()) { +extern fn rpc_send_async(service: u32, tag: &CSlice, data: *const *const ()) { while rpc_queue::full() {} rpc_queue::enqueue(|mut slice| { let length = { @@ -201,15 +203,18 @@ fn terminate(exception: &eh_artiq::Exception, backtrace: &mut [usize]) -> ! { } #[unwind(aborts)] -extern fn cache_get(key: CSlice) -> CSlice<'static, i32> { +extern fn cache_get<'a>(ret: &'a mut CSlice, key: &CSlice) -> &'a CSlice<'a, i32> { send(&CacheGetRequest { key: str::from_utf8(key.as_ref()).unwrap() }); - recv!(&CacheGetReply { value } => value.as_c_slice()) + recv!(&CacheGetReply { value } => { + *ret = value.as_c_slice(); + ret + }) } #[unwind(allowed)] -extern fn cache_put(key: CSlice, list: CSlice) { +extern fn cache_put(key: &CSlice, list: &CSlice) { send(&CachePutRequest { key: str::from_utf8(key.as_ref()).unwrap(), value: list.as_ref() @@ -243,7 +248,7 @@ fn dma_record_flush() { } #[unwind(allowed)] -extern fn dma_record_start(name: CSlice) { +extern fn dma_record_start(name: &CSlice) { let name = str::from_utf8(name.as_ref()).unwrap(); unsafe { @@ -324,7 +329,7 @@ unsafe fn dma_record_output_prepare(timestamp: i64, target: i32, #[unwind(aborts)] extern fn dma_record_output(target: i32, word: i32) { unsafe { - let timestamp = *(csr::rtio::NOW_HI_ADDR as *const i64); + let timestamp = ((csr::rtio::now_hi_read() as i64) << 32) | (csr::rtio::now_lo_read() as i64); let data = dma_record_output_prepare(timestamp, target, 1); data.copy_from_slice(&[ (word >> 0) as u8, @@ -340,7 +345,7 @@ extern fn dma_record_output_wide(target: i32, words: CSlice) { assert!(words.len() <= 16); // enforce the hardware limit unsafe { - let timestamp = *(csr::rtio::NOW_HI_ADDR as *const i64); + let timestamp = ((csr::rtio::now_hi_read() as i64) << 32) | (csr::rtio::now_lo_read() as i64); let mut data = dma_record_output_prepare(timestamp, target, words.len()); for word in words.as_ref().iter() { data[..4].copy_from_slice(&[ @@ -355,7 +360,7 @@ extern fn dma_record_output_wide(target: i32, words: CSlice) { } #[unwind(aborts)] -extern fn dma_erase(name: CSlice) { +extern fn dma_erase(name: &CSlice) { let name = str::from_utf8(name.as_ref()).unwrap(); send(&DmaEraseRequest { name: name }); @@ -368,7 +373,7 @@ struct DmaTrace { } #[unwind(allowed)] -extern fn dma_retrieve(name: CSlice) -> DmaTrace { +extern fn dma_retrieve(name: &CSlice) -> DmaTrace { let name = str::from_utf8(name.as_ref()).unwrap(); send(&DmaRetrieveRequest { name: name }); @@ -454,7 +459,7 @@ unsafe fn attribute_writeback(typeinfo: *const ()) { attributes = attributes.offset(1); if (*attribute).tag.len() > 0 { - rpc_send_async(0, (*attribute).tag, [ + rpc_send_async(0, &(*attribute).tag, [ &object as *const _ as *const (), &(*attribute).name as *const _ as *const (), (object as usize + (*attribute).offset) as *const () @@ -488,11 +493,16 @@ pub unsafe fn main() { let _end = library.lookup(b"_end").unwrap(); let __modinit__ = library.lookup(b"__modinit__").unwrap(); let typeinfo = library.lookup(b"typeinfo"); + let _sstack_guard = library.lookup(b"_sstack_guard").unwrap(); LIBRARY = Some(library); ptr::write_bytes(__bss_start as *mut u8, 0, (_end - __bss_start) as usize); + board_misoc::pmp::init_stack_guard(_sstack_guard as usize); + board_misoc::cache::flush_cpu_dcache(); + board_misoc::cache::flush_cpu_icache(); + (mem::transmute::(__modinit__))(); if let Some(typeinfo) = typeinfo { @@ -519,8 +529,11 @@ pub unsafe fn main() { #[no_mangle] #[unwind(allowed)] -pub extern fn exception(vect: u32, _regs: *const u32, pc: u32, ea: u32) { - panic!("exception {:?} at PC 0x{:x}, EA 0x{:x}", vect, pc, ea) +pub extern fn exception(_regs: *const u32) { + let pc = mepc::read(); + let cause = mcause::read().cause(); + let mtval = mtval::read(); + panic!("{:?} at PC {:#08x}, trap value {:#08x}", cause, u32::try_from(pc).unwrap(), mtval); } #[no_mangle] diff --git a/artiq/firmware/ksupport/rtio.rs b/artiq/firmware/ksupport/rtio.rs index a83a45ec1..d9f568f75 100644 --- a/artiq/firmware/ksupport/rtio.rs +++ b/artiq/firmware/ksupport/rtio.rs @@ -23,6 +23,8 @@ mod imp { pub const RTIO_I_STATUS_WAIT_STATUS: u8 = 4; pub const RTIO_I_STATUS_DESTINATION_UNREACHABLE: u8 = 8; + const OFFSET_MULTIPLE: isize = (csr::CONFIG_DATA_WIDTH_BYTES / 4) as isize; + pub extern fn init() { send(&RtioInitRequest); } @@ -47,19 +49,19 @@ mod imp { #[inline(always)] pub unsafe fn rtio_o_data_write(offset: usize, data: u32) { write_volatile( - csr::rtio::O_DATA_ADDR.offset((csr::rtio::O_DATA_SIZE - 1 - offset) as isize), + csr::rtio::O_DATA_ADDR.offset(OFFSET_MULTIPLE*(csr::rtio::O_DATA_SIZE - 1 - offset) as isize), data); } #[inline(always)] pub unsafe fn rtio_i_data_read(offset: usize) -> u32 { read_volatile( - csr::rtio::I_DATA_ADDR.offset((csr::rtio::I_DATA_SIZE - 1 - offset) as isize)) + csr::rtio::I_DATA_ADDR.offset(OFFSET_MULTIPLE*(csr::rtio::I_DATA_SIZE - 1 - offset) as isize)) } #[inline(never)] unsafe fn process_exceptional_status(channel: i32, status: u8) { - let timestamp = *(csr::rtio::NOW_HI_ADDR as *const i64); + let timestamp = ((csr::rtio::now_hi_read() as i64) << 32) | (csr::rtio::now_lo_read() as i64); if status & RTIO_O_STATUS_WAIT != 0 { while csr::rtio::o_status_read() & RTIO_O_STATUS_WAIT != 0 {} } diff --git a/artiq/firmware/liballoc_list/lib.rs b/artiq/firmware/liballoc_list/lib.rs index f01262007..345f5e15f 100644 --- a/artiq/firmware/liballoc_list/lib.rs +++ b/artiq/firmware/liballoc_list/lib.rs @@ -3,9 +3,6 @@ use core::{ptr, mem, fmt}; use core::alloc::{GlobalAlloc, Layout}; -// The minimum alignment guaranteed by the architecture. -const MIN_ALIGN: usize = 4; - const MAGIC_FREE: usize = 0xDEADDEAD; const MAGIC_BUSY: usize = 0xFEEDFEED; @@ -41,10 +38,6 @@ impl ListAlloc { unsafe impl GlobalAlloc for ListAlloc { unsafe fn alloc(&self, layout: Layout) -> *mut u8 { - if layout.align() > MIN_ALIGN { - panic!("cannot allocate with alignment {}", layout.align()) - } - let header_size = mem::size_of::
(); let size; if layout.size() % header_size != 0 { @@ -52,6 +45,7 @@ unsafe impl GlobalAlloc for ListAlloc { } else { size = layout.size() } + let align = layout.align(); let mut curr = self.root; while !curr.is_null() { @@ -67,20 +61,48 @@ unsafe impl GlobalAlloc for ListAlloc { next = (*curr).next; } - if (*curr).size > size + header_size * 2 { - // Split - let offset = header_size + size; - let next = (curr as *mut u8).offset(offset as isize) as *mut Header; + unsafe fn split(header: *mut Header, split_size: usize) { + let offset = mem::size_of::
() + split_size; + let next = (header as *mut u8).offset(offset as isize) as *mut Header; (*next).magic = MAGIC_FREE; - (*next).size = (*curr).size - offset; - (*next).next = (*curr).next; - (*curr).next = next; - (*curr).size = size; + (*next).size = (*header).size - offset; + (*next).next = (*header).next; + (*header).next = next; + (*header).size = split_size; } - if (*curr).size >= size { - (*curr).magic = MAGIC_BUSY; - return curr.offset(1) as *mut u8 + // Case 1: Memory can be allocated straight from the current chunk + if (curr.offset(1) as usize) % align == 0 { + // Check available space + if (*curr).size > size + header_size * 2 { + split(curr, size); + } + + if (*curr).size >= size { + (*curr).magic = MAGIC_BUSY; + return curr.offset(1) as *mut u8 + } + } + + // Case 2: Padding is needed to satisfy the alignment + else { + let alloc_addr = curr.offset(2) as usize; + let padding_size = align - (alloc_addr % align); + + if (*curr).size >= size + padding_size + header_size { + // Create a padding region + split(curr, padding_size); + + curr = (*curr).next; + + // Check if a padding is needed at the rear + if (*curr).size > size + header_size * 2 { + split(curr, size); + } + + (*curr).magic = MAGIC_BUSY; + return curr.offset(1) as *mut u8 + } } }, _ => panic!("heap corruption detected at {:p}", curr) diff --git a/artiq/firmware/libboard_artiq/drtioaux.rs b/artiq/firmware/libboard_artiq/drtioaux.rs index f72072702..818775d7b 100644 --- a/artiq/firmware/libboard_artiq/drtioaux.rs +++ b/artiq/firmware/libboard_artiq/drtioaux.rs @@ -137,11 +137,10 @@ pub fn send(linkno: u8, packet: &Packet) -> Result<(), Error> { packet.write_to(&mut writer)?; - let padding = 4 - (writer.position() % 4); - if padding != 4 { - for _ in 0..padding { - writer.write_u8(0)?; - } + // Pad till offset 4, insert checksum there + let padding = (12 - (writer.position() % 8)) % 8; + for _ in 0..padding { + writer.write_u8(0)?; } let checksum = crc::crc32::checksum_ieee(&writer.get_ref()[0..writer.position()]); diff --git a/artiq/firmware/libboard_artiq/lib.rs b/artiq/firmware/libboard_artiq/lib.rs index 448b10f9e..694805cfa 100644 --- a/artiq/firmware/libboard_artiq/lib.rs +++ b/artiq/firmware/libboard_artiq/lib.rs @@ -1,12 +1,10 @@ -#![feature(asm, lang_items, never_type)] +#![feature(lang_items, never_type)] #![no_std] extern crate failure; #[cfg(has_drtio)] #[macro_use] extern crate failure_derive; -#[macro_use] -extern crate bitflags; extern crate byteorder; extern crate crc; #[macro_use] @@ -15,8 +13,6 @@ extern crate io; extern crate board_misoc; extern crate proto_artiq; -pub mod pcr; - pub mod spi; #[cfg(has_kernel_cpu)] diff --git a/artiq/firmware/libboard_artiq/pcr.rs b/artiq/firmware/libboard_artiq/pcr.rs deleted file mode 100644 index d16575398..000000000 --- a/artiq/firmware/libboard_artiq/pcr.rs +++ /dev/null @@ -1,44 +0,0 @@ -use board_misoc::spr::*; - -bitflags! { - pub struct Counters: u32 { - const LA = SPR_PCMR_LA; - const SA = SPR_PCMR_SA; - const IF = SPR_PCMR_IF; - const DCM = SPR_PCMR_DCM; - const ICM = SPR_PCMR_ICM; - const IFS = SPR_PCMR_IFS; - const LSUS = SPR_PCMR_LSUS; - const BS = SPR_PCMR_BS; - const DTLBM = SPR_PCMR_DTLBM; - const ITLBM = SPR_PCMR_ITLBM; - const DDS = SPR_PCMR_DDS; - - const INSTRN = Self::IF.bits; - const MEMORY = Self::LA.bits | Self::SA.bits; - const STALL = Self::DCM.bits | Self::ICM.bits | Self::IFS.bits | - Self::LSUS.bits | Self::BS.bits | Self::DDS.bits ; - const MISS = Self::DTLBM.bits | Self::ITLBM.bits ; - } -} - -fn is_valid(index: u32) -> bool { - index < 8 && unsafe { mfspr(SPR_PCMR0 + index) } & SPR_PCMR_CP != 0 -} - -#[inline] -pub fn setup(index: u32, counters: Counters) { - debug_assert!(is_valid(index)); - - unsafe { - mtspr(SPR_PCMR0 + index, SPR_PCMR_CISM | SPR_PCMR_CIUM | counters.bits); - mtspr(SPR_PCCR0 + index, 0); - } -} - -#[inline] -pub fn read(index: u32) -> u32 { - unsafe { - mfspr(SPR_PCCR0 + index) - } -} diff --git a/artiq/firmware/libboard_artiq/rpc_queue.rs b/artiq/firmware/libboard_artiq/rpc_queue.rs index 0e2049c81..877ed8c89 100644 --- a/artiq/firmware/libboard_artiq/rpc_queue.rs +++ b/artiq/firmware/libboard_artiq/rpc_queue.rs @@ -1,9 +1,9 @@ use core::ptr::{read_volatile, write_volatile}; use core::slice; -use board_misoc::{mem, cache}; +use board_misoc::{mem, cache, csr::CONFIG_DATA_WIDTH_BYTES}; -const SEND_MAILBOX: *mut usize = (mem::MAILBOX_BASE + 4) as *mut usize; -const RECV_MAILBOX: *mut usize = (mem::MAILBOX_BASE + 8) as *mut usize; +const SEND_MAILBOX: *mut usize = (mem::MAILBOX_BASE + CONFIG_DATA_WIDTH_BYTES as usize) as *mut usize; +const RECV_MAILBOX: *mut usize = (mem::MAILBOX_BASE + (CONFIG_DATA_WIDTH_BYTES * 2) as usize) as *mut usize; const QUEUE_BEGIN: usize = 0x44000000; const QUEUE_END: usize = 0x44ffff80; diff --git a/artiq/firmware/libboard_misoc/Cargo.toml b/artiq/firmware/libboard_misoc/Cargo.toml index 81ebf979f..0d8d705cc 100644 --- a/artiq/firmware/libboard_misoc/Cargo.toml +++ b/artiq/firmware/libboard_misoc/Cargo.toml @@ -16,6 +16,7 @@ build_misoc = { path = "../libbuild_misoc" } byteorder = { version = "1.0", default-features = false } log = { version = "0.4", default-features = false, optional = true } smoltcp = { version = "0.6.0", default-features = false, optional = true } +riscv = { version = "0.6.0", features = ["inline-asm"] } [features] uart_console = [] diff --git a/artiq/firmware/libboard_misoc/build.rs b/artiq/firmware/libboard_misoc/build.rs index c0a5971eb..11d5aac2f 100644 --- a/artiq/firmware/libboard_misoc/build.rs +++ b/artiq/firmware/libboard_misoc/build.rs @@ -1,18 +1,16 @@ extern crate build_misoc; extern crate cc; -use std::env; use std::path::Path; fn main() { build_misoc::cfg(); - let triple = env::var("TARGET").unwrap(); - let arch = triple.split("-").next().unwrap(); - let vectors_path = Path::new(arch).join("vectors.S"); + let vectors_path = "riscv32/vectors.S"; - println!("cargo:rerun-if-changed={}", vectors_path.to_str().unwrap()); + println!("cargo:rerun-if-changed={}", vectors_path); cc::Build::new() - .file(vectors_path) + .flag("--target=riscv32-unknown-elf") + .file(Path::new(vectors_path)) .compile("vectors"); } diff --git a/artiq/firmware/libboard_misoc/config.rs b/artiq/firmware/libboard_misoc/config.rs index 7ed368efd..9a737e303 100644 --- a/artiq/firmware/libboard_misoc/config.rs +++ b/artiq/firmware/libboard_misoc/config.rs @@ -77,10 +77,10 @@ mod imp { mod lock { use core::slice; - use core::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; + use core::sync::atomic::{AtomicUsize, Ordering}; use super::Error; - static LOCKED: AtomicUsize = ATOMIC_USIZE_INIT; + static LOCKED: AtomicUsize = AtomicUsize::new(0); pub struct Lock; @@ -216,7 +216,7 @@ mod imp { let mut offset = 0; let mut iter = Iter::new(old_data); 'iter: while let Some(result) = iter.next() { - let (key, mut value) = result?; + let (key, value) = result?; if value.is_empty() { // This is a removed entry, ignore it. continue diff --git a/artiq/firmware/libboard_misoc/lib.rs b/artiq/firmware/libboard_misoc/lib.rs index 5e8a92972..3189ccfcd 100644 --- a/artiq/firmware/libboard_misoc/lib.rs +++ b/artiq/firmware/libboard_misoc/lib.rs @@ -1,5 +1,5 @@ #![no_std] -#![feature(asm, try_from)] +#![feature(llvm_asm)] extern crate byteorder; #[cfg(feature = "log")] @@ -7,10 +7,13 @@ extern crate log; #[cfg(feature = "smoltcp")] extern crate smoltcp; -#[cfg(target_arch = "or1k")] -#[path = "or1k/mod.rs"] +#[cfg(target_arch = "riscv32")] +#[path = "riscv32/mod.rs"] mod arch; +#[cfg(target_arch = "riscv32")] +extern crate riscv; + pub use arch::*; include!(concat!(env!("BUILDINC_DIRECTORY"), "/generated/mem.rs")); diff --git a/artiq/firmware/libboard_misoc/or1k/boot.rs b/artiq/firmware/libboard_misoc/or1k/boot.rs deleted file mode 100644 index 51a368e6e..000000000 --- a/artiq/firmware/libboard_misoc/or1k/boot.rs +++ /dev/null @@ -1,49 +0,0 @@ -use super::{irq, cache}; - -pub unsafe fn reset() -> ! { - irq::set_ie(false); - asm!(r#" - l.j _reset_handler - l.nop - "# : : : : "volatile"); - loop {} -} - -pub unsafe fn jump(addr: usize) -> ! { - irq::set_ie(false); - cache::flush_cpu_icache(); - asm!(r#" - l.jr $0 - l.nop - "# : : "r"(addr) : : "volatile"); - loop {} -} - -pub unsafe fn hotswap(firmware: &[u8]) -> ! { - irq::set_ie(false); - asm!(r#" - # This loop overwrites itself, but it's structured in such a way - # that before that happens, it loads itself into I$$ fully. - l.movhi r4, hi(_reset_handler) - l.ori r4, r4, lo(_reset_handler) - l.or r7, r4, r0 - 0: l.sfnei r5, 0 - l.bf 1f - l.nop - l.jr r7 - l.nop - 1: l.lwz r6, 0(r3) - l.sw 0(r4), r6 - l.addi r3, r3, 4 - l.addi r4, r4, 4 - l.addi r5, r5, -4 - l.bf 0b - l.nop - "# - : - : "{r3}"(firmware.as_ptr() as usize), - "{r5}"(firmware.len()) - : - : "volatile"); - loop {} -} diff --git a/artiq/firmware/libboard_misoc/or1k/cache.rs b/artiq/firmware/libboard_misoc/or1k/cache.rs deleted file mode 100644 index 9357917c9..000000000 --- a/artiq/firmware/libboard_misoc/or1k/cache.rs +++ /dev/null @@ -1,49 +0,0 @@ -#[cfg(has_ddrphy)] -use core::ptr; -use super::spr::*; -#[cfg(has_ddrphy)] -use csr; -#[cfg(has_ddrphy)] -use mem; - -pub fn flush_cpu_icache() { - unsafe { - let iccfgr = mfspr(SPR_ICCFGR); - let ways = 1 << (iccfgr & SPR_ICCFGR_NCW); - let set_size = 1 << ((iccfgr & SPR_ICCFGR_NCS) >> 3); - let block_size = if iccfgr & SPR_ICCFGR_CBS != 0 { 32 } else { 16 }; - let size = set_size * ways * block_size; - - let mut i = 0; - while i < size { - mtspr(SPR_ICBIR, i); - i += block_size; - } - } -} - -pub fn flush_cpu_dcache() { - unsafe { - let dccfgr = mfspr(SPR_DCCFGR); - let ways = 1 << (dccfgr & SPR_ICCFGR_NCW); - let set_size = 1 << ((dccfgr & SPR_DCCFGR_NCS) >> 3); - let block_size = if dccfgr & SPR_DCCFGR_CBS != 0 { 32 } else { 16 }; - let size = set_size * ways * block_size; - - let mut i = 0; - while i < size { - mtspr(SPR_DCBIR, i); - i += block_size; - } - } -} - -#[cfg(has_ddrphy)] -pub fn flush_l2_cache() { - unsafe { - for i in 0..2 * (csr::CONFIG_L2_SIZE as usize) / 4 { - let addr = mem::MAIN_RAM_BASE + i * 4; - ptr::read_volatile(addr as *const usize); - } - } -} diff --git a/artiq/firmware/libboard_misoc/or1k/irq.rs b/artiq/firmware/libboard_misoc/or1k/irq.rs deleted file mode 100644 index bc745ae02..000000000 --- a/artiq/firmware/libboard_misoc/or1k/irq.rs +++ /dev/null @@ -1,107 +0,0 @@ -use core::{fmt, convert}; - -use super::spr::*; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] -pub enum Exception { - Reset = 0x1, - BusError = 0x2, - DataPageFault = 0x3, - InsnPageFault = 0x4, - Tick = 0x5, - Alignment = 0x6, - IllegalInsn = 0x7, - Interrupt = 0x8, - DtlbMiss = 0x9, - ItlbMiss = 0xa, - Range = 0xb, - Syscall = 0xc, - FloatingPoint = 0xd, - Trap = 0xe, -} - -impl fmt::Display for Exception { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Exception::Reset => write!(f, "reset"), - Exception::BusError => write!(f, "bus error"), - Exception::DataPageFault => write!(f, "data page fault"), - Exception::InsnPageFault => write!(f, "instruction page fault"), - Exception::Tick => write!(f, "tick"), - Exception::Alignment => write!(f, "alignment"), - Exception::IllegalInsn => write!(f, "illegal instruction"), - Exception::Interrupt => write!(f, "interrupt"), - Exception::DtlbMiss => write!(f, "D-TLB miss"), - Exception::ItlbMiss => write!(f, "I-TLB miss"), - Exception::Range => write!(f, "range"), - Exception::Syscall => write!(f, "system call"), - Exception::FloatingPoint => write!(f, "floating point"), - Exception::Trap => write!(f, "trap"), - } - } -} - -impl convert::TryFrom for Exception { - type Error = (); - - fn try_from(num: u32) -> Result { - match num { - 0x1 => Ok(Exception::Reset), - 0x2 => Ok(Exception::BusError), - 0x3 => Ok(Exception::DataPageFault), - 0x4 => Ok(Exception::InsnPageFault), - 0x5 => Ok(Exception::Tick), - 0x6 => Ok(Exception::Alignment), - 0x7 => Ok(Exception::IllegalInsn), - 0x8 => Ok(Exception::Interrupt), - 0x9 => Ok(Exception::DtlbMiss), - 0xa => Ok(Exception::ItlbMiss), - 0xb => Ok(Exception::Range), - 0xc => Ok(Exception::Syscall), - 0xd => Ok(Exception::FloatingPoint), - 0xe => Ok(Exception::Trap), - _ => Err(()) - } - } -} - -#[inline] -pub fn get_ie() -> bool { - unsafe { mfspr(SPR_SR) & SPR_SR_IEE != 0 } -} - -#[inline] -pub fn set_ie(ie: bool) { - if ie { - unsafe { mtspr(SPR_SR, mfspr(SPR_SR) | SPR_SR_IEE) } - } else { - unsafe { mtspr(SPR_SR, mfspr(SPR_SR) & !SPR_SR_IEE) } - } -} - -#[inline] -pub fn get_mask() -> u32 { - unsafe { mfspr(SPR_PICMR) } -} - -#[inline] -pub fn set_mask(mask: u32) { - unsafe { mtspr(SPR_PICMR, mask) } -} - -#[inline] -pub fn pending_mask() -> u32 { - unsafe { mfspr(SPR_PICSR) } -} - -pub fn enable(irq: u32) { - set_mask(get_mask() | (1 << irq)) -} - -pub fn disable(irq: u32) { - set_mask(get_mask() & !(1 << irq)) -} - -pub fn is_pending(irq: u32) -> bool { - get_mask() & (1 << irq) != 0 -} diff --git a/artiq/firmware/libboard_misoc/or1k/spr.rs b/artiq/firmware/libboard_misoc/or1k/spr.rs deleted file mode 100644 index 14bb5ae3d..000000000 --- a/artiq/firmware/libboard_misoc/or1k/spr.rs +++ /dev/null @@ -1,231 +0,0 @@ -#[inline(always)] -pub unsafe fn mfspr(reg: u32) -> u32 { - let value: u32; - asm!("l.mfspr $0, $1, 0" : "=r"(value) : "r"(reg) : : "volatile"); - value -} - -#[inline(always)] -pub unsafe fn mtspr(reg: u32, value: u32) { - asm!("l.mtspr $0, $1, 0" : : "r"(reg), "r"(value) : : "volatile") -} - -/* Definition of special-purpose registers (SPRs). */ - -pub const MAX_GRPS: u32 = 32; -pub const MAX_SPRS_PER_GRP_BITS: u32 = 11; -pub const MAX_SPRS_PER_GRP: u32 = 1 << MAX_SPRS_PER_GRP_BITS; -pub const MAX_SPRS: u32 = 0x10000; - -/* Base addresses for the groups */ -pub const SPRGROUP_SYS: u32 = 0 << MAX_SPRS_PER_GRP_BITS; -pub const SPRGROUP_DMMU: u32 = 1 << MAX_SPRS_PER_GRP_BITS; -pub const SPRGROUP_IMMU: u32 = 2 << MAX_SPRS_PER_GRP_BITS; -pub const SPRGROUP_DC: u32 = 3 << MAX_SPRS_PER_GRP_BITS; -pub const SPRGROUP_IC: u32 = 4 << MAX_SPRS_PER_GRP_BITS; -pub const SPRGROUP_MAC: u32 = 5 << MAX_SPRS_PER_GRP_BITS; -pub const SPRGROUP_D: u32 = 6 << MAX_SPRS_PER_GRP_BITS; -pub const SPRGROUP_PC: u32 = 7 << MAX_SPRS_PER_GRP_BITS; -pub const SPRGROUP_PM: u32 = 8 << MAX_SPRS_PER_GRP_BITS; -pub const SPRGROUP_PIC: u32 = 9 << MAX_SPRS_PER_GRP_BITS; -pub const SPRGROUP_TT: u32 = 10 << MAX_SPRS_PER_GRP_BITS; -pub const SPRGROUP_FP: u32 = 11 << MAX_SPRS_PER_GRP_BITS; - -/* System control and status group */ -pub const SPR_VR: u32 = SPRGROUP_SYS + 0; -pub const SPR_UPR: u32 = SPRGROUP_SYS + 1; -pub const SPR_CPUCFGR: u32 = SPRGROUP_SYS + 2; -pub const SPR_DMMUCFGR: u32 = SPRGROUP_SYS + 3; -pub const SPR_IMMUCFGR: u32 = SPRGROUP_SYS + 4; -pub const SPR_DCCFGR: u32 = SPRGROUP_SYS + 5; -pub const SPR_ICCFGR: u32 = SPRGROUP_SYS + 6; -pub const SPR_DCFGR: u32 = SPRGROUP_SYS + 7; -pub const SPR_PCCFGR: u32 = SPRGROUP_SYS + 8; -pub const SPR_VR2: u32 = SPRGROUP_SYS + 9; -pub const SPR_AVR: u32 = SPRGROUP_SYS + 10; -pub const SPR_EVBAR: u32 = SPRGROUP_SYS + 11; -pub const SPR_AECR: u32 = SPRGROUP_SYS + 12; -pub const SPR_AESR: u32 = SPRGROUP_SYS + 13; -pub const SPR_NPC: u32 = SPRGROUP_SYS + 16; /* CZ 21/06/01 */ -pub const SPR_SR: u32 = SPRGROUP_SYS + 17; /* CZ 21/06/01 */ -pub const SPR_PPC: u32 = SPRGROUP_SYS + 18; /* CZ 21/06/01 */ -pub const SPR_FPCSR: u32 = SPRGROUP_SYS + 20; /* CZ 21/06/01 */ -pub const SPR_ISR_BASE: u32 = SPRGROUP_SYS + 21; -pub const SPR_EPCR_BASE: u32 = SPRGROUP_SYS + 32; /* CZ 21/06/01 */ -pub const SPR_EPCR_LAST: u32 = SPRGROUP_SYS + 47; /* CZ 21/06/01 */ -pub const SPR_EEAR_BASE: u32 = SPRGROUP_SYS + 48; -pub const SPR_EEAR_LAST: u32 = SPRGROUP_SYS + 63; -pub const SPR_ESR_BASE: u32 = SPRGROUP_SYS + 64; -pub const SPR_ESR_LAST: u32 = SPRGROUP_SYS + 79; -pub const SPR_GPR_BASE: u32 = SPRGROUP_SYS + 1024; - -/* Data MMU group */ -pub const SPR_DMMUCR: u32 = SPRGROUP_DMMU + 0; -pub const SPR_DTLBEIR: u32 = SPRGROUP_DMMU + 2; - -/* Instruction MMU group */ -pub const SPR_IMMUCR: u32 = SPRGROUP_IMMU + 0; -pub const SPR_ITLBEIR: u32 = SPRGROUP_IMMU + 2; - -/* Data cache group */ -pub const SPR_DCCR: u32 = SPRGROUP_DC + 0; -pub const SPR_DCBPR: u32 = SPRGROUP_DC + 1; -pub const SPR_DCBFR: u32 = SPRGROUP_DC + 2; -pub const SPR_DCBIR: u32 = SPRGROUP_DC + 3; -pub const SPR_DCBWR: u32 = SPRGROUP_DC + 4; -pub const SPR_DCBLR: u32 = SPRGROUP_DC + 5; - -/* Instruction cache group */ -pub const SPR_ICCR: u32 = SPRGROUP_IC + 0; -pub const SPR_ICBPR: u32 = SPRGROUP_IC + 1; -pub const SPR_ICBIR: u32 = SPRGROUP_IC + 2; -pub const SPR_ICBLR: u32 = SPRGROUP_IC + 3; - -// [snip] - -/* Performance counters group */ -pub const SPR_PCCR0: u32 = SPRGROUP_PC + 0; -pub const SPR_PCCR1: u32 = SPRGROUP_PC + 1; -pub const SPR_PCCR2: u32 = SPRGROUP_PC + 2; -pub const SPR_PCCR3: u32 = SPRGROUP_PC + 3; -pub const SPR_PCCR4: u32 = SPRGROUP_PC + 4; -pub const SPR_PCCR5: u32 = SPRGROUP_PC + 5; -pub const SPR_PCCR6: u32 = SPRGROUP_PC + 6; -pub const SPR_PCCR7: u32 = SPRGROUP_PC + 7; -pub const SPR_PCMR0: u32 = SPRGROUP_PC + 8; -pub const SPR_PCMR1: u32 = SPRGROUP_PC + 9; -pub const SPR_PCMR2: u32 = SPRGROUP_PC + 10; -pub const SPR_PCMR3: u32 = SPRGROUP_PC + 11; -pub const SPR_PCMR4: u32 = SPRGROUP_PC + 12; -pub const SPR_PCMR5: u32 = SPRGROUP_PC + 13; -pub const SPR_PCMR6: u32 = SPRGROUP_PC + 14; -pub const SPR_PCMR7: u32 = SPRGROUP_PC + 15; - -/* PIC group */ -pub const SPR_PICMR: u32 = SPRGROUP_PIC + 0; -pub const SPR_PICPR: u32 = SPRGROUP_PIC + 1; -pub const SPR_PICSR: u32 = SPRGROUP_PIC + 2; - -// [snip] - -/* - * Bit definitions for the Supervision Register - * - */ -pub const SPR_SR_SM: u32 = 0x00000001; /* Supervisor Mode */ -pub const SPR_SR_TEE: u32 = 0x00000002; /* Tick timer Exception Enable */ -pub const SPR_SR_IEE: u32 = 0x00000004; /* Interrupt Exception Enable */ -pub const SPR_SR_DCE: u32 = 0x00000008; /* Data Cache Enable */ -pub const SPR_SR_ICE: u32 = 0x00000010; /* Instruction Cache Enable */ -pub const SPR_SR_DME: u32 = 0x00000020; /* Data MMU Enable */ -pub const SPR_SR_IME: u32 = 0x00000040; /* Instruction MMU Enable */ -pub const SPR_SR_LEE: u32 = 0x00000080; /* Little Endian Enable */ -pub const SPR_SR_CE: u32 = 0x00000100; /* CID Enable */ -pub const SPR_SR_F: u32 = 0x00000200; /* Condition Flag */ -pub const SPR_SR_CY: u32 = 0x00000400; /* Carry flag */ -pub const SPR_SR_OV: u32 = 0x00000800; /* Overflow flag */ -pub const SPR_SR_OVE: u32 = 0x00001000; /* Overflow flag Exception */ -pub const SPR_SR_DSX: u32 = 0x00002000; /* Delay Slot Exception */ -pub const SPR_SR_EPH: u32 = 0x00004000; /* Exception Prefix High */ -pub const SPR_SR_FO: u32 = 0x00008000; /* Fixed one */ -pub const SPR_SR_SUMRA: u32 = 0x00010000; /* Supervisor SPR read access */ -pub const SPR_SR_RES: u32 = 0x0ffe0000; /* Reserved */ -pub const SPR_SR_CID: u32 = 0xf0000000; /* Context ID */ - -/* - * Bit definitions for Data Cache Control register - * - */ -pub const SPR_DCCR_EW: u32 = 0x000000ff; /* Enable ways */ - -/* - * Bit definitions for Insn Cache Control register - * - */ -pub const SPR_ICCR_EW: u32 = 0x000000ff; /* Enable ways */ - -/* - * Bit definitions for Data Cache Configuration Register - * - */ -pub const SPR_DCCFGR_NCW: u32 = 0x00000007; -pub const SPR_DCCFGR_NCS: u32 = 0x00000078; -pub const SPR_DCCFGR_CBS: u32 = 0x00000080; -pub const SPR_DCCFGR_CWS: u32 = 0x00000100; -pub const SPR_DCCFGR_CCRI: u32 = 0x00000200; -pub const SPR_DCCFGR_CBIRI: u32 = 0x00000400; -pub const SPR_DCCFGR_CBPRI: u32 = 0x00000800; -pub const SPR_DCCFGR_CBLRI: u32 = 0x00001000; -pub const SPR_DCCFGR_CBFRI: u32 = 0x00002000; -pub const SPR_DCCFGR_CBWBRI: u32 = 0x00004000; - -pub const SPR_DCCFGR_NCW_OFF: u32 = 0; -pub const SPR_DCCFGR_NCS_OFF: u32 = 3; -pub const SPR_DCCFGR_CBS_OFF: u32 = 7; - -/* - * Bit definitions for Instruction Cache Configuration Register - * - */ -pub const SPR_ICCFGR_NCW: u32 = 0x00000007; -pub const SPR_ICCFGR_NCS: u32 = 0x00000078; -pub const SPR_ICCFGR_CBS: u32 = 0x00000080; -pub const SPR_ICCFGR_CCRI: u32 = 0x00000200; -pub const SPR_ICCFGR_CBIRI: u32 = 0x00000400; -pub const SPR_ICCFGR_CBPRI: u32 = 0x00000800; -pub const SPR_ICCFGR_CBLRI: u32 = 0x00001000; - -pub const SPR_ICCFGR_NCW_OFF: u32 = 0; -pub const SPR_ICCFGR_NCS_OFF: u32 = 3; -pub const SPR_ICCFGR_CBS_OFF: u32 = 7; - -/* - * Bit definitions for Data MMU Configuration Register - * - */ -pub const SPR_DMMUCFGR_NTW: u32 = 0x00000003; -pub const SPR_DMMUCFGR_NTS: u32 = 0x0000001C; -pub const SPR_DMMUCFGR_NAE: u32 = 0x000000E0; -pub const SPR_DMMUCFGR_CRI: u32 = 0x00000100; -pub const SPR_DMMUCFGR_PRI: u32 = 0x00000200; -pub const SPR_DMMUCFGR_TEIRI: u32 = 0x00000400; -pub const SPR_DMMUCFGR_HTR: u32 = 0x00000800; - -pub const SPR_DMMUCFGR_NTW_OFF: u32 = 0; -pub const SPR_DMMUCFGR_NTS_OFF: u32 = 2; - -/* - * Bit definitions for Instruction MMU Configuration Register - * - */ -pub const SPR_IMMUCFGR_NTW: u32 = 0x00000003; -pub const SPR_IMMUCFGR_NTS: u32 = 0x0000001C; -pub const SPR_IMMUCFGR_NAE: u32 = 0x000000E0; -pub const SPR_IMMUCFGR_CRI: u32 = 0x00000100; -pub const SPR_IMMUCFGR_PRI: u32 = 0x00000200; -pub const SPR_IMMUCFGR_TEIRI: u32 = 0x00000400; -pub const SPR_IMMUCFGR_HTR: u32 = 0x00000800; - -pub const SPR_IMMUCFGR_NTW_OFF: u32 = 0; -pub const SPR_IMMUCFGR_NTS_OFF: u32 = 2; - -/* - * Bit definitions for Performance counters mode registers - * - */ -pub const SPR_PCMR_CP: u32 = 0x00000001; /* Counter present */ -pub const SPR_PCMR_UMRA: u32 = 0x00000002; /* User mode read access */ -pub const SPR_PCMR_CISM: u32 = 0x00000004; /* Count in supervisor mode */ -pub const SPR_PCMR_CIUM: u32 = 0x00000008; /* Count in user mode */ -pub const SPR_PCMR_LA: u32 = 0x00000010; /* Load access event */ -pub const SPR_PCMR_SA: u32 = 0x00000020; /* Store access event */ -pub const SPR_PCMR_IF: u32 = 0x00000040; /* Instruction fetch event*/ -pub const SPR_PCMR_DCM: u32 = 0x00000080; /* Data cache miss event */ -pub const SPR_PCMR_ICM: u32 = 0x00000100; /* Insn cache miss event */ -pub const SPR_PCMR_IFS: u32 = 0x00000200; /* Insn fetch stall event */ -pub const SPR_PCMR_LSUS: u32 = 0x00000400; /* LSU stall event */ -pub const SPR_PCMR_BS: u32 = 0x00000800; /* Branch stall event */ -pub const SPR_PCMR_DTLBM: u32 = 0x00001000; /* DTLB miss event */ -pub const SPR_PCMR_ITLBM: u32 = 0x00002000; /* ITLB miss event */ -pub const SPR_PCMR_DDS: u32 = 0x00004000; /* Data dependency stall event */ -pub const SPR_PCMR_WPE: u32 = 0x03ff8000; /* Watchpoint events */ diff --git a/artiq/firmware/libboard_misoc/or1k/vectors.S b/artiq/firmware/libboard_misoc/or1k/vectors.S deleted file mode 100644 index 708aed9df..000000000 --- a/artiq/firmware/libboard_misoc/or1k/vectors.S +++ /dev/null @@ -1,413 +0,0 @@ -/* - * (C) Copyright 2012, Stefan Kristiansson - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License as - * published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, - * MA 02111-1307 USA - */ - -#include - -/* - * OR1K Architecture has a 128 byte "red zone" after the stack that can not be - * touched by exception handlers. GCC uses this red zone for locals and - * temps without needing to change the stack pointer. - */ -#define OR1K_RED_ZONE_SIZE 128 - -/* - * We need 4 bytes (32 bits) * 32 registers space on the stack to save all the - * registers. - */ -#define EXCEPTION_STACK_SIZE ((4*32) + OR1K_RED_ZONE_SIZE) - -#define HANDLE_EXCEPTION ; \ - l.addi r1, r1, -EXCEPTION_STACK_SIZE ; \ - l.sw 0x1c(r1), r9 ; \ - l.jal _exception_handler ; \ - l.nop ; \ - l.lwz r9, 0x1c(r1) ; \ - l.addi r1, r1, EXCEPTION_STACK_SIZE ; \ - l.rfe ; \ - l.nop - - -.section .vectors, "ax", @progbits -.global _reset_handler -_reset_handler: - l.movhi r0, 0 - l.movhi r1, 0 - l.movhi r2, 0 - l.movhi r3, 0 - l.movhi r4, 0 - l.movhi r5, 0 - l.movhi r6, 0 - l.movhi r7, 0 - l.movhi r8, 0 - l.movhi r9, 0 - l.movhi r10, 0 - l.movhi r11, 0 - l.movhi r12, 0 - l.movhi r13, 0 - l.movhi r14, 0 - l.movhi r15, 0 - l.movhi r16, 0 - l.movhi r17, 0 - l.movhi r18, 0 - l.movhi r19, 0 - l.movhi r20, 0 - l.movhi r21, 0 - l.movhi r22, 0 - l.movhi r23, 0 - l.movhi r24, 0 - l.movhi r25, 0 - l.movhi r26, 0 - l.movhi r27, 0 - l.movhi r28, 0 - l.movhi r29, 0 - l.movhi r30, 0 - l.movhi r31, 0 - - l.ori r21, r0, SPR_SR_SM - l.mtspr r0, r21, SPR_SR - l.movhi r21, hi(_reset_handler) - l.ori r21, r21, lo(_reset_handler) - l.mtspr r0, r21, SPR_EVBAR - /* enable caches */ - l.jal _cache_init - l.nop - l.j _crt0 - l.nop - - /* bus error */ - .org 0x200 - HANDLE_EXCEPTION - - /* data page fault */ - .org 0x300 - HANDLE_EXCEPTION - - /* instruction page fault */ - .org 0x400 - HANDLE_EXCEPTION - - /* tick timer */ - .org 0x500 - HANDLE_EXCEPTION - - /* alignment */ - .org 0x600 - HANDLE_EXCEPTION - - /* illegal instruction */ - .org 0x700 - HANDLE_EXCEPTION - - /* external interrupt */ - .org 0x800 - HANDLE_EXCEPTION - - /* D-TLB miss */ - .org 0x900 - HANDLE_EXCEPTION - - /* I-TLB miss */ - .org 0xa00 - HANDLE_EXCEPTION - - /* range */ - .org 0xb00 - HANDLE_EXCEPTION - - /* system call */ - .org 0xc00 - HANDLE_EXCEPTION - - /* floating point */ - .org 0xd00 - HANDLE_EXCEPTION - - /* trap */ - .org 0xe00 - HANDLE_EXCEPTION - - /* reserved */ - .org 0xf00 - HANDLE_EXCEPTION - - .org 0x1000 -_crt0: - /* Setup stack and global pointer */ - l.movhi r1, hi(_fstack) - l.ori r1, r1, lo(_fstack) - - /* Clear BSS */ - l.movhi r21, hi(_fbss) - l.ori r21, r21, lo(_fbss) - l.movhi r3, hi(_ebss) - l.ori r3, r3, lo(_ebss) -.clearBSS: - l.sfeq r21, r3 - l.bf .callMain - l.nop - l.sw 0(r21), r0 - l.addi r21, r21, 4 - l.j .clearBSS - l.nop - -.callMain: - l.j main - l.nop - -_exception_handler: - .cfi_startproc - .cfi_return_column 32 - .cfi_signal_frame - .cfi_def_cfa_offset EXCEPTION_STACK_SIZE - l.sw 0x00(r1), r2 - .cfi_offset 2, 0x00-EXCEPTION_STACK_SIZE - l.sw 0x04(r1), r3 - .cfi_offset 3, 0x04-EXCEPTION_STACK_SIZE - l.sw 0x08(r1), r4 - .cfi_offset 4, 0x08-EXCEPTION_STACK_SIZE - l.sw 0x0c(r1), r5 - .cfi_offset 5, 0x0c-EXCEPTION_STACK_SIZE - l.sw 0x10(r1), r6 - .cfi_offset 6, 0x10-EXCEPTION_STACK_SIZE - l.sw 0x14(r1), r7 - .cfi_offset 7, 0x14-EXCEPTION_STACK_SIZE - l.sw 0x18(r1), r8 - .cfi_offset 8, 0x18-EXCEPTION_STACK_SIZE - /* r9 saved in HANDLE_EXCEPTION */ - .cfi_offset 9, 0x1c-EXCEPTION_STACK_SIZE - l.sw 0x20(r1), r10 - .cfi_offset 10, 0x20-EXCEPTION_STACK_SIZE - l.sw 0x24(r1), r11 - .cfi_offset 11, 0x24-EXCEPTION_STACK_SIZE - l.sw 0x28(r1), r12 - .cfi_offset 12, 0x28-EXCEPTION_STACK_SIZE - l.sw 0x2c(r1), r13 - .cfi_offset 13, 0x2c-EXCEPTION_STACK_SIZE - l.sw 0x30(r1), r14 - .cfi_offset 14, 0x30-EXCEPTION_STACK_SIZE - l.sw 0x34(r1), r15 - .cfi_offset 15, 0x34-EXCEPTION_STACK_SIZE - l.sw 0x38(r1), r16 - .cfi_offset 16, 0x38-EXCEPTION_STACK_SIZE - l.sw 0x3c(r1), r17 - .cfi_offset 17, 0x3c-EXCEPTION_STACK_SIZE - l.sw 0x40(r1), r18 - .cfi_offset 18, 0x40-EXCEPTION_STACK_SIZE - l.sw 0x44(r1), r19 - .cfi_offset 19, 0x44-EXCEPTION_STACK_SIZE - l.sw 0x48(r1), r20 - .cfi_offset 20, 0x48-EXCEPTION_STACK_SIZE - l.sw 0x4c(r1), r21 - .cfi_offset 21, 0x4c-EXCEPTION_STACK_SIZE - l.sw 0x50(r1), r22 - .cfi_offset 22, 0x50-EXCEPTION_STACK_SIZE - l.sw 0x54(r1), r23 - .cfi_offset 23, 0x54-EXCEPTION_STACK_SIZE - l.sw 0x58(r1), r24 - .cfi_offset 24, 0x58-EXCEPTION_STACK_SIZE - l.sw 0x5c(r1), r25 - .cfi_offset 25, 0x5c-EXCEPTION_STACK_SIZE - l.sw 0x60(r1), r26 - .cfi_offset 26, 0x60-EXCEPTION_STACK_SIZE - l.sw 0x64(r1), r27 - .cfi_offset 27, 0x64-EXCEPTION_STACK_SIZE - l.sw 0x68(r1), r28 - .cfi_offset 28, 0x68-EXCEPTION_STACK_SIZE - l.sw 0x6c(r1), r29 - .cfi_offset 29, 0x6c-EXCEPTION_STACK_SIZE - l.sw 0x70(r1), r30 - .cfi_offset 30, 0x70-EXCEPTION_STACK_SIZE - l.sw 0x74(r1), r31 - .cfi_offset 31, 0x74-EXCEPTION_STACK_SIZE - - /* Save return address */ - l.or r14, r0, r9 - /* Calculate exception vector from handler address */ - l.andi r3, r9, 0xf00 - l.srli r3, r3, 8 - /* Pass saved register state */ - l.or r4, r0, r1 - /* Extract exception PC */ - l.mfspr r5, r0, SPR_EPCR_BASE - /* Tell exception PC to the unwinder */ - l.sw 0x78(r1), r5 - .cfi_offset 32, 0x78-EXCEPTION_STACK_SIZE - /* Extract exception effective address */ - l.mfspr r6, r0, SPR_EEAR_BASE - /* Extract exception SR */ - l.mfspr r7, r0, SPR_ESR_BASE - /* Call exception handler with the link address as argument */ - l.jal exception - l.nop - - /* Load return address */ - l.or r9, r0, r14 - /* Restore state */ - l.lwz r2, 0x00(r1) - l.lwz r3, 0x04(r1) - l.lwz r4, 0x08(r1) - l.lwz r5, 0x0c(r1) - l.lwz r6, 0x10(r1) - l.lwz r7, 0x14(r1) - l.lwz r8, 0x18(r1) - l.lwz r10, 0x20(r1) - l.lwz r11, 0x24(r1) - l.lwz r12, 0x28(r1) - l.lwz r13, 0x2c(r1) - l.lwz r14, 0x30(r1) - l.lwz r15, 0x34(r1) - l.lwz r16, 0x38(r1) - l.lwz r17, 0x3c(r1) - l.lwz r18, 0x40(r1) - l.lwz r19, 0x44(r1) - l.lwz r20, 0x48(r1) - l.lwz r21, 0x4c(r1) - l.lwz r22, 0x50(r1) - l.lwz r23, 0x54(r1) - l.lwz r24, 0x58(r1) - l.lwz r25, 0x5c(r1) - l.lwz r26, 0x60(r1) - l.lwz r27, 0x64(r1) - l.lwz r28, 0x68(r1) - l.lwz r29, 0x6c(r1) - l.lwz r30, 0x70(r1) - l.lwz r31, 0x74(r1) - l.jr r9 - l.nop - .cfi_endproc - -.global _cache_init -_cache_init: - /* - This function is to be used ONLY during reset, before main() is called. - TODO: Perhaps break into individual enable instruction/data cache - sections functions, and provide disable functions, also, all - callable from C - */ - - /* Instruction cache enable */ - /* Check if IC present and skip enabling otherwise */ -#if 1 -.L6: - l.mfspr r3,r0,SPR_UPR - l.andi r7,r3,SPR_UPR_ICP - l.sfeq r7,r0 - l.bf .L8 - l.nop - - /* Disable IC */ - l.mfspr r6,r0,SPR_SR - l.addi r5,r0,-1 - l.xori r5,r5,SPR_SR_ICE - l.and r5,r6,r5 - l.mtspr r0,r5,SPR_SR - - /* Establish cache block size - If BS=0, 16; - If BS=1, 32; - r14 contain block size - */ - l.mfspr r3,r0,SPR_ICCFGR - l.andi r7,r3,SPR_ICCFGR_CBS - l.srli r8,r7,7 - l.ori r4,r0,16 - l.sll r14,r4,r8 - - /* Establish number of cache sets - r10 contains number of cache sets - r8 contains log(# of cache sets) - */ - l.andi r7,r3,SPR_ICCFGR_NCS - l.srli r8,r7,3 - l.ori r4,r0,1 - l.sll r10,r4,r8 - - /* Invalidate IC */ - l.addi r6,r0,0 - l.sll r5,r14,r8 - -.L7: l.mtspr r0,r6,SPR_ICBIR - l.sfne r6,r5 - l.bf .L7 - l.add r6,r6,r14 - - /* Enable IC */ - l.mfspr r6,r0,SPR_SR - l.ori r6,r6,SPR_SR_ICE - l.mtspr r0,r6,SPR_SR - l.nop - l.nop - l.nop - l.nop - l.nop - l.nop - l.nop - l.nop - /* Data cache enable */ - /* Check if DC present and skip enabling otherwise */ -#endif -.L8: -#if 1 - l.mfspr r3,r0,SPR_UPR - l.andi r7,r3,SPR_UPR_DCP - l.sfeq r7,r0 - l.bf .L10 - l.nop - /* Disable DC */ - l.mfspr r6,r0,SPR_SR - l.addi r5,r0,-1 - l.xori r5,r5,SPR_SR_DCE - l.and r5,r6,r5 - l.mtspr r0,r5,SPR_SR - /* Establish cache block size - If BS=0, 16; - If BS=1, 32; - r14 contain block size - */ - l.mfspr r3,r0,SPR_DCCFGR - l.andi r7,r3,SPR_DCCFGR_CBS - l.srli r8,r7,7 - l.ori r4,r0,16 - l.sll r14,r4,r8 - /* Establish number of cache sets - r10 contains number of cache sets - r8 contains log(# of cache sets) - */ - l.andi r7,r3,SPR_DCCFGR_NCS - l.srli r8,r7,3 - l.ori r4,r0,1 - l.sll r10,r4,r8 - /* Invalidate DC */ - l.addi r6,r0,0 - l.sll r5,r14,r8 - -.L9: - l.mtspr r0,r6,SPR_DCBIR - l.sfne r6,r5 - l.bf .L9 - l.add r6,r6,r14 - /* Enable DC */ - l.mfspr r6,r0,SPR_SR - l.ori r6,r6,SPR_SR_DCE - l.mtspr r0,r6,SPR_SR -#endif -.L10: - /* Return */ - l.jr r9 - l.nop diff --git a/artiq/firmware/libboard_misoc/riscv32/boot.rs b/artiq/firmware/libboard_misoc/riscv32/boot.rs new file mode 100644 index 000000000..0d2254da1 --- /dev/null +++ b/artiq/firmware/libboard_misoc/riscv32/boot.rs @@ -0,0 +1,30 @@ +use super::{cache, pmp}; +use riscv::register::*; + +pub unsafe fn reset() -> ! { + llvm_asm!(r#" + j _reset_handler + nop + "# : : : : "volatile"); + loop {} +} + +pub unsafe fn jump(addr: usize) -> ! { + cache::flush_cpu_icache(); + llvm_asm!(r#" + jalr x0, 0($0) + nop + "# : : "r"(addr) : : "volatile"); + loop {} +} + +pub unsafe fn start_user(addr: usize) -> ! { + pmp::enable_user_memory(); + mstatus::set_mpp(mstatus::MPP::User); + mepc::write(addr); + llvm_asm!( + "mret" + : : : : "volatile" + ); + unreachable!() +} diff --git a/artiq/firmware/libboard_misoc/riscv32/cache.rs b/artiq/firmware/libboard_misoc/riscv32/cache.rs new file mode 100644 index 000000000..12fc9f3bb --- /dev/null +++ b/artiq/firmware/libboard_misoc/riscv32/cache.rs @@ -0,0 +1,35 @@ +#[cfg(has_ddrphy)] +use core::ptr; +#[cfg(has_ddrphy)] +use csr; +#[cfg(has_ddrphy)] +use mem; + +pub fn flush_cpu_icache() { + unsafe { + llvm_asm!(r#" + fence.i + nop + nop + nop + nop + nop + "# : : : : "volatile"); + } +} + +pub fn flush_cpu_dcache() { + unsafe { + llvm_asm!(".word(0x500F)" : : : : "volatile"); + } +} + +#[cfg(has_ddrphy)] +pub fn flush_l2_cache() { + unsafe { + for i in 0..2 * (csr::CONFIG_L2_SIZE as usize) / 4 { + let addr = mem::MAIN_RAM_BASE + i * 4; + ptr::read_volatile(addr as *const usize); + } + } +} diff --git a/artiq/firmware/libboard_misoc/or1k/mod.rs b/artiq/firmware/libboard_misoc/riscv32/mod.rs similarity index 52% rename from artiq/firmware/libboard_misoc/or1k/mod.rs rename to artiq/firmware/libboard_misoc/riscv32/mod.rs index 52a619d1b..a8f498adc 100644 --- a/artiq/firmware/libboard_misoc/or1k/mod.rs +++ b/artiq/firmware/libboard_misoc/riscv32/mod.rs @@ -1,4 +1,3 @@ -pub mod spr; -pub mod irq; pub mod cache; pub mod boot; +pub mod pmp; diff --git a/artiq/firmware/libboard_misoc/riscv32/pmp.rs b/artiq/firmware/libboard_misoc/riscv32/pmp.rs new file mode 100644 index 000000000..9ac80b9e3 --- /dev/null +++ b/artiq/firmware/libboard_misoc/riscv32/pmp.rs @@ -0,0 +1,55 @@ +use riscv::register::{pmpaddr0, pmpaddr1, pmpaddr2, pmpaddr3, pmpcfg0}; + +static mut THREAD_DEPTH: u8 = 0; + +const PMP_L : usize = 0b10000000; +const PMP_NAPOT: usize = 0b00011000; +const PMP_X : usize = 0b00000100; +const PMP_W : usize = 0b00000010; +const PMP_R : usize = 0b00000001; +const PMP_OFF : usize = 0b00000000; + +#[inline(always)] +pub unsafe fn init_stack_guard(guard_base: usize) { + pmpaddr2::write((guard_base >> 2) | ((0x1000 - 1) >> 3)); + pmpcfg0::write((PMP_L | PMP_NAPOT) << 16); +} + +#[inline(always)] +pub fn enable_user_memory() { + pmpaddr3::write((0x80000000 - 1) >> 3); + pmpcfg0::write((PMP_L | PMP_NAPOT | PMP_X | PMP_W | PMP_R) << 24); +} + +#[inline(always)] +pub unsafe fn push_pmp_region(addr: usize) { + let pmp_addr = (addr >> 2) | ((0x1000 - 1) >> 3); + match THREAD_DEPTH { + // Activate PMP0 when switching from main stack to thread + 0 => { + pmpaddr0::write(pmp_addr); + pmpcfg0::write(PMP_NAPOT); + } + + // Temporarily activate PMP1 when spawning a thread from a thread + // The thread should swap back to the main stack very soon after init + 1 => { + pmpaddr1::write(pmp_addr); + pmpcfg0::write(PMP_NAPOT << 8 | PMP_NAPOT); + } + + // Thread *running* another thread should not be possible + _ => unreachable!() + } + THREAD_DEPTH += 1; +} + +#[inline(always)] +pub unsafe fn pop_pmp_region() { + THREAD_DEPTH -= 1; + match THREAD_DEPTH { + 0 => pmpcfg0::write(PMP_OFF), + 1 => pmpcfg0::write(PMP_NAPOT), + _ => unreachable!() + } +} diff --git a/artiq/firmware/libboard_misoc/riscv32/vectors.S b/artiq/firmware/libboard_misoc/riscv32/vectors.S new file mode 100644 index 000000000..19b6c5c3f --- /dev/null +++ b/artiq/firmware/libboard_misoc/riscv32/vectors.S @@ -0,0 +1,176 @@ +# Adapted from riscv-rt project, with the following license: +# +# Copyright 2018 RISC-V team +# +# Permission to use, copy, modify, and/or distribute this software for any purpose +# with or without fee is hereby granted, provided that the above copyright notice and +# this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +# FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, +# OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA +# OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +# ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +#if __riscv_xlen == 64 +# define STORE sd +# define LOAD ld +# define LOG_REGBYTES 3 +#else +# define STORE sw +# define LOAD lw +# define LOG_REGBYTES 2 +#endif +#define REGBYTES (1 << LOG_REGBYTES) + +/* + Entry point of all programs (_reset_handler). + + It initializes DWARF call frame information, the stack pointer, the + frame pointer (needed for closures to work in start_rust) and the global + pointer. Then it calls _start_rust. +*/ + +.section .vectors, "ax", @progbits +.global _reset_handler + +_reset_handler: + /* Jump to the absolute address defined by the linker script. */ + // for 32bit + .if __riscv_xlen == 32 + lui ra, %hi(_abs_start) + jr %lo(_abs_start)(ra) + .endif + + // for 64bit + .if __riscv_xlen == 64 +.option push +.option norelax // to prevent an unsupported R_RISCV_ALIGN relocation from being generated +1: + auipc ra, %pcrel_hi(1f) + ld ra, %pcrel_lo(1b)(ra) + jr ra + .align 3 +1: + .dword _abs_start +.option pop + .endif + +_abs_start: + .cfi_startproc + .cfi_undefined ra + + csrw mie, 0 + csrw mip, 0 + + li x1, 0 + li x2, 0 + li x3, 0 + li x4, 0 + li x5, 0 + li x6, 0 + li x7, 0 + li x8, 0 + li x9, 0 + li x10,0 + li x11,0 + li x12,0 + li x13,0 + li x14,0 + li x15,0 + li x16,0 + li x17,0 + li x18,0 + li x19,0 + li x20,0 + li x21,0 + li x22,0 + li x23,0 + li x24,0 + li x25,0 + li x26,0 + li x27,0 + li x28,0 + li x29,0 + li x30,0 + li x31,0 + + // Check hart id + csrr a2, mhartid + + // Allocate stacks + la sp, _fstack + + // Set frame pointer + add s0, sp, zero + + // Set trap vector + la t0, _start_trap + csrw mtvec, t0 + + // Zero initialize .bss + la t0, _fbss + la t1, _ebss + beq t0, t1, 2f +1: STORE zero, 0(t0) + addi t0, t0, REGBYTES + bne t0, t1, 1b +2: + // Enter main firmware + jal zero, main + + .cfi_endproc + +/* + Trap entry point (_start_trap) + + Saves caller saved registers ra, t0..6, a0..7, calls exception, + restores caller saved registers and then returns. +*/ +.global _start_trap +/* Make it .weak so PAC/HAL can provide their own if needed. */ +.weak _start_trap + +_start_trap: + addi sp, sp, -16*REGBYTES + + STORE ra, 0*REGBYTES(sp) + STORE t0, 1*REGBYTES(sp) + STORE t1, 2*REGBYTES(sp) + STORE t2, 3*REGBYTES(sp) + STORE t3, 4*REGBYTES(sp) + STORE t4, 5*REGBYTES(sp) + STORE t5, 6*REGBYTES(sp) + STORE t6, 7*REGBYTES(sp) + STORE a0, 8*REGBYTES(sp) + STORE a1, 9*REGBYTES(sp) + STORE a2, 10*REGBYTES(sp) + STORE a3, 11*REGBYTES(sp) + STORE a4, 12*REGBYTES(sp) + STORE a5, 13*REGBYTES(sp) + STORE a6, 14*REGBYTES(sp) + STORE a7, 15*REGBYTES(sp) + + add a0, sp, zero + jal ra, exception + + LOAD ra, 0*REGBYTES(sp) + LOAD t0, 1*REGBYTES(sp) + LOAD t1, 2*REGBYTES(sp) + LOAD t2, 3*REGBYTES(sp) + LOAD t3, 4*REGBYTES(sp) + LOAD t4, 5*REGBYTES(sp) + LOAD t5, 6*REGBYTES(sp) + LOAD t6, 7*REGBYTES(sp) + LOAD a0, 8*REGBYTES(sp) + LOAD a1, 9*REGBYTES(sp) + LOAD a2, 10*REGBYTES(sp) + LOAD a3, 11*REGBYTES(sp) + LOAD a4, 12*REGBYTES(sp) + LOAD a5, 13*REGBYTES(sp) + LOAD a6, 14*REGBYTES(sp) + LOAD a7, 15*REGBYTES(sp) + + addi sp, sp, 16*REGBYTES + mret diff --git a/artiq/firmware/libboard_misoc/sdram.rs b/artiq/firmware/libboard_misoc/sdram.rs index 6e3071c4b..b3e112eb0 100644 --- a/artiq/firmware/libboard_misoc/sdram.rs +++ b/artiq/firmware/libboard_misoc/sdram.rs @@ -2,6 +2,7 @@ mod ddr { use core::{ptr, fmt}; use csr::{dfii, ddrphy}; + use csr::CONFIG_DATA_WIDTH_BYTES; use sdram_phy::{self, spin_cycles}; use sdram_phy::{DFII_COMMAND_CS, DFII_COMMAND_WE, DFII_COMMAND_CAS, DFII_COMMAND_RAS, DFII_COMMAND_WRDATA, DFII_COMMAND_RDDATA}; @@ -14,6 +15,8 @@ mod ddr { const DQS_SIGNAL_COUNT: usize = DFII_PIX_DATA_SIZE / 2; + const CSR_SEPARATION: isize = CONFIG_DATA_WIDTH_BYTES as isize / 4; + macro_rules! log { ($logger:expr, $( $arg:expr ),+) => ( if let &mut Some(ref mut f) = $logger { @@ -31,7 +34,7 @@ mod ddr { } #[cfg(ddrphy_wlevel)] - unsafe fn write_level_scan(logger: &mut Option<&mut fmt::Write>) { + unsafe fn write_level_scan(logger: &mut Option<&mut dyn fmt::Write>) { #[cfg(kusddrphy)] log!(logger, "DQS initial delay: {} taps\n", ddrphy::wdly_dqs_taps_read()); log!(logger, "Write leveling scan:\n"); @@ -46,7 +49,7 @@ mod ddr { for n in 0..DQS_SIGNAL_COUNT { let dq_addr = dfii::PI0_RDDATA_ADDR - .offset((DQS_SIGNAL_COUNT - 1 - n) as isize); + .offset(CSR_SEPARATION * (DQS_SIGNAL_COUNT - 1 - n) as isize); log!(logger, "Module {}:\n", DQS_SIGNAL_COUNT - 1 - n); @@ -82,7 +85,7 @@ mod ddr { } #[cfg(ddrphy_wlevel)] - unsafe fn write_level(logger: &mut Option<&mut fmt::Write>, + unsafe fn write_level(logger: &mut Option<&mut dyn fmt::Write>, delay: &mut [u16; DQS_SIGNAL_COUNT], high_skew: &mut [bool; DQS_SIGNAL_COUNT]) -> bool { #[cfg(kusddrphy)] @@ -100,7 +103,7 @@ mod ddr { let mut failed = false; for n in 0..DQS_SIGNAL_COUNT { let dq_addr = dfii::PI0_RDDATA_ADDR - .offset((DQS_SIGNAL_COUNT - 1 - n) as isize); + .offset(CSR_SEPARATION * (DQS_SIGNAL_COUNT - 1 - n) as isize); delay[n] = 0; high_skew[n] = false; @@ -172,7 +175,7 @@ mod ddr { } #[cfg(ddrphy_wlevel)] - unsafe fn read_bitslip(logger: &mut Option<&mut fmt::Write>, + unsafe fn read_bitslip(logger: &mut Option<&mut dyn fmt::Write>, delay: &[u16; DQS_SIGNAL_COUNT], high_skew: &[bool; DQS_SIGNAL_COUNT]) { let threshold_opt = delay.iter().zip(high_skew.iter()) @@ -203,7 +206,7 @@ mod ddr { } } - unsafe fn read_level_scan(logger: &mut Option<&mut fmt::Write>) { + unsafe fn read_level_scan(logger: &mut Option<&mut dyn fmt::Write>) { log!(logger, "Read leveling scan:\n"); // Generate pseudo-random sequence @@ -223,7 +226,7 @@ mod ddr { // Write test pattern for p in 0..DFII_NPHASES { for offset in 0..DFII_PIX_DATA_SIZE { - let addr = DFII_PIX_WRDATA_ADDR[p].offset(offset as isize); + let addr = DFII_PIX_WRDATA_ADDR[p].offset(CSR_SEPARATION * offset as isize); let data = prs[DFII_PIX_DATA_SIZE * p + offset]; ptr::write_volatile(addr, data as u32); } @@ -258,7 +261,7 @@ mod ddr { for p in 0..DFII_NPHASES { for &offset in [n, n + DQS_SIGNAL_COUNT].iter() { - let addr = DFII_PIX_RDDATA_ADDR[p].offset(offset as isize); + let addr = DFII_PIX_RDDATA_ADDR[p].offset(CSR_SEPARATION * offset as isize); let data = prs[DFII_PIX_DATA_SIZE * p + offset]; if ptr::read_volatile(addr) as u8 != data { working = false; @@ -286,7 +289,7 @@ mod ddr { spin_cycles(15); } - unsafe fn read_level(logger: &mut Option<&mut fmt::Write>) -> bool { + unsafe fn read_level(logger: &mut Option<&mut dyn fmt::Write>) -> bool { log!(logger, "Read leveling: "); // Generate pseudo-random sequence @@ -306,7 +309,7 @@ mod ddr { // Write test pattern for p in 0..DFII_NPHASES { for offset in 0..DFII_PIX_DATA_SIZE { - let addr = DFII_PIX_WRDATA_ADDR[p].offset(offset as isize); + let addr = DFII_PIX_WRDATA_ADDR[p].offset(CSR_SEPARATION * offset as isize); let data = prs[DFII_PIX_DATA_SIZE * p + offset]; ptr::write_volatile(addr, data as u32); } @@ -349,7 +352,7 @@ mod ddr { for p in 0..DFII_NPHASES { for &offset in [n, n + DQS_SIGNAL_COUNT].iter() { - let addr = DFII_PIX_RDDATA_ADDR[p].offset(offset as isize); + let addr = DFII_PIX_RDDATA_ADDR[p].offset(CSR_SEPARATION * offset as isize); let data = prs[DFII_PIX_DATA_SIZE * p + offset]; if ptr::read_volatile(addr) as u8 != data { valid = false; @@ -417,7 +420,7 @@ mod ddr { true } - pub unsafe fn level(logger: &mut Option<&mut fmt::Write>) -> bool { + pub unsafe fn level(logger: &mut Option<&mut dyn fmt::Write>) -> bool { #[cfg(ddrphy_wlevel)] { let mut delay = [0; DQS_SIGNAL_COUNT]; @@ -442,7 +445,7 @@ use core::fmt; use csr; use sdram_phy; -pub unsafe fn init(mut _logger: Option<&mut fmt::Write>) -> bool { +pub unsafe fn init(mut _logger: Option<&mut dyn fmt::Write>) -> bool { sdram_phy::initialize(); #[cfg(has_ddrphy)] diff --git a/artiq/firmware/libboard_misoc/spiflash.rs b/artiq/firmware/libboard_misoc/spiflash.rs index 3a4e7cb59..9b5ca31f5 100644 --- a/artiq/firmware/libboard_misoc/spiflash.rs +++ b/artiq/firmware/libboard_misoc/spiflash.rs @@ -113,3 +113,9 @@ pub unsafe fn write(mut addr: usize, mut data: &[u8]) { data = &data[size..]; } } + +#[cfg(any(soc_platform = "kasli", soc_platform = "metlino", soc_platform = "kc705"))] +pub unsafe fn reload () -> ! { + csr::icap::iprog_write(1); + loop {} +} diff --git a/artiq/firmware/libc/Cargo.toml b/artiq/firmware/libc/Cargo.toml new file mode 100644 index 000000000..7231c77c4 --- /dev/null +++ b/artiq/firmware/libc/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "libc" +version = "0.1.0" +authors = ["M-Labs"] +edition = "2018" diff --git a/artiq/firmware/libc/src/lib.rs b/artiq/firmware/libc/src/lib.rs new file mode 100644 index 000000000..e5eb97a23 --- /dev/null +++ b/artiq/firmware/libc/src/lib.rs @@ -0,0 +1,8 @@ +// Helper crate for dealing with c ffi +#![allow(non_camel_case_types)] +#![no_std] +pub type c_char = i8; +pub type c_int = i32; +pub type size_t = usize; +pub type uintptr_t = usize; +pub type c_void = core::ffi::c_void; diff --git a/artiq/firmware/libdyld/elf.rs b/artiq/firmware/libdyld/elf.rs index 0385bc768..a10d9b4c3 100644 --- a/artiq/firmware/libdyld/elf.rs +++ b/artiq/firmware/libdyld/elf.rs @@ -131,6 +131,7 @@ pub const EM_TILEPRO: u16 = 188; pub const EM_MICROBLAZE: u16 = 189; pub const EM_TILEGX: u16 = 191; pub const EM_NUM: u16 = 192; +pub const EM_RISCV: u16 = 243; pub const EM_ALPHA: u16 = 36902; pub const EV_NONE: u8 = 0; pub const EV_CURRENT: u8 = 1; @@ -2229,6 +2230,67 @@ pub const R_OR1K_TLS_TPOFF: u8 = 32; pub const R_OR1K_TLS_DTPOFF: u8 = 33; pub const R_OR1K_TLS_DTPMOD: u8 = 34; pub const R_OR1K_NUM: u8 = 35; +pub const EF_RISCV_RVC: u32 = 1; +pub const EF_RISCV_FLOAT_ABI: u32 = 6; +pub const EF_RISCV_FLOAT_ABI_SOFT: u32 = 0; +pub const EF_RISCV_FLOAT_ABI_SINGLE: u32 = 2; +pub const EF_RISCV_FLOAT_ABI_DOUBLE: u32 = 4; +pub const EF_RISCV_FLOAT_ABI_QUAD: u32 = 6; +pub const R_RISCV_NONE: u8 = 0; +pub const R_RISCV_32: u8 = 1; +pub const R_RISCV_64: u8 = 2; +pub const R_RISCV_RELATIVE: u8 = 3; +pub const R_RISCV_COPY: u8 = 4; +pub const R_RISCV_JUMP_SLOT: u8 = 5; +pub const R_RISCV_TLS_DTPMOD32: u8 = 6; +pub const R_RISCV_TLS_DTPMOD64: u8 = 7; +pub const R_RISCV_TLS_DTPREL32: u8 = 8; +pub const R_RISCV_TLS_DTPREL64: u8 = 9; +pub const R_RISCV_TLS_TPREL32: u8 = 10; +pub const R_RISCV_TLS_TPREL64: u8 = 11; +pub const R_RISCV_BRANCH: u8 = 16; +pub const R_RISCV_JAL: u8 = 17; +pub const R_RISCV_CALL: u8 = 18; +pub const R_RISCV_CALL_PLT: u8 = 19; +pub const R_RISCV_GOT_HI20: u8 = 20; +pub const R_RISCV_TLS_GOT_HI20: u8 = 21; +pub const R_RISCV_TLS_GD_HI20: u8 = 22; +pub const R_RISCV_PCREL_HI20: u8 = 23; +pub const R_RISCV_PCREL_LO12_I: u8 = 24; +pub const R_RISCV_PCREL_LO12_S: u8 = 25; +pub const R_RISCV_HI20: u8 = 26; +pub const R_RISCV_LO12_I: u8 = 27; +pub const R_RISCV_LO12_S: u8 = 28; +pub const R_RISCV_TPREL_HI20: u8 = 29; +pub const R_RISCV_TPREL_LO12_I: u8 = 30; +pub const R_RISCV_TPREL_LO12_S: u8 = 31; +pub const R_RISCV_TPREL_ADD: u8 = 32; +pub const R_RISCV_ADD8: u8 = 33; +pub const R_RISCV_ADD16: u8 = 34; +pub const R_RISCV_ADD32: u8 = 35; +pub const R_RISCV_ADD64: u8 = 36; +pub const R_RISCV_SUB8: u8 = 37; +pub const R_RISCV_SUB16: u8 = 38; +pub const R_RISCV_SUB32: u8 = 39; +pub const R_RISCV_SUB64: u8 = 40; +pub const R_RISCV_GNU_VTINHERIT: u8 = 41; +pub const R_RISCV_GNU_VTENTRY: u8 = 42; +pub const R_RISCV_ALIGN: u8 = 43; +pub const R_RISCV_RVC_BRANCH: u8 = 44; +pub const R_RISCV_RVC_JUMP: u8 = 45; +pub const R_RISCV_RVC_LUI: u8 = 46; +pub const R_RISCV_GPREL_I: u8 = 47; +pub const R_RISCV_GPREL_S: u8 = 48; +pub const R_RISCV_TPREL_I: u8 = 49; +pub const R_RISCV_TPREL_S: u8 = 50; +pub const R_RISCV_RELAX: u8 = 51; +pub const R_RISCV_SUB6: u8 = 52; +pub const R_RISCV_SET6: u8 = 53; +pub const R_RISCV_SET8: u8 = 54; +pub const R_RISCV_SET16: u8 = 55; +pub const R_RISCV_SET32: u8 = 56; +pub const R_RISCV_32_PCREL: u8 = 57; +pub const R_RISCV_NUM: u8 = 58; pub type Elf32_Half = u16; pub type Elf64_Half = u16; diff --git a/artiq/firmware/libdyld/lib.rs b/artiq/firmware/libdyld/lib.rs index bbf4f59c5..1d89edaee 100644 --- a/artiq/firmware/libdyld/lib.rs +++ b/artiq/firmware/libdyld/lib.rs @@ -75,6 +75,12 @@ impl<'a> fmt::Display for Error<'a> { } } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum Arch { + RiscV, + OpenRisc, +} + pub struct Library<'a> { image_off: Elf32_Addr, image_sz: usize, @@ -134,7 +140,7 @@ impl<'a> Library<'a> { pub unsafe fn rebind(&self, name: &[u8], addr: Elf32_Word) -> Result<(), Error<'a>> { for rela in self.pltrel.iter() { match ELF32_R_TYPE(rela.r_info) { - R_OR1K_32 | R_OR1K_GLOB_DAT | R_OR1K_JMP_SLOT => { + R_RISCV_32 | R_RISCV_JUMP_SLOT => { let sym = self.symtab.get(ELF32_R_SYM(rela.r_info) as usize) .ok_or("symbol out of bounds of symbol table")?; let sym_name = self.name_starting_at(sym.st_name as usize)?; @@ -151,7 +157,7 @@ impl<'a> Library<'a> { Ok(()) } - fn resolve_rela(&self, rela: &Elf32_Rela, resolve: &Fn(&[u8]) -> Option) + fn resolve_rela(&self, rela: &Elf32_Rela, resolve: &dyn Fn(&[u8]) -> Option) -> Result<(), Error<'a>> { let sym; if ELF32_R_SYM(rela.r_info) == 0 { @@ -163,13 +169,13 @@ impl<'a> Library<'a> { let value; match ELF32_R_TYPE(rela.r_info) { - R_OR1K_NONE => + R_RISCV_NONE => return Ok(()), - R_OR1K_RELATIVE => + R_RISCV_RELATIVE => value = self.image_off + rela.r_addend as Elf32_Word, - R_OR1K_32 | R_OR1K_GLOB_DAT | R_OR1K_JMP_SLOT => { + R_RISCV_32 | R_RISCV_JUMP_SLOT => { let sym = sym.ok_or("relocation requires an associated symbol")?; let sym_name = self.name_starting_at(sym.st_name as usize)?; @@ -195,7 +201,7 @@ impl<'a> Library<'a> { self.update_rela(rela, value) } - pub fn load(data: &[u8], image: &'a mut [u8], resolve: &Fn(&[u8]) -> Option) + pub fn load(data: &[u8], image: &'a mut [u8], resolve: &dyn Fn(&[u8]) -> Option) -> Result, Error<'a>> { #![allow(unused_assignments)] @@ -204,16 +210,22 @@ impl<'a> Library<'a> { const IDENT: [u8; EI_NIDENT] = [ ELFMAG0, ELFMAG1, ELFMAG2, ELFMAG3, - ELFCLASS32, ELFDATA2MSB, EV_CURRENT, ELFOSABI_NONE, + ELFCLASS32, ELFDATA2LSB, EV_CURRENT, ELFOSABI_NONE, /* ABI version */ 0, /* padding */ 0, 0, 0, 0, 0, 0, 0 ]; - #[cfg(target_arch = "or1k")] - const ARCH: u16 = EM_OPENRISC; - #[cfg(not(target_arch = "or1k"))] + #[cfg(target_arch = "riscv32")] + const ARCH: u16 = EM_RISCV; + #[cfg(not(target_arch = "riscv32"))] const ARCH: u16 = EM_NONE; - if ehdr.e_ident != IDENT || ehdr.e_type != ET_DYN || ehdr.e_machine != ARCH { + #[cfg(all(target_feature = "f", target_feature = "d"))] + const FLAGS: u32 = EF_RISCV_FLOAT_ABI_DOUBLE; + + #[cfg(not(all(target_feature = "f", target_feature = "d")))] + const FLAGS: u32 = EF_RISCV_FLOAT_ABI_SOFT; + + if ehdr.e_ident != IDENT || ehdr.e_type != ET_DYN || ehdr.e_machine != ARCH || ehdr.e_flags != FLAGS { return Err("not a shared library for current architecture")? } diff --git a/artiq/firmware/libeh/Cargo.toml b/artiq/firmware/libeh/Cargo.toml index c1bea6f4f..6998fcd35 100644 --- a/artiq/firmware/libeh/Cargo.toml +++ b/artiq/firmware/libeh/Cargo.toml @@ -9,3 +9,6 @@ path = "lib.rs" [dependencies] cslice = { version = "0.3" } +libc = { path = "../libc" } +unwind = { path = "../libunwind" } +compiler_builtins = "=0.1.39" # A dependency of alloc, libeh doesn't need it diff --git a/artiq/firmware/libeh/dwarf.rs b/artiq/firmware/libeh/dwarf.rs index 0956dc267..4dfc04a67 100644 --- a/artiq/firmware/libeh/dwarf.rs +++ b/artiq/firmware/libeh/dwarf.rs @@ -1,51 +1,71 @@ -#![allow(non_upper_case_globals, dead_code)] +//! Parsing of GCC-style Language-Specific Data Area (LSDA) +//! For details see: +//! * +//! * +//! * +//! * +//! +//! A reference implementation may be found in the GCC source tree +//! (`/libgcc/unwind-c.c` as of this writing). -use core::{ptr, mem}; -use cslice::CSlice; +#![allow(non_upper_case_globals)] +#![allow(unused)] -const DW_EH_PE_omit: u8 = 0xFF; -const DW_EH_PE_absptr: u8 = 0x00; +use core::mem; -const DW_EH_PE_uleb128: u8 = 0x01; -const DW_EH_PE_udata2: u8 = 0x02; -const DW_EH_PE_udata4: u8 = 0x03; -const DW_EH_PE_udata8: u8 = 0x04; -const DW_EH_PE_sleb128: u8 = 0x09; -const DW_EH_PE_sdata2: u8 = 0x0A; -const DW_EH_PE_sdata4: u8 = 0x0B; -const DW_EH_PE_sdata8: u8 = 0x0C; +pub const DW_EH_PE_omit: u8 = 0xFF; +pub const DW_EH_PE_absptr: u8 = 0x00; -const DW_EH_PE_pcrel: u8 = 0x10; -const DW_EH_PE_textrel: u8 = 0x20; -const DW_EH_PE_datarel: u8 = 0x30; -const DW_EH_PE_funcrel: u8 = 0x40; -const DW_EH_PE_aligned: u8 = 0x50; +pub const DW_EH_PE_uleb128: u8 = 0x01; +pub const DW_EH_PE_udata2: u8 = 0x02; +pub const DW_EH_PE_udata4: u8 = 0x03; +pub const DW_EH_PE_udata8: u8 = 0x04; +pub const DW_EH_PE_sleb128: u8 = 0x09; +pub const DW_EH_PE_sdata2: u8 = 0x0A; +pub const DW_EH_PE_sdata4: u8 = 0x0B; +pub const DW_EH_PE_sdata8: u8 = 0x0C; -const DW_EH_PE_indirect: u8 = 0x80; +pub const DW_EH_PE_pcrel: u8 = 0x10; +pub const DW_EH_PE_textrel: u8 = 0x20; +pub const DW_EH_PE_datarel: u8 = 0x30; +pub const DW_EH_PE_funcrel: u8 = 0x40; +pub const DW_EH_PE_aligned: u8 = 0x50; -#[derive(Clone)] -struct DwarfReader { +pub const DW_EH_PE_indirect: u8 = 0x80; + +#[derive(Copy, Clone)] +pub struct EHContext<'a> { + pub ip: usize, // Current instruction pointer + pub func_start: usize, // Address of the current function + pub get_text_start: &'a dyn Fn() -> usize, // Get address of the code section + pub get_data_start: &'a dyn Fn() -> usize, // Get address of the data section +} + +pub struct DwarfReader { pub ptr: *const u8, } +#[repr(C, packed)] +struct Unaligned(T); + impl DwarfReader { - fn new(ptr: *const u8) -> DwarfReader { - DwarfReader { ptr: ptr } + pub fn new(ptr: *const u8) -> DwarfReader { + DwarfReader { ptr } } - // DWARF streams are packed, so e.g. a u32 would not necessarily be aligned + // DWARF streams are packed, so e.g., a u32 would not necessarily be aligned // on a 4-byte boundary. This may cause problems on platforms with strict // alignment requirements. By wrapping data in a "packed" struct, we are // telling the backend to generate "misalignment-safe" code. - unsafe fn read(&mut self) -> T { - let result = ptr::read_unaligned(self.ptr as *const T); - self.ptr = self.ptr.offset(mem::size_of::() as isize); + pub unsafe fn read(&mut self) -> T { + let Unaligned(result) = *(self.ptr as *const Unaligned); + self.ptr = self.ptr.add(mem::size_of::()); result } // ULEB128 and SLEB128 encodings are defined in Section 7.6 - "Variable // Length Data". - unsafe fn read_uleb128(&mut self) -> u64 { + pub unsafe fn read_uleb128(&mut self) -> u64 { let mut shift: usize = 0; let mut result: u64 = 0; let mut byte: u8; @@ -60,8 +80,8 @@ impl DwarfReader { result } - unsafe fn read_sleb128(&mut self) -> i64 { - let mut shift: usize = 0; + pub unsafe fn read_sleb128(&mut self) -> i64 { + let mut shift: u32 = 0; let mut result: u64 = 0; let mut byte: u8; loop { @@ -73,70 +93,61 @@ impl DwarfReader { } } // sign-extend - if shift < 8 * mem::size_of::() && (byte & 0x40) != 0 { + if shift < u64::BITS && (byte & 0x40) != 0 { result |= (!0 as u64) << shift; } result as i64 } - - unsafe fn read_encoded_pointer(&mut self, encoding: u8) -> usize { - fn round_up(unrounded: usize, align: usize) -> usize { - debug_assert!(align.is_power_of_two()); - (unrounded + align - 1) & !(align - 1) - } - - debug_assert!(encoding != DW_EH_PE_omit); - - // DW_EH_PE_aligned implies it's an absolute pointer value - if encoding == DW_EH_PE_aligned { - self.ptr = round_up(self.ptr as usize, mem::size_of::()) as *const u8; - return self.read::() - } - - let value_ptr = self.ptr; - let mut result = match encoding & 0x0F { - DW_EH_PE_absptr => self.read::(), - DW_EH_PE_uleb128 => self.read_uleb128() as usize, - DW_EH_PE_udata2 => self.read::() as usize, - DW_EH_PE_udata4 => self.read::() as usize, - DW_EH_PE_udata8 => self.read::() as usize, - DW_EH_PE_sleb128 => self.read_sleb128() as usize, - DW_EH_PE_sdata2 => self.read::() as usize, - DW_EH_PE_sdata4 => self.read::() as usize, - DW_EH_PE_sdata8 => self.read::() as usize, - _ => panic!(), - }; - - result += match encoding & 0x70 { - DW_EH_PE_absptr => 0, - // relative to address of the encoded value, despite the name - DW_EH_PE_pcrel => value_ptr as usize, - _ => panic!(), - }; - - if encoding & DW_EH_PE_indirect != 0 { - result = *(result as *const usize); - } - - result - } } -fn encoding_size(encoding: u8) -> usize { +unsafe fn read_encoded_pointer( + reader: &mut DwarfReader, + context: &EHContext<'_>, + encoding: u8, +) -> Result { if encoding == DW_EH_PE_omit { - return 0 + return Err(()); } - match encoding & 0x0F { - DW_EH_PE_absptr => mem::size_of::(), - DW_EH_PE_udata2 => 2, - DW_EH_PE_udata4 => 4, - DW_EH_PE_udata8 => 8, - DW_EH_PE_sdata2 => 2, - DW_EH_PE_sdata4 => 4, - DW_EH_PE_sdata8 => 8, - _ => panic!() + // DW_EH_PE_aligned implies it's an absolute pointer value + if encoding == DW_EH_PE_aligned { + reader.ptr = round_up(reader.ptr as usize, mem::size_of::())? as *const u8; + return Ok(reader.read::()); } + + let mut result = match encoding & 0x0F { + DW_EH_PE_absptr => reader.read::(), + DW_EH_PE_uleb128 => reader.read_uleb128() as usize, + DW_EH_PE_udata2 => reader.read::() as usize, + DW_EH_PE_udata4 => reader.read::() as usize, + DW_EH_PE_udata8 => reader.read::() as usize, + DW_EH_PE_sleb128 => reader.read_sleb128() as usize, + DW_EH_PE_sdata2 => reader.read::() as usize, + DW_EH_PE_sdata4 => reader.read::() as usize, + DW_EH_PE_sdata8 => reader.read::() as usize, + _ => return Err(()), + }; + + result += match encoding & 0x70 { + DW_EH_PE_absptr => 0, + // relative to address of the encoded value, despite the name + DW_EH_PE_pcrel => reader.ptr as usize, + DW_EH_PE_funcrel => { + if context.func_start == 0 { + return Err(()); + } + context.func_start + } + DW_EH_PE_textrel => (*context.get_text_start)(), + DW_EH_PE_datarel => (*context.get_data_start)(), + _ => return Err(()), + }; + + if encoding & DW_EH_PE_indirect != 0 { + result = *(result as *const usize); + } + + Ok(result) } pub enum EHAction { @@ -146,98 +157,94 @@ pub enum EHAction { Terminate, } -pub unsafe fn find_eh_action(lsda: *const u8, func_start: usize, ip: usize, - exn_name: CSlice) -> EHAction { +pub const USING_SJLJ_EXCEPTIONS: bool = cfg!(all(target_os = "ios", target_arch = "arm")); + +pub unsafe fn find_eh_action(lsda: *const u8, context: &EHContext<'_>) -> Result { if lsda.is_null() { - return EHAction::None + return Ok(EHAction::None); } + let func_start = context.func_start; let mut reader = DwarfReader::new(lsda); let start_encoding = reader.read::(); // base address for landing pad offsets let lpad_base = if start_encoding != DW_EH_PE_omit { - reader.read_encoded_pointer(start_encoding) + read_encoded_pointer(&mut reader, context, start_encoding)? } else { func_start }; let ttype_encoding = reader.read::(); - let ttype_encoding_size = encoding_size(ttype_encoding) as isize; - - let class_info; if ttype_encoding != DW_EH_PE_omit { - let class_info_offset = reader.read_uleb128(); - class_info = reader.ptr.offset(class_info_offset as isize); - } else { - class_info = ptr::null(); + // Rust doesn't analyze exception types, so we don't care about the type table + reader.read_uleb128(); } - assert!(!class_info.is_null()); let call_site_encoding = reader.read::(); let call_site_table_length = reader.read_uleb128(); let action_table = reader.ptr.offset(call_site_table_length as isize); + let ip = context.ip; - while reader.ptr < action_table { - let cs_start = reader.read_encoded_pointer(call_site_encoding); - let cs_len = reader.read_encoded_pointer(call_site_encoding); - let cs_lpad = reader.read_encoded_pointer(call_site_encoding); - let cs_action = reader.read_uleb128(); - - if ip < func_start + cs_start { + if !USING_SJLJ_EXCEPTIONS { + while reader.ptr < action_table { + let cs_start = read_encoded_pointer(&mut reader, context, call_site_encoding)?; + let cs_len = read_encoded_pointer(&mut reader, context, call_site_encoding)?; + let cs_lpad = read_encoded_pointer(&mut reader, context, call_site_encoding)?; + let cs_action = reader.read_uleb128(); // Callsite table is sorted by cs_start, so if we've passed the ip, we // may stop searching. - break + if ip < func_start + cs_start { + break; + } + if ip < func_start + cs_start + cs_len { + if cs_lpad == 0 { + return Ok(EHAction::None); + } else { + let lpad = lpad_base + cs_lpad; + return Ok(interpret_cs_action(cs_action, lpad)); + } + } } - if ip > func_start + cs_start + cs_len { - continue + // Ip is not present in the table. This should not happen... but it does: issue #35011. + // So rather than returning EHAction::Terminate, we do this. + Ok(EHAction::None) + } else { + // SjLj version: + // The "IP" is an index into the call-site table, with two exceptions: + // -1 means 'no-action', and 0 means 'terminate'. + match ip as isize { + -1 => return Ok(EHAction::None), + 0 => return Ok(EHAction::Terminate), + _ => (), } - - if cs_lpad == 0 { - return EHAction::None - } - - let lpad = lpad_base + cs_lpad; - if cs_action == 0 { - return EHAction::Cleanup(lpad) - } - - let action_entry = action_table.offset((cs_action - 1) as isize); - let mut action_reader = DwarfReader::new(action_entry); + let mut idx = ip; loop { - let type_info_offset = action_reader.read_sleb128() as isize; - let action_offset = action_reader.clone().read_sleb128() as isize; - assert!(type_info_offset >= 0); - - if type_info_offset > 0 { - let type_info_ptr_ptr = class_info.offset(-type_info_offset * ttype_encoding_size); - let type_info_ptr = DwarfReader::new(type_info_ptr_ptr) - .read_encoded_pointer(ttype_encoding); - let type_info = *(type_info_ptr as *const CSlice); - - if type_info.as_ref() == exn_name.as_ref() { - return EHAction::Catch(lpad) - } - - if type_info.len() == 0 { - // This is a catch-all clause. We don't compare type_info_ptr with null here - // because, in PIC mode, the OR1K LLVM backend emits a literal zero - // encoded with DW_EH_PE_pcrel, which of course doesn't result in - // a proper null pointer. - return EHAction::Catch(lpad) - } - } - - if action_offset == 0 { - break - } else { - action_reader.ptr = action_reader.ptr.offset(action_offset) + let cs_lpad = reader.read_uleb128(); + let cs_action = reader.read_uleb128(); + idx -= 1; + if idx == 0 { + // Can never have null landing pad for sjlj -- that would have + // been indicated by a -1 call site index. + let lpad = (cs_lpad + 1) as usize; + return Ok(interpret_cs_action(cs_action, lpad)); } } - - return EHAction::None } - - // the function has a personality but no landing pads; this is fine - EHAction::None +} + +fn interpret_cs_action(cs_action: u64, lpad: usize) -> EHAction { + if cs_action == 0 { + // If cs_action is 0 then this is a cleanup (Drop::drop). We run these + // for both Rust panics and foreign exceptions. + EHAction::Cleanup(lpad) + } else { + // Stop unwinding Rust panics at catch_unwind. + EHAction::Catch(lpad) + } +} + +#[inline] +fn round_up(unrounded: usize, align: usize) -> Result { + if align.is_power_of_two() { Ok((unrounded + align - 1) & !(align - 1)) } else { Err(()) } } diff --git a/artiq/firmware/libeh/eh_rust.rs b/artiq/firmware/libeh/eh_rust.rs index 7fb14193d..166aae843 100644 --- a/artiq/firmware/libeh/eh_rust.rs +++ b/artiq/firmware/libeh/eh_rust.rs @@ -14,13 +14,10 @@ // // By design, this personality function is only ever called in the search phase, although // to keep things simple and close to upstream, it is not modified -#![allow(private_no_mangle_fns)] - use unwind as uw; use libc::{c_int, uintptr_t}; -use cslice::AsCSlice; -use dwarf::{self, EHAction}; +use dwarf::{self, EHAction, EHContext}; // Register ids were lifted from LLVM's TargetLowering::getExceptionPointerRegister() // and TargetLowering::getExceptionSelectorRegister() for each architecture, @@ -34,8 +31,8 @@ const UNWIND_DATA_REG: (i32, i32) = (0, 2); // EAX, EDX #[cfg(target_arch = "x86_64")] const UNWIND_DATA_REG: (i32, i32) = (0, 1); // RAX, RDX -#[cfg(any(target_arch = "or1k"))] -const UNWIND_DATA_REG: (i32, i32) = (3, 4); // R3, R4 +#[cfg(any(target_arch = "riscv32"))] +const UNWIND_DATA_REG: (i32, i32) = (10, 11); // X10, X11 // The following code is based on GCC's C and C++ personality routines. For reference, see: // https://github.com/gcc-mirror/gcc/blob/master/libstdc++-v3/libsupc++/eh_personality.cc @@ -78,11 +75,17 @@ unsafe extern "C" fn rust_eh_personality(version: c_int, } } -unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) - -> Result -{ +unsafe fn find_eh_action(context: *mut uw::_Unwind_Context) -> Result { let lsda = uw::_Unwind_GetLanguageSpecificData(context) as *const u8; - let func = uw::_Unwind_GetRegionStart(context); - let ip = uw::_Unwind_GetIP(context); - Ok(dwarf::find_eh_action(lsda, func, ip, [].as_c_slice())) + let mut ip_before_instr: c_int = 0; + let ip = uw::_Unwind_GetIPInfo(context, &mut ip_before_instr); + let eh_context = EHContext { + // The return address points 1 byte past the call instruction, + // which could be in the next IP range in LSDA range table. + ip: if ip_before_instr != 0 { ip } else { ip - 1 }, + func_start: uw::_Unwind_GetRegionStart(context), + get_text_start: &|| uw::_Unwind_GetTextRelBase(context), + get_data_start: &|| uw::_Unwind_GetDataRelBase(context), + }; + dwarf::find_eh_action(lsda, &eh_context) } diff --git a/artiq/firmware/libeh/lib.rs b/artiq/firmware/libeh/lib.rs index da209ea25..b1a200da3 100644 --- a/artiq/firmware/libeh/lib.rs +++ b/artiq/firmware/libeh/lib.rs @@ -1,4 +1,4 @@ -#![feature(lang_items, panic_unwind, libc, unwind_attributes)] +#![feature(lang_items, panic_unwind, libc, unwind_attributes, int_bits_const)] #![no_std] extern crate cslice; diff --git a/artiq/firmware/libio/Cargo.toml b/artiq/firmware/libio/Cargo.toml index 6aacf0c47..f4d1cbbb9 100644 --- a/artiq/firmware/libio/Cargo.toml +++ b/artiq/firmware/libio/Cargo.toml @@ -8,8 +8,8 @@ name = "io" path = "lib.rs" [dependencies] -failure = { version = "0.1", default-features = false } -failure_derive = { version = "0.1", default-features = false } +failure = { version = "=0.1.1", default-features = false } +failure_derive = { version = "=0.1.1", default-features = false } byteorder = { version = "1.0", default-features = false, optional = true } [features] diff --git a/artiq/firmware/libio/cursor.rs b/artiq/firmware/libio/cursor.rs index b820bdddc..85ff478fe 100644 --- a/artiq/firmware/libio/cursor.rs +++ b/artiq/firmware/libio/cursor.rs @@ -69,7 +69,7 @@ impl<'a> Write for Cursor<&'a mut [u8]> { } #[cfg(feature = "alloc")] -impl Write for Cursor<::alloc::Vec> { +impl Write for Cursor<::alloc::vec::Vec> { type WriteError = !; type FlushError = !; diff --git a/artiq/firmware/libio/lib.rs b/artiq/firmware/libio/lib.rs index b10c41345..cb1c34002 100644 --- a/artiq/firmware/libio/lib.rs +++ b/artiq/firmware/libio/lib.rs @@ -1,6 +1,5 @@ #![no_std] #![feature(never_type)] -#![cfg_attr(feature = "alloc", feature(alloc))] extern crate failure; #[macro_use] @@ -131,7 +130,7 @@ impl<'a> Write for &'a mut [u8] { } #[cfg(feature = "alloc")] -impl<'a> Write for alloc::Vec { +impl<'a> Write for alloc::vec::Vec { type WriteError = !; type FlushError = !; diff --git a/artiq/firmware/libio/proto.rs b/artiq/firmware/libio/proto.rs index 3df4a04aa..377a51383 100644 --- a/artiq/firmware/libio/proto.rs +++ b/artiq/firmware/libio/proto.rs @@ -1,6 +1,6 @@ #[cfg(feature = "alloc")] -use {core::str::Utf8Error, alloc::String}; -use byteorder::{ByteOrder, NetworkEndian}; +use {core::str::Utf8Error, alloc::string::String}; +use byteorder::{ByteOrder, NativeEndian}; use ::{Read, Write, Error as IoError}; @@ -29,21 +29,21 @@ pub trait ProtoRead { fn read_u16(&mut self) -> Result { let mut bytes = [0; 2]; self.read_exact(&mut bytes)?; - Ok(NetworkEndian::read_u16(&bytes)) + Ok(NativeEndian::read_u16(&bytes)) } #[inline] fn read_u32(&mut self) -> Result { let mut bytes = [0; 4]; self.read_exact(&mut bytes)?; - Ok(NetworkEndian::read_u32(&bytes)) + Ok(NativeEndian::read_u32(&bytes)) } #[inline] fn read_u64(&mut self) -> Result { let mut bytes = [0; 8]; self.read_exact(&mut bytes)?; - Ok(NetworkEndian::read_u64(&bytes)) + Ok(NativeEndian::read_u64(&bytes)) } #[inline] @@ -53,7 +53,7 @@ pub trait ProtoRead { #[cfg(feature = "alloc")] #[inline] - fn read_bytes(&mut self) -> Result<::alloc::Vec, Self::ReadError> { + fn read_bytes(&mut self) -> Result<::alloc::vec::Vec, Self::ReadError> { let length = self.read_u32()?; let mut value = vec![0; length as usize]; self.read_exact(&mut value)?; @@ -62,7 +62,7 @@ pub trait ProtoRead { #[cfg(feature = "alloc")] #[inline] - fn read_string(&mut self) -> Result<::alloc::String, ReadStringError> { + fn read_string(&mut self) -> Result<::alloc::string::String, ReadStringError> { let bytes = self.read_bytes().map_err(ReadStringError::Other)?; String::from_utf8(bytes).map_err(|err| ReadStringError::Utf8(err.utf8_error())) } @@ -88,42 +88,42 @@ pub trait ProtoWrite { #[inline] fn write_u16(&mut self, value: u16) -> Result<(), Self::WriteError> { let mut bytes = [0; 2]; - NetworkEndian::write_u16(&mut bytes, value); + NativeEndian::write_u16(&mut bytes, value); self.write_all(&bytes) } #[inline] fn write_i16(&mut self, value: i16) -> Result<(), Self::WriteError> { let mut bytes = [0; 2]; - NetworkEndian::write_i16(&mut bytes, value); + NativeEndian::write_i16(&mut bytes, value); self.write_all(&bytes) } #[inline] fn write_u32(&mut self, value: u32) -> Result<(), Self::WriteError> { let mut bytes = [0; 4]; - NetworkEndian::write_u32(&mut bytes, value); + NativeEndian::write_u32(&mut bytes, value); self.write_all(&bytes) } #[inline] fn write_i32(&mut self, value: i32) -> Result<(), Self::WriteError> { let mut bytes = [0; 4]; - NetworkEndian::write_i32(&mut bytes, value); + NativeEndian::write_i32(&mut bytes, value); self.write_all(&bytes) } #[inline] fn write_u64(&mut self, value: u64) -> Result<(), Self::WriteError> { let mut bytes = [0; 8]; - NetworkEndian::write_u64(&mut bytes, value); + NativeEndian::write_u64(&mut bytes, value); self.write_all(&bytes) } #[inline] fn write_i64(&mut self, value: i64) -> Result<(), Self::WriteError> { let mut bytes = [0; 8]; - NetworkEndian::write_i64(&mut bytes, value); + NativeEndian::write_i64(&mut bytes, value); self.write_all(&bytes) } diff --git a/artiq/firmware/libproto_artiq/lib.rs b/artiq/firmware/libproto_artiq/lib.rs index 66c04d5e6..d343d7d61 100644 --- a/artiq/firmware/libproto_artiq/lib.rs +++ b/artiq/firmware/libproto_artiq/lib.rs @@ -1,5 +1,4 @@ #![no_std] -#![cfg_attr(feature = "alloc", feature(alloc))] extern crate failure; #[macro_use] diff --git a/artiq/firmware/libproto_artiq/mgmt_proto.rs b/artiq/firmware/libproto_artiq/mgmt_proto.rs index ffa2a9763..911799ef4 100644 --- a/artiq/firmware/libproto_artiq/mgmt_proto.rs +++ b/artiq/firmware/libproto_artiq/mgmt_proto.rs @@ -1,5 +1,5 @@ use core::str::Utf8Error; -use alloc::{Vec, String}; +use alloc::{vec::Vec, string::String}; #[cfg(feature = "log")] use log; @@ -63,15 +63,6 @@ pub enum Request { ConfigRemove { key: String }, ConfigErase, - StartProfiler { - interval_us: u32, - hits_size: u32, - edges_size: u32, - }, - StopProfiler, - GetProfile, - - Hotswap(Vec), Reboot, DebugAllocator, @@ -86,8 +77,6 @@ pub enum Reply<'a> { ConfigData(&'a [u8]), - Profile, - RebootImminent, } @@ -130,15 +119,6 @@ impl Request { }, 15 => Request::ConfigErase, - 9 => Request::StartProfiler { - interval_us: reader.read_u32()?, - hits_size: reader.read_u32()?, - edges_size: reader.read_u32()?, - }, - 10 => Request::StopProfiler, - 11 => Request::GetProfile, - - 4 => Request::Hotswap(reader.read_bytes()?), 5 => Request::Reboot, 8 => Request::DebugAllocator, @@ -174,11 +154,6 @@ impl<'a> Reply<'a> { writer.write_bytes(bytes)?; }, - Reply::Profile => { - writer.write_u8(5)?; - // profile data follows - } - Reply::RebootImminent => { writer.write_u8(3)?; } diff --git a/artiq/firmware/libproto_artiq/rpc_proto.rs b/artiq/firmware/libproto_artiq/rpc_proto.rs index afc98e101..0295b5297 100644 --- a/artiq/firmware/libproto_artiq/rpc_proto.rs +++ b/artiq/firmware/libproto_artiq/rpc_proto.rs @@ -1,19 +1,31 @@ use core::str; use core::slice; use cslice::{CSlice, CMutSlice}; -use byteorder::{NetworkEndian, ByteOrder}; +use byteorder::{NativeEndian, ByteOrder}; use io::{ProtoRead, Read, Write, ProtoWrite, Error}; use self::tag::{Tag, TagIterator, split_tag}; +unsafe fn align_ptr(ptr: *const ()) -> *const T { + let alignment = core::mem::align_of::() as isize; + let fix = (alignment - (ptr as isize) % alignment) % alignment; + ((ptr as isize) + fix) as *const T +} + +unsafe fn align_ptr_mut(ptr: *mut ()) -> *mut T { + let alignment = core::mem::align_of::() as isize; + let fix = (alignment - (ptr as isize) % alignment) % alignment; + ((ptr as isize) + fix) as *mut T +} + unsafe fn recv_value(reader: &mut R, tag: Tag, data: &mut *mut (), - alloc: &Fn(usize) -> Result<*mut (), E>) + alloc: &dyn Fn(usize) -> Result<*mut (), E>) -> Result<(), E> where R: Read + ?Sized, E: From> { macro_rules! consume_value { ($ty:ty, |$ptr:ident| $map:expr) => ({ - let $ptr = (*data) as *mut $ty; + let $ptr = align_ptr_mut::<$ty>(*data) as *mut $ty; *data = $ptr.offset(1) as *mut (); $map }) @@ -51,7 +63,7 @@ unsafe fn recv_value(reader: &mut R, tag: Tag, data: &mut *mut (), } Tag::List(it) => { #[repr(C)] - struct List { elements: *mut (), length: u32 }; + struct List { elements: *mut (), length: u32 } consume_value!(List, |ptr| { (*ptr).length = reader.read_u32()?; let length = (*ptr).length as usize; @@ -69,13 +81,13 @@ unsafe fn recv_value(reader: &mut R, tag: Tag, data: &mut *mut (), let dest = slice::from_raw_parts_mut(data as *mut u8, length * 4); reader.read_exact(dest)?; let dest = slice::from_raw_parts_mut(data as *mut i32, length); - NetworkEndian::from_slice_i32(dest); + NativeEndian::from_slice_i32(dest); }, Tag::Int64 | Tag::Float64 => { let dest = slice::from_raw_parts_mut(data as *mut u8, length * 8); reader.read_exact(dest)?; let dest = slice::from_raw_parts_mut(data as *mut i64, length); - NetworkEndian::from_slice_i64(dest); + NativeEndian::from_slice_i64(dest); }, _ => { for _ in 0..length { @@ -109,13 +121,13 @@ unsafe fn recv_value(reader: &mut R, tag: Tag, data: &mut *mut (), let dest = slice::from_raw_parts_mut(data as *mut u8, length * 4); reader.read_exact(dest)?; let dest = slice::from_raw_parts_mut(data as *mut i32, length); - NetworkEndian::from_slice_i32(dest); + NativeEndian::from_slice_i32(dest); }, Tag::Int64 | Tag::Float64 => { let dest = slice::from_raw_parts_mut(data as *mut u8, length * 8); reader.read_exact(dest)?; let dest = slice::from_raw_parts_mut(data as *mut i64, length); - NetworkEndian::from_slice_i64(dest); + NativeEndian::from_slice_i64(dest); }, _ => { for _ in 0..length { @@ -139,7 +151,7 @@ unsafe fn recv_value(reader: &mut R, tag: Tag, data: &mut *mut (), } pub fn recv_return(reader: &mut R, tag_bytes: &[u8], data: *mut (), - alloc: &Fn(usize) -> Result<*mut (), E>) + alloc: &dyn Fn(usize) -> Result<*mut (), E>) -> Result<(), E> where R: Read + ?Sized, E: From> @@ -161,7 +173,7 @@ unsafe fn send_value(writer: &mut W, tag: Tag, data: &mut *const ()) { macro_rules! consume_value { ($ty:ty, |$ptr:ident| $map:expr) => ({ - let $ptr = (*data) as *const $ty; + let $ptr = align_ptr::<$ty>(*data); *data = $ptr.offset(1) as *const (); $map }) @@ -196,7 +208,7 @@ unsafe fn send_value(writer: &mut W, tag: Tag, data: &mut *const ()) } Tag::List(it) => { #[repr(C)] - struct List { elements: *const (), length: u32 }; + struct List { elements: *const (), length: u32 } consume_value!(List, |ptr| { let length = (*ptr).length as usize; writer.write_u32((*ptr).length)?; @@ -204,8 +216,8 @@ unsafe fn send_value(writer: &mut W, tag: Tag, data: &mut *const ()) let mut data = (*ptr).elements; writer.write_u8(tag.as_u8())?; match tag { - // we cannot use NetworkEndian::from_slice_i32 as the data is not mutable, - // and that is not needed as the data is already in network endian + // we cannot use NativeEndian::from_slice_i32 as the data is not mutable, + // and that is not needed as the data is already in native endian Tag::Bool => { let slice = slice::from_raw_parts(data as *const u8, length); writer.write_all(slice)?; @@ -243,8 +255,8 @@ unsafe fn send_value(writer: &mut W, tag: Tag, data: &mut *const ()) let mut data = *buffer; writer.write_u8(elt_tag.as_u8())?; match elt_tag { - // we cannot use NetworkEndian::from_slice_i32 as the data is not mutable, - // and that is not needed as the data is already in network endian + // we cannot use NativeEndian::from_slice_i32 as the data is not mutable, + // and that is not needed as the data is already in native endian Tag::Bool => { let slice = slice::from_raw_parts(data as *const u8, length); writer.write_all(slice)?; @@ -275,7 +287,7 @@ unsafe fn send_value(writer: &mut W, tag: Tag, data: &mut *const ()) } Tag::Keyword(it) => { #[repr(C)] - struct Keyword<'a> { name: CSlice<'a, u8> }; + struct Keyword<'a> { name: CSlice<'a, u8> } consume_value!(Keyword, |ptr| { writer.write_string(str::from_utf8((*ptr).name.as_ref()).unwrap())?; let tag = it.clone().next().expect("truncated tag"); @@ -287,7 +299,7 @@ unsafe fn send_value(writer: &mut W, tag: Tag, data: &mut *const ()) } Tag::Object => { #[repr(C)] - struct Object { id: u32 }; + struct Object { id: u32 } consume_value!(*const Object, |ptr| writer.write_u32((**ptr).id)) } diff --git a/artiq/firmware/libproto_artiq/session_proto.rs b/artiq/firmware/libproto_artiq/session_proto.rs index db353e874..0475a4489 100644 --- a/artiq/firmware/libproto_artiq/session_proto.rs +++ b/artiq/firmware/libproto_artiq/session_proto.rs @@ -1,5 +1,5 @@ use core::str::Utf8Error; -use alloc::{Vec, String}; +use alloc::{vec::Vec, string::String}; use io::{Read, ProtoRead, Write, ProtoWrite, Error as IoError, ReadStringError}; @@ -90,7 +90,9 @@ pub enum Reply<'a> { LoadCompleted, LoadFailed(&'a str), - KernelFinished, + KernelFinished { + async_errors: u8 + }, KernelStartupFailed, KernelException { name: &'a str, @@ -100,7 +102,8 @@ pub enum Reply<'a> { line: u32, column: u32, function: &'a str, - backtrace: &'a [usize] + backtrace: &'a [usize], + async_errors: u8 }, RpcRequest { async: bool }, @@ -160,14 +163,16 @@ impl<'a> Reply<'a> { writer.write_string(reason)?; }, - Reply::KernelFinished => { + Reply::KernelFinished { async_errors } => { writer.write_u8(7)?; + writer.write_u8(async_errors)?; }, Reply::KernelStartupFailed => { writer.write_u8(8)?; }, Reply::KernelException { - name, message, param, file, line, column, function, backtrace + name, message, param, file, line, column, function, backtrace, + async_errors } => { writer.write_u8(9)?; writer.write_string(name)?; @@ -183,6 +188,7 @@ impl<'a> Reply<'a> { for &addr in backtrace { writer.write_u32(addr as u32)? } + writer.write_u8(async_errors)?; }, Reply::RpcRequest { async } => { diff --git a/artiq/firmware/libunwind/Cargo.toml b/artiq/firmware/libunwind/Cargo.toml new file mode 100644 index 000000000..6760102d5 --- /dev/null +++ b/artiq/firmware/libunwind/Cargo.toml @@ -0,0 +1,30 @@ +[package] +authors = ["The Rust Project Developers"] +name = "unwind" +version = "0.0.0" +license = "MIT OR Apache-2.0" +repository = "https://github.com/rust-lang/rust.git" +edition = "2018" +include = [ + '/libunwind/*', +] + +[lib] +test = false +bench = false +doc = false + +[dependencies] +libc = { path = "../libc" } +cfg-if = "0.1.8" + +[features] + +# Only applies for Linux and Fuchsia targets +# Static link to the in-tree build of llvm libunwind +llvm-libunwind = [] + +# Only applies for Linux and Fuchsia targets +# If crt-static is enabled, static link to `libunwind.a` provided by system +# If crt-static is disabled, dynamic link to `libunwind.so` provided by system +system-llvm-libunwind = [] \ No newline at end of file diff --git a/artiq/firmware/libunwind/src/lib.rs b/artiq/firmware/libunwind/src/lib.rs new file mode 100644 index 000000000..a087a59ec --- /dev/null +++ b/artiq/firmware/libunwind/src/lib.rs @@ -0,0 +1,11 @@ +#![no_std] +#![unstable(feature = "panic_unwind", issue = "32837")] +#![feature(link_cfg)] +#![feature(nll)] +#![feature(staged_api)] +#![feature(unwind_attributes)] +#![feature(static_nobundle)] +#![cfg_attr(not(target_env = "msvc"), feature(libc))] + +mod libunwind; +pub use libunwind::*; diff --git a/artiq/firmware/libunwind/src/libunwind.rs b/artiq/firmware/libunwind/src/libunwind.rs new file mode 100644 index 000000000..646bf912e --- /dev/null +++ b/artiq/firmware/libunwind/src/libunwind.rs @@ -0,0 +1,282 @@ +#![allow(nonstandard_style)] + +use libc::{c_int, c_void, uintptr_t}; + +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum _Unwind_Reason_Code { + _URC_NO_REASON = 0, + _URC_FOREIGN_EXCEPTION_CAUGHT = 1, + _URC_FATAL_PHASE2_ERROR = 2, + _URC_FATAL_PHASE1_ERROR = 3, + _URC_NORMAL_STOP = 4, + _URC_END_OF_STACK = 5, + _URC_HANDLER_FOUND = 6, + _URC_INSTALL_CONTEXT = 7, + _URC_CONTINUE_UNWIND = 8, + _URC_FAILURE = 9, // used only by ARM EHABI +} +pub use _Unwind_Reason_Code::*; + +pub type _Unwind_Exception_Class = u64; +pub type _Unwind_Word = uintptr_t; +pub type _Unwind_Ptr = uintptr_t; +pub type _Unwind_Trace_Fn = + extern "C" fn(ctx: *mut _Unwind_Context, arg: *mut c_void) -> _Unwind_Reason_Code; + +#[cfg(target_arch = "x86")] +pub const unwinder_private_data_size: usize = 5; + +#[cfg(target_arch = "x86_64")] +pub const unwinder_private_data_size: usize = 6; + +#[cfg(all(target_arch = "arm", not(target_os = "ios")))] +pub const unwinder_private_data_size: usize = 20; + +#[cfg(all(target_arch = "arm", target_os = "ios"))] +pub const unwinder_private_data_size: usize = 5; + +#[cfg(all(target_arch = "aarch64", target_pointer_width = "64"))] +pub const unwinder_private_data_size: usize = 2; + +#[cfg(all(target_arch = "aarch64", target_pointer_width = "32"))] +pub const unwinder_private_data_size: usize = 5; + +#[cfg(target_arch = "mips")] +pub const unwinder_private_data_size: usize = 2; + +#[cfg(target_arch = "mips64")] +pub const unwinder_private_data_size: usize = 2; + +#[cfg(any(target_arch = "powerpc", target_arch = "powerpc64"))] +pub const unwinder_private_data_size: usize = 2; + +#[cfg(target_arch = "s390x")] +pub const unwinder_private_data_size: usize = 2; + +#[cfg(any(target_arch = "sparc", target_arch = "sparc64"))] +pub const unwinder_private_data_size: usize = 2; + +#[cfg(any(target_arch = "riscv64", target_arch = "riscv32"))] +pub const unwinder_private_data_size: usize = 2; + +#[cfg(target_os = "emscripten")] +pub const unwinder_private_data_size: usize = 20; + +#[cfg(all(target_arch = "hexagon", target_os = "linux"))] +pub const unwinder_private_data_size: usize = 35; + +#[repr(C)] +pub struct _Unwind_Exception { + pub exception_class: _Unwind_Exception_Class, + pub exception_cleanup: _Unwind_Exception_Cleanup_Fn, + pub private: [_Unwind_Word; unwinder_private_data_size], +} + +pub enum _Unwind_Context {} + +pub type _Unwind_Exception_Cleanup_Fn = + extern "C" fn(unwind_code: _Unwind_Reason_Code, exception: *mut _Unwind_Exception); +#[cfg_attr( + all(feature = "llvm-libunwind", any(target_os = "fuchsia", target_os = "linux")), + link(name = "unwind", kind = "static") +)] +extern "C" { + #[unwind(allowed)] + pub fn _Unwind_Resume(exception: *mut _Unwind_Exception) -> !; + pub fn _Unwind_DeleteException(exception: *mut _Unwind_Exception); + pub fn _Unwind_GetLanguageSpecificData(ctx: *mut _Unwind_Context) -> *mut c_void; + pub fn _Unwind_GetRegionStart(ctx: *mut _Unwind_Context) -> _Unwind_Ptr; + pub fn _Unwind_GetTextRelBase(ctx: *mut _Unwind_Context) -> _Unwind_Ptr; + pub fn _Unwind_GetDataRelBase(ctx: *mut _Unwind_Context) -> _Unwind_Ptr; +} + +cfg_if::cfg_if! { +if #[cfg(any(target_os = "ios", target_os = "netbsd", not(target_arch = "arm")))] { + // Not ARM EHABI + #[repr(C)] + #[derive(Copy, Clone, PartialEq)] + pub enum _Unwind_Action { + _UA_SEARCH_PHASE = 1, + _UA_CLEANUP_PHASE = 2, + _UA_HANDLER_FRAME = 4, + _UA_FORCE_UNWIND = 8, + _UA_END_OF_STACK = 16, + } + pub use _Unwind_Action::*; + + #[cfg_attr(all(feature = "llvm-libunwind", + any(target_os = "fuchsia", target_os = "linux")), + link(name = "unwind", kind = "static"))] + extern "C" { + pub fn _Unwind_GetGR(ctx: *mut _Unwind_Context, reg_index: c_int) -> _Unwind_Word; + pub fn _Unwind_SetGR(ctx: *mut _Unwind_Context, reg_index: c_int, value: _Unwind_Word); + pub fn _Unwind_GetIP(ctx: *mut _Unwind_Context) -> _Unwind_Word; + pub fn _Unwind_SetIP(ctx: *mut _Unwind_Context, value: _Unwind_Word); + pub fn _Unwind_GetIPInfo(ctx: *mut _Unwind_Context, ip_before_insn: *mut c_int) + -> _Unwind_Word; + pub fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void; + } + +} else { + // ARM EHABI + #[repr(C)] + #[derive(Copy, Clone, PartialEq)] + pub enum _Unwind_State { + _US_VIRTUAL_UNWIND_FRAME = 0, + _US_UNWIND_FRAME_STARTING = 1, + _US_UNWIND_FRAME_RESUME = 2, + _US_ACTION_MASK = 3, + _US_FORCE_UNWIND = 8, + _US_END_OF_STACK = 16, + } + pub use _Unwind_State::*; + + #[repr(C)] + enum _Unwind_VRS_Result { + _UVRSR_OK = 0, + _UVRSR_NOT_IMPLEMENTED = 1, + _UVRSR_FAILED = 2, + } + #[repr(C)] + enum _Unwind_VRS_RegClass { + _UVRSC_CORE = 0, + _UVRSC_VFP = 1, + _UVRSC_FPA = 2, + _UVRSC_WMMXD = 3, + _UVRSC_WMMXC = 4, + } + use _Unwind_VRS_RegClass::*; + #[repr(C)] + enum _Unwind_VRS_DataRepresentation { + _UVRSD_UINT32 = 0, + _UVRSD_VFPX = 1, + _UVRSD_FPAX = 2, + _UVRSD_UINT64 = 3, + _UVRSD_FLOAT = 4, + _UVRSD_DOUBLE = 5, + } + use _Unwind_VRS_DataRepresentation::*; + + pub const UNWIND_POINTER_REG: c_int = 12; + pub const UNWIND_SP_REG: c_int = 13; + pub const UNWIND_IP_REG: c_int = 15; + + #[cfg_attr(all(feature = "llvm-libunwind", + any(target_os = "fuchsia", target_os = "linux")), + link(name = "unwind", kind = "static"))] + extern "C" { + fn _Unwind_VRS_Get(ctx: *mut _Unwind_Context, + regclass: _Unwind_VRS_RegClass, + regno: _Unwind_Word, + repr: _Unwind_VRS_DataRepresentation, + data: *mut c_void) + -> _Unwind_VRS_Result; + + fn _Unwind_VRS_Set(ctx: *mut _Unwind_Context, + regclass: _Unwind_VRS_RegClass, + regno: _Unwind_Word, + repr: _Unwind_VRS_DataRepresentation, + data: *mut c_void) + -> _Unwind_VRS_Result; + } + + // On Android or ARM/Linux, these are implemented as macros: + + pub unsafe fn _Unwind_GetGR(ctx: *mut _Unwind_Context, reg_index: c_int) -> _Unwind_Word { + let mut val: _Unwind_Word = 0; + _Unwind_VRS_Get(ctx, _UVRSC_CORE, reg_index as _Unwind_Word, _UVRSD_UINT32, + &mut val as *mut _ as *mut c_void); + val + } + + pub unsafe fn _Unwind_SetGR(ctx: *mut _Unwind_Context, reg_index: c_int, value: _Unwind_Word) { + let mut value = value; + _Unwind_VRS_Set(ctx, _UVRSC_CORE, reg_index as _Unwind_Word, _UVRSD_UINT32, + &mut value as *mut _ as *mut c_void); + } + + pub unsafe fn _Unwind_GetIP(ctx: *mut _Unwind_Context) + -> _Unwind_Word { + let val = _Unwind_GetGR(ctx, UNWIND_IP_REG); + (val & !1) as _Unwind_Word + } + + pub unsafe fn _Unwind_SetIP(ctx: *mut _Unwind_Context, + value: _Unwind_Word) { + // Propagate thumb bit to instruction pointer + let thumb_state = _Unwind_GetGR(ctx, UNWIND_IP_REG) & 1; + let value = value | thumb_state; + _Unwind_SetGR(ctx, UNWIND_IP_REG, value); + } + + pub unsafe fn _Unwind_GetIPInfo(ctx: *mut _Unwind_Context, + ip_before_insn: *mut c_int) + -> _Unwind_Word { + *ip_before_insn = 0; + _Unwind_GetIP(ctx) + } + + // This function also doesn't exist on Android or ARM/Linux, so make it a no-op + pub unsafe fn _Unwind_FindEnclosingFunction(pc: *mut c_void) -> *mut c_void { + pc + } +} +} // cfg_if! + +cfg_if::cfg_if! { +if #[cfg(not(all(target_os = "ios", target_arch = "arm")))] { + // Not 32-bit iOS + #[cfg_attr(all(feature = "llvm-libunwind", + any(target_os = "fuchsia", target_os = "linux")), + link(name = "unwind", kind = "static"))] + extern "C" { + #[unwind(allowed)] + pub fn _Unwind_RaiseException(exception: *mut _Unwind_Exception) -> _Unwind_Reason_Code; + pub fn _Unwind_Backtrace(trace: _Unwind_Trace_Fn, + trace_argument: *mut c_void) + -> _Unwind_Reason_Code; + } +} else { + // 32-bit iOS uses SjLj and does not provide _Unwind_Backtrace() + #[cfg_attr(all(feature = "llvm-libunwind", + any(target_os = "fuchsia", target_os = "linux")), + link(name = "unwind", kind = "static"))] + extern "C" { + #[unwind(allowed)] + pub fn _Unwind_SjLj_RaiseException(e: *mut _Unwind_Exception) -> _Unwind_Reason_Code; + } + + #[inline] + pub unsafe fn _Unwind_RaiseException(exc: *mut _Unwind_Exception) -> _Unwind_Reason_Code { + _Unwind_SjLj_RaiseException(exc) + } +} +} // cfg_if! + +cfg_if::cfg_if! { +if #[cfg(all(windows, target_arch = "x86_64", target_env = "gnu"))] { + // We declare these as opaque types. This is fine since you just need to + // pass them to _GCC_specific_handler and forget about them. + pub enum EXCEPTION_RECORD {} + pub type LPVOID = *mut c_void; + pub enum CONTEXT {} + pub enum DISPATCHER_CONTEXT {} + pub type EXCEPTION_DISPOSITION = c_int; + type PersonalityFn = unsafe extern "C" fn(version: c_int, + actions: _Unwind_Action, + exception_class: _Unwind_Exception_Class, + exception_object: *mut _Unwind_Exception, + context: *mut _Unwind_Context) + -> _Unwind_Reason_Code; + + extern "C" { + pub fn _GCC_specific_handler(exceptionRecord: *mut EXCEPTION_RECORD, + establisherFrame: LPVOID, + contextRecord: *mut CONTEXT, + dispatcherContext: *mut DISPATCHER_CONTEXT, + personality: PersonalityFn) + -> EXCEPTION_DISPOSITION; + } +} +} // cfg_if! \ No newline at end of file diff --git a/artiq/firmware/libunwind_backtrace/Cargo.toml b/artiq/firmware/libunwind_backtrace/Cargo.toml index 637e55bc6..1925f4f1e 100644 --- a/artiq/firmware/libunwind_backtrace/Cargo.toml +++ b/artiq/firmware/libunwind_backtrace/Cargo.toml @@ -6,3 +6,7 @@ version = "0.0.0" [lib] name = "unwind_backtrace" path = "lib.rs" + +[dependencies] +unwind = { path = "../libunwind" } +libc = { path = "../libc" } diff --git a/artiq/firmware/riscv32g-unknown-none-elf.json b/artiq/firmware/riscv32g-unknown-none-elf.json new file mode 100644 index 000000000..2a3fb8bfb --- /dev/null +++ b/artiq/firmware/riscv32g-unknown-none-elf.json @@ -0,0 +1,40 @@ +{ + "arch": "riscv32", + "code-model": "medium", + "cpu": "generic-rv32", + "crt-static-respected": true, + "data-layout": "e-m:e-p:32:32-i64:64-n32-S128", + "dynamic-linking": true, + "executables": true, + "features": "+m,+a,+f,+d", + "has-elf-tls": true, + "has-rpath": true, + "llvm-abiname": "ilp32d", + "llvm-target": "riscv32-unknown-linux", + "max-atomic-width": 32, + "position-independent-executables": true, + "pre-link-args": { + "gcc": [ + "-Wl,--as-needed", + "-Wl,-z,noexecstack" + ] + }, + "relro-level": "full", + "target-family": "unix", + "target-pointer-width": "32", + "unsupported-abis": [ + "cdecl", + "stdcall", + "fastcall", + "vectorcall", + "thiscall", + "aapcs", + "win64", + "sysv64", + "ptx-kernel", + "msp430-interrupt", + "x86-interrupt", + "amdgpu-kernel" + ] +} + \ No newline at end of file diff --git a/artiq/firmware/riscv32ima-unknown-none-elf.json b/artiq/firmware/riscv32ima-unknown-none-elf.json new file mode 100644 index 000000000..e41408842 --- /dev/null +++ b/artiq/firmware/riscv32ima-unknown-none-elf.json @@ -0,0 +1,31 @@ +{ + "arch": "riscv32", + "cpu": "generic-rv32", + "data-layout": "e-m:e-p:32:32-i64:64-n32-S128", + "eh-frame-header": false, + "emit-debug-gdb-scripts": false, + "executables": true, + "features": "+m,+a,-c", + "is-builtin": false, + "linker": "rust-lld", + "linker-flavor": "ld.lld", + "llvm-target": "riscv32", + "max-atomic-width": 32, + "panic-strategy": "unwind", + "relocation-model": "static", + "target-pointer-width": "32", + "unsupported-abis": [ + "cdecl", + "stdcall", + "fastcall", + "vectorcall", + "thiscall", + "aapcs", + "win64", + "sysv64", + "ptx-kernel", + "msp430-interrupt", + "x86-interrupt", + "amdgpu-kernel" + ] +} diff --git a/artiq/firmware/runtime/Cargo.toml b/artiq/firmware/runtime/Cargo.toml index bbb9877bb..ee987ad2d 100644 --- a/artiq/firmware/runtime/Cargo.toml +++ b/artiq/firmware/runtime/Cargo.toml @@ -18,7 +18,7 @@ failure_derive = { version = "0.1", default-features = false } byteorder = { version = "1.0", default-features = false } cslice = { version = "0.3" } log = { version = "0.4", default-features = false } -managed = { version = "= 0.7.0", default-features = false, features = ["alloc", "map"] } +managed = { version = "^0.7.1", default-features = false, features = ["alloc", "map"] } eh = { path = "../libeh" } unwind_backtrace = { path = "../libunwind_backtrace" } io = { path = "../libio", features = ["byteorder"] } @@ -27,10 +27,11 @@ board_misoc = { path = "../libboard_misoc", features = ["uart_console", "smoltcp logger_artiq = { path = "../liblogger_artiq" } board_artiq = { path = "../libboard_artiq" } proto_artiq = { path = "../libproto_artiq", features = ["log", "alloc"] } -smoltcp = { version = "0.6.0", default-features = false, features = ["rust-1_28", "alloc", "ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp"] } +smoltcp = { version = "0.6.0", default-features = false, features = ["alloc", "ethernet", "proto-ipv4", "proto-ipv6", "socket-tcp"] } +riscv = { version = "0.6.0", features = ["inline-asm"] } [dependencies.fringe] -git = "https://github.com/m-labs/libfringe" -rev = "b8a6d8f" +git = "https://git.m-labs.hk/M-Labs/libfringe.git" +rev = "3ecbe5" default-features = false features = ["alloc"] diff --git a/artiq/firmware/runtime/Makefile b/artiq/firmware/runtime/Makefile index 5fa6dbdf8..3e4b91ba3 100644 --- a/artiq/firmware/runtime/Makefile +++ b/artiq/firmware/runtime/Makefile @@ -10,21 +10,25 @@ LDFLAGS += \ RUSTFLAGS += -Cpanic=unwind +export XBUILD_SYSROOT_PATH=$(BUILDINC_DIRECTORY)/../sysroot + all:: runtime.bin runtime.fbi .PHONY: $(RUSTOUT)/libruntime.a $(RUSTOUT)/libruntime.a: - $(cargo) --manifest-path $(RUNTIME_DIRECTORY)/Cargo.toml + $(cargo) --target-dir ./cargo \ + --manifest-path $(RUNTIME_DIRECTORY)/Cargo.toml \ + --target $(RUNTIME_DIRECTORY)/../$(CARGO_TRIPLE).json runtime.elf: $(RUSTOUT)/libruntime.a ksupport_data.o $(link) -T $(RUNTIME_DIRECTORY)/runtime.ld \ - -lunwind-bare + -lunwind-bare -m elf32lriscv ksupport_data.o: ../ksupport/ksupport.elf - $(LD) -r -b binary -o $@ $< + $(LD) -r -m elf32lriscv -b binary -o $@ $< %.bin: %.elf $(objcopy) -O binary %.fbi: %.bin - $(mscimg) -f + $(mscimg) -f --little diff --git a/artiq/firmware/runtime/analyzer.rs b/artiq/firmware/runtime/analyzer.rs index 23abdba41..2e9d74c14 100644 --- a/artiq/firmware/runtime/analyzer.rs +++ b/artiq/firmware/runtime/analyzer.rs @@ -51,7 +51,7 @@ fn worker(stream: &mut TcpStream) -> Result<(), IoError> { }; debug!("{:?}", header); - stream.write_all("E".as_bytes())?; + stream.write_all("e".as_bytes())?; header.write_to(stream)?; if wraparound { stream.write_all(&data[pointer..])?; diff --git a/artiq/firmware/runtime/cache.rs b/artiq/firmware/runtime/cache.rs index 2f1948924..a950fab3b 100644 --- a/artiq/firmware/runtime/cache.rs +++ b/artiq/firmware/runtime/cache.rs @@ -1,4 +1,4 @@ -use alloc::{Vec, String, BTreeMap}; +use alloc::{vec::Vec, string::String, collections::btree_map::BTreeMap}; #[derive(Debug)] struct Entry { diff --git a/artiq/firmware/runtime/main.rs b/artiq/firmware/runtime/main.rs index 91646cc5e..45fb76f91 100644 --- a/artiq/firmware/runtime/main.rs +++ b/artiq/firmware/runtime/main.rs @@ -1,6 +1,4 @@ -#![feature(lang_items, alloc, try_from, nonzero, asm, - panic_implementation, panic_info_message, - const_slice_len)] +#![feature(lang_items, panic_info_message)] #![no_std] extern crate eh; @@ -25,12 +23,13 @@ extern crate board_misoc; extern crate board_artiq; extern crate logger_artiq; extern crate proto_artiq; +extern crate riscv; use core::cell::RefCell; use core::convert::TryFrom; use smoltcp::wire::IpCidr; -use board_misoc::{csr, irq, ident, clock, boot, config, net_settings}; +use board_misoc::{csr, ident, clock, spiflash, config, net_settings, pmp, boot}; #[cfg(has_ethmac)] use board_misoc::ethmac; #[cfg(has_drtio)] @@ -41,6 +40,8 @@ use proto_artiq::{mgmt_proto, moninj_proto, rpc_proto, session_proto, kernel_pro #[cfg(has_rtio_analyzer)] use proto_artiq::analyzer_proto; +use riscv::register::{mcause, mepc, mtval}; + mod rtio_clocking; mod rtio_mgt; @@ -50,7 +51,6 @@ mod cache; mod rtio_dma; mod mgmt; -mod profiler; mod kernel; mod kern_hwreq; mod session; @@ -88,8 +88,6 @@ fn setup_log_levels() { } fn startup() { - irq::set_mask(0); - irq::set_ie(true); clock::init(); info!("ARTIQ runtime starting..."); info!("software ident {}", csr::CONFIG_IDENTIFIER_STR); @@ -145,7 +143,7 @@ fn startup() { }; let neighbor_cache = - smoltcp::iface::NeighborCache::new(alloc::btree_map::BTreeMap::new()); + smoltcp::iface::NeighborCache::new(alloc::collections::btree_map::BTreeMap::new()); let net_addresses = net_settings::get_adresses(); info!("network addresses: {}", net_addresses); let mut interface = match net_addresses.ipv6_addr { @@ -249,29 +247,64 @@ pub extern fn main() -> i32 { extern { static mut _fheap: u8; static mut _eheap: u8; + static mut _sstack_guard: u8; } ALLOC.add_range(&mut _fheap, &mut _eheap); - logger_artiq::BufferLogger::new(&mut LOG_BUFFER[..]).register(startup); + pmp::init_stack_guard(&_sstack_guard as *const u8 as usize); + + logger_artiq::BufferLogger::new(&mut LOG_BUFFER[..]).register(|| + boot::start_user(startup as usize) + ); 0 } } +#[derive(Debug, Clone, Copy)] +#[repr(C)] +pub struct TrapFrame { + pub ra: usize, + pub t0: usize, + pub t1: usize, + pub t2: usize, + pub t3: usize, + pub t4: usize, + pub t5: usize, + pub t6: usize, + pub a0: usize, + pub a1: usize, + pub a2: usize, + pub a3: usize, + pub a4: usize, + pub a5: usize, + pub a6: usize, + pub a7: usize, +} + #[no_mangle] -pub extern fn exception(vect: u32, _regs: *const u32, pc: u32, ea: u32) { - let vect = irq::Exception::try_from(vect).expect("unknown exception"); - match vect { - irq::Exception::Interrupt => - while irq::pending_mask() != 0 { - match () { - #[cfg(has_timer1)] - () if irq::is_pending(csr::TIMER1_INTERRUPT) => - profiler::sample(pc as usize), - _ => panic!("spurious irq {}", irq::pending_mask().trailing_zeros()) +pub extern fn exception(regs: *const TrapFrame) { + let pc = mepc::read(); + let cause = mcause::read().cause(); + match cause { + mcause::Trap::Interrupt(source) => { + info!("Called interrupt with {:?}", source); + }, + + mcause::Trap::Exception(mcause::Exception::UserEnvCall) => { + unsafe { + if (*regs).a7 == 0 { + pmp::pop_pmp_region() + } else { + pmp::push_pmp_region((*regs).a7) } - }, - _ => { + } + mepc::write(pc + 4); + }, + + mcause::Trap::Exception(e) => { + println!("Trap frame: {:x?}", unsafe { *regs }); + fn hexdump(addr: u32) { let addr = (addr - addr % 4) as *const u32; let mut ptr = addr; @@ -285,9 +318,9 @@ pub extern fn exception(vect: u32, _regs: *const u32, pc: u32, ea: u32) { } } - hexdump(pc); - hexdump(ea); - panic!("exception {:?} at PC 0x{:x}, EA 0x{:x}", vect, pc, ea) + hexdump(u32::try_from(pc).unwrap()); + let mtval = mtval::read(); + panic!("exception {:?} at PC 0x{:x}, trap value 0x{:x}", e, u32::try_from(pc).unwrap(), mtval) } } } @@ -305,10 +338,8 @@ pub fn oom(layout: core::alloc::Layout) -> ! { } #[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647} -#[panic_implementation] +#[panic_handler] pub fn panic_impl(info: &core::panic::PanicInfo) -> ! { - irq::set_ie(false); - #[cfg(has_error_led)] unsafe { csr::error_led::out_write(1); @@ -327,16 +358,17 @@ pub fn panic_impl(info: &core::panic::PanicInfo) -> ! { println!("backtrace for software version {}:", csr::CONFIG_IDENTIFIER_STR); let _ = unwind_backtrace::backtrace(|ip| { - // Backtrace gives us the return address, i.e. the address after the delay slot, + // Backtrace gives us the return address, i.e. the address after jal(r) insn, // but we're interested in the call instruction. - println!("{:#08x}", ip - 2 * 4); + println!("{:#08x}", ip - 4); }); - if config::read_str("panic_reset", |r| r == Ok("1")) { + if config::read_str("panic_reset", |r| r == Ok("1")) && + cfg!(any(soc_platform = "kasli", soc_platform = "metlino", soc_platform = "kc705")) { println!("restarting..."); unsafe { kernel::stop(); - boot::reset(); + spiflash::reload(); } } else { println!("halting."); diff --git a/artiq/firmware/runtime/mgmt.rs b/artiq/firmware/runtime/mgmt.rs index 3a16e94aa..c100df05f 100644 --- a/artiq/firmware/runtime/mgmt.rs +++ b/artiq/firmware/runtime/mgmt.rs @@ -1,11 +1,10 @@ use log::{self, LevelFilter}; use io::{Write, ProtoWrite, Error as IoError}; -use board_misoc::{config, boot}; +use board_misoc::{config, spiflash}; use logger_artiq::BufferLogger; use mgmt_proto::*; use sched::{Io, TcpListener, TcpStream, Error as SchedError}; -use profiler; impl From for Error { fn from(value: SchedError) -> Error { @@ -15,7 +14,7 @@ impl From for Error { fn worker(io: &Io, stream: &mut TcpStream) -> Result<(), Error> { read_magic(stream)?; - Write::write_all(stream, "E".as_bytes())?; + Write::write_all(stream, "e".as_bytes())?; info!("new connection from {}", stream.remote_endpoint()); loop { @@ -103,64 +102,13 @@ fn worker(io: &Io, stream: &mut TcpStream) -> Result<(), Error> { }?; } - Request::StartProfiler { interval_us, hits_size, edges_size } => { - match profiler::start(interval_us as u64, - hits_size as usize, edges_size as usize) { - Ok(()) => Reply::Success.write_to(stream)?, - Err(()) => Reply::Unavailable.write_to(stream)? - } - } - Request::StopProfiler => { - profiler::stop(); - Reply::Success.write_to(stream)?; - } - Request::GetProfile => { - profiler::pause(|profile| { - let profile = match profile { - None => return Reply::Unavailable.write_to(stream), - Some(profile) => profile - }; - - Reply::Profile.write_to(stream)?; - { - let hits = profile.hits(); - stream.write_u32(hits.len() as u32)?; - for (&addr, &count) in hits.iter() { - stream.write_u32(addr.as_raw() as u32)?; - stream.write_u32(count)?; - } - } - { - let edges = profile.edges(); - stream.write_u32(edges.len() as u32)?; - for (&(caller, callee), &count) in edges.iter() { - stream.write_u32(caller.as_raw() as u32)?; - stream.write_u32(callee.as_raw() as u32)?; - stream.write_u32(count)?; - } - } - - Ok(()) - })?; - } - - Request::Hotswap(firmware) => { - Reply::RebootImminent.write_to(stream)?; - stream.close()?; - stream.flush()?; - - profiler::stop(); - warn!("hotswapping firmware"); - unsafe { boot::hotswap(&firmware) } - } Request::Reboot => { Reply::RebootImminent.write_to(stream)?; stream.close()?; stream.flush()?; - profiler::stop(); warn!("restarting"); - unsafe { boot::reset() } + unsafe { spiflash::reload(); } } Request::DebugAllocator => diff --git a/artiq/firmware/runtime/moninj.rs b/artiq/firmware/runtime/moninj.rs index 3d3d06c40..c1bba04fc 100644 --- a/artiq/firmware/runtime/moninj.rs +++ b/artiq/firmware/runtime/moninj.rs @@ -1,4 +1,4 @@ -use alloc::btree_map::BTreeMap; +use alloc::collections::btree_map::BTreeMap; use core::cell::RefCell; use io::Error as IoError; @@ -123,7 +123,7 @@ fn connection_worker(io: &Io, _aux_mutex: &Mutex, _routing_table: &drtio_routing let mut next_check = 0; read_magic(&mut stream)?; - stream.write_all("E".as_bytes())?; + stream.write_all("e".as_bytes())?; info!("new connection from {}", stream.remote_endpoint()); loop { diff --git a/artiq/firmware/runtime/profiler.rs b/artiq/firmware/runtime/profiler.rs deleted file mode 100644 index 6df9c3f8c..000000000 --- a/artiq/firmware/runtime/profiler.rs +++ /dev/null @@ -1,278 +0,0 @@ -#![cfg_attr(not(has_timer1), allow(dead_code))] - -use core::mem; -use core::fmt; -use core::num::NonZeroUsize; -use alloc::Vec; -use managed::ManagedMap; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub struct Address(NonZeroUsize); - -impl Address { - pub fn new(raw: usize) -> Address { - Address(NonZeroUsize::new(raw).expect("null address")) - } - - pub fn as_raw(&self) -> usize { - self.0.get() - } -} - -pub struct Profile { - hits: Vec>, - edges: Vec>, -} - -impl fmt::Debug for Profile { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Profile {{ hits: vec![...; {}], edges: vec![...; {}] }}", - self.hits.len(), self.edges.len()) - } -} - -impl Profile { - pub fn new(hits_size: usize, edges_size: usize) -> Profile { - let mut hits = vec![None; hits_size]; - hits.shrink_to_fit(); - let mut edges = vec![None; edges_size]; - edges.shrink_to_fit(); - Profile { - hits: hits.into(), - edges: edges.into(), - } - } - - pub fn overhead(&self) -> usize { - let hit_size = mem::size_of::>(); - let edge_size = mem::size_of::>(); - self.hits.capacity() * hit_size + - self.edges.capacity() * edge_size - } - - pub fn has_edges(&self) -> bool { - self.edges.is_empty() - } - - pub fn hits<'a>(&'a mut self) -> ManagedMap<'a, Address, u32> { - ManagedMap::Borrowed(&mut self.hits[..]) - } - - pub fn edges<'a>(&'a mut self) -> ManagedMap<'a, (Address, Address), u32> { - ManagedMap::Borrowed(&mut self.edges[..]) - } - - pub fn record_hit(&mut self, addr: Address) -> Result<(), ()> { - let mut hits = self.hits(); - if let Some(count) = hits.get_mut(&addr) { - return Ok(*count = count.saturating_add(1)) - } - if let Err(_) = hits.insert(addr, 1) { - return Err(()) - } - return Ok(()) - } - - #[allow(dead_code)] - pub fn record_edge(&mut self, caller: Address, callee: Address) -> Result<(), ()> { - let mut edges = self.edges(); - if let Some(count) = edges.get_mut(&(caller, callee)) { - return Ok(*count = count.saturating_add(1)) - } - if let Err(_) = edges.insert((caller, callee), 1) { - return Err(()) - } - Ok(()) - } -} - -#[cfg(has_timer1)] -mod imp { - use unwind_backtrace::backtrace; - use board_misoc::{csr, irq}; - use super::{Address, Profile}; - - static mut PROFILE: Option = None; - - mod lock { - use core::ops::{Deref, DerefMut}; - use core::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT}; - - static LOCKED: AtomicUsize = ATOMIC_USIZE_INIT; - - pub struct Lock; - - impl Lock { - pub fn take() -> Result { - if LOCKED.swap(1, Ordering::SeqCst) != 0 { - Err(()) - } else { - Ok(Lock) - } - } - } - - impl Deref for Lock { - type Target = Option; - - fn deref(&self) -> &Option { - unsafe { &super::PROFILE } - } - } - - impl DerefMut for Lock { - fn deref_mut(&mut self) -> &mut Option { - unsafe { &mut super::PROFILE } - } - } - - impl Drop for Lock { - fn drop(&mut self) { - LOCKED.store(0, Ordering::SeqCst) - } - } - } - - use self::lock::Lock; - - pub fn start(interval_us: u64, hits_size: usize, edges_size: usize) -> Result<(), ()> { - stop(); - - let profile = Profile::new(hits_size, edges_size); - info!("starting at {}us interval using {} heap bytes", - interval_us, profile.overhead()); - - *Lock::take().expect("cannot lock") = Some(profile); - - unsafe { - let reload = csr::CONFIG_CLOCK_FREQUENCY as u64 * interval_us / 1_000_000; - csr::timer1::load_write(reload); - csr::timer1::reload_write(reload); - csr::timer1::ev_pending_write(1); - csr::timer1::ev_enable_write(1); - irq::enable(csr::TIMER1_INTERRUPT); - csr::timer1::en_write(1); - } - - Ok(()) - } - - pub fn stop() { - unsafe { - if csr::timer1::en_read() == 0 || csr::timer1::ev_enable_read() == 0 { - return - } - - irq::disable(csr::TIMER1_INTERRUPT); - csr::timer1::en_write(0); - - *Lock::take().expect("cannot lock") = None; - - info!("stopped"); - } - } - - pub fn pause) -> R, R>(f: F) -> R { - unsafe { - if csr::timer1::en_read() == 0 { - return f(None) - } - - irq::disable(csr::TIMER1_INTERRUPT); - csr::timer1::en_write(0); - - let result = { - let mut profile = Lock::take().expect("cannot lock"); - f(profile.as_mut()) - }; - - irq::enable(csr::TIMER1_INTERRUPT); - csr::timer1::en_write(1); - - result - } - } - - // Skip frames: ::profiler::sample, ::exception, exception vector. - const SKIP_FRAMES: i32 = 3; - - #[inline(always)] // make the top of backtrace predictable - fn record(profile: &mut Profile, exn_pc: usize) -> Result<(), ()> { - let mut result = Ok(()); - let mut frame = -SKIP_FRAMES; - - // If we have storage for edges, use the DWARF unwinder. - // Otherwise, don't bother and use a much faster path that just looks at EPCR. - // Also, acquiring a meaningful backtrace requires libunwind - // with the https://reviews.llvm.org/D46971 patch applied. - if profile.has_edges() { - let mut prev_pc = 0; - let _ = backtrace(|pc| { - // Backtrace gives us the return address, i.e. the address after the delay slot, - // but we're interested in the call instruction, *except* when going through - // the frame directly below the exception frame, which has the address that's - // being executed. - let pc = if pc != exn_pc { pc - 2 * 4 } else { pc }; - - if frame == 0 { - result = result.and_then(|()| - profile.record_hit(Address::new(pc))); - prev_pc = pc; - } else if frame > 0 { - result = result.and_then(|()| - profile.record_edge(Address::new(pc), - Address::new(prev_pc))); - } - - prev_pc = pc; - frame += 1; - }); - } - - // If we couldn't get anything useful out of a backtrace, at least - // record a hit at the exception PC. - if frame <= 0 { - result = profile.record_hit(Address::new(exn_pc)); - } - - result - } - - #[inline(never)] // see above - pub fn sample(pc: usize) { - let result = { - let mut profile = Lock::take().expect("cannot lock"); - record(profile.as_mut().expect("profiler not running"), pc) - }; - - if result.is_err() { - warn!("out of space"); - stop(); - } else { - unsafe { - csr::timer1::ev_pending_write(1); - } - } - } -} - -#[cfg(not(has_timer1))] -mod imp { - #![allow(dead_code)] - - pub fn start(_interval_us: u64, _hits_size: usize, _edges_size: usize) -> Result<(), ()> { - error!("timer not available"); - - Err(()) - } - - pub fn stop() {} - - pub fn pause) -> R, R>(f: F) -> R { - f(None) - } - - pub fn sample(_pc: usize) {} -} - -pub use self::imp::*; diff --git a/artiq/firmware/runtime/rtio_clocking.rs b/artiq/firmware/runtime/rtio_clocking.rs index b6cf5f395..f0330b4d9 100644 --- a/artiq/firmware/runtime/rtio_clocking.rs +++ b/artiq/firmware/runtime/rtio_clocking.rs @@ -4,29 +4,66 @@ use board_artiq::si5324; #[cfg(has_drtio)] use board_misoc::{csr, clock}; -#[derive(Debug)] +#[derive(Debug, PartialEq)] +#[allow(non_camel_case_types)] pub enum RtioClock { - Internal = 0, - External = 1 + Default, + Int_125, + Int_100, + Int_150, + Ext0_Bypass, + Ext0_Synth0_10to125, + Ext0_Synth0_100to125, + Ext0_Synth0_125to125, } +#[allow(unreachable_code)] fn get_rtio_clock_cfg() -> RtioClock { - config::read("rtio_clock", |result| { - match result { - Ok(b"i") => { - info!("using internal RTIO clock"); - RtioClock::Internal + config::read_str("rtio_clock", |result| { + let res = match result { + Ok("int_125") => RtioClock::Int_125, + Ok("int_100") => RtioClock::Int_100, + Ok("int_150") => RtioClock::Int_150, + Ok("ext0_bypass") => RtioClock::Ext0_Bypass, + Ok("ext0_bypass_125") => RtioClock::Ext0_Bypass, + Ok("ext0_bypass_100") => RtioClock::Ext0_Bypass, + Ok("ext0_synth0_10to125") => RtioClock::Ext0_Synth0_10to125, + Ok("ext0_synth0_100to125") => RtioClock::Ext0_Synth0_100to125, + Ok("ext0_synth0_125to125") => RtioClock::Ext0_Synth0_125to125, + Ok("i") => { + warn!("Using legacy rtio_clock setting ('i'). Falling back to default. This will be deprecated."); + RtioClock::Default }, - Ok(b"e") => { - info!("using external RTIO clock"); - RtioClock::External + Ok("e") => { + warn!("Using legacy rtio_clock setting ('e'). This will be deprecated."); + RtioClock::Ext0_Bypass }, _ => { - info!("using internal RTIO clock (by default)"); - RtioClock::Internal - }, + warn!("rtio_clock setting not recognised. Falling back to default."); + RtioClock::Default + } + }; + if res == RtioClock::Default { + #[cfg(any(si5324_ext_ref, ext_ref_frequency))] + warn!("si5324_ext_ref and ext_ref_frequency compile-time options are deprecated. Please use the rtio_clock coreconfig settings instead."); + #[cfg(all(rtio_frequency = "125.0", si5324_ext_ref, ext_ref_frequency = "10.0"))] + return RtioClock::Ext0_Synth0_10to125; + #[cfg(all(rtio_frequency = "125.0", si5324_ext_ref, ext_ref_frequency = "100.0"))] + return RtioClock::Ext0_Synth0_100to125; + #[cfg(all(rtio_frequency = "125.0", si5324_ext_ref, ext_ref_frequency = "125.0"))] + return RtioClock::Ext0_Synth0_125to125; + #[cfg(all(rtio_frequency = "125.0", not(si5324_ext_ref)))] + return RtioClock::Int_125; + #[cfg(all(rtio_frequency = "150.0", not(si5324_ext_ref)))] + return RtioClock::Int_150; + #[cfg(all(rtio_frequency = "100.0", not(si5324_ext_ref)))] + return RtioClock::Int_100; + //in case nothing is set + return RtioClock::Int_125; } - }) + res + }) + } #[cfg(has_rtio_crg)] @@ -41,9 +78,23 @@ pub mod crg { #[cfg(has_rtio_clock_switch)] pub fn init(clk: RtioClock) -> bool { + let clk_sel: u8 = match clk { + RtioClock::Ext0_Bypass => { + info!("Using external clock"); + 1 + }, + RtioClock::Int_125 => { + info!("Using internal RTIO clock"); + 0 + }, + _ => { + warn!("rtio_clock setting '{:?}' is not supported. Using default internal RTIO clock instead", clk); + 0 + } + }; unsafe { csr::rtio_crg::pll_reset_write(1); - csr::rtio_crg::clock_sel_write(clk as u8); + csr::rtio_crg::clock_sel_write(clk_sel); csr::rtio_crg::pll_reset_write(0); } clock::spin_us(150); @@ -52,6 +103,7 @@ pub mod crg { #[cfg(not(has_rtio_clock_switch))] pub fn init() -> bool { + info!("Using internal RTIO clock"); unsafe { csr::rtio_crg::pll_reset_write(0); } @@ -66,84 +118,99 @@ pub mod crg { } #[cfg(si5324_as_synthesizer)] -fn setup_si5324_as_synthesizer() { - // 125 MHz output from 10 MHz CLKINx reference, 504 Hz BW - #[cfg(all(rtio_frequency = "125.0", si5324_ext_ref, ext_ref_frequency = "10.0"))] - const SI5324_SETTINGS: si5324::FrequencySettings - = si5324::FrequencySettings { - n1_hs : 10, - nc1_ls : 4, - n2_hs : 10, - n2_ls : 300, - n31 : 6, - n32 : 6, - bwsel : 4, - crystal_ref: false - }; - // 125MHz output, from 100MHz CLKINx reference, 586 Hz loop bandwidth - #[cfg(all(rtio_frequency = "125.0", si5324_ext_ref, ext_ref_frequency = "100.0"))] - const SI5324_SETTINGS: si5324::FrequencySettings - = si5324::FrequencySettings { - n1_hs : 10, - nc1_ls : 4, - n2_hs : 10, - n2_ls : 260, - n31 : 52, - n32 : 52, - bwsel : 4, - crystal_ref: false - }; - // 125MHz output, from 125MHz CLKINx reference, 606 Hz loop bandwidth - #[cfg(all(rtio_frequency = "125.0", si5324_ext_ref, ext_ref_frequency = "125.0"))] - const SI5324_SETTINGS: si5324::FrequencySettings - = si5324::FrequencySettings { - n1_hs : 5, - nc1_ls : 8, - n2_hs : 7, - n2_ls : 360, - n31 : 63, - n32 : 63, - bwsel : 4, - crystal_ref: false - }; - // 125MHz output, from crystal, 7 Hz - #[cfg(all(rtio_frequency = "125.0", not(si5324_ext_ref)))] - const SI5324_SETTINGS: si5324::FrequencySettings - = si5324::FrequencySettings { - n1_hs : 10, - nc1_ls : 4, - n2_hs : 10, - n2_ls : 19972, - n31 : 4565, - n32 : 4565, - bwsel : 4, - crystal_ref: true - }; - // 150MHz output, from crystal - #[cfg(all(rtio_frequency = "150.0", not(si5324_ext_ref)))] - const SI5324_SETTINGS: si5324::FrequencySettings - = si5324::FrequencySettings { - n1_hs : 9, - nc1_ls : 4, - n2_hs : 10, - n2_ls : 33732, - n31 : 7139, - n32 : 7139, - bwsel : 3, - crystal_ref: true - }; - // 100MHz output, from crystal. Also used as reference for Sayma HMC830. - #[cfg(all(rtio_frequency = "100.0", not(si5324_ext_ref)))] - const SI5324_SETTINGS: si5324::FrequencySettings - = si5324::FrequencySettings { - n1_hs : 9, - nc1_ls : 6, - n2_hs : 10, - n2_ls : 33732, - n31 : 7139, - n32 : 7139, - bwsel : 3, - crystal_ref: true +fn setup_si5324_as_synthesizer(cfg: RtioClock) { + let si5324_settings = match cfg { + RtioClock::Ext0_Synth0_10to125 => { // 125 MHz output from 10 MHz CLKINx reference, 504 Hz BW + info!("using 10MHz reference to make 125MHz RTIO clock with PLL"); + si5324::FrequencySettings { + n1_hs : 10, + nc1_ls : 4, + n2_hs : 10, + n2_ls : 300, + n31 : 6, + n32 : 6, + bwsel : 4, + crystal_ref: false + } + }, + RtioClock::Ext0_Synth0_100to125 => { // 125MHz output, from 100MHz CLKINx reference, 586 Hz loop bandwidth + info!("using 10MHz reference to make 125MHz RTIO clock with PLL"); + si5324::FrequencySettings { + n1_hs : 10, + nc1_ls : 4, + n2_hs : 10, + n2_ls : 260, + n31 : 52, + n32 : 52, + bwsel : 4, + crystal_ref: false + } + }, + RtioClock::Ext0_Synth0_125to125 => { // 125MHz output, from 125MHz CLKINx reference, 606 Hz loop bandwidth + info!("using 10MHz reference to make 125MHz RTIO clock with PLL"); + si5324::FrequencySettings { + n1_hs : 5, + nc1_ls : 8, + n2_hs : 7, + n2_ls : 360, + n31 : 63, + n32 : 63, + bwsel : 4, + crystal_ref: false + } + }, + RtioClock::Int_150 => { // 150MHz output, from crystal + info!("using internal 150MHz RTIO clock"); + si5324::FrequencySettings { + n1_hs : 9, + nc1_ls : 4, + n2_hs : 10, + n2_ls : 33732, + n31 : 7139, + n32 : 7139, + bwsel : 3, + crystal_ref: true + } + }, + RtioClock::Int_100 => { // 100MHz output, from crystal. Also used as reference for Sayma HMC830. + info!("using internal 100MHz RTIO clock"); + si5324::FrequencySettings { + n1_hs : 9, + nc1_ls : 6, + n2_hs : 10, + n2_ls : 33732, + n31 : 7139, + n32 : 7139, + bwsel : 3, + crystal_ref: true + } + }, + RtioClock::Int_125 => { // 125MHz output, from crystal, 7 Hz + info!("using internal 125MHz RTIO clock"); + si5324::FrequencySettings { + n1_hs : 10, + nc1_ls : 4, + n2_hs : 10, + n2_ls : 19972, + n31 : 4565, + n32 : 4565, + bwsel : 4, + crystal_ref: true + } + } + _ => { // 125MHz output like above, default (if chosen option is not supported) + warn!("rtio_clock setting '{:?}' is not supported. Falling back to default internal 125MHz RTIO clock.", cfg); + si5324::FrequencySettings { + n1_hs : 10, + nc1_ls : 4, + n2_hs : 10, + n2_ls : 19972, + n31 : 4565, + n32 : 4565, + bwsel : 4, + crystal_ref: true + } + } }; #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0", not(si5324_ext_ref)))] let si5324_ref_input = si5324::Input::Ckin2; @@ -155,10 +222,11 @@ fn setup_si5324_as_synthesizer() { let si5324_ref_input = si5324::Input::Ckin2; #[cfg(soc_platform = "kc705")] let si5324_ref_input = si5324::Input::Ckin2; - si5324::setup(&SI5324_SETTINGS, si5324_ref_input).expect("cannot initialize Si5324"); + si5324::setup(&si5324_settings, si5324_ref_input).expect("cannot initialize Si5324"); } pub fn init() { + let clock_cfg = get_rtio_clock_cfg(); #[cfg(si5324_as_synthesizer)] { #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] @@ -169,9 +237,12 @@ pub fn init() { let si5324_ext_input = si5324::Input::Ckin2; #[cfg(soc_platform = "kc705")] let si5324_ext_input = si5324::Input::Ckin2; - match get_rtio_clock_cfg() { - RtioClock::Internal => setup_si5324_as_synthesizer(), - RtioClock::External => si5324::bypass(si5324_ext_input).expect("cannot bypass Si5324") + match clock_cfg { + RtioClock::Ext0_Bypass => { + info!("using external RTIO clock with PLL bypass"); + si5324::bypass(si5324_ext_input).expect("cannot bypass Si5324") + }, + _ => setup_si5324_as_synthesizer(clock_cfg), } } @@ -189,7 +260,7 @@ pub fn init() { #[cfg(has_rtio_crg)] { #[cfg(has_rtio_clock_switch)] - let result = crg::init(get_rtio_clock_cfg()); + let result = crg::init(clock_cfg); #[cfg(not(has_rtio_clock_switch))] let result = crg::init(); if !result { diff --git a/artiq/firmware/runtime/rtio_dma.rs b/artiq/firmware/runtime/rtio_dma.rs index 39e61d621..292874047 100644 --- a/artiq/firmware/runtime/rtio_dma.rs +++ b/artiq/firmware/runtime/rtio_dma.rs @@ -1,5 +1,5 @@ use core::mem; -use alloc::{Vec, String, BTreeMap}; +use alloc::{vec::Vec, string::String, collections::btree_map::BTreeMap}; const ALIGNMENT: usize = 64; diff --git a/artiq/firmware/runtime/rtio_mgt.rs b/artiq/firmware/runtime/rtio_mgt.rs index 825900b78..1a1d1660b 100644 --- a/artiq/firmware/runtime/rtio_mgt.rs +++ b/artiq/firmware/runtime/rtio_mgt.rs @@ -326,6 +326,14 @@ pub mod drtio { pub fn reset(_io: &Io, _aux_mutex: &Mutex) {} } +static mut SEEN_ASYNC_ERRORS: u8 = 0; + +pub unsafe fn get_async_errors() -> u8 { + let mut errors = SEEN_ASYNC_ERRORS; + SEEN_ASYNC_ERRORS = 0; + errors +} + fn async_error_thread(io: Io) { loop { unsafe { @@ -343,6 +351,7 @@ fn async_error_thread(io: Io) { error!("RTIO sequence error involving channel {}", csr::rtio_core::sequence_error_channel_read()); } + SEEN_ASYNC_ERRORS = errors; csr::rtio_core::async_error_write(errors); } } diff --git a/artiq/firmware/runtime/runtime.ld b/artiq/firmware/runtime/runtime.ld index a7bcc5a0d..9f60bf3ac 100644 --- a/artiq/firmware/runtime/runtime.ld +++ b/artiq/firmware/runtime/runtime.ld @@ -36,10 +36,14 @@ SECTIONS __eh_frame_hdr_start = SIZEOF(.eh_frame_hdr) > 0 ? ADDR(.eh_frame_hdr) : 0; __eh_frame_hdr_end = SIZEOF(.eh_frame_hdr) > 0 ? . : 0; + .gcc_except_table : + { + *(.gcc_except_table) + } > runtime + /* https://sourceware.org/bugzilla/show_bug.cgi?id=20475 */ .got : { - _GLOBAL_OFFSET_TABLE_ = .; *(.got) } > runtime @@ -58,20 +62,23 @@ SECTIONS *(.data .data.*) } > runtime - .bss ALIGN(4) : + .bss (NOLOAD) : ALIGN(4) { _fbss = .; - *(.bss .bss.*) + *(.sbss .sbss.* .bss .bss.*); _ebss = .; } > runtime - .stack : + .stack (NOLOAD) : ALIGN(0x1000) { - . += 0x4000; - _fstack = . - 4; + _sstack_guard = .; + . += 0x1000; + _estack = .; + . += 0x10000; + _fstack = . - 16; } > runtime - .heap : + .heap (NOLOAD) : ALIGN(16) { _fheap = .; . = ORIGIN(runtime) + LENGTH(runtime); diff --git a/artiq/firmware/runtime/sched.rs b/artiq/firmware/runtime/sched.rs index 7a2a85723..d6a347269 100644 --- a/artiq/firmware/runtime/sched.rs +++ b/artiq/firmware/runtime/sched.rs @@ -3,7 +3,7 @@ use core::mem; use core::result; use core::cell::{Cell, RefCell}; -use alloc::Vec; +use alloc::vec::Vec; use fringe::OwnedStack; use fringe::generator::{Generator, Yielder, State as GeneratorState}; use smoltcp::time::Duration; @@ -35,7 +35,7 @@ type SocketSet = ::smoltcp::socket::SocketSet<'static, 'static, 'static>; #[derive(Debug)] struct WaitRequest { - event: Option<*mut FnMut() -> bool>, + event: Option<*mut dyn FnMut() -> bool>, timeout: Option } @@ -50,7 +50,7 @@ enum WaitResult { #[derive(Debug)] struct Thread { - generator: Generator, + generator: Generator<'static, WaitResult, WaitRequest, OwnedStack>, waiting_for: WaitRequest, interrupted: bool } @@ -61,7 +61,8 @@ impl Thread { let spawned = io.spawned.clone(); let sockets = io.sockets.clone(); - let stack = OwnedStack::new(stack_size); + // Add a 4k stack guard to the stack of any new threads + let stack = OwnedStack::new(stack_size + 4096); ThreadHandle::new(Thread { generator: Generator::unsafe_new(stack, |yielder, _| { f(Io { @@ -194,7 +195,7 @@ impl Scheduler { #[derive(Clone)] pub struct Io<'a> { - yielder: Option<&'a Yielder>, + yielder: Option<&'a Yielder>, spawned: Urc>>, sockets: Urc>, } @@ -207,7 +208,7 @@ impl<'a> Io<'a> { handle } - fn yielder(&self) -> &'a Yielder { + fn yielder(&self) -> &'a Yielder { self.yielder.expect("cannot suspend the scheduler thread") } @@ -240,7 +241,7 @@ impl<'a> Io<'a> { } pub fn until bool>(&self, mut f: F) -> Result<(), Error> { - let f = unsafe { mem::transmute::<&mut FnMut() -> bool, *mut FnMut() -> bool>(&mut f) }; + let f = unsafe { mem::transmute::<&mut dyn FnMut() -> bool, *mut dyn FnMut() -> bool>(&mut f) }; self.suspend(WaitRequest { timeout: None, event: Some(f) diff --git a/artiq/firmware/runtime/session.rs b/artiq/firmware/runtime/session.rs index 08bc5f188..260a1b385 100644 --- a/artiq/firmware/runtime/session.rs +++ b/artiq/firmware/runtime/session.rs @@ -1,6 +1,6 @@ use core::{mem, str, cell::{Cell, RefCell}, fmt::Write as FmtWrite}; -use alloc::{Vec, String}; -use byteorder::{ByteOrder, NetworkEndian}; +use alloc::{vec::Vec, string::String}; +use byteorder::{ByteOrder, NativeEndian}; use io::{Read, Write, Error as IoError}; use board_misoc::{ident, cache, config}; @@ -9,6 +9,7 @@ use urc::Urc; use sched::{ThreadHandle, Io, Mutex, TcpListener, TcpStream, Error as SchedError}; use rtio_clocking; use rtio_dma::Manager as DmaManager; +use rtio_mgt::get_async_errors; use cache::Cache; use kern_hwreq; use board_artiq::drtio_routing; @@ -431,7 +432,9 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex, match stream { None => return Ok(true), Some(ref mut stream) => - host_write(stream, host::Reply::KernelFinished).map_err(|e| e.into()) + host_write(stream, host::Reply::KernelFinished { + async_errors: unsafe { get_async_errors() } + }).map_err(|e| e.into()) } } &kern::RunException { @@ -458,7 +461,8 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex, line: line, column: column, function: function, - backtrace: backtrace + backtrace: backtrace, + async_errors: unsafe { get_async_errors() } }).map_err(|e| e.into()) } } @@ -473,7 +477,7 @@ fn process_kern_queued_rpc(stream: &mut TcpStream, _session: &mut Session) -> Result<(), Error> { rpc_queue::dequeue(|slice| { debug!("comm<-kern (async RPC)"); - let length = NetworkEndian::read_u32(slice) as usize; + let length = NativeEndian::read_u32(slice) as usize; host_write(stream, host::Reply::RpcRequest { async: true })?; debug!("{:?}", &slice[4..][..length]); stream.write_all(&slice[4..][..length])?; @@ -615,7 +619,7 @@ pub fn thread(io: Io, aux_mutex: &Mutex, continue } } - match stream.write_all("E".as_bytes()) { + match stream.write_all("e".as_bytes()) { Ok(()) => (), Err(_) => { warn!("cannot send endian byte"); diff --git a/artiq/firmware/satman/Cargo.toml b/artiq/firmware/satman/Cargo.toml index fdccaf27a..0db54cd40 100644 --- a/artiq/firmware/satman/Cargo.toml +++ b/artiq/firmware/satman/Cargo.toml @@ -16,3 +16,4 @@ build_misoc = { path = "../libbuild_misoc" } log = { version = "0.4", default-features = false } board_misoc = { path = "../libboard_misoc", features = ["uart_console", "log"] } board_artiq = { path = "../libboard_artiq" } +riscv = { version = "0.6.0", features = ["inline-asm"] } diff --git a/artiq/firmware/satman/Makefile b/artiq/firmware/satman/Makefile index b96938d1b..82e65d730 100644 --- a/artiq/firmware/satman/Makefile +++ b/artiq/firmware/satman/Makefile @@ -5,11 +5,15 @@ LDFLAGS += -L../libbase RUSTFLAGS += -Cpanic=abort +export XBUILD_SYSROOT_PATH=$(BUILDINC_DIRECTORY)/../sysroot + all:: satman.bin satman.fbi .PHONY: $(RUSTOUT)/libsatman.a $(RUSTOUT)/libsatman.a: - $(cargo) --manifest-path $(SATMAN_DIRECTORY)/Cargo.toml + $(cargo) --target-dir ./cargo \ + --manifest-path $(SATMAN_DIRECTORY)/Cargo.toml \ + --target $(SATMAN_DIRECTORY)/../$(CARGO_TRIPLE).json satman.elf: $(RUSTOUT)/libsatman.a $(link) -T $(SATMAN_DIRECTORY)/satman.ld @@ -18,4 +22,4 @@ satman.elf: $(RUSTOUT)/libsatman.a $(objcopy) -O binary %.fbi: %.bin - $(mscimg) -f + $(mscimg) -f --little diff --git a/artiq/firmware/satman/main.rs b/artiq/firmware/satman/main.rs index 0b6d1cccb..c6685f895 100644 --- a/artiq/firmware/satman/main.rs +++ b/artiq/firmware/satman/main.rs @@ -1,4 +1,4 @@ -#![feature(never_type, panic_implementation, panic_info_message, const_slice_len, try_from)] +#![feature(never_type, panic_info_message, llvm_asm)] #![no_std] #[macro_use] @@ -6,9 +6,10 @@ extern crate log; #[macro_use] extern crate board_misoc; extern crate board_artiq; +extern crate riscv; use core::convert::TryFrom; -use board_misoc::{csr, irq, ident, clock, uart_logger, i2c}; +use board_misoc::{csr, ident, clock, uart_logger, i2c, pmp}; #[cfg(has_si5324)] use board_artiq::si5324; #[cfg(has_wrpll)] @@ -17,6 +18,7 @@ use board_artiq::{spi, drtioaux}; use board_artiq::drtio_routing; #[cfg(has_hmc830_7043)] use board_artiq::hmc830_7043; +use riscv::register::{mcause, mepc, mtval}; mod repeater; #[cfg(has_jdcg)] @@ -445,8 +447,29 @@ const SI5324_SETTINGS: si5324::FrequencySettings crystal_ref: true }; +#[cfg(all(has_si5324, rtio_frequency = "100.0"))] +const SI5324_SETTINGS: si5324::FrequencySettings + = si5324::FrequencySettings { + n1_hs : 5, + nc1_ls : 10, + n2_hs : 10, + n2_ls : 250, + n31 : 50, + n32 : 50, + bwsel : 4, + crystal_ref: true +}; + #[no_mangle] pub extern fn main() -> i32 { + extern { + static mut _sstack_guard: u8; + } + + unsafe { + pmp::init_stack_guard(&_sstack_guard as *const u8 as usize); + } + clock::init(); uart_logger::ConsoleLogger::register(); @@ -537,7 +560,7 @@ pub extern fn main() -> i32 { } while !drtiosat_link_rx_up() { drtiosat_process_errors(); - for mut rep in repeaters.iter_mut() { + for rep in repeaters.iter_mut() { rep.service(&routing_table, rank); } #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] @@ -566,7 +589,7 @@ pub extern fn main() -> i32 { while drtiosat_link_rx_up() { drtiosat_process_errors(); process_aux_packets(&mut repeaters, &mut routing_table, &mut rank); - for mut rep in repeaters.iter_mut() { + for rep in repeaters.iter_mut() { rep.service(&routing_table, rank); } #[cfg(all(soc_platform = "kasli", hw_rev = "v2.0"))] @@ -642,9 +665,10 @@ pub extern fn main() -> i32 { } #[no_mangle] -pub extern fn exception(vect: u32, _regs: *const u32, pc: u32, ea: u32) { - let vect = irq::Exception::try_from(vect).expect("unknown exception"); - +pub extern fn exception(_regs: *const u32) { + let pc = mepc::read(); + let cause = mcause::read().cause(); + fn hexdump(addr: u32) { let addr = (addr - addr % 4) as *const u32; let mut ptr = addr; @@ -658,9 +682,9 @@ pub extern fn exception(vect: u32, _regs: *const u32, pc: u32, ea: u32) { } } - hexdump(pc); - hexdump(ea); - panic!("exception {:?} at PC 0x{:x}, EA 0x{:x}", vect, pc, ea) + hexdump(u32::try_from(pc).unwrap()); + let mtval = mtval::read(); + panic!("exception {:?} at PC 0x{:x}, trap value 0x{:x}", cause, u32::try_from(pc).unwrap(), mtval) } #[no_mangle] @@ -670,7 +694,7 @@ pub extern fn abort() { } #[no_mangle] // https://github.com/rust-lang/rust/issues/{38281,51647} -#[panic_implementation] +#[panic_handler] pub fn panic_fmt(info: &core::panic::PanicInfo) -> ! { #[cfg(has_error_led)] unsafe { diff --git a/artiq/firmware/satman/satman.ld b/artiq/firmware/satman/satman.ld index 69cc737d2..37de797c2 100644 --- a/artiq/firmware/satman/satman.ld +++ b/artiq/firmware/satman/satman.ld @@ -14,6 +14,13 @@ SECTIONS *(.text .text.*) } > main_ram + .eh_frame : + { + __eh_frame_start = .; + KEEP(*(.eh_frame)) + __eh_frame_end = .; + } > main_ram + /* https://sourceware.org/bugzilla/show_bug.cgi?id=20475 */ .got : { @@ -38,18 +45,25 @@ SECTIONS *(.data .data.*) } > main_ram - .bss ALIGN(4) : + .sdata : + { + *(.sdata .sdata.*) + } > main_ram + + .bss (NOLOAD) : ALIGN(4) { _fbss = .; - *(.bss .bss.*) + *(.sbss .sbss.* .bss .bss.*); . = ALIGN(4); _ebss = .; } > main_ram - .stack : + .stack (NOLOAD) : ALIGN(0x1000) { + _sstack_guard = .; + . += 0x1000; _estack = .; . += 0x10000; - _fstack = . - 4; + _fstack = . - 16; } > main_ram } diff --git a/artiq/frontend/artiq_coremgmt.py b/artiq/frontend/artiq_coremgmt.py index 542e12925..789c26bf7 100755 --- a/artiq/frontend/artiq_coremgmt.py +++ b/artiq/frontend/artiq_coremgmt.py @@ -9,7 +9,6 @@ from artiq import __version__ as artiq_version from artiq.master.databases import DeviceDB from artiq.coredevice.comm_kernel import CommKernel from artiq.coredevice.comm_mgmt import CommMgmt -from artiq.coredevice.profiler import CallgrindWriter def get_argparser(): @@ -84,45 +83,7 @@ def get_argparser(): # booting t_boot = tools.add_parser("reboot", - help="reboot the currently running firmware") - - t_hotswap = tools.add_parser("hotswap", - help="load the specified firmware in RAM") - - t_hotswap.add_argument("image", metavar="IMAGE", type=argparse.FileType("rb"), - help="runtime image to be executed") - - # profiling - t_profile = tools.add_parser("profile", - help="account for communications CPU time") - - subparsers = t_profile.add_subparsers(dest="action") - subparsers.required = True - - p_start = subparsers.add_parser("start", - help="start profiling") - p_start.add_argument("--interval", metavar="MICROS", type=int, default=2000, - help="sampling interval, in microseconds") - p_start.add_argument("--hits-size", metavar="ENTRIES", type=int, default=8192, - help="hit buffer size") - p_start.add_argument("--edges-size", metavar="ENTRIES", type=int, default=8192, - help="edge buffer size") - - p_stop = subparsers.add_parser("stop", - help="stop profiling") - - p_save = subparsers.add_parser("save", - help="save profile") - p_save.add_argument("output", metavar="OUTPUT", type=argparse.FileType("w"), - help="file to save profile to, in Callgrind format") - p_save.add_argument("firmware", metavar="FIRMWARE", type=str, - help="path to firmware ELF file") - p_save.add_argument("--no-compression", - dest="compression", default=True, action="store_false", - help="disable profile compression") - p_save.add_argument("--no-demangle", - dest="demangle", default=True, action="store_false", - help="disable symbol demangling") + help="reboot the running system") # misc debug t_debug = tools.add_parser("debug", @@ -180,24 +141,6 @@ def main(): if args.tool == "reboot": mgmt.reboot() - if args.tool == "hotswap": - mgmt.hotswap(args.image.read()) - - if args.tool == "profile": - if args.action == "start": - mgmt.start_profiler(args.interval, args.hits_size, args.edges_size) - elif args.action == "stop": - mgmt.stop_profiler() - elif args.action == "save": - hits, edges = mgmt.get_profile() - writer = CallgrindWriter(args.output, args.firmware, "or1k-linux", - args.compression, args.demangle) - writer.header() - for addr, count in hits.items(): - writer.hit(addr, count) - for (caller, callee), count in edges.items(): - writer.edge(caller, callee, count) - if args.tool == "debug": if args.action == "allocator": mgmt.debug_allocator() diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index 9d7359187..0a14a06be 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -11,14 +11,16 @@ from artiq.coredevice import jsondesc def process_header(output, description): - if description["target"] not in ("kasli", "kasli_soc"): + if description["target"] == "kasli": + if description["hw_rev"] in ("v1.0", "v1.1"): + cpu_target = "rv32ima" + else: + cpu_target = "rv32g" + elif description["target"] == "kasli_soc": + cpu_target = "cortexa9" + else: raise NotImplementedError - cpu_target = { - "kasli": "or1k", - "kasli_soc": "cortexa9" - }[description["target"]] - print(textwrap.dedent(""" # Autogenerated for the {variant} variant core_addr = "{core_addr}" @@ -82,7 +84,7 @@ class PeripheralManager: def gen(self, string, **kwargs): print(textwrap.dedent(string).format(**kwargs), file=self.output) - def process_dio(self, rtio_offset, peripheral): + def process_dio(self, rtio_offset, peripheral, num_channels=8): class_names = { "input": "TTLInOut", "output": "TTLOut" @@ -92,7 +94,8 @@ class PeripheralManager: class_names[peripheral["bank_direction_high"]] ] channel = count(0) - for i in range(8): + name = [self.get_name("ttl") for _ in range(num_channels)] + for i in range(num_channels): self.gen(""" device_db["{name}"] = {{ "type": "local", @@ -101,23 +104,23 @@ class PeripheralManager: "arguments": {{"channel": 0x{channel:06x}}}, }} """, - name=self.get_name("ttl"), - class_name=classes[i//4], - channel=rtio_offset+next(channel)) + name=name[i], + class_name=classes[i // 4], + channel=rtio_offset + next(channel)) if peripheral.get("edge_counter", False): - for i in range(8): - class_name = classes[i//4] + for i in range(num_channels): + class_name = classes[i // 4] if class_name == "TTLInOut": self.gen(""" - device_db["{name}"] = {{ + device_db["{name}_counter"] = {{ "type": "local", "module": "artiq.coredevice.edge_counter", "class": "EdgeCounter", "arguments": {{"channel": 0x{channel:06x}}}, }} """, - name=self.get_name("ttl_counter"), - channel=rtio_offset+next(channel)) + name=name[i], + channel=rtio_offset + next(channel)) return next(channel) def process_urukul(self, rtio_offset, peripheral): @@ -361,8 +364,7 @@ class PeripheralManager: def process_suservo(self, rtio_offset, peripheral): suservo_name = self.get_name("suservo") sampler_name = self.get_name("sampler") - urukul0_name = self.get_name("urukul") - urukul1_name = self.get_name("urukul") + urukul_names = [self.get_name("urukul") for _ in range(2)] channel = count(0) for i in range(8): self.gen(""" @@ -383,16 +385,14 @@ class PeripheralManager: "arguments": {{ "channel": 0x{suservo_channel:06x}, "pgia_device": "spi_{sampler_name}_pgia", - "cpld0_device": "{urukul0_name}_cpld", - "cpld1_device": "{urukul1_name}_cpld", - "dds0_device": "{urukul0_name}_dds", - "dds1_device": "{urukul1_name}_dds" + "cpld_devices": {cpld_names_list}, + "dds_devices": {dds_names_list} }} }}""", suservo_name=suservo_name, sampler_name=sampler_name, - urukul0_name=urukul0_name, - urukul1_name=urukul1_name, + cpld_names_list=[urukul_name + "_cpld" for urukul_name in urukul_names], + dds_names_list=[urukul_name + "_dds" for urukul_name in urukul_names], suservo_channel=rtio_offset+next(channel)) self.gen(""" device_db["spi_{sampler_name}_pgia"] = {{ @@ -404,7 +404,7 @@ class PeripheralManager: sampler_name=sampler_name, sampler_channel=rtio_offset+next(channel)) pll_vco = peripheral.get("pll_vco") - for urukul_name in (urukul0_name, urukul1_name): + for urukul_name in urukul_names: self.gen(""" device_db["spi_{urukul_name}"] = {{ "type": "local", @@ -515,6 +515,21 @@ class PeripheralManager: channel=rtio_offset) return 5 + def process_hvamp(self, rtio_offset, peripheral): + hvamp_name = self.get_name("hvamp") + for i in range(8): + self.gen(""" + device_db["ttl_{name}_sw{ch}"] = {{ + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {{"channel": 0x{channel:06x}}} + }}""", + name=hvamp_name, + ch=i, + channel=rtio_offset+i) + return 8 + def process(self, rtio_offset, peripheral): processor = getattr(self, "process_"+str(peripheral["type"])) return processor(rtio_offset, peripheral) diff --git a/artiq/frontend/artiq_flash.py b/artiq/frontend/artiq_flash.py index 8868b3fda..62b8dc39c 100755 --- a/artiq/frontend/artiq_flash.py +++ b/artiq/frontend/artiq_flash.py @@ -79,28 +79,22 @@ Prerequisites: help="actions to perform, default: flash everything") return parser -def which_openocd(): +def openocd_root(): openocd = shutil.which("openocd") if not openocd: raise FileNotFoundError("OpenOCD is required but was not found in PATH. Is it installed?") - return openocd + return os.path.dirname(os.path.dirname(openocd)) + def scripts_path(): p = ["share", "openocd", "scripts"] if os.name == "nt": p.insert(0, "Library") - p = os.path.abspath(os.path.join( - os.path.dirname(os.path.realpath(which_openocd())), - "..", *p)) - return p + return os.path.abspath(os.path.join(openocd_root(), *p)) def proxy_path(): - p = ["share", "bscan-spi-bitstreams"] - p = os.path.abspath(os.path.join( - os.path.dirname(os.path.realpath(which_openocd())), - "..", *p)) - return p + return os.path.abspath(os.path.join(openocd_root(), "share", "bscan-spi-bitstreams")) def find_proxy_bitfile(filename): @@ -368,7 +362,10 @@ def main(): variants.remove("rtm") except ValueError: pass - if len(variants) == 0: + if all(action in ["rtm_gateware", "storage", "rtm_load", "erase", "start"] + for action in args.action) and args.action: + pass + elif len(variants) == 0: raise FileNotFoundError("no variants found, did you install a board binary package?") elif len(variants) == 1: variant = variants[0] @@ -424,8 +421,8 @@ def main(): magic = 0x5352544d # "SRTM", see sayma_rtm target length = bin_file.tell() - 8 bin_file.seek(0) - bin_file.write(magic.to_bytes(4, byteorder="big")) - bin_file.write(length.to_bytes(4, byteorder="big")) + bin_file.write(magic.to_bytes(4, byteorder="little")) + bin_file.write(length.to_bytes(4, byteorder="little")) atexit.register(lambda: os.unlink(bin_filename)) return bin_filename @@ -446,13 +443,17 @@ def main(): storage_img = args.storage programmer.write_binary(*config["storage"], storage_img) elif action == "firmware": - if variant.endswith("satellite"): - firmware = "satman" - else: - firmware = "runtime" - - firmware_fbi = artifact_path(variant_dir, "software", firmware, firmware + ".fbi") - programmer.write_binary(*config["firmware"], firmware_fbi) + firmware_fbis = [] + for firmware in "satman", "runtime": + filename = artifact_path(variant_dir, "software", firmware, firmware + ".fbi") + if os.path.exists(filename): + firmware_fbis.append(filename) + if not firmware_fbis: + raise FileNotFoundError("no firmware found") + if len(firmware_fbis) > 1: + raise ValueError("more than one firmware file, please clean up your build directory. " + "Found firmware files: {}".format(" ".join(firmware_fbis))) + programmer.write_binary(*config["firmware"], firmware_fbis[0]) elif action == "load": if args.target == "sayma": gateware_bit = artifact_path(variant_dir, "gateware", "top.bit") diff --git a/artiq/frontend/artiq_master.py b/artiq/frontend/artiq_master.py index 1a5073692..ef46fb9bb 100755 --- a/artiq/frontend/artiq_master.py +++ b/artiq/frontend/artiq_master.py @@ -50,6 +50,10 @@ def get_argparser(): group.add_argument( "-r", "--repository", default="repository", help="path to the repository (default: '%(default)s')") + group.add_argument( + "--experiment-subdir", default="", + help=("path to the experiment folder from the repository root " + "(default: '%(default)s')")) log_args(parser) @@ -104,7 +108,8 @@ def main(): repo_backend = GitBackend(args.repository) else: repo_backend = FilesystemBackend(args.repository) - experiment_db = ExperimentDB(repo_backend, worker_handlers) + experiment_db = ExperimentDB( + repo_backend, worker_handlers, args.experiment_subdir) atexit.register(experiment_db.close) scheduler = Scheduler(RIDCounter(), worker_handlers, experiment_db) diff --git a/artiq/frontend/artiq_run.py b/artiq/frontend/artiq_run.py index 4e6f2b7a3..21baf3f05 100755 --- a/artiq/frontend/artiq_run.py +++ b/artiq/frontend/artiq_run.py @@ -10,7 +10,7 @@ from collections import defaultdict import h5py -from llvmlite_artiq import binding as llvm +from llvmlite import binding as llvm from sipyco import common_args @@ -21,7 +21,6 @@ from artiq.master.databases import DeviceDB, DatasetDB from artiq.master.worker_db import DeviceManager, DatasetManager from artiq.coredevice.core import CompileError, host_only from artiq.compiler.embedding import EmbeddingMap -from artiq.compiler.targets import OR1KTarget from artiq.compiler import import_cache from artiq.tools import * @@ -53,7 +52,7 @@ class FileRunner(EnvExperiment): def build(self, file): self.setattr_device("core") self.file = file - self.target = OR1KTarget() + self.target = self.core.target_cls() def run(self): kernel_library = self.compile() diff --git a/artiq/frontend/artiq_sinara_tester.py b/artiq/frontend/artiq_sinara_tester.py index 5c88d5917..2a0392eec 100755 --- a/artiq/frontend/artiq_sinara_tester.py +++ b/artiq/frontend/artiq_sinara_tester.py @@ -1,8 +1,10 @@ #!/usr/bin/env python3 -import sys +import argparse +import inspect import os import select +import sys from artiq.experiment import * from artiq.coredevice.ad9910 import AD9910, SyncDataEeprom @@ -55,6 +57,8 @@ class SinaraTester(EnvExperiment): self.grabbers = dict() self.mirny_cplds = dict() self.mirnies = dict() + self.suservos = dict() + self.suschannels = dict() ddb = self.get_device_db() for name, desc in ddb.items(): @@ -88,9 +92,14 @@ class SinaraTester(EnvExperiment): self.mirny_cplds[name] = self.get_device(name) elif (module, cls) == ("artiq.coredevice.adf5356", "ADF5356"): self.mirnies[name] = self.get_device(name) + elif (module, cls) == ("artiq.coredevice.suservo", "SUServo"): + self.suservos[name] = self.get_device(name) + elif (module, cls) == ("artiq.coredevice.suservo", "Channel"): + self.suschannels[name] = self.get_device(name) # Remove Urukul, Sampler, Zotino and Mirny control signals - # from TTL outs (tested separately) + # from TTL outs (tested separately) and remove Urukuls covered by + # SUServo ddb = self.get_device_db() for name, desc in ddb.items(): if isinstance(desc, dict) and desc["type"] == "local": @@ -101,8 +110,16 @@ class SinaraTester(EnvExperiment): sw_device = desc["arguments"]["sw_device"] del self.ttl_outs[sw_device] elif (module, cls) == ("artiq.coredevice.urukul", "CPLD"): - io_update_device = desc["arguments"]["io_update_device"] - del self.ttl_outs[io_update_device] + if "io_update_device" in desc["arguments"]: + io_update_device = desc["arguments"]["io_update_device"] + del self.ttl_outs[io_update_device] + # check for suservos and delete respective urukuls + elif (module, cls) == ("artiq.coredevice.suservo", "SUServo"): + del self.urukuls[desc["arguments"]["dds0_device"]] + del self.urukul_cplds[desc["arguments"]["cpld0_device"]] + if "dds1_device" in desc["arguments"]: + del self.urukuls[desc["arguments"]["dds1_device"]] + del self.urukul_cplds[desc["arguments"]["cpld1_device"]] elif (module, cls) == ("artiq.coredevice.sampler", "Sampler"): cnv_device = desc["arguments"]["cnv_device"] del self.ttl_outs[cnv_device] @@ -126,6 +143,8 @@ class SinaraTester(EnvExperiment): self.phasers = sorted(self.phasers.items(), key=lambda x: x[1].channel_base) self.grabbers = sorted(self.grabbers.items(), key=lambda x: x[1].channel_base) self.mirnies = sorted(self.mirnies.items(), key=lambda x: (x[1].cpld.bus.channel, x[1].channel)) + self.suservos = sorted(self.suservos.items(), key=lambda x: x[1].channel) + self.suschannels = sorted(self.suschannels.items(), key=lambda x: x[1].channel) @kernel def test_led(self, led): @@ -210,6 +229,17 @@ class SinaraTester(EnvExperiment): self.core.break_realtime() cpld.init() + @kernel + def test_urukul_att(self, cpld): + self.core.break_realtime() + for i in range(32): + test_word = 1 << i + cpld.set_all_att_mu(test_word) + readback_word = cpld.get_att_mu() + if readback_word != test_word: + print(readback_word, test_word) + raise ValueError + @kernel def calibrate_urukul(self, channel): self.core.break_realtime() @@ -249,11 +279,12 @@ class SinaraTester(EnvExperiment): def test_urukuls(self): print("*** Testing Urukul DDSes.") - print("Initializing CPLDs...") for name, cpld in sorted(self.urukul_cplds.items(), key=lambda x: x[0]): - print(name + "...") + print(name + ": initializing CPLD...") self.init_urukul(cpld) - print("...done") + print(name + ": testing attenuator digital control...") + self.test_urukul_att(cpld) + print(name + ": done") print("Calibrating inter-device synchronization...") for channel_name, channel_dev in self.urukuls: @@ -555,38 +586,142 @@ class SinaraTester(EnvExperiment): print(card_name) self.grabber_capture(card_dev, rois) - def run(self): + @kernel + def setup_suservo(self, channel): + self.core.break_realtime() + channel.init() + delay(1*us) + # ADC PGIA gain 0 + for i in range(8): + channel.set_pgia_mu(i, 0) + delay(10*us) + # DDS attenuator 10dB + for i in range(4): + channel.cpld0.set_att(i, 10.) + channel.cpld1.set_att(i, 10.) + delay(1*us) + # Servo is done and disabled + assert channel.get_status() & 0xff == 2 + delay(10*us) + + @kernel + def setup_suservo_loop(self, channel, loop_nr): + self.core.break_realtime() + channel.set_y( + profile=loop_nr, + y=0. # clear integrator + ) + channel.set_iir( + profile=loop_nr, + adc=loop_nr, # take data from Sampler channel + kp=-1., # -1 P gain + ki=0./s, # no integrator gain + g=0., # no integrator gain limit + delay=0. # no IIR update delay after enabling + ) + # setpoint 0.5 (5 V with above PGIA gain setting) + delay(100*us) + channel.set_dds( + profile=loop_nr, + offset=-.3, # 3 V with above PGIA settings + frequency=10*MHz, + phase=0.) + # enable RF, IIR updates and set profile + delay(10*us) + channel.set(en_out=1, en_iir=1, profile=loop_nr) + + @kernel + def setup_start_suservo(self, channel): + self.core.break_realtime() + channel.set_config(enable=1) + delay(10*us) + # check servo enabled + assert channel.get_status() & 0x01 == 1 + delay(10*us) + + def test_suservos(self): + print("*** Testing SUServos.") + print("Initializing modules...") + for card_name, card_dev in self.suservos: + print(card_name) + self.setup_suservo(card_dev) + print("...done") + print("Setting up SUServo channels...") + for channels in chunker(self.suschannels, 8): + for i, (channel_name, channel_dev) in enumerate(channels): + print(channel_name) + self.setup_suservo_loop(channel_dev, i) + print("...done") + print("Enabling...") + for card_name, card_dev in self.suservos: + print(card_name) + self.setup_start_suservo(card_dev) + print("...done") + print("Each Sampler channel applies proportional amplitude control") + print("on the respective Urukul0 (ADC 0-3) and Urukul1 (ADC 4-7, if") + print("present) channels.") + print("Frequency: 10 MHz, output power: about -9 dBm at 0 V and about -15 dBm at 1.5 V") + print("Verify frequency and power behavior.") + print("Press ENTER when done.") + input() + + def run(self, tests): print("****** Sinara system tester ******") print("") self.core.reset() - if self.leds: - self.test_leds() - if self.ttl_outs: - self.test_ttl_outs() - if self.ttl_ins: - self.test_ttl_ins() - if self.urukuls: - self.test_urukuls() - if self.mirnies: - self.test_mirnies() - if self.samplers: - self.test_samplers() - if self.zotinos: - self.test_zotinos() - if self.fastinos: - self.test_fastinos() - if self.phasers: - self.test_phasers() - if self.grabbers: - self.test_grabbers() + + for name in tests: + if getattr(self, name): + getattr(self, f"test_{name}")() + + @classmethod + def available_tests(cls): + # listed in definition order + return [ + name.split("_", maxsplit=1)[1] + for name, obj in vars(cls).items() + if is_hw_test(obj) + ] + + +def is_hw_test(obj): + return ( + inspect.isfunction(obj) and + obj.__name__.startswith("test_") and + len(inspect.signature(obj).parameters) == 1 + ) + + +def get_argparser(available_tests): + parser = argparse.ArgumentParser(description="Sinara crate testing tool") + + parser.add_argument("--device-db", default="device_db.py", + help="device database file (default: '%(default)s')") + group = parser.add_mutually_exclusive_group() + group.add_argument("-x", "--exclude", nargs="*", choices=available_tests, + help="do not run the listed tests") + group.add_argument("-o", "--only", nargs="*", choices=available_tests, + help="run only the listed tests") + return parser def main(): - device_mgr = DeviceManager(DeviceDB("device_db.py")) + available_tests = SinaraTester.available_tests() + args = get_argparser(available_tests).parse_args() + + if args.exclude is not None: + # don't use set in order to keep the order + tests = [test for test in available_tests if test not in args.exclude] + elif args.only is not None: + tests = args.only + else: + tests = available_tests + + device_mgr = DeviceManager(DeviceDB(args.device_db)) try: experiment = SinaraTester((device_mgr, None, None, None)) experiment.prepare() - experiment.run() + experiment.run(tests) experiment.analyze() finally: device_mgr.close_devices() diff --git a/artiq/gateware/amp/kernel_cpu.py b/artiq/gateware/amp/kernel_cpu.py index 9294cb353..6979b7b4f 100644 --- a/artiq/gateware/amp/kernel_cpu.py +++ b/artiq/gateware/amp/kernel_cpu.py @@ -1,7 +1,8 @@ from migen import * +from migen.build.platforms.sinara import kasli from misoc.interconnect.csr import * from misoc.interconnect import wishbone -from misoc.cores import mor1kx +from misoc.cores import vexriscv from misoc.integration.wb_slaves import WishboneSlaveManager @@ -14,21 +15,22 @@ class KernelCPU(Module): # # # - self._wb_slaves = WishboneSlaveManager(0x80000000) - # CPU core self.clock_domains.cd_sys_kernel = ClockDomain() self.comb += [ self.cd_sys_kernel.clk.eq(ClockSignal()), self.cd_sys_kernel.rst.eq(self._reset.storage) ] + kasli_v1 = isinstance(platform, kasli.Platform) and platform.hw_rev in ("v1.0", "v1.1") self.submodules.cpu = ClockDomainsRenamer("sys_kernel")( - mor1kx.MOR1KX( - platform, - OPTION_RESET_PC=exec_address)) + vexriscv.VexRiscv(platform, exec_address, + variant="VexRiscv_IMA" if kasli_v1 else "VexRiscv_G")) + + self.cpu_dw = len(self.cpu.dbus.dat_w) + self._wb_slaves = WishboneSlaveManager(0x80000000, dw=self.cpu_dw) # DRAM access - self.wb_sdram = wishbone.Interface() + self.wb_sdram = wishbone.Interface(data_width=self.cpu_dw, adr_width=32-log2_int(self.cpu_dw//8)) self.add_wb_slave(main_mem_origin, 0x10000000, self.wb_sdram) def get_csrs(self): @@ -37,7 +39,7 @@ class KernelCPU(Module): def do_finalize(self): self.submodules.wishbonecon = wishbone.InterconnectShared( [self.cpu.ibus, self.cpu.dbus], - self._wb_slaves.get_interconnect_slaves(), register=True) + self._wb_slaves.get_interconnect_slaves(), register=True, dw=self.cpu_dw) def add_wb_slave(self, origin, length, interface): if self.finalized: diff --git a/artiq/gateware/amp/mailbox.py b/artiq/gateware/amp/mailbox.py index 7c60056d1..6c498b821 100644 --- a/artiq/gateware/amp/mailbox.py +++ b/artiq/gateware/amp/mailbox.py @@ -3,9 +3,9 @@ from misoc.interconnect import wishbone class Mailbox(Module): - def __init__(self, size=1): - self.i1 = wishbone.Interface() - self.i2 = wishbone.Interface() + def __init__(self, size=1, adr_width=30): + self.i1 = wishbone.Interface(data_width=32, adr_width=adr_width) + self.i2 = wishbone.Interface(data_width=32, adr_width=adr_width) # # # diff --git a/artiq/gateware/amp/soc.py b/artiq/gateware/amp/soc.py index 76d79807c..91b75ca3a 100644 --- a/artiq/gateware/amp/soc.py +++ b/artiq/gateware/amp/soc.py @@ -1,3 +1,4 @@ +from migen import * from misoc.cores import timer from misoc.interconnect import wishbone @@ -19,21 +20,24 @@ class AMPSoC: self.csr_devices.append("kernel_cpu") mailbox_size = 3 - self.submodules.mailbox = Mailbox(mailbox_size) - self.add_wb_slave(self.mem_map["mailbox"], 4*mailbox_size, + self.csr_separation = self.kernel_cpu.cpu_dw//8 + + self.submodules.mailbox = Mailbox(mailbox_size, adr_width=32-log2_int(self.csr_separation)) + self.add_wb_slave(self.mem_map["mailbox"], self.csr_separation*mailbox_size, self.mailbox.i1) - self.kernel_cpu.add_wb_slave(self.mem_map["mailbox"], 4*mailbox_size, + self.kernel_cpu.add_wb_slave(self.mem_map["mailbox"], self.csr_separation*mailbox_size, self.mailbox.i2) self.add_memory_region("mailbox", self.mem_map["mailbox"] | 0x80000000, - 4*mailbox_size) + self.csr_separation*mailbox_size) def register_kernel_cpu_csrdevice(self, name, csrs=None): if csrs is None: csrs = getattr(self, name).get_csrs() - bank = wishbone.CSRBank(csrs) + csr_bus = wishbone.Interface(data_width=32, adr_width=32-log2_int(self.csr_separation)) + bank = wishbone.CSRBank(csrs, bus=csr_bus) self.submodules += bank - self.kernel_cpu.add_wb_slave(self.mem_map[name], 4*2**bank.decode_bits, bank.bus) + self.kernel_cpu.add_wb_slave(self.mem_map[name], self.csr_separation*2**bank.decode_bits, bank.bus) self.add_csr_region(name, self.mem_map[name] | 0x80000000, 32, csrs) diff --git a/artiq/gateware/drtio/aux_controller.py b/artiq/gateware/drtio/aux_controller.py index 8effda67d..051a03a20 100644 --- a/artiq/gateware/drtio/aux_controller.py +++ b/artiq/gateware/drtio/aux_controller.py @@ -212,14 +212,15 @@ class Receiver(Module, AutoCSR): # TODO: FullMemoryWE should be applied by migen.build @FullMemoryWE() class DRTIOAuxController(Module): - def __init__(self, link_layer): - self.bus = wishbone.Interface() + def __init__(self, link_layer, dw=32): + wsb = log2_int(dw//8) + + self.bus = wishbone.Interface(data_width=dw, adr_width=32-wsb) self.submodules.transmitter = Transmitter(link_layer, len(self.bus.dat_w)) self.submodules.receiver = Receiver(link_layer, len(self.bus.dat_w)) - tx_sdram_if = wishbone.SRAM(self.transmitter.mem, read_only=False) - rx_sdram_if = wishbone.SRAM(self.receiver.mem, read_only=True) - wsb = log2_int(len(self.bus.dat_w)//8) + tx_sdram_if = wishbone.SRAM(self.transmitter.mem, read_only=False, data_width=dw) + rx_sdram_if = wishbone.SRAM(self.receiver.mem, read_only=True, data_width=dw) decoder = wishbone.Decoder(self.bus, [(lambda a: a[log2_int(max_packet)-wsb] == 0, tx_sdram_if.bus), (lambda a: a[log2_int(max_packet)-wsb] == 1, rx_sdram_if.bus)], diff --git a/artiq/gateware/drtio/siphaser.py b/artiq/gateware/drtio/siphaser.py index 81dacaed0..5237b7453 100644 --- a/artiq/gateware/drtio/siphaser.py +++ b/artiq/gateware/drtio/siphaser.py @@ -4,7 +4,7 @@ from migen.genlib.cdc import MultiReg, PulseSynchronizer from misoc.interconnect.csr import * -# This code assumes 125/62.5MHz reference clock and 125MHz or 150MHz RTIO +# This code assumes 125/62.5MHz reference clock and 100MHz, 125MHz or 150MHz RTIO # frequency. class SiPhaser7Series(Module, AutoCSR): @@ -15,9 +15,9 @@ class SiPhaser7Series(Module, AutoCSR): self.phase_shift_done = CSRStatus(reset=1) self.error = CSR() - assert rtio_clk_freq in (125e6, 150e6) + assert rtio_clk_freq in (100e6, 125e6, 150e6) - # 125MHz/62.5MHz reference clock to 125MHz/150MHz. VCO @ 750MHz. + # 125MHz/62.5MHz reference clock to 100MHz/125MHz/150MHz. VCO @ 750MHz. # Used to provide a startup clock to the transceiver through the Si, # we do not use the crystal reference so that the PFD (f3) frequency # can be high. @@ -43,8 +43,8 @@ class SiPhaser7Series(Module, AutoCSR): else: mmcm_freerun_output = mmcm_freerun_output_raw - # 125MHz/150MHz to 125MHz/150MHz with controllable phase shift, - # VCO @ 1000MHz/1200MHz. + # 100MHz/125MHz/150MHz to 100MHz/125MHz/150MHz with controllable phase shift, + # VCO @ 800MHz/1000MHz/1200MHz. # Inserted between CDR and output to Si, used to correct # non-determinstic skew of Si5324. mmcm_ps_fb = Signal() diff --git a/artiq/gateware/drtio/transceiver/gtx_7series.py b/artiq/gateware/drtio/transceiver/gtx_7series.py index a2da39822..28b9536db 100644 --- a/artiq/gateware/drtio/transceiver/gtx_7series.py +++ b/artiq/gateware/drtio/transceiver/gtx_7series.py @@ -16,7 +16,7 @@ class GTX_20X(Module): # * GTX PLL frequency @ 2.5GHz # * GTX line rate (TX & RX) @ 2.5Gb/s # * GTX TX/RX USRCLK @ 125MHz == coarse RTIO frequency - def __init__(self, refclk, tx_pads, rx_pads, sys_clk_freq, rtio_clk_freq=125e6, tx_mode="single", rx_mode="single"): + def __init__(self, refclk, pads, sys_clk_freq, rtio_clk_freq=125e6, tx_mode="single", rx_mode="single"): assert tx_mode in ["single", "master", "slave"] assert rx_mode in ["single", "master", "slave"] @@ -229,10 +229,10 @@ class GTX_20X(Module): p_RXCDR_LOCK_CFG=0b010101, # Pads - i_GTXRXP=rx_pads.p, - i_GTXRXN=rx_pads.n, - o_GTXTXP=tx_pads.p, - o_GTXTXN=tx_pads.n, + i_GTXRXP=pads.rxp, + i_GTXRXN=pads.rxn, + o_GTXTXP=pads.txp, + o_GTXTXN=pads.txn, # Other parameters p_PCS_RSVD_ATTR=( @@ -282,9 +282,8 @@ class GTX_20X(Module): class GTX(Module, TransceiverInterface): - def __init__(self, clock_pads, tx_pads, rx_pads, sys_clk_freq, rtio_clk_freq=125e6, master=0): - assert len(tx_pads) == len(rx_pads) - self.nchannels = nchannels = len(tx_pads) + def __init__(self, clock_pads, pads, sys_clk_freq, rtio_clk_freq=125e6, master=0): + self.nchannels = nchannels = len(pads) self.gtxs = [] self.rtio_clk_freq = rtio_clk_freq @@ -307,7 +306,7 @@ class GTX(Module, TransceiverInterface): else: mode = "master" if i == master else "slave" # Note: RX phase alignment is to be done on individual lanes, not multi-lane. - gtx = GTX_20X(refclk, tx_pads[i], rx_pads[i], sys_clk_freq, rtio_clk_freq=rtio_clk_freq, tx_mode=mode, rx_mode="single") + gtx = GTX_20X(refclk, pads[i], sys_clk_freq, rtio_clk_freq=rtio_clk_freq, tx_mode=mode, rx_mode="single") # Fan-out (to slave) / Fan-in (from master) of the TXUSRCLK if mode == "slave": self.comb += gtx.cd_rtio_tx.clk.eq(rtio_tx_clk) diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 0180989de..7f5fe3fdf 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -660,3 +660,24 @@ class Phaser(_EEM): rtio.Channel.from_phy(phy.ch1.frequency), rtio.Channel.from_phy(phy.ch1.phase_amplitude), ]) + + +class HVAmp(_EEM): + @staticmethod + def io(eem, iostandard): + return [ + ("hvamp{}_out_en".format(eem), i, + Subsignal("p", Pins(_eem_pin(eem, i, "p"))), + Subsignal("n", Pins(_eem_pin(eem, i, "n"))), + iostandard(eem) + ) for i in range(8)] + + @classmethod + def add_std(cls, target, eem, ttl_out_cls, iostandard=default_iostandard): + cls.add_extension(target, eem, iostandard=iostandard) + + for i in range(8): + pads = target.platform.request("hvamp{}_out_en".format(eem), i) + phy = ttl_out_cls(pads.p, pads.n) + target.submodules += phy + target.rtio_channels.append(rtio.Channel.from_phy(phy)) diff --git a/artiq/gateware/eem_7series.py b/artiq/gateware/eem_7series.py index bbd883a83..232150211 100644 --- a/artiq/gateware/eem_7series.py +++ b/artiq/gateware/eem_7series.py @@ -110,6 +110,13 @@ def peripheral_phaser(module, peripheral, **kwargs): eem.Phaser.add_std(module, peripheral["ports"][0], **kwargs) +def peripheral_hvamp(module, peripheral, **kwargs): + if len(peripheral["ports"]) != 1: + raise ValueError("wrong number of ports") + eem.HVAmp.add_std(module, peripheral["ports"][0], + ttl_simple.Output, **kwargs) + + peripheral_processors = { "dio": peripheral_dio, "urukul": peripheral_urukul, @@ -121,6 +128,7 @@ peripheral_processors = { "mirny": peripheral_mirny, "fastino": peripheral_fastino, "phaser": peripheral_phaser, + "hvamp": peripheral_hvamp, } diff --git a/artiq/gateware/rtio/analyzer.py b/artiq/gateware/rtio/analyzer.py index 342da2acf..ef154affa 100644 --- a/artiq/gateware/rtio/analyzer.py +++ b/artiq/gateware/rtio/analyzer.py @@ -3,6 +3,7 @@ from migen.genlib.record import Record, layout_len from misoc.interconnect.csr import * from misoc.interconnect import stream +from artiq.gateware.rtio import dma from artiq.gateware.rtio.cri import commands as cri_commands from artiq.coredevice.comm_analyzer import MessageType, ExceptionType @@ -136,7 +137,7 @@ class MessageEncoder(Module, AutoCSR): class DMAWriter(Module, AutoCSR): - def __init__(self, membus): + def __init__(self, membus, cpu_dw): aw = len(membus.adr) dw = len(membus.dat_w) messages_per_dw = dw//message_len @@ -161,7 +162,7 @@ class DMAWriter(Module, AutoCSR): membus.stb.eq(self.sink.stb), self.sink.ack.eq(membus.ack), membus.we.eq(1), - membus.dat_w.eq(self.sink.data) + membus.dat_w.eq(dma.convert_signal(self.sink.data, cpu_dw//8)) ] if messages_per_dw > 1: for i in range(dw//8): @@ -193,7 +194,7 @@ class DMAWriter(Module, AutoCSR): class Analyzer(Module, AutoCSR): - def __init__(self, tsc, cri, membus, fifo_depth=128): + def __init__(self, tsc, cri, membus, fifo_depth=128, cpu_dw=32): # shutdown procedure: set enable to 0, wait until busy=0 self.enable = CSRStorage() self.busy = CSRStatus() @@ -205,7 +206,7 @@ class Analyzer(Module, AutoCSR): self.submodules.converter = stream.Converter( message_len, len(membus.dat_w), reverse=True, report_valid_token_count=True) - self.submodules.dma = DMAWriter(membus) + self.submodules.dma = DMAWriter(membus, cpu_dw) enable_r = Signal() self.sync += [ diff --git a/artiq/gateware/rtio/dma.py b/artiq/gateware/rtio/dma.py index 20d558672..f81559dde 100644 --- a/artiq/gateware/rtio/dma.py +++ b/artiq/gateware/rtio/dma.py @@ -11,8 +11,20 @@ def _reverse_bytes(s, g): return Cat(reversed(list(s[i*g:(i+1)*g] for i in range(len(s)//g)))) +def convert_signal(signal, granularity): + assert len(signal) % 8 == 0 + nbytes = len(signal)//8 + assert nbytes % granularity == 0 + nwords = nbytes//granularity + signal_words = [] + for i in range(nwords): + signal_words.append(_reverse_bytes( + signal[i*granularity*8:(i+1)*granularity*8], 8)) + return Cat(signal_words) + + class WishboneReader(Module): - def __init__(self, bus): + def __init__(self, bus, cpu_dw): self.bus = bus aw = len(bus.adr) @@ -37,18 +49,18 @@ class WishboneReader(Module): If(self.source.ack, data_reg_loaded.eq(0)), If(bus.ack, data_reg_loaded.eq(1), - self.source.data.eq(bus.dat_r), + self.source.data.eq(convert_signal(bus.dat_r, cpu_dw//8)), self.source.eop.eq(self.sink.eop) ) ] class DMAReader(Module, AutoCSR): - def __init__(self, membus, enable): + def __init__(self, membus, enable, cpu_dw): aw = len(membus.adr) data_alignment = log2_int(len(membus.dat_w)//8) - self.submodules.wb_reader = WishboneReader(membus) + self.submodules.wb_reader = WishboneReader(membus, cpu_dw) self.source = self.wb_reader.source # All numbers in bytes @@ -324,11 +336,11 @@ class CRIMaster(Module, AutoCSR): class DMA(Module): - def __init__(self, membus): + def __init__(self, membus, cpu_dw): self.enable = CSR() flow_enable = Signal() - self.submodules.dma = DMAReader(membus, flow_enable) + self.submodules.dma = DMAReader(membus, flow_enable, cpu_dw) self.submodules.slicer = RecordSlicer(len(membus.dat_w)) self.submodules.time_offset = TimeOffset() self.submodules.cri_master = CRIMaster() diff --git a/artiq/gateware/rtio/phy/fastino.py b/artiq/gateware/rtio/phy/fastino.py index ba8a7b5c9..474bab0f3 100644 --- a/artiq/gateware/rtio/phy/fastino.py +++ b/artiq/gateware/rtio/phy/fastino.py @@ -27,16 +27,16 @@ class Fastino(Module): # dac data words dacs = [Signal(16) for i in range(32)] + header = Record([ ("cfg", 4), ("leds", 8), - ("reserved", 8), + ("typ", 1), + ("reserved", 7), ("addr", 4), ("enable", len(dacs)), ]) - body = Cat(header.raw_bits(), dacs) - assert len(body) == len(self.serializer.payload) - self.comb += self.serializer.payload.eq(body) + assert len(Cat(header.raw_bits(), dacs)) == len(self.serializer.payload) # # # @@ -62,38 +62,61 @@ class Fastino(Module): # address space is sparse. hold = Signal.like(header.enable) + continuous = Signal.like(header.enable) + cic_config = Signal(16) read_regs = Array([Signal.like(self.serializer.readback) for _ in range(1 << len(header.addr))]) cases = { # update - 0x20: header.enable.eq(header.enable | self.rtlink.o.data), + 0x20: [ + header.enable.eq(self.rtlink.o.data), + header.typ.eq(0), + ], # hold 0x21: hold.eq(self.rtlink.o.data), # cfg 0x22: header.cfg.eq(self.rtlink.o.data), # leds 0x23: header.leds.eq(self.rtlink.o.data), - # reserved + # reserved bits 0x24: header.reserved.eq(self.rtlink.o.data), + # force continuous DAC updates + 0x25: continuous.eq(self.rtlink.o.data), + # interpolator configuration stage + 0x26: cic_config.eq(self.rtlink.o.data), + # interpolator update flags + 0x27: [ + header.enable.eq(self.rtlink.o.data), + header.typ.eq(1), + ], } for i in range(0, len(dacs), width): cases[i] = [ Cat(dacs[i:i + width]).eq(self.rtlink.o.data), - [If(~hold[i + j], + [If(~hold[i + j] & (header.typ == 0), header.enable[i + j].eq(1), ) for j in range(width)] ] + self.comb += [ + If(header.typ == 0, + self.serializer.payload.eq(Cat(header.raw_bits(), dacs)), + ).Else( + self.serializer.payload.eq(Cat(header.raw_bits(), Replicate(cic_config, len(dacs)))), + ), + ] + self.sync.rio_phy += [ If(self.serializer.stb, - header.enable.eq(0), + header.typ.eq(0), + header.enable.eq(continuous), read_regs[header.addr].eq(self.serializer.readback), header.addr.eq(header.addr + 1), ), - If(self.rtlink.o.stb & ~self.rtlink.o.address[-1], - Case(self.rtlink.o.address[:-1], cases), + If(self.rtlink.o.stb, + Case(self.rtlink.o.address, cases), ), ] diff --git a/artiq/gateware/rtio/xilinx_clocking.py b/artiq/gateware/rtio/xilinx_clocking.py new file mode 100644 index 000000000..57e6683c4 --- /dev/null +++ b/artiq/gateware/rtio/xilinx_clocking.py @@ -0,0 +1,45 @@ +from migen import * +from migen.genlib.cdc import MultiReg +from misoc.interconnect.csr import * + + +class RTIOClockMultiplier(Module, AutoCSR): + def __init__(self, rtio_clk_freq): + self.pll_reset = CSRStorage(reset=1) + self.pll_locked = CSRStatus() + self.clock_domains.cd_rtiox4 = ClockDomain(reset_less=True) + + # See "Global Clock Network Deskew Using Two BUFGs" in ug472. + clkfbout = Signal() + clkfbin = Signal() + rtiox4_clk = Signal() + pll_locked = Signal() + self.specials += [ + Instance("MMCME2_BASE", + p_CLKIN1_PERIOD=1e9/rtio_clk_freq, + i_CLKIN1=ClockSignal("rtio"), + i_RST=self.pll_reset.storage, + o_LOCKED=pll_locked, + + p_CLKFBOUT_MULT_F=8.0, p_DIVCLK_DIVIDE=1, + + o_CLKFBOUT=clkfbout, i_CLKFBIN=clkfbin, + + p_CLKOUT0_DIVIDE_F=2.0, o_CLKOUT0=rtiox4_clk, + ), + Instance("BUFG", i_I=clkfbout, o_O=clkfbin), + Instance("BUFG", i_I=rtiox4_clk, o_O=self.cd_rtiox4.clk), + + MultiReg(pll_locked, self.pll_locked.status) + ] + + +def fix_serdes_timing_path(platform): + # ignore timing of path from OSERDESE2 through the pad to ISERDESE2 + platform.add_platform_command( + "set_false_path -quiet " + "-through [get_pins -filter {{REF_PIN_NAME == OQ || REF_PIN_NAME == TQ}} " + "-of [get_cells -filter {{REF_NAME == OSERDESE2}}]] " + "-to [get_pins -filter {{REF_PIN_NAME == D}} " + "-of [get_cells -filter {{REF_NAME == ISERDESE2}}]]" + ) diff --git a/artiq/gateware/suservo/__init__.py b/artiq/gateware/suservo/__init__.py index e69de29bb..7a1df77ac 100644 --- a/artiq/gateware/suservo/__init__.py +++ b/artiq/gateware/suservo/__init__.py @@ -0,0 +1,10 @@ +"""Gateware implementation of the Sampler-Urukul (AD9910) DDS amplitude servo. + +General conventions: + + - ``t_...`` signals and constants refer to time spans measured in the gateware + module's default clock (typically a 125 MHz RTIO clock). + - ``start`` signals cause modules to proceed with the next servo iteration iff + they are currently idle (i.e. their value is irrelevant while the module is + busy, so they are not necessarily one-clock-period strobes). +""" diff --git a/artiq/gateware/suservo/iir.py b/artiq/gateware/suservo/iir.py index 0ebab3f13..0ec9bfa09 100644 --- a/artiq/gateware/suservo/iir.py +++ b/artiq/gateware/suservo/iir.py @@ -1,9 +1,7 @@ from collections import namedtuple import logging - from migen import * - logger = logging.getLogger(__name__) @@ -222,31 +220,30 @@ class IIR(Module): assert w.word <= w.coeff # same memory assert w.state + w.coeff + 3 <= w.accu - # m_coeff of active profiles should only be accessed during + # m_coeff of active profiles should only be accessed externally during # ~processing self.specials.m_coeff = Memory( width=2*w.coeff, # Cat(pow/ftw/offset, cfg/a/b) depth=4 << w.profile + w.channel) - # m_state[x] should only be read during ~(shifting | - # loading) - # m_state[y] of active profiles should only be read during + # m_state[x] should only be read externally during ~(shifting | loading) + # m_state[y] of active profiles should only be read externally during # ~processing self.specials.m_state = Memory( width=w.state, # y1,x0,x1 depth=(1 << w.profile + w.channel) + (2 << w.channel)) # ctrl should only be updated synchronously self.ctrl = [Record([ - ("profile", w.profile), - ("en_out", 1), - ("en_iir", 1), - ("clip", 1), - ("stb", 1)]) - for i in range(1 << w.channel)] + ("profile", w.profile), + ("en_out", 1), + ("en_iir", 1), + ("clip", 1), + ("stb", 1)]) + for i in range(1 << w.channel)] # only update during ~loading self.adc = [Signal((w.adc, True), reset_less=True) for i in range(1 << w.channel)] # Cat(ftw0, ftw1, pow, asf) - # only read during ~processing + # only read externally during ~processing self.dds = [Signal(4*w.word, reset_less=True) for i in range(1 << w.channel)] # perform one IIR iteration, start with loading, @@ -270,100 +267,116 @@ class IIR(Module): en_iirs = Array([ch.en_iir for ch in self.ctrl]) clips = Array([ch.clip for ch in self.ctrl]) - # state counter - state = Signal(w.channel + 2) - # pipeline group activity flags (SR) - stage = Signal(3) + # Main state machine sequencing the steps of each servo iteration. The + # module IDLEs until self.start is asserted, and then runs through LOAD, + # PROCESS and SHIFT in order (see description of corresponding flags + # above). The steps share the same memory ports, and are executed + # strictly sequentially. + # + # LOAD/SHIFT just read/write one address per cycle; the duration needed + # to iterate over all channels is determined by counting cycles. + # + # The PROCESSing step is split across a three-stage pipeline, where each + # stage has up to four clock cycles latency. We feed the first stage + # using the (MSBs of) t_current_step, and, after all channels have been + # covered, proceed once the pipeline has completely drained. self.submodules.fsm = fsm = FSM("IDLE") - state_clr = Signal() - stage_en = Signal() + t_current_step = Signal(w.channel + 2) + t_current_step_clr = Signal() + + # pipeline group activity flags (SR) + # 0: load from memory + # 1: compute + # 2: write to output registers (DDS profiles, clip flags) + stages_active = Signal(3) fsm.act("IDLE", self.done.eq(1), - state_clr.eq(1), + t_current_step_clr.eq(1), If(self.start, NextState("LOAD") ) ) fsm.act("LOAD", self.loading.eq(1), - If(state == (1 << w.channel) - 1, - state_clr.eq(1), - stage_en.eq(1), + If(t_current_step == (1 << w.channel) - 1, + t_current_step_clr.eq(1), + NextValue(stages_active[0], 1), NextState("PROCESS") ) ) fsm.act("PROCESS", self.processing.eq(1), # this is technically wasting three cycles - # (one for setting stage, and phase=2,3 with stage[2]) - If(stage == 0, - state_clr.eq(1), - NextState("SHIFT") + # (one for setting stages_active, and phase=2,3 with stages_active[2]) + If(stages_active == 0, + t_current_step_clr.eq(1), + NextState("SHIFT"), ) ) fsm.act("SHIFT", self.shifting.eq(1), - If(state == (2 << w.channel) - 1, + If(t_current_step == (2 << w.channel) - 1, NextState("IDLE") ) ) self.sync += [ - state.eq(state + 1), - If(state_clr, - state.eq(0), - ), - If(stage_en, - stage[0].eq(1) + If(t_current_step_clr, + t_current_step.eq(0) + ).Else( + t_current_step.eq(t_current_step + 1) ) ] - # pipeline group channel pointer + # global pipeline phase (lower two bits of t_current_step) + pipeline_phase = Signal(2, reset_less=True) + # pipeline group channel pointer (SR) # for each pipeline stage, this is the channel currently being # processed channel = [Signal(w.channel, reset_less=True) for i in range(3)] + self.comb += Cat(pipeline_phase, channel[0]).eq(t_current_step) + self.sync += [ + If(pipeline_phase == 3, + Cat(channel[1:]).eq(Cat(channel[:-1])), + stages_active[1:].eq(stages_active[:-1]), + If(channel[0] == (1 << w.channel) - 1, + stages_active[0].eq(0) + ) + ) + ] + # pipeline group profile pointer (SR) # for each pipeline stage, this is the profile currently being # processed profile = [Signal(w.profile, reset_less=True) for i in range(2)] - # pipeline phase (lower two bits of state) - phase = Signal(2, reset_less=True) - - self.comb += Cat(phase, channel[0]).eq(state) self.sync += [ - Case(phase, { - 0: [ - profile[0].eq(profiles[channel[0]]), - profile[1].eq(profile[0]) - ], - 3: [ - Cat(channel[1:]).eq(Cat(channel[:-1])), - stage[1:].eq(stage[:-1]), - If(channel[0] == (1 << w.channel) - 1, - stage[0].eq(0) - ) - ] - }) + If(pipeline_phase == 0, + profile[0].eq(profiles[channel[0]]), + profile[1].eq(profile[0]), + ) ] m_coeff = self.m_coeff.get_port() m_state = self.m_state.get_port(write_capable=True) # mode=READ_FIRST self.specials += m_state, m_coeff + # + # Hook up main IIR filter. + # + dsp = DSP(w) self.submodules += dsp offset_clr = Signal() - self.comb += [ - m_coeff.adr.eq(Cat(phase, profile[0], - Mux(phase==0, channel[1], channel[0]))), + m_coeff.adr.eq(Cat(pipeline_phase, profile[0], + Mux(pipeline_phase == 0, channel[1], channel[0]))), dsp.offset[-w.coeff - 1:].eq(Mux(offset_clr, 0, Cat(m_coeff.dat_r[:w.coeff], m_coeff.dat_r[w.coeff - 1]) )), dsp.coeff.eq(m_coeff.dat_r[w.coeff:]), dsp.state.eq(m_state.dat_r), - Case(phase, { + Case(pipeline_phase, { 0: dsp.accu_clr.eq(1), 2: [ offset_clr.eq(1), @@ -373,6 +386,11 @@ class IIR(Module): }) ] + + # + # Arbitrate state memory access between steps. + # + # selected adc and profile delay (combinatorial from dat_r) # both share the same coeff word (sel in the lower 8 bits) sel_profile = Signal(w.channel) @@ -389,13 +407,13 @@ class IIR(Module): sel_profile.eq(m_coeff.dat_r[w.coeff:]), dly_profile.eq(m_coeff.dat_r[w.coeff + 8:]), If(self.shifting, - m_state.adr.eq(state | (1 << w.profile + w.channel)), + m_state.adr.eq(t_current_step | (1 << w.profile + w.channel)), m_state.dat_w.eq(m_state.dat_r), - m_state.we.eq(state[0]) + m_state.we.eq(t_current_step[0]) ), If(self.loading, - m_state.adr.eq((state << 1) | (1 << w.profile + w.channel)), - m_state.dat_w[-w.adc - 1:-1].eq(Array(self.adc)[state]), + m_state.adr.eq((t_current_step << 1) | (1 << w.profile + w.channel)), + m_state.dat_w[-w.adc - 1:-1].eq(Array(self.adc)[t_current_step]), m_state.dat_w[-1].eq(m_state.dat_w[-2]), m_state.we.eq(1) ), @@ -405,16 +423,20 @@ class IIR(Module): Cat(profile[1], channel[2]), # read old y Cat(profile[0], channel[0]), - # x0 (recent) + # read x0 (recent) 0 | (sel_profile << 1) | (1 << w.profile + w.channel), - # x1 (old) + # read x1 (old) 1 | (sel << 1) | (1 << w.profile + w.channel), - ])[phase]), + ])[pipeline_phase]), m_state.dat_w.eq(dsp.output), - m_state.we.eq((phase == 0) & stage[2] & en[1]), + m_state.we.eq((pipeline_phase == 0) & stages_active[2] & en[1]), ) ] + # + # Compute auxiliary signals (delayed servo enable, clip indicators, etc.). + # + # internal channel delay counters dlys = Array([Signal(w.dly) for i in range(1 << w.channel)]) @@ -434,51 +456,65 @@ class IIR(Module): en_out = Signal(reset_less=True) # latched channel en_iir en_iir = Signal(reset_less=True) + + self.sync += [ + Case(pipeline_phase, { + 0: [ + dly.eq(dlys[channel[0]]), + en_out.eq(en_outs[channel[0]]), + en_iir.eq(en_iirs[channel[0]]), + If(stages_active[2] & en[1] & dsp.clip, + clips[channel[2]].eq(1) + ) + ], + 2: [ + en[0].eq(0), + en[1].eq(en[0]), + sel.eq(sel_profile), + If(stages_active[0] & en_out, + If(dly != dly_profile, + dlys[channel[0]].eq(dly + 1) + ).Elif(en_iir, + en[0].eq(1) + ) + ) + ], + }), + ] + + # + # Update DDS profile with FTW/POW/ASF + # Stage 0 loads the POW, stage 1 the FTW, and stage 2 writes + # the ASF computed by the IIR filter. + # + # muxing ddss = Array(self.dds) self.sync += [ - Case(phase, { - 0: [ - dly.eq(dlys[channel[0]]), - en_out.eq(en_outs[channel[0]]), - en_iir.eq(en_iirs[channel[0]]), - If(stage[1], - ddss[channel[1]][:w.word].eq(m_coeff.dat_r) - ), - If(stage[2] & en[1] & dsp.clip, - clips[channel[2]].eq(1) - ) - ], - 1: [ - If(stage[1], - ddss[channel[1]][w.word:2*w.word].eq( - m_coeff.dat_r), - ), - If(stage[2], - ddss[channel[2]][3*w.word:].eq( - m_state.dat_r[w.state - w.asf - 1:w.state - 1]) - ) - ], - 2: [ - en[0].eq(0), - en[1].eq(en[0]), - sel.eq(sel_profile), - If(stage[0], - ddss[channel[0]][2*w.word:3*w.word].eq( - m_coeff.dat_r), - If(en_out, - If(dly != dly_profile, - dlys[channel[0]].eq(dly + 1) - ).Elif(en_iir, - en[0].eq(1) - ) - ) - ) - ], - 3: [ - ], - }), + Case(pipeline_phase, { + 0: [ + If(stages_active[1], + ddss[channel[1]][:w.word].eq(m_coeff.dat_r), # ftw0 + ), + ], + 1: [ + If(stages_active[1], + ddss[channel[1]][w.word:2 * w.word].eq(m_coeff.dat_r), # ftw1 + ), + If(stages_active[2], + ddss[channel[2]][3*w.word:].eq( # asf + m_state.dat_r[w.state - w.asf - 1:w.state - 1]) + ) + ], + 2: [ + If(stages_active[0], + ddss[channel[0]][2*w.word:3*w.word].eq(m_coeff.dat_r), # pow + ), + ], + 3: [ + ], + }), ] def _coeff(self, channel, profile, coeff): diff --git a/artiq/gateware/suservo/servo.py b/artiq/gateware/suservo/servo.py index 08b31a3bc..1aec95f02 100644 --- a/artiq/gateware/suservo/servo.py +++ b/artiq/gateware/suservo/servo.py @@ -5,32 +5,76 @@ from .iir import IIR, IIRWidths from .dds_ser import DDS, DDSParams +def predict_timing(adc_p, iir_p, dds_p): + """ + The following is a sketch of the timing for 1 Sampler (8 ADCs) and N Urukuls + Shown here, the cycle duration is limited by the IIR loading+processing time. + + ADC|CONVH|CONV|READ|RTT|IDLE|CONVH|CONV|READ|RTT|IDLE|CONVH|CONV|READ|RTT|... + |4 |57 |16 |8 | .. |4 |57 |16 |8 | .. |4 |57 |16 |8 |... + ---+-------------------+------------------------+------------------------+--- + IIR| |LOAD|PROC |SHIFT|LOAD|PROC |SHIFT|... + | |8 |16*N+9 |16 |8 |16*N+9 |16 |... + ---+--------------------------------------+------------------------+--------- + DDS| |CMD|PROF|WAIT|IO_UP|IDLE|CMD|PR... + | |16 |128 |1 |1 | .. |16 | ... + + IIR loading starts once the ADC presents its data, the DDSes are updated + once the IIR processing is over. These are the only blocking processes. + IIR shifting happens in parallel to writing to the DDSes and ADC conversions + take place while the IIR filter is processing or the DDSes are being + written to, depending on the cycle duration (given by whichever module + takes the longest). + """ + t_adc = (adc_p.t_cnvh + adc_p.t_conv + adc_p.t_rtt + + adc_p.channels*adc_p.width//adc_p.lanes) + 1 + # load adc_p.channels values, process dds_p.channels + # (4 processing phases and 2 additional stages à 4 phases + # to complete the processing of the last channel) + t_iir = adc_p.channels + 4*dds_p.channels + 8 + 1 + t_dds = (dds_p.width*2 + 1)*dds_p.clk + 1 + t_cycle = max(t_adc, t_iir, t_dds) + return t_adc, t_iir, t_dds, t_cycle + class Servo(Module): def __init__(self, adc_pads, dds_pads, adc_p, iir_p, dds_p): + t_adc, t_iir, t_dds, t_cycle = predict_timing(adc_p, iir_p, dds_p) + assert t_iir + 2*adc_p.channels < t_cycle, "need shifting time" + self.submodules.adc = ADC(adc_pads, adc_p) self.submodules.iir = IIR(iir_p) self.submodules.dds = DDS(dds_pads, dds_p) # adc channels are reversed on Sampler - for i, j, k, l in zip(reversed(self.adc.data), self.iir.adc, - self.iir.dds, self.dds.profile): - self.comb += j.eq(i), l.eq(k) - - t_adc = (adc_p.t_cnvh + adc_p.t_conv + adc_p.t_rtt + - adc_p.channels*adc_p.width//adc_p.lanes) + 1 - t_iir = ((1 + 4 + 1) << iir_p.channel) + 1 - t_dds = (dds_p.width*2 + 1)*dds_p.clk + 1 - - t_cycle = max(t_adc, t_iir, t_dds) - assert t_iir + (2 << iir_p.channel) < t_cycle, "need shifting time" + for iir, adc in zip(self.iir.adc, reversed(self.adc.data)): + self.comb += iir.eq(adc) + for dds, iir in zip(self.dds.profile, self.iir.dds): + self.comb += dds.eq(iir) + # If high, a new cycle is started if the current cycle (if any) is + # finished. Consequently, if low, servo iterations cease after the + # current cycle is finished. Don't care while the first step (ADC) + # is active. self.start = Signal() + + # Counter for delay between end of ADC cycle and start of next one, + # depending on the duration of the other steps. t_restart = t_cycle - t_adc + 1 assert t_restart > 1 cnt = Signal(max=t_restart) cnt_done = Signal() active = Signal(3) + + # Indicates whether different steps (0: ADC, 1: IIR, 2: DDS) are + # currently active (exposed for simulation only), with each bit being + # reset once the successor step is launched. Depending on the + # timing details of the different steps, any number can be concurrently + # active (e.g. ADC read from iteration n, IIR computation from iteration + # n - 1, and DDS write from iteration n - 2). + + # Asserted once per cycle when the DDS write has been completed. self.done = Signal() + self.sync += [ If(self.dds.done, active[2].eq(0) diff --git a/artiq/gateware/targets/kasli.py b/artiq/gateware/targets/kasli.py index 91c6fb9a0..71ee933a5 100755 --- a/artiq/gateware/targets/kasli.py +++ b/artiq/gateware/targets/kasli.py @@ -17,6 +17,7 @@ from misoc.integration.builder import builder_args, builder_argdict from artiq.gateware.amp import AMPSoC from artiq.gateware import rtio from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_7series, edge_counter +from artiq.gateware.rtio.xilinx_clocking import RTIOClockMultiplier, fix_serdes_timing_path from artiq.gateware import eem from artiq.gateware.drtio.transceiver import gtp_7series from artiq.gateware.drtio.siphaser import SiPhaser7Series @@ -94,17 +95,6 @@ class SMAClkinForward(Module): ] -def fix_serdes_timing_path(platform): - # ignore timing of path from OSERDESE2 through the pad to ISERDESE2 - platform.add_platform_command( - "set_false_path -quiet " - "-through [get_pins -filter {{REF_PIN_NAME == OQ || REF_PIN_NAME == TQ}} " - "-of [get_cells -filter {{REF_NAME == OSERDESE2}}]] " - "-to [get_pins -filter {{REF_PIN_NAME == D}} " - "-of [get_cells -filter {{REF_NAME == ISERDESE2}}]]" - ) - - class StandaloneBase(MiniSoC, AMPSoC): mem_map = { "cri_con": 0x10000000, @@ -114,9 +104,15 @@ class StandaloneBase(MiniSoC, AMPSoC): } mem_map.update(MiniSoC.mem_map) - def __init__(self, gateware_identifier_str=None, **kwargs): + def __init__(self, gateware_identifier_str=None, hw_rev="v2.0", **kwargs): + if hw_rev in ("v1.0", "v1.1"): + cpu_bus_width = 32 + else: + cpu_bus_width = 64 MiniSoC.__init__(self, - cpu_type="or1k", + cpu_type="vexriscv", + hw_rev=hw_rev, + cpu_bus_width=cpu_bus_width, sdram_controller_type="minicon", l2_size=128*1024, integrated_sram_size=8192, @@ -139,16 +135,16 @@ class StandaloneBase(MiniSoC, AMPSoC): self.config["HAS_SI5324"] = None self.config["SI5324_SOFT_RESET"] = None - def add_rtio(self, rtio_channels): + def add_rtio(self, rtio_channels, sed_lanes=8): self.submodules.rtio_crg = _RTIOCRG(self.platform) self.csr_devices.append("rtio_crg") fix_serdes_timing_path(self.platform) self.submodules.rtio_tsc = rtio.TSC("async", glbl_fine_ts_width=3) - self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels) + self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels, lane_count=sed_lanes) self.csr_devices.append("rtio_core") self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) self.submodules.rtio_dma = ClockDomainsRenamer("sys_kernel")( - rtio.DMA(self.get_native_sdram_if())) + rtio.DMA(self.get_native_sdram_if(), self.cpu_dw)) self.register_kernel_cpu_csrdevice("rtio") self.register_kernel_cpu_csrdevice("rtio_dma") self.submodules.cri_con = rtio.CRIInterconnectShared( @@ -166,7 +162,7 @@ class StandaloneBase(MiniSoC, AMPSoC): self.rtio_crg.cd_rtio.clk) self.submodules.rtio_analyzer = rtio.Analyzer(self.rtio_tsc, self.rtio_core.cri, - self.get_native_sdram_if()) + self.get_native_sdram_if(), cpu_dw=self.cpu_dw) self.csr_devices.append("rtio_analyzer") @@ -232,9 +228,9 @@ class SUServo(StandaloneBase): ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X) # EEM3/2: Sampler, EEM5/4: Urukul, EEM7/6: Urukul - eem.SUServo.add_std( - self, eems_sampler=(3, 2), - eems_urukul0=(5, 4), eems_urukul1=(7, 6)) + eem.SUServo.add_std(self, + eems_sampler=(3, 2), + eems_urukul=[[5, 4], [7, 6]]) for i in (1, 2): sfp_ctl = self.platform.request("sfp_ctl", i) @@ -255,37 +251,6 @@ class SUServo(StandaloneBase): pads.clkout, self.crg.cd_sys.clk) -class _RTIOClockMultiplier(Module, AutoCSR): - def __init__(self, rtio_clk_freq): - self.pll_reset = CSRStorage(reset=1) - self.pll_locked = CSRStatus() - self.clock_domains.cd_rtiox4 = ClockDomain(reset_less=True) - - # See "Global Clock Network Deskew Using Two BUFGs" in ug472. - clkfbout = Signal() - clkfbin = Signal() - rtiox4_clk = Signal() - pll_locked = Signal() - self.specials += [ - Instance("MMCME2_BASE", - p_CLKIN1_PERIOD=1e9/rtio_clk_freq, - i_CLKIN1=ClockSignal("rtio"), - i_RST=self.pll_reset.storage, - o_LOCKED=pll_locked, - - p_CLKFBOUT_MULT_F=8.0, p_DIVCLK_DIVIDE=1, - - o_CLKFBOUT=clkfbout, i_CLKFBIN=clkfbin, - - p_CLKOUT0_DIVIDE_F=2.0, o_CLKOUT0=rtiox4_clk, - ), - Instance("BUFG", i_I=clkfbout, o_O=clkfbin), - Instance("BUFG", i_I=rtiox4_clk, o_O=self.cd_rtiox4.clk), - - MultiReg(pll_locked, self.pll_locked.status) - ] - - class MasterBase(MiniSoC, AMPSoC): mem_map = { "cri_con": 0x10000000, @@ -296,9 +261,15 @@ class MasterBase(MiniSoC, AMPSoC): } mem_map.update(MiniSoC.mem_map) - def __init__(self, rtio_clk_freq=125e6, enable_sata=False, gateware_identifier_str=None, **kwargs): + def __init__(self, rtio_clk_freq=125e6, enable_sata=False, gateware_identifier_str=None, hw_rev="v2.0", **kwargs): + if hw_rev in ("v1.0", "v1.1"): + cpu_bus_width = 32 + else: + cpu_bus_width = 64 MiniSoC.__init__(self, - cpu_type="or1k", + cpu_type="vexriscv", + hw_rev=hw_rev, + cpu_bus_width=cpu_bus_width, sdram_controller_type="minicon", l2_size=128*1024, integrated_sram_size=8192, @@ -374,7 +345,7 @@ class MasterBase(MiniSoC, AMPSoC): self.drtio_cri.append(core.cri) self.csr_devices.append(core_name) - coreaux = cdr(DRTIOAuxController(core.link_layer)) + coreaux = cdr(DRTIOAuxController(core.link_layer, self.cpu_dw)) setattr(self.submodules, coreaux_name, coreaux) self.csr_devices.append(coreaux_name) @@ -400,22 +371,22 @@ class MasterBase(MiniSoC, AMPSoC): platform.add_false_path_constraints( self.crg.cd_sys.clk, gtp.rxoutclk) - self.submodules.rtio_crg = _RTIOClockMultiplier(rtio_clk_freq) + self.submodules.rtio_crg = RTIOClockMultiplier(rtio_clk_freq) self.csr_devices.append("rtio_crg") fix_serdes_timing_path(platform) - def add_rtio(self, rtio_channels): + def add_rtio(self, rtio_channels, sed_lanes=8): # Only add MonInj core if there is anything to monitor if any([len(c.probes) for c in rtio_channels]): self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) self.csr_devices.append("rtio_moninj") - self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels) + self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels, lane_count=sed_lanes) self.csr_devices.append("rtio_core") self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) self.submodules.rtio_dma = ClockDomainsRenamer("sys_kernel")( - rtio.DMA(self.get_native_sdram_if())) + rtio.DMA(self.get_native_sdram_if(), self.cpu_dw)) self.register_kernel_cpu_csrdevice("rtio") self.register_kernel_cpu_csrdevice("rtio_dma") self.submodules.cri_con = rtio.CRIInterconnectShared( @@ -427,7 +398,7 @@ class MasterBase(MiniSoC, AMPSoC): self.csr_devices.append("routing_table") self.submodules.rtio_analyzer = rtio.Analyzer(self.rtio_tsc, self.cri_con.switch.slave, - self.get_native_sdram_if()) + self.get_native_sdram_if(), cpu_dw=self.cpu_dw) self.csr_devices.append("rtio_analyzer") # Never running out of stupid features, GTs on A7 make you pack @@ -472,9 +443,15 @@ class SatelliteBase(BaseSoC): } mem_map.update(BaseSoC.mem_map) - def __init__(self, rtio_clk_freq=125e6, enable_sata=False, *, with_wrpll=False, gateware_identifier_str=None, **kwargs): + def __init__(self, rtio_clk_freq=125e6, enable_sata=False, *, with_wrpll=False, gateware_identifier_str=None, hw_rev="v2.0", **kwargs): + if hw_rev in ("v1.0", "v1.1"): + cpu_bus_width = 32 + else: + cpu_bus_width = 64 BaseSoC.__init__(self, - cpu_type="or1k", + cpu_type="vexriscv", + hw_rev=hw_rev, + cpu_bus_width=cpu_bus_width, sdram_controller_type="minicon", l2_size=128*1024, **kwargs) @@ -562,7 +539,7 @@ class SatelliteBase(BaseSoC): self.drtio_cri.append(core.cri) self.csr_devices.append(corerep_name) - coreaux = cdr(DRTIOAuxController(core.link_layer)) + coreaux = cdr(DRTIOAuxController(core.link_layer, self.cpu_dw)) setattr(self.submodules, coreaux_name, coreaux) self.csr_devices.append(coreaux_name) @@ -627,17 +604,17 @@ class SatelliteBase(BaseSoC): platform.add_false_path_constraints( self.crg.cd_sys.clk, gtp.rxoutclk) - self.submodules.rtio_crg = _RTIOClockMultiplier(rtio_clk_freq) + self.submodules.rtio_crg = RTIOClockMultiplier(rtio_clk_freq) self.csr_devices.append("rtio_crg") fix_serdes_timing_path(platform) - def add_rtio(self, rtio_channels): + def add_rtio(self, rtio_channels, sed_lanes=8): # Only add MonInj core if there is anything to monitor if any([len(c.probes) for c in rtio_channels]): self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) self.csr_devices.append("rtio_moninj") - self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels) + self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels, lane_count=sed_lanes) self.comb += self.drtiosat.async_errors.eq(self.local_io.async_errors) self.submodules.cri_con = rtio.CRIInterconnectShared( [self.drtiosat.cri], diff --git a/artiq/gateware/targets/kasli_generic.py b/artiq/gateware/targets/kasli_generic.py index 85fbdc70d..bb822f41e 100755 --- a/artiq/gateware/targets/kasli_generic.py +++ b/artiq/gateware/targets/kasli_generic.py @@ -48,15 +48,17 @@ class GenericStandalone(StandaloneBase): self.rtio_channels.append(rtio.Channel.from_phy(phy)) if hw_rev == "v2.0": for i in (1, 2): + print("USER LED at RTIO channel 0x{:06x}".format(len(self.rtio_channels))) phy = ttl_simple.Output(self.platform.request("user_led", i)) self.submodules += phy - self.rtio_channels.append(rtio.Channel.from_phy(phy)) + self.rtio_channels.append(rtio.Channel.from_phy(phy)) self.config["HAS_RTIO_LOG"] = None self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels) self.rtio_channels.append(rtio.LogChannel()) - self.add_rtio(self.rtio_channels) + self.add_rtio(self.rtio_channels, sed_lanes=description["sed_lanes"]) + if has_grabber: self.config["HAS_GRABBER"] = None self.add_csr_group("grabber", self.grabber_csr_group) @@ -93,7 +95,7 @@ class GenericMaster(MasterBase): self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels) self.rtio_channels.append(rtio.LogChannel()) - self.add_rtio(self.rtio_channels) + self.add_rtio(self.rtio_channels, sed_lanes=description["sed_lanes"]) if has_grabber: self.config["HAS_GRABBER"] = None self.add_csr_group("grabber", self.grabber_csr_group) @@ -126,7 +128,7 @@ class GenericSatellite(SatelliteBase): self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels) self.rtio_channels.append(rtio.LogChannel()) - self.add_rtio(self.rtio_channels) + self.add_rtio(self.rtio_channels, sed_lanes=description["sed_lanes"]) if has_grabber: self.config["HAS_GRABBER"] = None self.add_csr_group("grabber", self.grabber_csr_group) diff --git a/artiq/gateware/targets/kc705.py b/artiq/gateware/targets/kc705.py index 1481fd351..e04088a92 100755 --- a/artiq/gateware/targets/kc705.py +++ b/artiq/gateware/targets/kc705.py @@ -11,12 +11,17 @@ from migen.build.xilinx.ise import XilinxISEToolchain from misoc.interconnect.csr import * from misoc.cores import gpio, timer -from misoc.targets.kc705 import MiniSoC, soc_kc705_args, soc_kc705_argdict +from misoc.targets.kc705 import BaseSoC, MiniSoC, soc_kc705_args, soc_kc705_argdict from misoc.integration.builder import builder_args, builder_argdict from artiq.gateware.amp import AMPSoC from artiq.gateware import rtio, nist_clock, nist_qc2 from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_7series, dds, spi2 +from artiq.gateware.rtio.xilinx_clocking import RTIOClockMultiplier, fix_serdes_timing_path +from artiq.gateware.drtio.transceiver import gtx_7series +from artiq.gateware.drtio.siphaser import SiPhaser7Series +from artiq.gateware.drtio.rx_synchronizer import XilinxRXSynchronizer +from artiq.gateware.drtio import * from artiq.build_soc import * @@ -78,16 +83,33 @@ class _RTIOCRG(Module, AutoCSR): ] -# The default user SMA voltage on KC705 is 2.5V, and the Migen platform +# The default voltage for these signals on KC705 is 2.5V, and the Migen platform # follows this default. But since the SMAs are on the same bank as the DDS, # which is set to 3.3V by reprogramming the KC705 power ICs, we need to # redefine them here. -_sma33_io = [ +_reprogrammed3v3_io = [ ("user_sma_gpio_p_33", 0, Pins("Y23"), IOStandard("LVCMOS33")), ("user_sma_gpio_n_33", 0, Pins("Y24"), IOStandard("LVCMOS33")), + ("si5324_33", 0, + Subsignal("rst_n", Pins("AE20"), IOStandard("LVCMOS33")), + Subsignal("int", Pins("AG24"), IOStandard("LVCMOS33")) + ), + ("sfp_tx_disable_n_33", 0, Pins("Y20"), IOStandard("LVCMOS33")), + # HACK: this should be LVDS, but TMDS is the only supported differential + # output standard at 3.3V. KC705 hardware design issue? + ("si5324_clkin_33", 0, + Subsignal("p", Pins("W27"), IOStandard("TMDS_33")), + Subsignal("n", Pins("W28"), IOStandard("TMDS_33")) + ), + ("sdcard_spi_33", 0, + Subsignal("miso", Pins("AC20"), Misc("PULLUP=TRUE")), + Subsignal("clk", Pins("AB23")), + Subsignal("mosi", Pins("AB22")), + Subsignal("cs_n", Pins("AC21")), + IOStandard("LVCMOS33") + ) ] - _ams101_dac = [ ("ams101_dac", 0, Subsignal("ldac", Pins("XADC:GPIO0")), @@ -98,18 +120,6 @@ _ams101_dac = [ ) ] -_sdcard_spi_33 = [ - ("sdcard_spi_33", 0, - Subsignal("miso", Pins("AC20"), Misc("PULLUP=TRUE")), - Subsignal("clk", Pins("AB23")), - Subsignal("mosi", Pins("AB22")), - Subsignal("cs_n", Pins("AC21")), - IOStandard("LVCMOS33") - ) -] - - - class _StandaloneBase(MiniSoC, AMPSoC): mem_map = { "cri_con": 0x10000000, @@ -119,9 +129,10 @@ class _StandaloneBase(MiniSoC, AMPSoC): } mem_map.update(MiniSoC.mem_map) - def __init__(self, gateware_identifier_str=None, **kwargs): + def __init__(self, gateware_identifier_str=None, drtio_100mhz=False, **kwargs): MiniSoC.__init__(self, - cpu_type="or1k", + cpu_type="vexriscv", + cpu_bus_width=64, sdram_controller_type="minicon", l2_size=128*1024, integrated_sram_size=8192, @@ -147,9 +158,8 @@ class _StandaloneBase(MiniSoC, AMPSoC): self.platform.request("user_led", 1))) self.csr_devices.append("leds") - self.platform.add_extension(_sma33_io) + self.platform.add_extension(_reprogrammed3v3_io) self.platform.add_extension(_ams101_dac) - self.platform.add_extension(_sdcard_spi_33) i2c = self.platform.request("i2c") self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda]) @@ -167,7 +177,7 @@ class _StandaloneBase(MiniSoC, AMPSoC): self.csr_devices.append("rtio_core") self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) self.submodules.rtio_dma = ClockDomainsRenamer("sys_kernel")( - rtio.DMA(self.get_native_sdram_if())) + rtio.DMA(self.get_native_sdram_if(), self.cpu_dw)) self.register_kernel_cpu_csrdevice("rtio") self.register_kernel_cpu_csrdevice("rtio_dma") self.submodules.cri_con = rtio.CRIInterconnectShared( @@ -183,17 +193,303 @@ class _StandaloneBase(MiniSoC, AMPSoC): self.rtio_crg.cd_rtio.clk) self.submodules.rtio_analyzer = rtio.Analyzer(self.rtio_tsc, self.rtio_core.cri, - self.get_native_sdram_if()) + self.get_native_sdram_if(), cpu_dw=self.cpu_dw) self.csr_devices.append("rtio_analyzer") -class NIST_CLOCK(_StandaloneBase): +class _MasterBase(MiniSoC, AMPSoC): + mem_map = { + "cri_con": 0x10000000, + "rtio": 0x20000000, + "rtio_dma": 0x30000000, + "drtioaux": 0x50000000, + "mailbox": 0x70000000 + } + mem_map.update(MiniSoC.mem_map) + + def __init__(self, gateware_identifier_str=None, drtio_100mhz=False, **kwargs): + MiniSoC.__init__(self, + cpu_type="vexriscv", + cpu_bus_width=64, + sdram_controller_type="minicon", + l2_size=128*1024, + integrated_sram_size=8192, + ethmac_nrxslots=4, + ethmac_ntxslots=4, + **kwargs) + AMPSoC.__init__(self) + add_identifier(self, gateware_identifier_str=gateware_identifier_str) + + if isinstance(self.platform.toolchain, XilinxVivadoToolchain): + self.platform.toolchain.bitstream_commands.extend([ + "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]", + ]) + if isinstance(self.platform.toolchain, XilinxISEToolchain): + self.platform.toolchain.bitgen_opt += " -g compress" + + platform = self.platform + platform.add_extension(_reprogrammed3v3_io) + platform.add_extension(_ams101_dac) + + self.comb += platform.request("sfp_tx_disable_n_33").eq(1) + data_pads = [ + platform.request("sfp"), platform.request("user_sma_mgt") + ] + + rtio_clk_freq = 100e6 if drtio_100mhz else 125e6 + + # 1000BASE_BX10 Ethernet compatible, 100/125MHz RTIO clock + self.submodules.drtio_transceiver = gtx_7series.GTX( + clock_pads=platform.request("si5324_clkout"), + pads=data_pads, + sys_clk_freq=self.clk_freq, + rtio_clk_freq=rtio_clk_freq) + self.csr_devices.append("drtio_transceiver") + + self.submodules.rtio_tsc = rtio.TSC("async", glbl_fine_ts_width=3) + + drtio_csr_group = [] + drtioaux_csr_group = [] + drtioaux_memory_group = [] + self.drtio_cri = [] + for i in range(len(self.drtio_transceiver.channels)): + core_name = "drtio" + str(i) + coreaux_name = "drtioaux" + str(i) + memory_name = "drtioaux" + str(i) + "_mem" + drtio_csr_group.append(core_name) + drtioaux_csr_group.append(coreaux_name) + drtioaux_memory_group.append(memory_name) + + cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)}) + + core = cdr(DRTIOMaster( + self.rtio_tsc, self.drtio_transceiver.channels[i])) + setattr(self.submodules, core_name, core) + self.drtio_cri.append(core.cri) + self.csr_devices.append(core_name) + + coreaux = cdr(DRTIOAuxController(core.link_layer, self.cpu_dw)) + setattr(self.submodules, coreaux_name, coreaux) + self.csr_devices.append(coreaux_name) + + memory_address = self.mem_map["drtioaux"] + 0x800*i + self.add_wb_slave(memory_address, 0x800, + coreaux.bus) + self.add_memory_region(memory_name, memory_address | self.shadow_base, 0x800) + self.config["HAS_DRTIO"] = None + self.config["HAS_DRTIO_ROUTING"] = None + self.add_csr_group("drtio", drtio_csr_group) + self.add_csr_group("drtioaux", drtioaux_csr_group) + self.add_memory_group("drtioaux_mem", drtioaux_memory_group) + + self.config["RTIO_FREQUENCY"] = str(self.drtio_transceiver.rtio_clk_freq/1e6) + self.submodules.si5324_rst_n = gpio.GPIOOut(platform.request("si5324_33").rst_n) + self.csr_devices.append("si5324_rst_n") + i2c = self.platform.request("i2c") + self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda]) + self.csr_devices.append("i2c") + self.config["I2C_BUS_COUNT"] = 1 + self.config["HAS_SI5324"] = None + self.config["SI5324_AS_SYNTHESIZER"] = None + + self.comb += [ + platform.request("user_sma_clock_p").eq(ClockSignal("rtio_rx0")), + platform.request("user_sma_clock_n").eq(ClockSignal("rtio")) + ] + + rtio_clk_period = 1e9/self.drtio_transceiver.rtio_clk_freq + # Constrain TX & RX timing for the first transceiver channel + # (First channel acts as master for phase alignment for all channels' TX) + gtx0 = self.drtio_transceiver.gtxs[0] + platform.add_period_constraint(gtx0.txoutclk, rtio_clk_period) + platform.add_period_constraint(gtx0.rxoutclk, rtio_clk_period) + platform.add_false_path_constraints( + self.crg.cd_sys.clk, + gtx0.txoutclk, gtx0.rxoutclk) + # Constrain RX timing for the each transceiver channel + # (Each channel performs single-lane phase alignment for RX) + for gtx in self.drtio_transceiver.gtxs[1:]: + platform.add_period_constraint(gtx.rxoutclk, rtio_clk_period) + platform.add_false_path_constraints( + self.crg.cd_sys.clk, gtx0.txoutclk, gtx.rxoutclk) + + self.submodules.rtio_crg = RTIOClockMultiplier(self.drtio_transceiver.rtio_clk_freq) + self.csr_devices.append("rtio_crg") + fix_serdes_timing_path(platform) + + def add_rtio(self, rtio_channels): + self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) + self.csr_devices.append("rtio_moninj") + + self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels) + self.csr_devices.append("rtio_core") + + self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) + self.submodules.rtio_dma = ClockDomainsRenamer("sys_kernel")( + rtio.DMA(self.get_native_sdram_if(), self.cpu_dw)) + self.register_kernel_cpu_csrdevice("rtio") + self.register_kernel_cpu_csrdevice("rtio_dma") + self.submodules.cri_con = rtio.CRIInterconnectShared( + [self.rtio.cri, self.rtio_dma.cri], + [self.rtio_core.cri] + self.drtio_cri, + enable_routing=True) + self.register_kernel_cpu_csrdevice("cri_con") + self.submodules.routing_table = rtio.RoutingTableAccess(self.cri_con) + self.csr_devices.append("routing_table") + + +class _SatelliteBase(BaseSoC): + mem_map = { + "drtioaux": 0x50000000, + } + mem_map.update(BaseSoC.mem_map) + + def __init__(self, gateware_identifier_str=None, sma_as_sat=False, drtio_100mhz=False, **kwargs): + BaseSoC.__init__(self, + cpu_type="vexriscv", + cpu_bus_width=64, + sdram_controller_type="minicon", + l2_size=128*1024, + integrated_sram_size=8192, + **kwargs) + add_identifier(self, gateware_identifier_str=gateware_identifier_str) + + if isinstance(self.platform.toolchain, XilinxVivadoToolchain): + self.platform.toolchain.bitstream_commands.extend([ + "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]", + ]) + if isinstance(self.platform.toolchain, XilinxISEToolchain): + self.platform.toolchain.bitgen_opt += " -g compress" + + platform = self.platform + platform.add_extension(_reprogrammed3v3_io) + platform.add_extension(_ams101_dac) + + self.comb += platform.request("sfp_tx_disable_n_33").eq(1) + data_pads = [ + platform.request("sfp"), platform.request("user_sma_mgt") + ] + if sma_as_sat: + data_pads = data_pads[::-1] + + rtio_clk_freq = 100e6 if drtio_100mhz else 125e6 + + # 1000BASE_BX10 Ethernet compatible, 100/125MHz RTIO clock + self.submodules.drtio_transceiver = gtx_7series.GTX( + clock_pads=platform.request("si5324_clkout"), + pads=data_pads, + sys_clk_freq=self.clk_freq, + rtio_clk_freq=rtio_clk_freq) + self.csr_devices.append("drtio_transceiver") + + self.submodules.rtio_tsc = rtio.TSC("sync", glbl_fine_ts_width=3) + + drtioaux_csr_group = [] + drtioaux_memory_group = [] + drtiorep_csr_group = [] + self.drtio_cri = [] + for i in range(len(self.drtio_transceiver.channels)): + coreaux_name = "drtioaux" + str(i) + memory_name = "drtioaux" + str(i) + "_mem" + drtioaux_csr_group.append(coreaux_name) + drtioaux_memory_group.append(memory_name) + + cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)}) + + # Satellite + if i == 0: + self.submodules.rx_synchronizer = cdr(XilinxRXSynchronizer()) + core = cdr(DRTIOSatellite( + self.rtio_tsc, self.drtio_transceiver.channels[0], self.rx_synchronizer)) + self.submodules.drtiosat = core + self.csr_devices.append("drtiosat") + # Repeaters + else: + corerep_name = "drtiorep" + str(i-1) + drtiorep_csr_group.append(corerep_name) + core = cdr(DRTIORepeater( + self.rtio_tsc, self.drtio_transceiver.channels[i])) + setattr(self.submodules, corerep_name, core) + self.drtio_cri.append(core.cri) + self.csr_devices.append(corerep_name) + + coreaux = cdr(DRTIOAuxController(core.link_layer, self.cpu_dw)) + setattr(self.submodules, coreaux_name, coreaux) + self.csr_devices.append(coreaux_name) + + memory_address = self.mem_map["drtioaux"] + 0x800*i + self.add_wb_slave(memory_address, 0x800, + coreaux.bus) + self.add_memory_region(memory_name, memory_address | self.shadow_base, 0x800) + self.config["HAS_DRTIO"] = None + self.config["HAS_DRTIO_ROUTING"] = None + 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) + + self.config["RTIO_FREQUENCY"] = str(self.drtio_transceiver.rtio_clk_freq/1e6) + # Si5324 Phaser + self.submodules.siphaser = SiPhaser7Series( + si5324_clkin=platform.request("si5324_clkin_33"), + rx_synchronizer=self.rx_synchronizer, + ultrascale=False, + rtio_clk_freq=self.drtio_transceiver.rtio_clk_freq) + platform.add_false_path_constraints( + self.crg.cd_sys.clk, self.siphaser.mmcm_freerun_output) + self.csr_devices.append("siphaser") + self.submodules.si5324_rst_n = gpio.GPIOOut(platform.request("si5324_33").rst_n) + self.csr_devices.append("si5324_rst_n") + i2c = self.platform.request("i2c") + self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda]) + self.csr_devices.append("i2c") + self.config["I2C_BUS_COUNT"] = 1 + self.config["HAS_SI5324"] = None + + self.comb += [ + platform.request("user_sma_clock_p").eq(ClockSignal("rtio_rx0")), + platform.request("user_sma_clock_n").eq(ClockSignal("rtio")) + ] + + rtio_clk_period = 1e9/self.drtio_transceiver.rtio_clk_freq + # Constrain TX & RX timing for the first transceiver channel + # (First channel acts as master for phase alignment for all channels' TX) + gtx0 = self.drtio_transceiver.gtxs[0] + platform.add_period_constraint(gtx0.txoutclk, rtio_clk_period) + platform.add_period_constraint(gtx0.rxoutclk, rtio_clk_period) + platform.add_false_path_constraints( + self.crg.cd_sys.clk, + gtx0.txoutclk, gtx0.rxoutclk) + # Constrain RX timing for the each transceiver channel + # (Each channel performs single-lane phase alignment for RX) + for gtx in self.drtio_transceiver.gtxs[1:]: + platform.add_period_constraint(gtx.rxoutclk, rtio_clk_period) + platform.add_false_path_constraints( + self.crg.cd_sys.clk, gtx.rxoutclk) + + self.submodules.rtio_crg = RTIOClockMultiplier(self.drtio_transceiver.rtio_clk_freq) + self.csr_devices.append("rtio_crg") + fix_serdes_timing_path(platform) + + def add_rtio(self, rtio_channels): + self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) + self.csr_devices.append("rtio_moninj") + + self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels) + self.comb += self.drtiosat.async_errors.eq(self.local_io.async_errors) + self.submodules.cri_con = rtio.CRIInterconnectShared( + [self.drtiosat.cri], + [self.local_io.cri] + self.drtio_cri, + mode="sync", enable_routing=True) + self.csr_devices.append("cri_con") + self.submodules.routing_table = rtio.RoutingTableAccess(self.cri_con) + self.csr_devices.append("routing_table") + + +class _NIST_CLOCK_RTIO: """ NIST clock hardware, with old backplane and 11 DDS channels """ - def __init__(self, **kwargs): - _StandaloneBase.__init__(self, **kwargs) - + def __init__(self): platform = self.platform platform.add_extension(nist_clock.fmc_adapter_io) @@ -257,14 +553,12 @@ class NIST_CLOCK(_StandaloneBase): self.add_rtio(rtio_channels) -class NIST_QC2(_StandaloneBase): +class _NIST_QC2_RTIO: """ NIST QC2 hardware, as used in Quantum I and Quantum II, with new backplane and 24 DDS channels. Two backplanes are used. """ - def __init__(self, **kwargs): - _StandaloneBase.__init__(self, **kwargs) - + def __init__(self): platform = self.platform platform.add_extension(nist_qc2.fmc_adapter_io) @@ -327,97 +621,66 @@ class NIST_QC2(_StandaloneBase): self.add_rtio(rtio_channels) -_sma_spi = [ - ("sma_spi", 0, - Subsignal("clk", Pins("Y23")), # user_sma_gpio_p - Subsignal("cs_n", Pins("Y24")), # user_sma_gpio_n - Subsignal("mosi", Pins("L25")), # user_sma_clk_p - Subsignal("miso", Pins("K25")), # user_sma_clk_n - IOStandard("LVCMOS25")), -] - - -class SMA_SPI(_StandaloneBase): - """ - SPI on 4 SMA for PDQ2 test/demo. - """ +class NIST_CLOCK(_StandaloneBase, _NIST_CLOCK_RTIO): def __init__(self, **kwargs): _StandaloneBase.__init__(self, **kwargs) - - platform = self.platform - self.platform.add_extension(_sma_spi) - - rtio_channels = [] - - phy = ttl_simple.Output(platform.request("user_led", 2)) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy)) - - ams101_dac = self.platform.request("ams101_dac", 0) - phy = ttl_simple.Output(ams101_dac.ldac) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy)) - - phy = spi2.SPIMaster(ams101_dac) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy( - phy, ififo_depth=4)) - - phy = spi2.SPIMaster(self.platform.request("sma_spi")) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy( - phy, ififo_depth=128)) - - self.config["HAS_RTIO_LOG"] = None - self.config["RTIO_LOG_CHANNEL"] = len(rtio_channels) - rtio_channels.append(rtio.LogChannel()) - - self.add_rtio(rtio_channels) - - def add_rtio(self, rtio_channels): - self.submodules.rtio_crg = _RTIOCRG(self.platform, self.crg.cd_sys.clk, - use_sma=False) - self.csr_devices.append("rtio_crg") - self.config["HAS_RTIO_CLOCK_SWITCH"] = None - self.submodules.rtio_tsc = rtio.TSC("async", glbl_fine_ts_width=3) - self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels) - self.csr_devices.append("rtio_core") - self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) - self.submodules.rtio_dma = ClockDomainsRenamer("sys_kernel")( - rtio.DMA(self.get_native_sdram_if())) - self.register_kernel_cpu_csrdevice("rtio") - self.register_kernel_cpu_csrdevice("rtio_dma") - self.submodules.cri_con = rtio.CRIInterconnectShared( - [self.rtio.cri, self.rtio_dma.cri], - [self.rtio_core.cri]) - self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) - self.csr_devices.append("rtio_moninj") - - self.platform.add_period_constraint(self.rtio_crg.cd_rtio.clk, 8.) - self.platform.add_false_path_constraints( - self.crg.cd_sys.clk, - self.rtio_crg.cd_rtio.clk) - - self.submodules.rtio_analyzer = rtio.Analyzer(self.rtio_tsc, self.rtio_core.cri, - self.get_native_sdram_if()) - self.csr_devices.append("rtio_analyzer") + _NIST_CLOCK_RTIO.__init__(self) -VARIANTS = {cls.__name__.lower(): cls for cls in [NIST_CLOCK, NIST_QC2, SMA_SPI]} +class NIST_QC2(_StandaloneBase, _NIST_QC2_RTIO): + def __init__(self, **kwargs): + _StandaloneBase.__init__(self, **kwargs) + _NIST_QC2_RTIO.__init__(self) + + +class NIST_CLOCK_Master(_MasterBase, _NIST_CLOCK_RTIO): + def __init__(self, **kwargs): + _MasterBase.__init__(self, **kwargs) + _NIST_CLOCK_RTIO.__init__(self) + + +class NIST_QC2_Master(_MasterBase, _NIST_QC2_RTIO): + def __init__(self, **kwargs): + _MasterBase.__init__(self, **kwargs) + _NIST_QC2_RTIO.__init__(self) + + +class NIST_CLOCK_Satellite(_SatelliteBase, _NIST_CLOCK_RTIO): + def __init__(self, **kwargs): + _SatelliteBase.__init__(self, **kwargs) + _NIST_CLOCK_RTIO.__init__(self) + + +class NIST_QC2_Satellite(_SatelliteBase, _NIST_QC2_RTIO): + def __init__(self, **kwargs): + _SatelliteBase.__init__(self, **kwargs) + _NIST_QC2_RTIO.__init__(self) + + +VARIANT_CLS = [ + NIST_CLOCK, NIST_QC2, + NIST_CLOCK_Master, NIST_QC2_Master, + NIST_CLOCK_Satellite, NIST_QC2_Satellite, +] +VARIANTS = {cls.__name__.lower(): cls for cls in VARIANT_CLS} def main(): parser = argparse.ArgumentParser( - description="KC705 gateware and firmware builder") + description="ARTIQ device binary builder for KC705 systems") builder_args(parser) soc_kc705_args(parser) parser.set_defaults(output_dir="artiq_kc705") parser.add_argument("-V", "--variant", default="nist_clock", help="variant: " - "nist_clock/nist_qc2/sma_spi " + "[standalone: nist_clock/nist_qc2] " + "[DRTIO master: nist_clock_master/nist_qc2_master] " + "[DRTIO satellite: nist_clock_satellite/nist_qc2_satellite] " "(default: %(default)s)") parser.add_argument("--gateware-identifier-str", default=None, help="Override ROM identifier") + parser.add_argument("--drtio100mhz", action="store_true", default=False, + help="DRTIO systems only - use 100MHz RTIO clock") args = parser.parse_args() variant = args.variant.lower() @@ -426,7 +689,7 @@ def main(): except KeyError: raise SystemExit("Invalid variant (-V/--variant)") - soc = cls(gateware_identifier_str=args.gateware_identifier_str, **soc_kc705_argdict(args)) + soc = cls(gateware_identifier_str=args.gateware_identifier_str, drtio_100mhz=args.drtio100mhz, **soc_kc705_argdict(args)) build_artiq_soc(soc, builder_argdict(args)) diff --git a/artiq/gateware/targets/kc705_drtio_master.py b/artiq/gateware/targets/kc705_drtio_master.py deleted file mode 100755 index 78d5d054b..000000000 --- a/artiq/gateware/targets/kc705_drtio_master.py +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/env python3 - -import argparse - -from migen import * -from migen.build.generic_platform import * -from migen.build.xilinx.vivado import XilinxVivadoToolchain -from migen.build.xilinx.ise import XilinxISEToolchain - -from misoc.cores import spi as spi_csr -from misoc.cores import gpio -from misoc.integration.builder import * -from misoc.targets.kc705 import MiniSoC, soc_kc705_args, soc_kc705_argdict - -from artiq.gateware.amp import AMPSoC -from artiq.gateware import rtio -from artiq.gateware.rtio.phy import ttl_simple -from artiq.gateware.drtio.transceiver import gtx_7series -from artiq.gateware.drtio import * -from artiq.build_soc import * - - -class Master(MiniSoC, AMPSoC): - mem_map = { - "cri_con": 0x10000000, - "rtio": 0x20000000, - "rtio_dma": 0x30000000, - "drtioaux": 0x50000000, - "mailbox": 0x70000000 - } - mem_map.update(MiniSoC.mem_map) - - def __init__(self, gateware_identifier_str=None, **kwargs): - MiniSoC.__init__(self, - cpu_type="or1k", - sdram_controller_type="minicon", - l2_size=128*1024, - integrated_sram_size=8192, - ethmac_nrxslots=4, - ethmac_ntxslots=4, - **kwargs) - AMPSoC.__init__(self) - add_identifier(self, gateware_identifier_str=gateware_identifier_str) - - if isinstance(self.platform.toolchain, XilinxVivadoToolchain): - self.platform.toolchain.bitstream_commands.extend([ - "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]", - ]) - if isinstance(self.platform.toolchain, XilinxISEToolchain): - self.platform.toolchain.bitgen_opt += " -g compress" - - platform = self.platform - - self.comb += platform.request("sfp_tx_disable_n").eq(1) - tx_pads = [ - platform.request("sfp_tx"), platform.request("user_sma_mgt_tx") - ] - rx_pads = [ - platform.request("sfp_rx"), platform.request("user_sma_mgt_rx") - ] - - # 1000BASE_BX10 Ethernet compatible, 125MHz RTIO clock - self.submodules.drtio_transceiver = gtx_7series.GTX( - clock_pads=platform.request("si5324_clkout"), - tx_pads=tx_pads, - rx_pads=rx_pads, - sys_clk_freq=self.clk_freq) - self.csr_devices.append("drtio_transceiver") - - self.submodules.rtio_tsc = rtio.TSC("async", glbl_fine_ts_width=3) - - drtio_csr_group = [] - drtioaux_csr_group = [] - drtioaux_memory_group = [] - drtio_cri = [] - for i in range(len(self.drtio_transceiver.channels)): - core_name = "drtio" + str(i) - coreaux_name = "drtioaux" + str(i) - memory_name = "drtioaux" + str(i) + "_mem" - drtio_csr_group.append(core_name) - drtioaux_csr_group.append(coreaux_name) - drtioaux_memory_group.append(memory_name) - - cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)}) - - core = cdr(DRTIOMaster( - self.rtio_tsc, self.drtio_transceiver.channels[i])) - setattr(self.submodules, core_name, core) - drtio_cri.append(core.cri) - self.csr_devices.append(core_name) - - coreaux = cdr(DRTIOAuxController(core.link_layer)) - setattr(self.submodules, coreaux_name, coreaux) - self.csr_devices.append(coreaux_name) - - memory_address = self.mem_map["drtioaux"] + 0x800*i - self.add_wb_slave(memory_address, 0x800, - coreaux.bus) - self.add_memory_region(memory_name, memory_address | self.shadow_base, 0x800) - self.config["HAS_DRTIO"] = None - self.config["HAS_DRTIO_ROUTING"] = None - self.add_csr_group("drtio", drtio_csr_group) - self.add_csr_group("drtioaux", drtioaux_csr_group) - self.add_memory_group("drtioaux_mem", drtioaux_memory_group) - - self.config["RTIO_FREQUENCY"] = str(self.drtio_transceiver.rtio_clk_freq/1e6) - self.submodules.si5324_rst_n = gpio.GPIOOut(platform.request("si5324").rst_n) - self.csr_devices.append("si5324_rst_n") - i2c = self.platform.request("i2c") - self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda]) - self.csr_devices.append("i2c") - self.config["I2C_BUS_COUNT"] = 1 - self.config["HAS_SI5324"] = None - self.config["SI5324_AS_SYNTHESIZER"] = None - - self.comb += [ - platform.request("user_sma_clock_p").eq(ClockSignal("rtio_rx0")), - platform.request("user_sma_clock_n").eq(ClockSignal("rtio")) - ] - - rtio_clk_period = 1e9/self.drtio_transceiver.rtio_clk_freq - # Constrain TX & RX timing for the first transceiver channel - # (First channel acts as master for phase alignment for all channels' TX) - gtx0 = self.drtio_transceiver.gtxs[0] - platform.add_period_constraint(gtx0.txoutclk, rtio_clk_period) - platform.add_period_constraint(gtx0.rxoutclk, rtio_clk_period) - platform.add_false_path_constraints( - self.crg.cd_sys.clk, - gtx0.txoutclk, gtx0.rxoutclk) - # Constrain RX timing for the each transceiver channel - # (Each channel performs single-lane phase alignment for RX) - for gtx in self.drtio_transceiver.gtxs[1:]: - platform.add_period_constraint(gtx.rxoutclk, rtio_clk_period) - platform.add_false_path_constraints( - self.crg.cd_sys.clk, gtx0.txoutclk, gtx.rxoutclk) - - rtio_channels = [] - for i in range(8): - phy = ttl_simple.Output(platform.request("user_led", i)) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy)) - for sma in "user_sma_gpio_p", "user_sma_gpio_n": - phy = ttl_simple.InOut(platform.request(sma)) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy)) - - self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) - self.csr_devices.append("rtio_moninj") - - self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels) - self.csr_devices.append("rtio_core") - - self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) - self.submodules.rtio_dma = ClockDomainsRenamer("sys_kernel")( - rtio.DMA(self.get_native_sdram_if())) - self.register_kernel_cpu_csrdevice("rtio") - self.register_kernel_cpu_csrdevice("rtio_dma") - self.submodules.cri_con = rtio.CRIInterconnectShared( - [self.rtio.cri, self.rtio_dma.cri], - [self.rtio_core.cri] + drtio_cri, - enable_routing=True) - self.register_kernel_cpu_csrdevice("cri_con") - self.submodules.routing_table = rtio.RoutingTableAccess(self.cri_con) - self.csr_devices.append("routing_table") - - -def main(): - parser = argparse.ArgumentParser( - description="ARTIQ device binary builder / KC705 DRTIO master") - builder_args(parser) - soc_kc705_args(parser) - parser.set_defaults(output_dir="artiq_kc705/master") - args = parser.parse_args() - - soc = Master(**soc_kc705_argdict(args)) - build_artiq_soc(soc, builder_argdict(args)) - - -if __name__ == "__main__": - main() diff --git a/artiq/gateware/targets/kc705_drtio_satellite.py b/artiq/gateware/targets/kc705_drtio_satellite.py deleted file mode 100755 index 4ff346fdf..000000000 --- a/artiq/gateware/targets/kc705_drtio_satellite.py +++ /dev/null @@ -1,194 +0,0 @@ -#!/usr/bin/env python3 - -import argparse - -from migen import * -from migen.build.generic_platform import * -from migen.build.xilinx.vivado import XilinxVivadoToolchain -from migen.build.xilinx.ise import XilinxISEToolchain - -from misoc.cores import spi as spi_csr -from misoc.cores import gpio -from misoc.integration.builder import * -from misoc.targets.kc705 import BaseSoC, soc_kc705_args, soc_kc705_argdict - -from artiq.gateware import rtio -from artiq.gateware.rtio.phy import ttl_simple -from artiq.gateware.drtio.transceiver import gtx_7series -from artiq.gateware.drtio.siphaser import SiPhaser7Series -from artiq.gateware.drtio.rx_synchronizer import XilinxRXSynchronizer -from artiq.gateware.drtio import * -from artiq.build_soc import * - - -class Satellite(BaseSoC): - mem_map = { - "drtioaux": 0x50000000, - } - mem_map.update(BaseSoC.mem_map) - - def __init__(self, gateware_identifier_str=None, sma_as_sat=False, **kwargs): - BaseSoC.__init__(self, - cpu_type="or1k", - sdram_controller_type="minicon", - l2_size=128*1024, - integrated_sram_size=8192, - **kwargs) - add_identifier(self, gateware_identifier_str=gateware_identifier_str) - - if isinstance(self.platform.toolchain, XilinxVivadoToolchain): - self.platform.toolchain.bitstream_commands.extend([ - "set_property BITSTREAM.GENERAL.COMPRESS True [current_design]", - ]) - if isinstance(self.platform.toolchain, XilinxISEToolchain): - self.platform.toolchain.bitgen_opt += " -g compress" - - platform = self.platform - - self.comb += platform.request("sfp_tx_disable_n").eq(1) - tx_pads = [ - platform.request("sfp_tx"), platform.request("user_sma_mgt_tx") - ] - rx_pads = [ - platform.request("sfp_rx"), platform.request("user_sma_mgt_rx") - ] - if sma_as_sat: - tx_pads = tx_pads[::-1] - rx_pads = rx_pads[::-1] - - # 1000BASE_BX10 Ethernet compatible, 125MHz RTIO clock - self.submodules.drtio_transceiver = gtx_7series.GTX( - clock_pads=platform.request("si5324_clkout"), - tx_pads=tx_pads, - rx_pads=rx_pads, - sys_clk_freq=self.clk_freq) - self.csr_devices.append("drtio_transceiver") - - self.submodules.rtio_tsc = rtio.TSC("sync", glbl_fine_ts_width=3) - - drtioaux_csr_group = [] - drtioaux_memory_group = [] - drtiorep_csr_group = [] - self.drtio_cri = [] - for i in range(len(self.drtio_transceiver.channels)): - coreaux_name = "drtioaux" + str(i) - memory_name = "drtioaux" + str(i) + "_mem" - drtioaux_csr_group.append(coreaux_name) - drtioaux_memory_group.append(memory_name) - - cdr = ClockDomainsRenamer({"rtio_rx": "rtio_rx" + str(i)}) - - # Satellite - if i == 0: - self.submodules.rx_synchronizer = cdr(XilinxRXSynchronizer()) - core = cdr(DRTIOSatellite( - self.rtio_tsc, self.drtio_transceiver.channels[0], self.rx_synchronizer)) - self.submodules.drtiosat = core - self.csr_devices.append("drtiosat") - # Repeaters - else: - corerep_name = "drtiorep" + str(i-1) - drtiorep_csr_group.append(corerep_name) - core = cdr(DRTIORepeater( - self.rtio_tsc, self.drtio_transceiver.channels[i])) - setattr(self.submodules, corerep_name, core) - self.drtio_cri.append(core.cri) - self.csr_devices.append(corerep_name) - - coreaux = cdr(DRTIOAuxController(core.link_layer)) - setattr(self.submodules, coreaux_name, coreaux) - self.csr_devices.append(coreaux_name) - - memory_address = self.mem_map["drtioaux"] + 0x800*i - self.add_wb_slave(memory_address, 0x800, - coreaux.bus) - self.add_memory_region(memory_name, memory_address | self.shadow_base, 0x800) - self.config["HAS_DRTIO"] = None - self.config["HAS_DRTIO_ROUTING"] = None - 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) - - self.config["RTIO_FREQUENCY"] = str(self.drtio_transceiver.rtio_clk_freq/1e6) - # Si5324 Phaser - self.submodules.siphaser = SiPhaser7Series( - si5324_clkin=platform.request("si5324_clkin"), - rx_synchronizer=self.rx_synchronizer, - ultrascale=False, - rtio_clk_freq=self.drtio_transceiver.rtio_clk_freq) - platform.add_false_path_constraints( - self.crg.cd_sys.clk, self.siphaser.mmcm_freerun_output) - self.csr_devices.append("siphaser") - self.submodules.si5324_rst_n = gpio.GPIOOut(platform.request("si5324").rst_n) - self.csr_devices.append("si5324_rst_n") - i2c = self.platform.request("i2c") - self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda]) - self.csr_devices.append("i2c") - self.config["I2C_BUS_COUNT"] = 1 - self.config["HAS_SI5324"] = None - - self.comb += [ - platform.request("user_sma_clock_p").eq(ClockSignal("rtio_rx0")), - platform.request("user_sma_clock_n").eq(ClockSignal("rtio")) - ] - - rtio_clk_period = 1e9/self.drtio_transceiver.rtio_clk_freq - # Constrain TX & RX timing for the first transceiver channel - # (First channel acts as master for phase alignment for all channels' TX) - gtx0 = self.drtio_transceiver.gtxs[0] - platform.add_period_constraint(gtx0.txoutclk, rtio_clk_period) - platform.add_period_constraint(gtx0.rxoutclk, rtio_clk_period) - platform.add_false_path_constraints( - self.crg.cd_sys.clk, - gtx0.txoutclk, gtx0.rxoutclk) - # Constrain RX timing for the each transceiver channel - # (Each channel performs single-lane phase alignment for RX) - for gtx in self.drtio_transceiver.gtxs[1:]: - platform.add_period_constraint(gtx.rxoutclk, rtio_clk_period) - platform.add_false_path_constraints( - self.crg.cd_sys.clk, gtx.rxoutclk) - - rtio_channels = [] - for i in range(8): - phy = ttl_simple.Output(platform.request("user_led", i)) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy)) - for sma in "user_sma_gpio_p", "user_sma_gpio_n": - phy = ttl_simple.InOut(platform.request(sma)) - self.submodules += phy - rtio_channels.append(rtio.Channel.from_phy(phy)) - - self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) - self.csr_devices.append("rtio_moninj") - - self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels) - self.comb += self.drtiosat.async_errors.eq(self.local_io.async_errors) - self.submodules.cri_con = rtio.CRIInterconnectShared( - [self.drtiosat.cri], - [self.local_io.cri] + self.drtio_cri, - mode="sync", enable_routing=True) - self.csr_devices.append("cri_con") - self.submodules.routing_table = rtio.RoutingTableAccess(self.cri_con) - self.csr_devices.append("routing_table") - - -def main(): - parser = argparse.ArgumentParser( - description="ARTIQ device binary builder / KC705 DRTIO satellite") - builder_args(parser) - soc_kc705_args(parser) - parser.set_defaults(output_dir="artiq_kc705/satellite") - parser.add_argument("--sma", default=False, action="store_true", - help="use the SMA connectors (RX: J17, J18, TX: J19, J20) " - "as DRTIO satellite channel instead of the SFP") - args = parser.parse_args() - - argdict = dict() - argdict["sma_as_sat"] = args.sma - - soc = Satellite(**soc_kc705_argdict(args), **argdict) - build_artiq_soc(soc, builder_argdict(args)) - - -if __name__ == "__main__": - main() diff --git a/artiq/gateware/targets/metlino.py b/artiq/gateware/targets/metlino.py index 4f609e1c7..ffb2b38b9 100755 --- a/artiq/gateware/targets/metlino.py +++ b/artiq/gateware/targets/metlino.py @@ -42,7 +42,8 @@ class Master(MiniSoC, AMPSoC): def __init__(self, gateware_identifier_str=None, **kwargs): MiniSoC.__init__(self, - cpu_type="or1k", + cpu_type="vexriscv", + cpu_bus_width=64, sdram_controller_type="minicon", l2_size=128*1024, integrated_sram_size=8192, @@ -96,7 +97,7 @@ class Master(MiniSoC, AMPSoC): drtio_cri.append(core.cri) self.csr_devices.append(core_name) - coreaux = cdr(DRTIOAuxController(core.link_layer)) + coreaux = cdr(DRTIOAuxController(core.link_layer, self.cpu_dw)) setattr(self.submodules, coreaux_name, coreaux) self.csr_devices.append(coreaux_name) @@ -149,7 +150,7 @@ class Master(MiniSoC, AMPSoC): self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) self.submodules.rtio_dma = ClockDomainsRenamer("sys_kernel")( - rtio.DMA(self.get_native_sdram_if())) + rtio.DMA(self.get_native_sdram_if(), self.cpu_dw)) self.register_kernel_cpu_csrdevice("rtio") self.register_kernel_cpu_csrdevice("rtio_dma") self.submodules.cri_con = rtio.CRIInterconnectShared( diff --git a/artiq/gateware/targets/sayma_amc.py b/artiq/gateware/targets/sayma_amc.py index f46942e67..527d37b5c 100755 --- a/artiq/gateware/targets/sayma_amc.py +++ b/artiq/gateware/targets/sayma_amc.py @@ -56,7 +56,8 @@ class SatelliteBase(MiniSoC): def __init__(self, rtio_clk_freq=125e6, identifier_suffix="", gateware_identifier_str=None, with_sfp=False, *, with_wrpll, **kwargs): MiniSoC.__init__(self, - cpu_type="or1k", + cpu_type="vexriscv", + cpu_bus_width=64, sdram_controller_type="minicon", l2_size=128*1024, integrated_sram_size=8192, @@ -117,7 +118,7 @@ class SatelliteBase(MiniSoC): self.drtio_cri.append(core.cri) self.csr_devices.append(corerep_name) - coreaux = cdr(DRTIOAuxController(core.link_layer)) + coreaux = cdr(DRTIOAuxController(core.link_layer, self.cpu_dw)) setattr(self.submodules, coreaux_name, coreaux) self.csr_devices.append(coreaux_name) diff --git a/artiq/gateware/targets/sayma_rtm.py b/artiq/gateware/targets/sayma_rtm.py index 294a17823..9d8a818b4 100755 --- a/artiq/gateware/targets/sayma_rtm.py +++ b/artiq/gateware/targets/sayma_rtm.py @@ -18,6 +18,7 @@ from misoc.integration.builder import Builder, builder_args, builder_argdict from artiq.gateware import rtio from artiq.gateware import jesd204_tools from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_7series +from artiq.gateware.rtio.xilinx_clocking import RTIOClockMultiplier, fix_serdes_timing_path from artiq.gateware.drtio.transceiver import gtp_7series from artiq.gateware.drtio.siphaser import SiPhaser7Series from artiq.gateware.drtio.wrpll import WRPLL, DDMTDSamplerGTP @@ -27,48 +28,6 @@ from artiq.build_soc import add_identifier from artiq import __artiq_dir__ as artiq_dir -def fix_serdes_timing_path(platform): - # ignore timing of path from OSERDESE2 through the pad to ISERDESE2 - platform.add_platform_command( - "set_false_path -quiet " - "-through [get_pins -filter {{REF_PIN_NAME == OQ || REF_PIN_NAME == TQ}} " - "-of [get_cells -filter {{REF_NAME == OSERDESE2}}]] " - "-to [get_pins -filter {{REF_PIN_NAME == D}} " - "-of [get_cells -filter {{REF_NAME == ISERDESE2}}]]" - ) - - -class _RTIOClockMultiplier(Module, AutoCSR): - def __init__(self, rtio_clk_freq): - self.pll_reset = CSRStorage(reset=1) - self.pll_locked = CSRStatus() - self.clock_domains.cd_rtiox4 = ClockDomain(reset_less=True) - - # See "Global Clock Network Deskew Using Two BUFGs" in ug472. - clkfbout = Signal() - clkfbin = Signal() - rtiox4_clk = Signal() - pll_locked = Signal() - self.specials += [ - Instance("MMCME2_BASE", - p_CLKIN1_PERIOD=1e9/rtio_clk_freq, - i_CLKIN1=ClockSignal("rtio"), - i_RST=self.pll_reset.storage, - o_LOCKED=pll_locked, - - p_CLKFBOUT_MULT_F=8.0, p_DIVCLK_DIVIDE=1, - - o_CLKFBOUT=clkfbout, i_CLKFBIN=clkfbin, - - p_CLKOUT0_DIVIDE_F=2.0, o_CLKOUT0=rtiox4_clk, - ), - Instance("BUFG", i_I=clkfbout, o_O=clkfbin), - Instance("BUFG", i_I=rtiox4_clk, o_O=self.cd_rtiox4.clk), - - MultiReg(pll_locked, self.pll_locked.status) - ] - - class _SatelliteBase(BaseSoC): mem_map = { "drtioaux": 0x50000000, @@ -77,7 +36,8 @@ class _SatelliteBase(BaseSoC): def __init__(self, rtio_clk_freq, *, with_wrpll, gateware_identifier_str, **kwargs): BaseSoC.__init__(self, - cpu_type="or1k", + cpu_type="vexriscv", + cpu_bus_width=64, **kwargs) add_identifier(self, gateware_identifier_str=gateware_identifier_str) self.rtio_clk_freq = rtio_clk_freq @@ -120,7 +80,7 @@ class _SatelliteBase(BaseSoC): self.submodules.drtiosat = core self.csr_devices.append("drtiosat") - coreaux = cdr(DRTIOAuxController(core.link_layer)) + coreaux = cdr(DRTIOAuxController(core.link_layer, self.cpu_dw)) self.submodules.drtioaux0 = coreaux self.csr_devices.append("drtioaux0") @@ -178,7 +138,7 @@ class _SatelliteBase(BaseSoC): self.crg.cd_sys.clk, gtp.txoutclk, gtp.rxoutclk) - self.submodules.rtio_crg = _RTIOClockMultiplier(rtio_clk_freq) + self.submodules.rtio_crg = RTIOClockMultiplier(rtio_clk_freq) self.csr_devices.append("rtio_crg") fix_serdes_timing_path(platform) @@ -281,7 +241,7 @@ class SatmanSoCBuilder(Builder): "satman.bin") with open(satman, "rb") as boot_file: boot_data = [] - unpack_endian = ">I" + unpack_endian = "`_ for a list of valid options. """ - if save is not None: - warnings.warn("set_dataset save parameter is deprecated, " - "use archive instead", FutureWarning) - archive = save - + self.__dataset_mgr.set( key, value, broadcast, persist, archive, hdf5_options ) diff --git a/artiq/master/experiments.py b/artiq/master/experiments.py index b9a46d7ba..918c92dc8 100644 --- a/artiq/master/experiments.py +++ b/artiq/master/experiments.py @@ -74,19 +74,20 @@ class _RepoScanner: entry_dict.update(entries) return entry_dict - async def scan(self, root): + async def scan(self, root, subdir=""): self.worker = Worker(self.worker_handlers) try: - r = await self._scan(root) + r = await self._scan(root, subdir) finally: await self.worker.close() return r class ExperimentDB: - def __init__(self, repo_backend, worker_handlers): + def __init__(self, repo_backend, worker_handlers, experiment_subdir=""): self.repo_backend = repo_backend self.worker_handlers = worker_handlers + self.experiment_subdir = experiment_subdir self.cur_rev = self.repo_backend.get_head_rev() self.repo_backend.request_rev(self.cur_rev) @@ -115,7 +116,8 @@ class ExperimentDB: self.cur_rev = new_cur_rev self.status["cur_rev"] = new_cur_rev t1 = time.monotonic() - new_explist = await _RepoScanner(self.worker_handlers).scan(wd) + new_explist = await _RepoScanner(self.worker_handlers).scan( + wd, self.experiment_subdir) logger.info("repository scan took %d seconds", time.monotonic()-t1) update_from_dict(self.explist, new_explist) finally: diff --git a/artiq/test/coredevice/test_embedding.py b/artiq/test/coredevice/test_embedding.py index cd8d4daab..8f5e4e3bc 100644 --- a/artiq/test/coredevice/test_embedding.py +++ b/artiq/test/coredevice/test_embedding.py @@ -261,19 +261,7 @@ class _RPCCalls(EnvExperiment): @kernel def numpy_things(self): - return (numpy.int32(10), numpy.int64(20), numpy.array([42,])) - - @kernel - def numpy_full(self): - return numpy.full(10, 20) - - @kernel - def numpy_full_matrix(self): - return numpy.full((3, 2), 13) - - @kernel - def numpy_nan(self): - return numpy.full(10, numpy.nan) + return (numpy.int32(10), numpy.int64(20)) @kernel def builtin(self): @@ -302,14 +290,11 @@ class RPCCallsTest(ExperimentCase): self.assertEqual(exp.kwargs2(), 2) self.assertEqual(exp.args1kwargs2(), 2) self.assertEqual(exp.numpy_things(), - (numpy.int32(10), numpy.int64(20), numpy.array([42,]))) + (numpy.int32(10), numpy.int64(20))) # Ensure lists of int64s don't decay to variable-length builtin integers. list_int64 = exp.list_int64() self.assertEqual(list_int64, [numpy.int64(1)]) self.assertTrue(isinstance(list_int64[0], numpy.int64)) - self.assertTrue((exp.numpy_full() == numpy.full(10, 20)).all()) - self.assertTrue((exp.numpy_full_matrix() == numpy.full((3, 2), 13)).all()) - self.assertTrue(numpy.isnan(exp.numpy_nan()).all()) exp.builtin() exp.async_in_try() diff --git a/artiq/test/coredevice/test_spi.py b/artiq/test/coredevice/test_spi.py index 17bf6eeb6..0faf4677f 100644 --- a/artiq/test/coredevice/test_spi.py +++ b/artiq/test/coredevice/test_spi.py @@ -20,6 +20,7 @@ class CardTest(EnvExperiment): @kernel def run(self): self.core.reset() + delay(1*ms) freq = 1*MHz cs = 1 diff --git a/artiq/test/libartiq_support/lib.rs b/artiq/test/libartiq_support/lib.rs index 77e8f4160..8f06852ba 100644 --- a/artiq/test/libartiq_support/lib.rs +++ b/artiq/test/libartiq_support/lib.rs @@ -1,4 +1,4 @@ -#![feature(libc, panic_unwind, unwind_attributes)] +#![feature(libc, panic_unwind, unwind_attributes, rustc_private, int_bits_const)] #![crate_name = "artiq_support"] #![crate_type = "cdylib"] diff --git a/artiq/test/lit/embedding/syscall_arg_attrs.py b/artiq/test/lit/embedding/syscall_arg_attrs.py new file mode 100644 index 000000000..67207dc32 --- /dev/null +++ b/artiq/test/lit/embedding/syscall_arg_attrs.py @@ -0,0 +1,30 @@ +# RUN: env ARTIQ_DUMP_LLVM=%t %python -m artiq.compiler.testbench.embedding +compile %s +# RUN: OutputCheck %s --file-to-check=%t.ll + +from artiq.language.core import * +from artiq.language.types import * + +# Make sure `byval` and `sret` are specified both at the call site and the +# declaration. This isn't caught by the LLVM IR validator, but mismatches +# lead to miscompilations (at least in LLVM 11). + + +@kernel +def entrypoint(): + # CHECK: call void @accept_str\({ i8\*, i32 }\* nonnull byval + accept_str("foo") + + # CHECK: call void @return_str\({ i8\*, i32 }\* nonnull sret + return_str() + + +# CHECK: declare void @accept_str\({ i8\*, i32 }\* byval\) +@syscall +def accept_str(name: TStr) -> TNone: + pass + + +# CHECK: declare void @return_str\({ i8\*, i32 }\* sret\) +@syscall +def return_str() -> TStr: + pass diff --git a/artiq/test/lit/embedding/syscall_flags.py b/artiq/test/lit/embedding/syscall_flags.py index 7d2a10341..f8c618c3f 100644 --- a/artiq/test/lit/embedding/syscall_flags.py +++ b/artiq/test/lit/embedding/syscall_flags.py @@ -4,9 +4,9 @@ from artiq.language.core import * from artiq.language.types import * -# CHECK: call void @foo\(\)(, !dbg !\d+)?, !tbaa !\d+ +# CHECK: call void @foo\(\)(, !dbg !\d+)? -# CHECK-L: ; Function Attrs: nounwind +# CHECK-L: ; Function Attrs: inaccessiblememonly nounwind # CHECK-NEXT-L: declare void @foo() @syscall(flags={"nounwind", "nowrite"}) diff --git a/artiq/test/test_device_db.py b/artiq/test/test_device_db.py index a05093aee..cde57e200 100644 --- a/artiq/test/test_device_db.py +++ b/artiq/test/test_device_db.py @@ -1,5 +1,6 @@ """Test device DB interface""" +import os import unittest import tempfile from pathlib import Path @@ -22,19 +23,27 @@ device_db = { } """ +# tempfile.NamedTemporaryFile: +# use delete=False and manual cleanup +# for Windows compatibility + class TestDeviceDBImport(unittest.TestCase): def test_no_device_db_in_file(self): - with tempfile.NamedTemporaryFile(mode="w+", suffix=".py") as f: + with tempfile.NamedTemporaryFile(mode="w+", suffix=".py", delete=False) as f: + print(f.name) print("", file=f, flush=True) with self.assertRaisesRegex(KeyError, "device_db"): DeviceDB(f.name) + os.unlink(f.name) + def test_import_same_level(self): with tempfile.TemporaryDirectory() as tmpdir: # make sure both files land in the same directory - args = dict(mode="w+", suffix=".py", dir=tmpdir) + # tempfiles are cleanup together with tmpdir + args = dict(mode="w+", suffix=".py", dir=tmpdir, delete=False) with tempfile.NamedTemporaryFile( **args ) as fileA, tempfile.NamedTemporaryFile(**args) as fileB: @@ -58,11 +67,17 @@ device_db["new_core_alias"] = "core" class TestDeviceDB(unittest.TestCase): def setUp(self): - self.ddb_file = tempfile.NamedTemporaryFile(mode="w+", suffix=".py") + self.ddb_file = tempfile.NamedTemporaryFile( + mode="w+", suffix=".py", delete=False + ) print(DUMMY_DDB_FILE, file=self.ddb_file, flush=True) self.ddb = DeviceDB(self.ddb_file.name) + def tearDown(self): + self.ddb_file.close() + os.unlink(self.ddb_file.name) + def test_get(self): core = self.ddb.get("core") self.assertEqual(core["class"], "Core") diff --git a/artiq/wavesynth/coefficients.py b/artiq/wavesynth/coefficients.py index 3c41990ed..e70a4080e 100644 --- a/artiq/wavesynth/coefficients.py +++ b/artiq/wavesynth/coefficients.py @@ -111,7 +111,7 @@ class CoefficientSource: """ t = np.rint(x/scale) x_sample = t*scale - durations = np.diff(t).astype(np.int) + durations = np.diff(t).astype(int) return x_sample, durations def __call__(self, x, **kwargs): @@ -201,7 +201,7 @@ class SplineSource(CoefficientSource): inc = np.diff(t) >= 0 inc = np.r_[inc, inc[-1]] t = np.where(inc, np.ceil(t), np.floor(t)) - dt = np.diff(t.astype(np.int)) + dt = np.diff(t.astype(int)) valid = np.absolute(dt) >= min_duration if not np.any(valid): diff --git a/doc/manual/conf.py b/doc/manual/conf.py index 997801987..bc092928f 100644 --- a/doc/manual/conf.py +++ b/doc/manual/conf.py @@ -20,11 +20,10 @@ from unittest.mock import Mock import sphinx_rtd_theme -# Hack-patch Sphinx so that ARTIQ-Python types are correctly printed +# Ensure that ARTIQ-Python types are correctly printed # See: https://github.com/m-labs/artiq/issues/741 -from sphinx.ext import autodoc -from sphinx.util import inspect -autodoc.repr = inspect.repr = str +import builtins +builtins.__in_sphinx__ = True # we cannot use autodoc_mock_imports (does not help with argparse) @@ -38,7 +37,7 @@ mock_modules = ["artiq.gui.waitingspinnerwidget", "qasync", "pyqtgraph", "matplotlib", "numpy", "dateutil", "dateutil.parser", "prettytable", "PyQt5", "h5py", "serial", "scipy", "scipy.interpolate", - "llvmlite_artiq", "Levenshtein", "pythonparser", + "llvmlite", "Levenshtein", "pythonparser", "sipyco", "sipyco.pc_rpc", "sipyco.sync_struct", "sipyco.asyncio_tools", "sipyco.logging_tools", "sipyco.broadcast", "sipyco.packed_exceptions"] diff --git a/doc/manual/core_device.rst b/doc/manual/core_device.rst index 9488e4d33..fa3466d32 100644 --- a/doc/manual/core_device.rst +++ b/doc/manual/core_device.rst @@ -164,6 +164,19 @@ See :mod:`artiq.coredevice.i2c` for more details. Clocking ++++++++ -The KC705 supports an internal 125MHz RTIO clock (based on its crystal oscillator) and an external clock, that can be selected using the ``rtio_clock`` configuration entry. Valid values are ``i`` and ``e``, and the default is ``i``. The selected option can be observed in the core device boot logs. +The KC705 in standalone variants supports an internal 125 MHz RTIO clock (based on its crystal oscillator, or external reference for PLL for DRTIO variants) and an external clock, that can be selected using the ``rtio_clock`` configuration entry. Valid values are: + * ``int_125`` - internal crystal oscillator, 125 MHz output (default), + * ``ext0_bypass`` - external clock. -On Kasli, when set to ``e``, the ``rtio_clock`` setting overrides the built-in (and variant-dependent) Si5324 synthesizer configuration and disables the Si5324. The user must apply a clock at the RTIO frequency on the Kasli front panel SMA. As the Si5324 is bypassed in this mode, its skew is deterministic, which is useful to distribute clocks externally to Kasli and Urukul when Urukul phase synchronization is desired. +KC705 in DRTIO variants and Kasli generates the RTIO clock using a PLL locked either to an internal crystal or to an external frequency reference. Valid values are: + * ``int_125`` - internal crystal oscillator using PLL, 125 MHz output (default), + * ``int_100`` - internal crystal oscillator using PLL, 100 MHz output, + * ``int_150`` - internal crystal oscillator using PLL, 150 MHz output, + * ``ext0_synth0_10to125`` - external 10 MHz reference using PLL, 125 MHz output, + * ``ext0_synth0_100to125`` - external 100 MHz reference using PLL, 125 MHz output, + * ``ext0_synth0_125to125`` - external 125 MHz reference using PLL, 125 MHz output, + * ``ext0_bypass``, ``ext0_bypass_125``, ``ext0_bypass_100`` - external clock - with explicit aliases available. + +The selected option can be observed in the core device boot logs. + +Options ``rtio_clock=int_XXX`` and ``rtio_clock=ext0_synth0_XXXXX`` generate the RTIO clock using a PLL locked either to an internal crystal or to an external frequency reference (depending on exact option). ``rtio_clock=ext0_bypass`` bypasses that PLL and the user must supply the RTIO clock (typically 125 MHz) at the Kasli front panel SMA input. Bypassing the PLL ensures the skews between input clock, Kasli downstream clock outputs, and RTIO clock are deterministic accross reboots of the system. This is useful when phase determinism is required in situtations where the reference clock fans out to other devices before reaching Kasli. diff --git a/doc/manual/developing.rst b/doc/manual/developing.rst index 350bc2ac4..fa26c14fd 100644 --- a/doc/manual/developing.rst +++ b/doc/manual/developing.rst @@ -7,17 +7,22 @@ Developing ARTIQ The easiest way to obtain an ARTIQ development environment is via the Nix package manager on Linux. The Nix system is used on the `M-Labs Hydra server `_ to build ARTIQ and its dependencies continuously; it ensures that all build instructions are up-to-date and allows binary packages to be used on developers' machines, in particular for large tools such as the Rust compiler. ARTIQ itself does not depend on Nix, and it is also possible to compile everything from source (look into the ``.nix`` files from the ``nix-scripts`` repository and run the commands manually) - but Nix makes the process a lot easier. -* Download Vivado from Xilinx and install it (by running the official installer in a FHS chroot environment if using NixOS). If you do not want to write to ``/opt``, you can install it in a folder of your home directory. The "appropriate" Vivado version to use for building the bitstream can vary. Some versions contain bugs that lead to hidden or visible failures, others work fine. Refer to the `M-Labs Hydra logs `_ to determine which version is currently used when building the binary packages. If the Vivado GUI installer crashes, you may be able to work around the problem by running it in unattended mode with a command such as ``./xsetup -a XilinxEULA,3rdPartyEULA,WebTalkTerms -b Install -e 'Vitis Unified Software Platform' -l /opt/Xilinx/``. +* Download Vivado from Xilinx and install it (by running the official installer in a FHS chroot environment if using NixOS). If you do not want to write to ``/opt``, you can install it in a folder of your home directory. The "appropriate" Vivado version to use for building the bitstream can vary. Some versions contain bugs that lead to hidden or visible failures, others work fine. Refer to `Hydra `_ and/or the ``vivado.nix`` file from the ``nix-scripts`` repository in order to determine which version is used at M-Labs. If the Vivado GUI installer crashes, you may be able to work around the problem by running it in unattended mode with a command such as ``./xsetup -a XilinxEULA,3rdPartyEULA,WebTalkTerms -b Install -e 'Vitis Unified Software Platform' -l /opt/Xilinx/``. * During the Vivado installation, uncheck ``Install cable drivers`` (they are not required as we use better and open source alternatives). * Install the `Nix package manager `_ and Git (e.g. ``$ nix-shell -p git``). * Set up the M-Labs binary substituter (:ref:`same procedure as the user section `) to allow binaries to be downloaded. Otherwise, tools such as LLVM and the Rust compiler will be compiled on your machine, which uses a lot of CPU time, memory, and disk space. * Clone the repositories https://github.com/m-labs/artiq and https://git.m-labs.hk/m-labs/nix-scripts. -* If you did not install Vivado in its default location ``/opt``, edit the Nix files accordingly. +* If you did not install Vivado in its default location ``/opt``, edit ``vivado.nix`` accordingly. * Run ``$ nix-shell -I artiqSrc=path_to_artiq_sources shell-dev.nix`` to obtain an environment containing all the required development tools (e.g. Migen, MiSoC, Clang, Rust, OpenOCD...) in addition to the ARTIQ user environment. ``artiqSrc`` should point to the root of the cloned ``artiq`` repository, and ``shell-dev.nix`` can be found in the ``artiq-fast`` folder of the ``nix-scripts`` repository. * You can then build the firmware and gateware with a command such as ``$ python -m artiq.gateware.targets.kasli``. If you are using a JSON system description file, use ``$ python -m artiq.gateware.targets.kasli_generic file.json``. * Flash the binaries into the FPGA board with a command such as ``$ artiq_flash --srcbuild -d artiq_kasli -V ``. You need to configure OpenOCD as explained :ref:`in the user section `. OpenOCD is already part of the shell started by ``shell-dev.nix``. * Check that the board boots and examine the UART messages by running a serial terminal program, e.g. ``$ flterm /dev/ttyUSB1`` (``flterm`` is part of MiSoC and installed by ``shell-dev.nix``). Leave the terminal running while you are flashing the board, so that you see the startup messages when the board boots immediately after flashing. You can also restart the board (without reflashing it) with ``$ artiq_flash start``. * The communication parameters are 115200 8-N-1. Ensure that your user has access to the serial device (e.g. by adding the user account to the ``dialout`` group). + +.. note:: + If you do not plan to modify ``nix-scripts``, with the ARTIQ channel configured you can simply enter the development Nix shell with ``nix-shell ""``. No repositories need to be cloned. This is especially useful if you simply want to build firmware using an unmodified version of ARTIQ. + + .. warning:: Nix will make a read-only copy of the ARTIQ source to use in the shell environment. Therefore, any modifications that you make to the source after the shell is started will not be taken into account. A solution applicable to ARTIQ (and several other Python packages such as Migen and MiSoC) is to prepend the ARTIQ source directory to the ``PYTHONPATH`` environment variable after entering the shell. If you want this to be done by default, edit ``profile`` in ``shell-dev.nix``. diff --git a/doc/manual/faq.rst b/doc/manual/faq.rst index 817b64130..bd5d4ac89 100644 --- a/doc/manual/faq.rst +++ b/doc/manual/faq.rst @@ -118,4 +118,5 @@ find the dashboard and browser configuration files are stored? -------------------------------------------------------------- :: + python -c "from artiq.tools import get_user_config_dir; print(get_user_config_dir())" diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 41b1ebc82..0b9ac8c18 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -288,7 +288,17 @@ If you are using DRTIO and the default routing table (for a star topology) is no * Select the RTIO clock source (KC705 and Kasli) -The KC705 may use either an external clock signal or its internal clock. The clock is selected at power-up. For Kasli, setting the RTIO clock source to "external" would bypass the Si5324 synthesiser, requiring that an input clock be present. To select the source, use one of these commands: :: +The KC705 may use either an external clock signal, or its internal clock with external frequency or internal crystal reference. The clock is selected at power-up. Setting the RTIO clock source to "ext0_bypass" would bypass the Si5324 synthesiser, requiring that an input clock be present. To select the source, use one of these commands: :: - $ artiq_coremgmt config write -s rtio_clock i # internal clock (default) - $ artiq_coremgmt config write -s rtio_clock e # external clock + $ artiq_coremgmt config write -s rtio_clock int_125 # internal 125MHz clock (default) + $ artiq_coremgmt config write -s rtio_clock ext0_bypass # external clock (bypass) + +Other options include: + - ``ext0_synth0_10to125`` - external 10MHz reference clock used by Si5324 to synthesize a 125MHz RTIO clock, + - ``ext0_synth0_100to125`` - exteral 100MHz reference clock used by Si5324 to synthesize a 125MHz RTIO clock, + - ``ext0_synth0_125to125`` - exteral 125MHz reference clock used by Si5324 to synthesize a 125MHz RTIO clock, + - ``int_100`` - internal crystal reference is used by Si5324 to synthesize a 100MHz RTIO clock, + - ``int_150`` - internal crystal reference is used by Si5324 to synthesize a 150MHz RTIO clock. + - ``ext0_bypass_125`` and ``ext0_bypass_100`` - explicit aliases for ``ext0_bypass``. + +Availability of these options depends on the board and their configuration - specific setting may or may not be supported. \ No newline at end of file diff --git a/doc/manual/introduction.rst b/doc/manual/introduction.rst index 7b499ba42..7dcc2f802 100644 --- a/doc/manual/introduction.rst +++ b/doc/manual/introduction.rst @@ -21,7 +21,7 @@ Like any open source software ARTIQ can equally be built and installed directly ARTIQ is supported by M-Labs and developed openly. Components, features, fixes, improvements, and extensions are funded by and developed for the partnering research groups. -Technologies employed include `Python `_, `Migen `_, `MiSoC `_/`mor1kx `_, `LLVM `_/`llvmlite `_, and `Qt5 `_. +Core technologies employed include `Python `_, `Migen `_, `Migen-AXI `_, `Rust `_, `MiSoC `_/`VexRiscv `_, `LLVM `_/`llvmlite `_, and `Qt5 `_. Website: https://m-labs.hk/artiq diff --git a/doc/manual/rtio.rst b/doc/manual/rtio.rst index 67c8bdcaf..f5901ab0d 100644 --- a/doc/manual/rtio.rst +++ b/doc/manual/rtio.rst @@ -131,7 +131,7 @@ Notes: * Configuring the gateware with more lanes for the RTIO core reduces the frequency of sequence errors. * The number of lanes is a hard limit on the number of simultaneous RTIO output events. * Whether a particular sequence of timestamps causes a sequence error or not is fully deterministic (starting from a known RTIO state, e.g. after a reset). Adding a constant offset to the whole sequence does not affect the result. -* Zero-duration methods (such as :meth:`artiq.coredevice.ttl.TTLOut.on()`) do not advance the timeline and so will consume additional lanes if they are scheduled simultaneously. Adding a tiny delay will prevent this (e.g. ``delay_mu(self.core.ref_multiplier)``, at least one coarse rtio cycle). +* Zero-duration methods (such as :meth:`artiq.coredevice.ttl.TTLOut.on()`) do not advance the timeline and so will consume additional lanes if they are scheduled simultaneously. Adding a tiny delay will prevent this (e.g. ``delay_mu(np.int64(self.core.ref_multiplier))``, at least one coarse rtio cycle). The offending event is discarded and the RTIO core keeps operating. diff --git a/doc/manual/utilities.rst b/doc/manual/utilities.rst index 9e056e55e..5efe5baf3 100644 --- a/doc/manual/utilities.rst +++ b/doc/manual/utilities.rst @@ -81,11 +81,11 @@ You can write several records at once:: To remove the previously written key ``my_key``:: - $ artiq_coremgmt config delete my_key + $ artiq_coremgmt config remove my_key You can remove several keys at once:: - $ artiq_coremgmt config delete key1 key2 + $ artiq_coremgmt config remove key1 key2 To erase the entire flash storage area:: diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..35c1fb29a --- /dev/null +++ b/flake.lock @@ -0,0 +1,114 @@ +{ + "nodes": { + "mozilla-overlay": { + "flake": false, + "locked": { + "lastModified": 1637337116, + "narHash": "sha256-LKqAcdL+woWeYajs02bDQ7q8rsqgXuzhC354NoRaV80=", + "owner": "mozilla", + "repo": "nixpkgs-mozilla", + "rev": "cbc7435f5b0b3d17b16fb1d20cf7b616eec5e093", + "type": "github" + }, + "original": { + "owner": "mozilla", + "repo": "nixpkgs-mozilla", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1638279546, + "narHash": "sha256-1KCwN7twjp1dBdp0jPgVdYFztDkCR8+roo0B34J9oBY=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "96b4157790fc96e70d6e6c115e3f34bba7be490f", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-21.11", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "mozilla-overlay": "mozilla-overlay", + "nixpkgs": "nixpkgs", + "src-migen": "src-migen", + "src-misoc": "src-misoc", + "src-pythonparser": "src-pythonparser", + "src-sipyco": "src-sipyco" + } + }, + "src-migen": { + "flake": false, + "locked": { + "lastModified": 1636715924, + "narHash": "sha256-V3ThFSo2d7OC4SHE0lCkKGQKeFXmvxtwZRWe5NMU3nM=", + "owner": "m-labs", + "repo": "migen", + "rev": "9a0be7a4210ff96043412539eb5388659b81831d", + "type": "github" + }, + "original": { + "owner": "m-labs", + "repo": "migen", + "type": "github" + } + }, + "src-misoc": { + "flake": false, + "locked": { + "lastModified": 1638683371, + "narHash": "sha256-sm2SxHmEGfE56+V+joDHMjpOaxg8+t3EJEk1d11C1E0=", + "ref": "master", + "rev": "71b74f87b41c56a6c6d767cdfde0356c15a379a7", + "revCount": 2418, + "submodules": true, + "type": "git", + "url": "https://github.com/m-labs/misoc.git" + }, + "original": { + "submodules": true, + "type": "git", + "url": "https://github.com/m-labs/misoc.git" + } + }, + "src-pythonparser": { + "flake": false, + "locked": { + "lastModified": 1628745371, + "narHash": "sha256-p6TgeeaK4NEmbhimEXp31W8hVRo4DgWmcCoqZ+UdN60=", + "owner": "m-labs", + "repo": "pythonparser", + "rev": "5413ee5c9f8760e95c6acd5d6e88dabb831ad201", + "type": "github" + }, + "original": { + "owner": "m-labs", + "repo": "pythonparser", + "type": "github" + } + }, + "src-sipyco": { + "flake": false, + "locked": { + "lastModified": 1632832039, + "narHash": "sha256-GYXXCCOxNZyy6j7qScB3/QWUUCEVX+4tM4bXXVGXty0=", + "owner": "m-labs", + "repo": "sipyco", + "rev": "b83d8e5d82b25dba9393f0c12bdc5253f8138545", + "type": "github" + }, + "original": { + "owner": "m-labs", + "repo": "sipyco", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..f5a29ff4a --- /dev/null +++ b/flake.nix @@ -0,0 +1,450 @@ +{ + description = "A leading-edge control system for quantum information experiments"; + + inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-21.11; + inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; }; + inputs.src-sipyco = { url = github:m-labs/sipyco; flake = false; }; + inputs.src-pythonparser = { url = github:m-labs/pythonparser; flake = false; }; + + inputs.src-migen = { url = github:m-labs/migen; flake = false; }; + inputs.src-misoc = { type = "git"; url = "https://github.com/m-labs/misoc.git"; submodules = true; flake = false; }; + + outputs = { self, nixpkgs, mozilla-overlay, src-sipyco, src-pythonparser, src-migen, src-misoc }: + let + pkgs = import nixpkgs { system = "x86_64-linux"; overlays = [ (import mozilla-overlay) ]; }; + rustManifest = pkgs.fetchurl { + url = "https://static.rust-lang.org/dist/2021-01-29/channel-rust-nightly.toml"; + sha256 = "sha256-EZKgw89AH4vxaJpUHmIMzMW/80wAFQlfcxRoBD9nz0c="; + }; + + targets = []; + rustChannelOfTargets = _channel: _date: targets: + (pkgs.lib.rustLib.fromManifestFile rustManifest { + inherit (pkgs) stdenv lib fetchurl patchelf; + }).rust.override { + inherit targets; + extensions = ["rust-src"]; + }; + rust = rustChannelOfTargets "nightly" null targets; + rustPlatform = pkgs.recurseIntoAttrs (pkgs.makeRustPlatform { + rustc = rust; + cargo = rust; + }); + + vivadoDeps = pkgs: with pkgs; [ + ncurses5 + zlib + libuuid + xorg.libSM + xorg.libICE + xorg.libXrender + xorg.libX11 + xorg.libXext + xorg.libXtst + xorg.libXi + freetype + fontconfig + ]; + + sipyco = pkgs.python3Packages.buildPythonPackage { + name = "sipyco"; + src = src-sipyco; + propagatedBuildInputs = with pkgs.python3Packages; [ pybase64 numpy ]; + }; + + pythonparser = pkgs.python3Packages.buildPythonPackage { + name = "pythonparser"; + src = src-pythonparser; + doCheck = false; + propagatedBuildInputs = with pkgs.python3Packages; [ regex ]; + }; + + qasync = pkgs.python3Packages.buildPythonPackage rec { + pname = "qasync"; + version = "0.10.0"; + src = pkgs.fetchFromGitHub { + owner = "CabbageDevelopment"; + repo = "qasync"; + rev = "v${version}"; + sha256 = "1zga8s6dr7gk6awmxkh4pf25gbg8n6dv1j4b0by7y0fhi949qakq"; + }; + propagatedBuildInputs = [ pkgs.python3Packages.pyqt5 ]; + checkInputs = [ pkgs.python3Packages.pytest ]; + checkPhase = '' + pytest -k 'test_qthreadexec.py' # the others cause the test execution to be aborted, I think because of asyncio + ''; + }; + + outputcheck = pkgs.python3Packages.buildPythonApplication rec { + pname = "outputcheck"; + version = "0.4.2"; + src = pkgs.fetchFromGitHub { + owner = "stp"; + repo = "OutputCheck"; + rev = "e0f533d3c5af2949349856c711bf4bca50022b48"; + sha256 = "1y27vz6jq6sywas07kz3v01sqjd0sga9yv9w2cksqac3v7wmf2a0"; + }; + prePatch = "echo ${version} > RELEASE-VERSION"; + }; + + libartiq-support = pkgs.stdenv.mkDerivation { + name = "libartiq-support"; + src = self; + buildInputs = [ rustPlatform.rust.rustc ]; + buildPhase = '' + rustc $src/artiq/test/libartiq_support/lib.rs -Cpanic=unwind -g + ''; + installPhase = '' + mkdir $out + cp libartiq_support.so $out + ''; + }; + + llvmlite-new = pkgs.python3Packages.buildPythonPackage rec { + pname = "llvmlite"; + version = "0.37.0-artiq"; + src = pkgs.python3Packages.fetchPypi { + inherit pname; + version = "0.37.0"; + sha256 = "sha256-Y5K4cM0BjsDGRda7uRjWqg7sqMYmdLqu4whi1raGWxU="; + }; + patches = [ + (pkgs.fetchurl { + url = "https://git.m-labs.hk/M-Labs/nix-scripts/raw/branch/master/artiq-fast/pkgs/llvmlite-abiname.diff"; + sha256 = "1zlss9vlhjgch6gf5gc0647kkjdwjk0833ld88xwd9vmwvkdmp3v"; + }) + (pkgs.fetchurl { + url = "https://git.m-labs.hk/M-Labs/nix-scripts/raw/branch/master/artiq-fast/pkgs/llvmlite-callsite.diff"; + sha256 = "sha256-JrIXPnI7E7Y5NIFxswVBmRfQvv61lqKDDnNJrr+nDCg="; + }) + ]; + nativeBuildInputs = [ pkgs.llvm_11 ]; + # Disable static linking + # https://github.com/numba/llvmlite/issues/93 + postPatch = '' + substituteInPlace ffi/Makefile.linux --replace "-static-libstdc++" "" + substituteInPlace llvmlite/tests/test_binding.py --replace "test_linux" "nope" + ''; + # Set directory containing llvm-config binary + preConfigure = '' + export LLVM_CONFIG=${pkgs.llvm_11.dev}/bin/llvm-config + ''; + doCheck = false; # FIXME + }; + + artiq = pkgs.python3Packages.buildPythonPackage rec { + pname = "artiq"; + version = "7.0-dev"; + src = self; + + preBuild = "export VERSIONEER_OVERRIDE=${version}"; + + nativeBuildInputs = [ pkgs.qt5.wrapQtAppsHook ]; + # keep llvm_x and lld_x in sync with llvmlite + propagatedBuildInputs = [ pkgs.llvm_11 pkgs.lld_11 llvmlite-new sipyco pythonparser ] + ++ (with pkgs.python3Packages; [ pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial python-Levenshtein h5py pyqt5 qasync ]); + + dontWrapQtApps = true; + postFixup = '' + wrapQtApp "$out/bin/artiq_dashboard" + wrapQtApp "$out/bin/artiq_browser" + wrapQtApp "$out/bin/artiq_session" + ''; + + # Modifies PATH to pass the wrapped python environment (i.e. python3.withPackages(...) to subprocesses. + # Allows subprocesses using python to find all packages you have installed + makeWrapperArgs = [ + ''--run 'if [ ! -z "$NIX_PYTHONPREFIX" ]; then export PATH=$NIX_PYTHONPREFIX/bin:$PATH;fi' '' + "--set FONTCONFIG_FILE ${pkgs.fontconfig.out}/etc/fonts/fonts.conf" + ]; + + # FIXME: automatically propagate lld_11 llvm_11 dependencies + checkInputs = [ pkgs.lld_11 pkgs.llvm_11 pkgs.lit outputcheck ]; + checkPhase = '' + python -m unittest discover -v artiq.test + + TESTDIR=`mktemp -d` + cp --no-preserve=mode,ownership -R $src/artiq/test/lit $TESTDIR + LIBARTIQ_SUPPORT=${libartiq-support}/libartiq_support.so lit -v $TESTDIR/lit + ''; + }; + + migen = pkgs.python3Packages.buildPythonPackage rec { + name = "migen"; + src = src-migen; + propagatedBuildInputs = [ pkgs.python3Packages.colorama ]; + }; + + asyncserial = pkgs.python3Packages.buildPythonPackage rec { + pname = "asyncserial"; + version = "0.1"; + src = pkgs.fetchFromGitHub { + owner = "m-labs"; + repo = "asyncserial"; + rev = "d95bc1d6c791b0e9785935d2f62f628eb5cdf98d"; + sha256 = "0yzkka9jk3612v8gx748x6ziwykq5lr7zmr9wzkcls0v2yilqx9k"; + }; + propagatedBuildInputs = [ pkgs.python3Packages.pyserial ]; + }; + + misoc = pkgs.python3Packages.buildPythonPackage { + name = "misoc"; + src = src-misoc; + doCheck = false; # TODO: fix misoc bitrot and re-enable tests + propagatedBuildInputs = with pkgs.python3Packages; [ jinja2 numpy migen pyserial asyncserial ]; + }; + + jesd204b = pkgs.python3Packages.buildPythonPackage rec { + pname = "jesd204b"; + version = "unstable-2021-05-05"; + src = pkgs.fetchFromGitHub { + owner = "m-labs"; + repo = "jesd204b"; + rev = "bf1cd9014c8b7a9db67609f653634daaf3bcd39b"; + sha256 = "sha256-wyYOCRIPANReeCl+KaIpiAStsn2mzfMlK+cSrUzVrAw="; + }; + propagatedBuildInputs = with pkgs.python3Packages; [ migen misoc ]; + }; + + microscope = pkgs.python3Packages.buildPythonPackage rec { + pname = "microscope"; + version = "unstable-2020-12-28"; + src = pkgs.fetchFromGitHub { + owner = "m-labs"; + repo = "microscope"; + rev = "c21afe7a53258f05bde57e5ebf2e2761f3d495e4"; + sha256 = "sha256-jzyiLRuEf7p8LdhmZvOQj/dyQx8eUE8p6uRlwoiT8vg="; + }; + propagatedBuildInputs = with pkgs.python3Packages; [ pyserial prettytable msgpack migen ]; + }; + + cargo-xbuild = rustPlatform.buildRustPackage rec { + pname = "cargo-xbuild"; + version = "0.6.5"; + + src = pkgs.fetchFromGitHub { + owner = "rust-osdev"; + repo = pname; + rev = "v${version}"; + sha256 = "18djvygq9v8rmfchvi2hfj0i6fhn36m716vqndqnj56fiqviwxvf"; + }; + + cargoSha256 = "13sj9j9kl6js75h9xq0yidxy63vixxm9q3f8jil6ymarml5wkhx8"; + }; + + vivadoEnv = pkgs.buildFHSUserEnv { + name = "vivado-env"; + targetPkgs = vivadoDeps; + }; + + vivado = pkgs.buildFHSUserEnv { + name = "vivado"; + targetPkgs = vivadoDeps; + profile = "source /opt/Xilinx/Vivado/2021.1/settings64.sh"; + runScript = "vivado"; + }; + + makeArtiqBoardPackage = { target, variant, buildCommand ? "python -m artiq.gateware.targets.${target} -V ${variant}" }: + pkgs.python3Packages.toPythonModule (pkgs.stdenv.mkDerivation { + name = "artiq-board-${target}-${variant}"; + phases = [ "buildPhase" "checkPhase" "installPhase" ]; + cargoDeps = rustPlatform.fetchCargoTarball { + name = "artiq-firmware-cargo-deps"; + src = "${self}/artiq/firmware"; + sha256 = "sha256-Lf6M4M/jdRiO5MsWSoqtOSNfRIhbze+qvg4kaiiBWW4="; + }; + nativeBuildInputs = [ + (pkgs.python3.withPackages(ps: [ migen misoc artiq ])) + rustPlatform.rust.rustc + rustPlatform.rust.cargo + pkgs.llvmPackages_11.clang-unwrapped + pkgs.llvm_11 + pkgs.lld_11 + vivado + rustPlatform.cargoSetupHook + cargo-xbuild + ]; + buildPhase = + '' + ARTIQ_PATH=`python -c "import artiq; print(artiq.__path__[0])"` + ln -s $ARTIQ_PATH/firmware/Cargo.lock . + cargoSetupPostUnpackHook + cargoSetupPostPatchHook + ${buildCommand} + ''; + doCheck = true; + checkPhase = + '' + # Search for PCREs in the Vivado output to check for errors + check_log() { + grep -Pe "$1" artiq_${target}/${variant}/gateware/vivado.log && exit 1 || true + } + check_log "\d+ constraint not met\." + check_log "Timing constraints are not met\." + ''; + installPhase = + '' + TARGET_DIR=$out/${pkgs.python3Packages.python.sitePackages}/artiq/board-support/${target}-${variant} + mkdir -p $TARGET_DIR + cp artiq_${target}/${variant}/gateware/top.bit $TARGET_DIR + if [ -e artiq_${target}/${variant}/software/bootloader/bootloader.bin ] + then cp artiq_${target}/${variant}/software/bootloader/bootloader.bin $TARGET_DIR + fi + if [ -e artiq_${target}/${variant}/software/runtime ] + then cp artiq_${target}/${variant}/software/runtime/runtime.{elf,fbi} $TARGET_DIR + else cp artiq_${target}/${variant}/software/satman/satman.{elf,fbi} $TARGET_DIR + fi + ''; + # don't mangle ELF files as they are not for NixOS + dontFixup = true; + }); + + openocd-bscanspi = let + bscan_spi_bitstreams-pkg = pkgs.stdenv.mkDerivation { + name = "bscan_spi_bitstreams"; + src = pkgs.fetchFromGitHub { + owner = "quartiq"; + repo = "bscan_spi_bitstreams"; + rev = "01d8f819f15baf9a8cc5d96945a51e4d267ff564"; + sha256 = "1zqv47kzgvbn4c8cr019a6wcja7gn5h1z4kvw5bhpc72fyhagal9"; + }; + phases = ["installPhase"]; + installPhase = + '' + mkdir -p $out/share/bscan-spi-bitstreams + cp $src/*.bit $out/share/bscan-spi-bitstreams + ''; + }; + # https://docs.lambdaconcept.com/screamer/troubleshooting.html#error-contents-differ + openocd-fixed = pkgs.openocd.overrideAttrs(oa: { + version = "unstable-2021-09-15"; + src = pkgs.fetchFromGitHub { + owner = "openocd-org"; + repo = "openocd"; + rev = "a0bd3c9924870c3b8f428648410181040dabc33c"; + sha256 = "sha256-YgUsl4/FohfsOncM4uiz/3c6g2ZN4oZ0y5vV/2Skwqg="; + fetchSubmodules = true; + }; + patches = oa.patches or [] ++ [ + (pkgs.fetchurl { + url = "https://git.m-labs.hk/M-Labs/nix-scripts/raw/commit/575ef05cd554c239e4cc8cb97ae4611db458a80d/artiq-fast/pkgs/openocd-jtagspi.diff"; + sha256 = "0g3crk8gby42gm661yxdcgapdi8sp050l5pb2d0yjfic7ns9cw81"; + }) + ]; + nativeBuildInputs = oa.nativeBuildInputs or [] ++ [ pkgs.autoreconfHook269 ]; + }); + in pkgs.buildEnv { + name = "openocd-bscanspi"; + paths = [ openocd-fixed bscan_spi_bitstreams-pkg ]; + }; + in rec { + packages.x86_64-linux = rec { + inherit migen misoc vivadoEnv vivado openocd-bscanspi artiq; + artiq-board-kc705-nist_clock = makeArtiqBoardPackage { + target = "kc705"; + variant = "nist_clock"; + }; + artiq-board-kc705-nist_qc2 = makeArtiqBoardPackage { + target = "kc705"; + variant = "nist_qc2"; + }; + artiq-board-kc705-nist_clock_master = makeArtiqBoardPackage { + target = "kc705"; + variant = "nist_clock_master"; + }; + artiq-board-kc705-nist_qc2_master = makeArtiqBoardPackage { + target = "kc705"; + variant = "nist_qc2_master"; + }; + artiq-board-kc705-nist_clock_satellite = makeArtiqBoardPackage { + target = "kc705"; + variant = "nist_clock"; + }; + artiq-board-kc705-nist_qc2_satellite = makeArtiqBoardPackage { + target = "kc705"; + variant = "nist_qc2"; + }; + }; + + defaultPackage.x86_64-linux = pkgs.python3.withPackages(ps: [ packages.x86_64-linux.artiq ]); + + devShell.x86_64-linux = pkgs.mkShell { + name = "artiq-dev-shell"; + buildInputs = [ + (pkgs.python3.withPackages(ps: with packages.x86_64-linux; [ migen misoc jesd204b artiq ps.paramiko ps.jsonschema microscope ])) + rustPlatform.rust.rustc + rustPlatform.rust.cargo + cargo-xbuild + pkgs.llvmPackages_11.clang-unwrapped + pkgs.llvm_11 + pkgs.lld_11 + # use the vivado-env command to enter a FHS shell that lets you run the Vivado installer + packages.x86_64-linux.vivadoEnv + packages.x86_64-linux.vivado + packages.x86_64-linux.openocd-bscanspi + ]; + }; + + hydraJobs = { + inherit (packages.x86_64-linux) artiq artiq-board-kc705-nist_clock openocd-bscanspi; + kc705-hitl = pkgs.stdenv.mkDerivation { + name = "kc705-hitl"; + + # requires patched Nix + __networked = true; + + buildInputs = [ + (pkgs.python3.withPackages(ps: with packages.x86_64-linux; [ artiq artiq-board-kc705-nist_clock ps.paramiko ])) + pkgs.llvm_11 + pkgs.lld_11 + pkgs.openssh + packages.x86_64-linux.openocd-bscanspi # for the bscanspi bitstreams + ]; + phases = [ "buildPhase" ]; + buildPhase = + '' + whoami + export HOME=`mktemp -d` + mkdir $HOME/.ssh + cp /opt/hydra_id_ed25519 $HOME/.ssh/id_ed25519 + cp /opt/hydra_id_ed25519.pub $HOME/.ssh/id_ed25519.pub + echo "rpi-1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPOBQVcsvk6WgRj18v4m0zkFeKrcN9gA+r6sxQxNwFpv" > $HOME/.ssh/known_hosts + chmod 600 $HOME/.ssh/id_ed25519 + LOCKCTL=$(mktemp -d) + mkfifo $LOCKCTL/lockctl + + cat $LOCKCTL/lockctl | ${pkgs.openssh}/bin/ssh \ + -i $HOME/.ssh/id_ed25519 \ + -o UserKnownHostsFile=$HOME/.ssh/known_hosts \ + rpi-1 \ + 'mkdir -p /tmp/board_lock && flock /tmp/board_lock/kc705-1 -c "echo Ok; cat"' \ + | ( + # End remote flock via FIFO + atexit_unlock() { + echo > $LOCKCTL/lockctl + } + trap atexit_unlock EXIT + + # Read "Ok" line when remote successfully locked + read LOCK_OK + + artiq_flash -t kc705 -H rpi-1 + sleep 15 + + export ARTIQ_ROOT=`python -c "import artiq; print(artiq.__path__[0])"`/examples/kc705_nist_clock + export ARTIQ_LOW_LATENCY=1 + python -m unittest discover -v artiq.test.coredevice + ) + + touch $out + ''; + }; + }; + }; + + nixConfig = { + binaryCachePublicKeys = ["nixbld.m-labs.hk-1:5aSRVA5b320xbNvu30tqxVPXpld73bhtOeH6uAjRyHc="]; + binaryCaches = ["https://nixbld.m-labs.hk" "https://cache.nixos.org"]; + sandboxPaths = ["/opt"]; + }; +} diff --git a/setup.py b/setup.py index b8b5d7e1f..d888e9402 100755 --- a/setup.py +++ b/setup.py @@ -6,8 +6,8 @@ import sys import versioneer -if sys.version_info[:3] < (3, 5, 3): - raise Exception("You need Python 3.5.3+") +if sys.version_info[:2] < (3, 7): + raise Exception("You need Python 3.7+") # Depends on PyQt5, but setuptools cannot check for it. @@ -15,7 +15,7 @@ requirements = [ "numpy", "scipy", "python-dateutil", "prettytable", "h5py", "qasync", "pyqtgraph", "pygit2", - "llvmlite_artiq", "pythonparser", "python-Levenshtein", + "llvmlite", "pythonparser", "python-Levenshtein", ] console_scripts = [ @@ -58,16 +58,12 @@ Intended Audience :: Science/Research License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+) Operating System :: Microsoft :: Windows Operating System :: POSIX :: Linux -Programming Language :: Python :: 3.5 +Programming Language :: Python :: 3.7 Topic :: Scientific/Engineering :: Physics Topic :: System :: Hardware """.splitlines(), install_requires=requirements, extras_require={}, - dependency_links=[ - "git+https://github.com/m-labs/pyqtgraph.git@develop#egg=pyqtgraph", - "git+https://github.com/m-labs/llvmlite.git@artiq#egg=llvmlite_artiq" - ], packages=find_packages(), namespace_packages=[], include_package_data=True,