diff --git a/.gitignore b/.gitignore index b06e5d757..d393f3109 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ __pycache__/ /dist /*.egg-info /.coverage -/artiq/test/lit/*/Output/ /artiq/binaries /artiq/firmware/target/ /misoc_*/ diff --git a/artiq/_version.py b/artiq/_version.py index 62e5e4dd8..4373164eb 100644 --- a/artiq/_version.py +++ b/artiq/_version.py @@ -4,4 +4,5 @@ def get_rev(): return os.getenv("VERSIONEER_REV", default="unknown") def get_version(): - return os.getenv("VERSIONEER_OVERRIDE", default="9.0+unknown.beta") + return os.getenv("VERSIONEER_OVERRIDE", default="10.0+unknown.beta") + diff --git a/artiq/compiler/__init__.py b/artiq/compiler/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/artiq/compiler/algorithms/__init__.py b/artiq/compiler/algorithms/__init__.py deleted file mode 100644 index 50fc4304b..000000000 --- a/artiq/compiler/algorithms/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -from .inline import inline -from .unroll import unroll diff --git a/artiq/compiler/algorithms/inline.py b/artiq/compiler/algorithms/inline.py deleted file mode 100644 index ce3e3315f..000000000 --- a/artiq/compiler/algorithms/inline.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -:func:`inline` inlines a call instruction in ARTIQ IR. -The call instruction must have a statically known callee, -it must be second to last in the basic block, and the basic -block must have exactly one successor. -""" - -from .. import types, builtins, iodelay, ir - -def inline(call_insn): - assert isinstance(call_insn, ir.Call) - assert call_insn.static_target_function is not None - assert len(call_insn.basic_block.successors()) == 1 - assert call_insn.basic_block.index(call_insn) == \ - len(call_insn.basic_block.instructions) - 2 - - value_map = {} - source_function = call_insn.static_target_function - target_function = call_insn.basic_block.function - target_predecessor = call_insn.basic_block - target_successor = call_insn.basic_block.successors()[0] - - if builtins.is_none(source_function.type.ret): - target_return_phi = None - else: - target_return_phi = target_successor.prepend(ir.Phi(source_function.type.ret)) - - closure = target_predecessor.insert(ir.GetAttr(call_insn.target_function(), '__closure__'), - before=call_insn) - for actual_arg, formal_arg in zip([closure] + call_insn.arguments(), - source_function.arguments): - value_map[formal_arg] = actual_arg - - for source_block in source_function.basic_blocks: - target_block = ir.BasicBlock([], "i." + source_block.name) - target_function.add(target_block) - value_map[source_block] = target_block - - def mapper(value): - if isinstance(value, ir.Constant): - return value - else: - return value_map[value] - - for source_insn in source_function.instructions(): - target_block = value_map[source_insn.basic_block] - if isinstance(source_insn, ir.Return): - if target_return_phi is not None: - target_return_phi.add_incoming(mapper(source_insn.value()), target_block) - target_insn = ir.Branch(target_successor) - elif isinstance(source_insn, ir.Phi): - target_insn = ir.Phi() - elif isinstance(source_insn, ir.Delay): - target_insn = source_insn.copy(mapper) - target_insn.interval = source_insn.interval.fold(call_insn.arg_exprs) - elif isinstance(source_insn, ir.Loop): - target_insn = source_insn.copy(mapper) - target_insn.trip_count = source_insn.trip_count.fold(call_insn.arg_exprs) - elif isinstance(source_insn, ir.Call): - target_insn = source_insn.copy(mapper) - target_insn.arg_exprs = \ - { arg: source_insn.arg_exprs[arg].fold(call_insn.arg_exprs) - for arg in source_insn.arg_exprs } - else: - target_insn = source_insn.copy(mapper) - target_insn.name = "i." + source_insn.name - value_map[source_insn] = target_insn - target_block.append(target_insn) - - for source_insn in source_function.instructions(): - if isinstance(source_insn, ir.Phi): - target_insn = value_map[source_insn] - for block, value in source_insn.incoming(): - target_insn.add_incoming(value_map[value], value_map[block]) - - target_predecessor.terminator().replace_with(ir.Branch(value_map[source_function.entry()])) - if target_return_phi is not None: - call_insn.replace_all_uses_with(target_return_phi) - call_insn.erase() diff --git a/artiq/compiler/algorithms/unroll.py b/artiq/compiler/algorithms/unroll.py deleted file mode 100644 index 392a31a8c..000000000 --- a/artiq/compiler/algorithms/unroll.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -:func:`unroll` unrolls a loop instruction in ARTIQ IR. -The loop's trip count must be constant. -The loop body must not have any control flow instructions -except for one branch back to the loop head. -The loop body must be executed if the condition to which -the instruction refers is true. -""" - -from .. import types, builtins, iodelay, ir -from ..analyses import domination - -def _get_body_blocks(root, limit): - postorder = [] - - visited = set() - def visit(block): - visited.add(block) - for next_block in block.successors(): - if next_block not in visited and next_block is not limit: - visit(next_block) - postorder.append(block) - - visit(root) - - postorder.reverse() - return postorder - -def unroll(loop_insn): - loop_head = loop_insn.basic_block - function = loop_head.function - assert isinstance(loop_insn, ir.Loop) - assert len(loop_head.predecessors()) == 2 - assert len(loop_insn.if_false().predecessors()) == 1 - assert iodelay.is_const(loop_insn.trip_count) - - trip_count = loop_insn.trip_count.fold().value - if trip_count == 0: - loop_insn.replace_with(ir.Branch(loop_insn.if_false())) - return - - source_blocks = _get_body_blocks(loop_insn.if_true(), loop_head) - source_indvar = loop_insn.induction_variable() - source_tail = loop_insn.if_false() - unroll_target = loop_head - for n in range(trip_count): - value_map = {source_indvar: ir.Constant(n, source_indvar.type)} - - for source_block in source_blocks: - target_block = ir.BasicBlock([], "u{}.{}".format(n, source_block.name)) - function.add(target_block) - value_map[source_block] = target_block - - def mapper(value): - if isinstance(value, ir.Constant): - return value - elif value in value_map: - return value_map[value] - else: - return value - - for source_block in source_blocks: - target_block = value_map[source_block] - for source_insn in source_block.instructions: - if isinstance(source_insn, ir.Phi): - target_insn = ir.Phi() - else: - target_insn = source_insn.copy(mapper) - target_insn.name = "u{}.{}".format(n, source_insn.name) - target_block.append(target_insn) - value_map[source_insn] = target_insn - - for source_block in source_blocks: - for source_insn in source_block.instructions: - if isinstance(source_insn, ir.Phi): - target_insn = value_map[source_insn] - for block, value in source_insn.incoming(): - target_insn.add_incoming(value_map[value], value_map[block]) - - assert isinstance(unroll_target.terminator(), (ir.Branch, ir.Loop)) - unroll_target.terminator().replace_with(ir.Branch(value_map[source_blocks[0]])) - unroll_target = value_map[source_blocks[-1]] - - assert isinstance(unroll_target.terminator(), ir.Branch) - assert len(source_blocks[-1].successors()) == 1 - unroll_target.terminator().replace_with(ir.Branch(source_tail)) - - for source_block in reversed(source_blocks): - for source_insn in reversed(source_block.instructions): - for use in set(source_insn.uses): - if isinstance(use, ir.Phi): - assert use.basic_block == loop_head - use.remove_incoming_value(source_insn) - source_insn.erase() - - for source_block in reversed(source_blocks): - source_block.erase() diff --git a/artiq/compiler/analyses/__init__.py b/artiq/compiler/analyses/__init__.py deleted file mode 100644 index bea15e2ca..000000000 --- a/artiq/compiler/analyses/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .domination import DominatorTree -from .devirtualization import Devirtualization -from .invariant_detection import InvariantDetection diff --git a/artiq/compiler/analyses/devirtualization.py b/artiq/compiler/analyses/devirtualization.py deleted file mode 100644 index 3a35639f2..000000000 --- a/artiq/compiler/analyses/devirtualization.py +++ /dev/null @@ -1,119 +0,0 @@ -""" -:class:`Devirtualizer` performs method resolution at -compile time. - -Devirtualization is implemented using a lattice -with three states: unknown → assigned once → diverges. -The lattice is computed individually for every -variable in scope as well as every -(instance type, field name) pair. -""" - -from pythonparser import algorithm -from .. import asttyped, ir, types - -def _advance(target_map, key, value): - if key not in target_map: - target_map[key] = value # unknown → assigned once - else: - target_map[key] = None # assigned once → diverges - -class FunctionResolver(algorithm.Visitor): - def __init__(self, variable_map): - self.variable_map = variable_map - - self.scope_map = dict() - self.queue = [] - - self.in_assign = False - self.current_scopes = [] - - def finalize(self): - for thunk in self.queue: - thunk() - - def visit_scope(self, node): - self.current_scopes.append(node) - self.generic_visit(node) - self.current_scopes.pop() - - def visit_in_assign(self, node): - self.in_assign = True - self.visit(node) - self.in_assign = False - - def visit_Assign(self, node): - self.visit(node.value) - self.visit_in_assign(node.targets) - - def visit_ForT(self, node): - self.visit(node.iter) - self.visit_in_assign(node.target) - self.visit(node.body) - self.visit(node.orelse) - - def visit_withitemT(self, node): - self.visit(node.context_expr) - self.visit_in_assign(node.optional_vars) - - def visit_comprehension(self, node): - self.visit(node.iter) - self.visit_in_assign(node.target) - self.visit(node.ifs) - - def visit_ModuleT(self, node): - self.visit_scope(node) - - def visit_FunctionDefT(self, node): - _advance(self.scope_map, (self.current_scopes[-1], node.name), node) - self.visit_scope(node) - - def visit_NameT(self, node): - if self.in_assign: - # Just give up if we assign anything at all to a variable, and - # assume it diverges. - _advance(self.scope_map, (self.current_scopes[-1], node.id), None) - else: - # Look up the final value in scope_map and copy it into variable_map. - keys = [(scope, node.id) for scope in reversed(self.current_scopes)] - def thunk(): - for key in keys: - if key in self.scope_map: - self.variable_map[node] = self.scope_map[key] - return - self.queue.append(thunk) - -class MethodResolver(algorithm.Visitor): - def __init__(self, variable_map, method_map): - self.variable_map = variable_map - self.method_map = method_map - - # embedding.Stitcher.finalize generates initialization statements - # of form "constructor.meth = meth_body". - def visit_Assign(self, node): - if node.value not in self.variable_map: - return - - value = self.variable_map[node.value] - for target in node.targets: - if isinstance(target, asttyped.AttributeT): - if types.is_constructor(target.value.type): - instance_type = target.value.type.instance - elif types.is_instance(target.value.type): - instance_type = target.value.type - else: - continue - _advance(self.method_map, (instance_type, target.attr), value) - -class Devirtualization: - def __init__(self): - self.variable_map = dict() - self.method_map = dict() - - def visit(self, node): - function_resolver = FunctionResolver(self.variable_map) - function_resolver.visit(node) - function_resolver.finalize() - - method_resolver = MethodResolver(self.variable_map, self.method_map) - method_resolver.visit(node) diff --git a/artiq/compiler/analyses/domination.py b/artiq/compiler/analyses/domination.py deleted file mode 100644 index e5d96ae39..000000000 --- a/artiq/compiler/analyses/domination.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -:class:`DominatorTree` computes the dominance relation over -control flow graphs. - -See http://www.cs.rice.edu/~keith/EMBED/dom.pdf. -""" - -class GenericDominatorTree: - def __init__(self): - self._assign_names() - self._compute() - - def _traverse_in_postorder(self): - raise NotImplementedError - - def _prev_block_names(self, block): - raise NotImplementedError - - def _assign_names(self): - postorder = self._traverse_in_postorder() - - self._start_name = len(postorder) - 1 - self._block_of_name = postorder - self._name_of_block = {} - for block_name, block in enumerate(postorder): - self._name_of_block[block] = block_name - - def _intersect(self, block_name_1, block_name_2): - finger_1, finger_2 = block_name_1, block_name_2 - while finger_1 != finger_2: - while finger_1 < finger_2: - finger_1 = self._doms[finger_1] - while finger_2 < finger_1: - finger_2 = self._doms[finger_2] - return finger_1 - - def _compute(self): - self._doms = {} - - # Start block dominates itself. - self._doms[self._start_name] = self._start_name - - # We don't yet know what blocks dominate all other blocks. - for block_name in range(self._start_name): - self._doms[block_name] = None - - changed = True - while changed: - changed = False - - # For all blocks except start block, in reverse postorder... - for block_name in reversed(range(self._start_name)): - # Select a new immediate dominator from the blocks we have - # already processed, and remember all others. - # We've already processed at least one previous block because - # of the graph traverse order. - new_idom, prev_block_names = None, [] - for prev_block_name in self._prev_block_names(block_name): - if new_idom is None and self._doms[prev_block_name] is not None: - new_idom = prev_block_name - else: - prev_block_names.append(prev_block_name) - - # Find a common previous block - for prev_block_name in prev_block_names: - if self._doms[prev_block_name] is not None: - new_idom = self._intersect(prev_block_name, new_idom) - - if self._doms[block_name] != new_idom: - self._doms[block_name] = new_idom - changed = True - - def immediate_dominator(self, block): - return self._block_of_name[self._doms[self._name_of_block[block]]] - - def dominators(self, block): - # Blocks that are statically unreachable from entry are considered - # dominated by every other block. - if block not in self._name_of_block: - yield from self._block_of_name - return - - block_name = self._name_of_block[block] - yield self._block_of_name[block_name] - while block_name != self._doms[block_name]: - block_name = self._doms[block_name] - yield self._block_of_name[block_name] - -class DominatorTree(GenericDominatorTree): - def __init__(self, function): - self.function = function - super().__init__() - - def _traverse_in_postorder(self): - postorder = [] - - visited = set() - def visit(block): - visited.add(block) - for next_block in block.successors(): - if next_block not in visited: - visit(next_block) - postorder.append(block) - - visit(self.function.entry()) - - return postorder - - def _prev_block_names(self, block_name): - for block in self._block_of_name[block_name].predecessors(): - # Only return predecessors that are statically reachable from entry. - if block in self._name_of_block: - yield self._name_of_block[block] - -class PostDominatorTree(GenericDominatorTree): - def __init__(self, function): - self.function = function - super().__init__() - - def _traverse_in_postorder(self): - postorder = [] - - visited = set() - def visit(block): - visited.add(block) - for next_block in block.predecessors(): - if next_block not in visited: - visit(next_block) - postorder.append(block) - - for block in self.function.basic_blocks: - if not any(block.successors()): - visit(block) - - postorder.append(None) # virtual exit block - return postorder - - def _prev_block_names(self, block_name): - succ_blocks = self._block_of_name[block_name].successors() - if len(succ_blocks) > 0: - for block in succ_blocks: - yield self._name_of_block[block] - else: - yield self._start_name diff --git a/artiq/compiler/analyses/invariant_detection.py b/artiq/compiler/analyses/invariant_detection.py deleted file mode 100644 index 665c7317b..000000000 --- a/artiq/compiler/analyses/invariant_detection.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -:class:`InvariantDetection` determines which attributes can be safely -marked kernel invariant. -""" - -from pythonparser import diagnostic -from .. import ir, types - -class InvariantDetection: - def __init__(self, engine): - self.engine = engine - - def process(self, functions): - self.attr_locs = dict() - self.attr_written = set() - - for func in functions: - self.process_function(func) - - for key in self.attr_locs: - if key not in self.attr_written: - typ, attr = key - if attr in typ.constant_attributes: - continue - - diag = diagnostic.Diagnostic("note", - "attribute '{attr}' of type '{type}' is never written to; " + - "it could be marked as kernel invariant to potentially increase performance", - {"attr": attr, - "type": typ.name}, - self.attr_locs[key]) - self.engine.process(diag) - - def process_function(self, func): - for block in func.basic_blocks: - for insn in block.instructions: - if not isinstance(insn, (ir.GetAttr, ir.SetAttr)): - continue - if not types.is_instance(insn.object().type): - continue - - key = (insn.object().type, insn.attr) - if isinstance(insn, ir.GetAttr): - if types.is_method(insn.type): - continue - if key not in self.attr_locs and insn.loc is not None: - self.attr_locs[key] = insn.loc - elif isinstance(insn, ir.SetAttr): - self.attr_written.add(key) diff --git a/artiq/compiler/asttyped.py b/artiq/compiler/asttyped.py deleted file mode 100644 index b6fb34274..000000000 --- a/artiq/compiler/asttyped.py +++ /dev/null @@ -1,119 +0,0 @@ -""" -The typedtree module exports the PythonParser AST enriched with -typing information. -""" - -from pythonparser import ast - -class commontyped(ast.commonloc): - """A mixin for typed AST nodes.""" - - _types = ("type",) - - def _reprfields(self): - return self._fields + self._locs + self._types - -class scoped(object): - """ - :ivar typing_env: (dict with string keys and :class:`.types.Type` values) - map of variable names to variable types - :ivar globals_in_scope: (set of string keys) - set of variables resolved as globals - """ - -class remote(object): - """ - :ivar remote_fn: (bool) whether function is ran on a remote device, - meaning arguments are received remotely and return is sent remotely - """ - -# Typed versions of untyped nodes -class argT(ast.arg, commontyped): - pass - -class ClassDefT(ast.ClassDef): - _types = ("constructor_type",) -class FunctionDefT(ast.FunctionDef, scoped, remote): - _types = ("signature_type",) -class QuotedFunctionDefT(FunctionDefT): - """ - :ivar flags: (set of str) Code generation flags (see :class:`ir.Function`). - """ -class ModuleT(ast.Module, scoped): - pass - -class ExceptHandlerT(ast.ExceptHandler): - _fields = ("filter", "name", "body") # rename ast.ExceptHandler.type to filter - _types = ("name_type",) - -class ForT(ast.For): - """ - :ivar trip_count: (:class:`iodelay.Expr`) - :ivar trip_interval: (:class:`iodelay.Expr`) - """ - -class withitemT(ast.withitem): - _types = ("enter_type", "exit_type") - -class SliceT(ast.Slice, commontyped): - pass - -class AttributeT(ast.Attribute, commontyped): - pass -class BinOpT(ast.BinOp, commontyped): - pass -class BoolOpT(ast.BoolOp, commontyped): - pass -class CallT(ast.Call, commontyped, remote): - """ - :ivar iodelay: (:class:`iodelay.Expr`) - :ivar arg_exprs: (dict of str to :class:`iodelay.Expr`) - """ -class CompareT(ast.Compare, commontyped): - pass -class DictT(ast.Dict, commontyped): - pass -class DictCompT(ast.DictComp, commontyped, scoped): - pass -class EllipsisT(ast.Ellipsis, commontyped): - pass -class GeneratorExpT(ast.GeneratorExp, commontyped, scoped): - pass -class IfExpT(ast.IfExp, commontyped): - pass -class LambdaT(ast.Lambda, commontyped, scoped): - pass -class ListT(ast.List, commontyped): - pass -class ListCompT(ast.ListComp, commontyped, scoped): - pass -class NameT(ast.Name, commontyped): - pass -class NameConstantT(ast.NameConstant, commontyped): - pass -class NumT(ast.Num, commontyped): - pass -class SetT(ast.Set, commontyped): - pass -class SetCompT(ast.SetComp, commontyped, scoped): - pass -class StrT(ast.Str, commontyped): - pass -class StarredT(ast.Starred, commontyped): - pass -class SubscriptT(ast.Subscript, commontyped): - pass -class TupleT(ast.Tuple, commontyped): - pass -class UnaryOpT(ast.UnaryOp, commontyped): - pass -class YieldT(ast.Yield, commontyped): - pass -class YieldFromT(ast.YieldFrom, commontyped): - pass - -# Novel typed nodes -class CoerceT(ast.expr, commontyped): - _fields = ('value',) # other_value deliberately not in _fields -class QuoteT(ast.expr, commontyped): - _fields = ('value',) diff --git a/artiq/compiler/builtins.py b/artiq/compiler/builtins.py deleted file mode 100644 index cb0834f71..000000000 --- a/artiq/compiler/builtins.py +++ /dev/null @@ -1,361 +0,0 @@ -""" -The :mod:`builtins` module contains the builtin Python -and ARTIQ types, such as int or float. -""" - -from collections import OrderedDict -from . import types - -# Types - -class TNone(types.TMono): - def __init__(self): - super().__init__("NoneType") - -class TBool(types.TMono): - def __init__(self): - super().__init__("bool") - - @staticmethod - def zero(): - return False - - @staticmethod - def one(): - return True - -class TInt(types.TMono): - def __init__(self, width=None): - if width is None: - width = types.TVar() - super().__init__("int", {"width": width}) - - @staticmethod - def zero(): - return 0 - - @staticmethod - def one(): - return 1 - -def TInt8(): - return TInt(types.TValue(8)) - -def TInt32(): - return TInt(types.TValue(32)) - -def TInt64(): - return TInt(types.TValue(64)) - -def _int_printer(typ, printer, depth, max_depth): - if types.is_var(typ["width"]): - return "numpy.int?" - else: - return "numpy.int{}".format(types.get_value(typ.find()["width"])) -types.TypePrinter.custom_printers["int"] = _int_printer - -class TFloat(types.TMono): - def __init__(self): - super().__init__("float") - - @staticmethod - def zero(): - return 0.0 - - @staticmethod - def one(): - return 1.0 - -class TStr(types.TMono): - def __init__(self): - super().__init__("str") - -class TBytes(types.TMono): - def __init__(self): - super().__init__("bytes") - -class TByteArray(types.TMono): - def __init__(self): - super().__init__("bytearray") - -class TList(types.TMono): - def __init__(self, elt=None): - if elt is None: - elt = types.TVar() - super().__init__("list", {"elt": elt}) - -class TArray(types.TMono): - def __init__(self, elt=None, num_dims=1): - if elt is None: - elt = types.TVar() - if isinstance(num_dims, int): - # Make TArray more convenient to instantiate from (ARTIQ) user code. - num_dims = types.TValue(num_dims) - # For now, enforce number of dimensions to be known, as we'd otherwise - # need to implement custom unification logic for the type of `shape`. - # Default to 1 to keep compatibility with old user code from before - # multidimensional array support. - assert isinstance(num_dims.value, int), "Number of dimensions must be resolved" - - super().__init__("array", {"elt": elt, "num_dims": num_dims}) - self.attributes = OrderedDict([ - ("buffer", types._TPointer(elt)), - ("shape", types.TTuple([TInt32()] * num_dims.value)), - ]) - -def _array_printer(typ, printer, depth, max_depth): - return "numpy.array(elt={}, num_dims={})".format( - printer.name(typ["elt"], depth, max_depth), typ["num_dims"].value) -types.TypePrinter.custom_printers["array"] = _array_printer - -class TRange(types.TMono): - def __init__(self, elt=None): - if elt is None: - elt = types.TVar() - super().__init__("range", {"elt": elt}) - self.attributes = OrderedDict([ - ("start", elt), - ("stop", elt), - ("step", elt), - ]) - -class TException(types.TMono): - # All exceptions share the same internal layout: - # * Pointer to the unique global with the name of the exception (str) - # (which also serves as the EHABI type_info). - # * File, line and column where it was raised (str, int, int). - # * Message, which can contain substitutions {0}, {1} and {2} (str). - # * Three 64-bit integers, parameterizing the message (numpy.int64). - # These attributes are prefixed with `#` so that users cannot access them, - # and we don't have to do string allocation in the runtime. - # #__name__ is now a string key in the host. TStr may not be an actual - # CSlice in the runtime, they might be a CSlice with length = i32::MAX and - # ptr = string key in the host. - - # Keep this in sync with the function ARTIQIRGenerator.alloc_exn. - attributes = OrderedDict([ - ("#__name__", TInt32()), - ("#__file__", TStr()), - ("#__line__", TInt32()), - ("#__col__", TInt32()), - ("#__func__", TStr()), - ("#__message__", TStr()), - ("#__param0__", TInt64()), - ("#__param1__", TInt64()), - ("#__param2__", TInt64()), - ]) - - def __init__(self, name="Exception", id=0): - super().__init__(name) - self.id = id - -def fn_bool(): - return types.TConstructor(TBool()) - -def fn_int(): - return types.TConstructor(TInt()) - -def fn_int32(): - return types.TBuiltinFunction("int32") - -def fn_int64(): - return types.TBuiltinFunction("int64") - -def fn_float(): - return types.TConstructor(TFloat()) - -def fn_str(): - return types.TConstructor(TStr()) - -def fn_bytes(): - return types.TConstructor(TBytes()) - -def fn_bytearray(): - return types.TConstructor(TByteArray()) - -def fn_list(): - return types.TConstructor(TList()) - -def fn_array(): - # numpy.array() is actually a "magic" macro that is expanded in-place, but - # just as for builtin functions, we do not want to quote it, etc. - return types.TBuiltinFunction("array") - -def fn_Exception(): - return types.TExceptionConstructor(TException("Exception")) - -def fn_IndexError(): - return types.TExceptionConstructor(TException("IndexError")) - -def fn_ValueError(): - return types.TExceptionConstructor(TException("ValueError")) - -def fn_ZeroDivisionError(): - return types.TExceptionConstructor(TException("ZeroDivisionError")) - -def fn_RuntimeError(): - return types.TExceptionConstructor(TException("RuntimeError")) - -def fn_range(): - return types.TBuiltinFunction("range") - -def fn_len(): - return types.TBuiltinFunction("len") - -def fn_round(): - return types.TBuiltinFunction("round") - -def fn_abs(): - return types.TBuiltinFunction("abs") - -def fn_min(): - return types.TBuiltinFunction("min") - -def fn_max(): - return types.TBuiltinFunction("max") - -def fn_make_array(): - return types.TBuiltinFunction("make_array") - -def fn_print(): - return types.TBuiltinFunction("print") - -def fn_kernel(): - return types.TBuiltinFunction("kernel") - -def obj_parallel(): - return types.TBuiltin("parallel") - -def obj_interleave(): - return types.TBuiltin("interleave") - -def obj_sequential(): - return types.TBuiltin("sequential") - -def fn_delay(): - return types.TBuiltinFunction("delay") - -def fn_now_mu(): - return types.TBuiltinFunction("now_mu") - -def fn_delay_mu(): - return types.TBuiltinFunction("delay_mu") - -def fn_at_mu(): - return types.TBuiltinFunction("at_mu") - -def fn_rtio_log(): - return types.TBuiltinFunction("rtio_log") - -def fn_subkernel_await(): - return types.TBuiltinFunction("subkernel_await") - -def fn_subkernel_preload(): - return types.TBuiltinFunction("subkernel_preload") - -def fn_subkernel_send(): - return types.TBuiltinFunction("subkernel_send") - -def fn_subkernel_recv(): - return types.TBuiltinFunction("subkernel_recv") - -# Accessors - -def is_none(typ): - return types.is_mono(typ, "NoneType") - -def is_bool(typ): - return types.is_mono(typ, "bool") - -def is_int(typ, width=None): - if width is not None: - return types.is_mono(typ, "int", width=width) - else: - return types.is_mono(typ, "int") - -def is_int32(typ): - return is_int(typ, types.TValue(32)) - -def is_int64(typ): - return is_int(typ, types.TValue(64)) - -def get_int_width(typ): - if is_int(typ): - return types.get_value(typ.find()["width"]) - -def is_float(typ): - return types.is_mono(typ, "float") - -def is_str(typ): - return types.is_mono(typ, "str") - -def is_bytes(typ): - return types.is_mono(typ, "bytes") - -def is_bytearray(typ): - return types.is_mono(typ, "bytearray") - -def is_numeric(typ): - typ = typ.find() - return isinstance(typ, types.TMono) and \ - typ.name in ('int', 'float') - -def is_list(typ, elt=None): - if elt is not None: - return types.is_mono(typ, "list", elt=elt) - else: - return types.is_mono(typ, "list") - -def is_array(typ, elt=None): - if elt is not None: - return types.is_mono(typ, "array", elt=elt) - else: - return types.is_mono(typ, "array") - -def is_listish(typ, elt=None): - if is_list(typ, elt) or is_array(typ, elt): - return True - elif elt is None: - return is_str(typ) or is_bytes(typ) or is_bytearray(typ) - else: - return False - -def is_range(typ, elt=None): - if elt is not None: - return types.is_mono(typ, "range", {"elt": elt}) - else: - return types.is_mono(typ, "range") - -def is_exception(typ, name=None): - if name is None: - return isinstance(typ.find(), TException) - else: - return isinstance(typ.find(), TException) and \ - typ.name == name - -def is_iterable(typ): - return is_listish(typ) or is_range(typ) - -def get_iterable_elt(typ): - # TODO: Arrays count as listish, but this returns the innermost element type for - # n-dimensional arrays, rather than the n-1 dimensional result of iterating over - # the first axis, which makes the name a bit misleading. - if is_str(typ) or is_bytes(typ) or is_bytearray(typ): - return TInt8() - elif types._is_pointer(typ) or is_iterable(typ): - return typ.find()["elt"].find() - else: - assert False - -def is_collection(typ): - typ = typ.find() - return isinstance(typ, types.TTuple) or \ - types.is_mono(typ, "list") - -def is_allocated(typ): - return not (is_none(typ) or is_bool(typ) or is_int(typ) or - is_float(typ) or is_range(typ) or - types._is_pointer(typ) or types.is_function(typ) or - types.is_external_function(typ) or types.is_rpc(typ) or - types.is_subkernel(typ) or types.is_method(typ) or - types.is_tuple(typ) or types.is_value(typ)) diff --git a/artiq/compiler/import_cache.py b/artiq/compiler/import_cache.py deleted file mode 100644 index 9300f5c39..000000000 --- a/artiq/compiler/import_cache.py +++ /dev/null @@ -1,58 +0,0 @@ -import sys -import builtins -import linecache -import tokenize -import logging -import importlib.machinery as im - -from artiq.experiment import kernel, portable - - -__all__ = ["install_hook"] - - -logger = logging.getLogger(__name__) - - -cache = dict() -im_exec_module = None -linecache_getlines = None - - -def hook_exec_module(self, module): - im_exec_module(self, module) - if (hasattr(module, "__file__") - # Heuristic to determine if the module may contain ARTIQ kernels. - # This breaks if kernel is not imported the usual way. - and ((getattr(module, "kernel", None) is kernel) - or (getattr(module, "portable", None) is portable))): - fn = module.__file__ - try: - with tokenize.open(fn) as fp: - lines = fp.readlines() - if lines and not lines[-1].endswith("\n"): - lines[-1] += "\n" - cache[fn] = lines - except: - logger.warning("failed to add '%s' to cache", fn, exc_info=True) - else: - logger.debug("added '%s' to cache", fn) - - -def hook_getlines(filename, module_globals=None): - if filename in cache: - return cache[filename] - else: - return linecache_getlines(filename, module_globals) - - -def install_hook(): - global im_exec_module, linecache_getlines - - im_exec_module = im.SourceFileLoader.exec_module - im.SourceFileLoader.exec_module = hook_exec_module - - linecache_getlines = linecache.getlines - linecache.getlines = hook_getlines - - logger.debug("hook installed") diff --git a/artiq/compiler/iodelay.py b/artiq/compiler/iodelay.py deleted file mode 100644 index 6cab8588c..000000000 --- a/artiq/compiler/iodelay.py +++ /dev/null @@ -1,249 +0,0 @@ -""" -The :mod:`iodelay` module contains the classes describing -the statically inferred RTIO delay arising from executing -a function. -""" - -from functools import reduce - -class Expr: - def __add__(lhs, rhs): - assert isinstance(rhs, Expr) - return Add(lhs, rhs) - __iadd__ = __add__ - - def __sub__(lhs, rhs): - assert isinstance(rhs, Expr) - return Sub(lhs, rhs) - __isub__ = __sub__ - - def __mul__(lhs, rhs): - assert isinstance(rhs, Expr) - return Mul(lhs, rhs) - __imul__ = __mul__ - - def __truediv__(lhs, rhs): - assert isinstance(rhs, Expr) - return TrueDiv(lhs, rhs) - __itruediv__ = __truediv__ - - def __floordiv__(lhs, rhs): - assert isinstance(rhs, Expr) - return FloorDiv(lhs, rhs) - __ifloordiv__ = __floordiv__ - - def __ne__(lhs, rhs): - return not (lhs == rhs) - - def free_vars(self): - return set() - - def fold(self, vars=None): - return self - -class Const(Expr): - _priority = 1 - - def __init__(self, value): - assert isinstance(value, (int, float)) - self.value = value - - def __str__(self): - return str(self.value) - - def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and lhs.value == rhs.value - - def eval(self, env): - return self.value - -class Var(Expr): - _priority = 1 - - def __init__(self, name): - assert isinstance(name, str) - self.name = name - - def __str__(self): - return self.name - - def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and lhs.name == rhs.name - - def free_vars(self): - return {self.name} - - def fold(self, vars=None): - if vars is not None and self.name in vars: - return vars[self.name] - else: - return self - -class Conv(Expr): - _priority = 1 - - def __init__(self, operand, ref_period): - assert isinstance(operand, Expr) - assert isinstance(ref_period, float) - self.operand, self.ref_period = operand, ref_period - - def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and \ - lhs.ref_period == rhs.ref_period and \ - lhs.operand == rhs.operand - - def free_vars(self): - return self.operand.free_vars() - -class MUToS(Conv): - def __str__(self): - return "mu->s({})".format(self.operand) - - def eval(self, env): - return self.operand.eval(env) * self.ref_period - - def fold(self, vars=None): - operand = self.operand.fold(vars) - if isinstance(operand, Const): - return Const(operand.value * self.ref_period) - else: - return MUToS(operand, ref_period=self.ref_period) - -class SToMU(Conv): - def __str__(self): - return "s->mu({})".format(self.operand) - - def eval(self, env): - return int(self.operand.eval(env) / self.ref_period) - - def fold(self, vars=None): - operand = self.operand.fold(vars) - if isinstance(operand, Const): - return Const(int(operand.value / self.ref_period)) - else: - return SToMU(operand, ref_period=self.ref_period) - -class BinOp(Expr): - def __init__(self, lhs, rhs): - self.lhs, self.rhs = lhs, rhs - - def __str__(self): - lhs = "({})".format(self.lhs) if self.lhs._priority > self._priority else str(self.lhs) - rhs = "({})".format(self.rhs) if self.rhs._priority > self._priority else str(self.rhs) - return "{} {} {}".format(lhs, self._symbol, rhs) - - def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and lhs.lhs == rhs.lhs and lhs.rhs == rhs.rhs - - def eval(self, env): - return self.__class__._op(self.lhs.eval(env), self.rhs.eval(env)) - - def free_vars(self): - return self.lhs.free_vars() | self.rhs.free_vars() - - def _fold_binop(self, lhs, rhs): - if isinstance(lhs, Const) and lhs.__class__ == rhs.__class__: - return Const(self.__class__._op(lhs.value, rhs.value)) - elif isinstance(lhs, (MUToS, SToMU)) and lhs.__class__ == rhs.__class__: - return lhs.__class__(self.__class__(lhs.operand, rhs.operand), - ref_period=lhs.ref_period).fold() - else: - return self.__class__(lhs, rhs) - - def fold(self, vars=None): - return self._fold_binop(self.lhs.fold(vars), self.rhs.fold(vars)) - -class BinOpFixpoint(BinOp): - def _fold_binop(self, lhs, rhs): - if isinstance(lhs, Const) and lhs.value == self._fixpoint: - return rhs - elif isinstance(rhs, Const) and rhs.value == self._fixpoint: - return lhs - else: - return super()._fold_binop(lhs, rhs) - -class Add(BinOpFixpoint): - _priority = 2 - _symbol = "+" - _op = lambda a, b: a + b - _fixpoint = 0 - -class Mul(BinOpFixpoint): - _priority = 1 - _symbol = "*" - _op = lambda a, b: a * b - _fixpoint = 1 - -class Sub(BinOp): - _priority = 2 - _symbol = "-" - _op = lambda a, b: a - b - - def _fold_binop(self, lhs, rhs): - if isinstance(rhs, Const) and rhs.value == 0: - return lhs - else: - return super()._fold_binop(lhs, rhs) - -class Div(BinOp): - def _fold_binop(self, lhs, rhs): - if isinstance(rhs, Const) and rhs.value == 1: - return lhs - else: - return super()._fold_binop(lhs, rhs) - -class TrueDiv(Div): - _priority = 1 - _symbol = "/" - _op = lambda a, b: a / b if b != 0 else 0 - -class FloorDiv(Div): - _priority = 1 - _symbol = "//" - _op = lambda a, b: a // b if b != 0 else 0 - -class Max(Expr): - _priority = 1 - - def __init__(self, operands): - assert isinstance(operands, list) - assert all([isinstance(operand, Expr) for operand in operands]) - assert operands != [] - self.operands = operands - - def __str__(self): - return "max({})".format(", ".join([str(operand) for operand in self.operands])) - - def __eq__(lhs, rhs): - return rhs.__class__ == lhs.__class__ and lhs.operands == rhs.operands - - def free_vars(self): - return reduce(lambda a, b: a | b, [operand.free_vars() for operand in self.operands]) - - def eval(self, env): - return max([operand.eval() for operand in self.operands]) - - def fold(self, vars=None): - consts, exprs = [], [] - for operand in self.operands: - operand = operand.fold(vars) - if isinstance(operand, Const): - consts.append(operand.value) - elif operand not in exprs: - exprs.append(operand) - if len(consts) > 0: - exprs.append(Const(max(consts))) - if len(exprs) == 1: - return exprs[0] - else: - return Max(exprs) - -def is_const(expr, value=None): - expr = expr.fold() - if value is None: - return isinstance(expr, Const) - else: - return isinstance(expr, Const) and expr.value == value - -def is_zero(expr): - return is_const(expr, 0) diff --git a/artiq/compiler/ir.py b/artiq/compiler/ir.py deleted file mode 100644 index 9d2dfec25..000000000 --- a/artiq/compiler/ir.py +++ /dev/null @@ -1,1621 +0,0 @@ -""" -The :mod:`ir` module contains the intermediate representation -of the ARTIQ compiler. -""" - -from collections import OrderedDict -from pythonparser import ast -from . import types, builtins, iodelay - -# Generic SSA IR classes - -def escape_name(name): - if all([str.isalnum(x) or x == "." for x in name]): - return name - else: - return "\"{}\"".format(name.replace("\"", "\\\"")) - -class TBasicBlock(types.TMono): - def __init__(self): - super().__init__("label") - -def is_basic_block(typ): - return isinstance(typ, TBasicBlock) - -class TOption(types.TMono): - def __init__(self, value): - super().__init__("option", {"value": value}) - -def is_option(typ): - return isinstance(typ, TOption) - -class TKeyword(types.TMono): - def __init__(self, value): - super().__init__("keyword", {"value": value}) - -def is_keyword(typ): - return isinstance(typ, TKeyword) - - -# See rpc_proto.rs and comm_kernel.py:_{send,receive}_rpc_value. -def rpc_tag(typ, error_handler): - typ = typ.find() - if types.is_tuple(typ): - assert len(typ.elts) < 256 - return b"t" + bytes([len(typ.elts)]) + \ - b"".join([rpc_tag(elt_type, error_handler) - for elt_type in typ.elts]) - elif builtins.is_none(typ): - return b"n" - elif builtins.is_bool(typ): - return b"b" - elif builtins.is_int(typ, types.TValue(32)): - return b"i" - elif builtins.is_int(typ, types.TValue(64)): - return b"I" - elif builtins.is_float(typ): - return b"f" - elif builtins.is_str(typ): - return b"s" - elif builtins.is_bytes(typ): - return b"B" - elif builtins.is_bytearray(typ): - return b"A" - elif builtins.is_list(typ): - return b"l" + rpc_tag(builtins.get_iterable_elt(typ), error_handler) - elif builtins.is_array(typ): - num_dims = typ["num_dims"].value - return b"a" + bytes([num_dims]) + rpc_tag(typ["elt"], error_handler) - elif builtins.is_range(typ): - return b"r" + rpc_tag(builtins.get_iterable_elt(typ), error_handler) - elif is_keyword(typ): - return b"k" + rpc_tag(typ.params["value"], error_handler) - elif types.is_function(typ) or types.is_method(typ) or types.is_rpc(typ): - raise ValueError("RPC tag for functional value") - elif '__objectid__' in typ.attributes: - return b"O" - else: - error_handler(typ) - - -class Value: - """ - An SSA value that keeps track of its uses. - - :ivar type: (:class:`.types.Type`) type of this value - :ivar uses: (list of :class:`Value`) values that use this value - """ - - def __init__(self, typ): - self.uses, self.type = set(), typ.find() - - def replace_all_uses_with(self, value): - for user in set(self.uses): - user.replace_uses_of(self, value) - - def __str__(self): - return self.as_entity(type_printer=types.TypePrinter()) - -class Constant(Value): - """ - A constant value. - - :ivar value: (Python object) value - """ - - def __init__(self, value, typ): - super().__init__(typ) - self.value = value - - def as_operand(self, type_printer): - return self.as_entity(type_printer) - - def as_entity(self, type_printer): - return "{} {}".format(type_printer.name(self.type), - repr(self.value)) - - def __hash__(self): - return hash(self.value) - - def __eq__(self, other): - return isinstance(other, Constant) and \ - other.type == self.type and other.value == self.value - - def __ne__(self, other): - return not (self == other) - -class NamedValue(Value): - """ - An SSA value that has a name. - - :ivar name: (string) name of this value - :ivar function: (:class:`Function`) function containing this value - """ - - def __init__(self, typ, name): - super().__init__(typ) - self.name, self.function = name, None - self.is_removed = False - - def set_name(self, new_name): - if self.function is not None: - self.function._remove_name(self.name) - self.name = self.function._add_name(new_name) - else: - self.name = new_name - - def _set_function(self, new_function): - if self.function != new_function: - if self.function is not None: - self.function._remove_name(self.name) - self.function = new_function - if self.function is not None: - self.name = self.function._add_name(self.name) - - def _detach(self): - self.function = None - - def as_operand(self, type_printer): - return "{} %{}".format(type_printer.name(self.type), - escape_name(self.name)) - -class User(NamedValue): - """ - An SSA value that has operands. - - :ivar operands: (list of :class:`Value`) operands of this value - """ - - def __init__(self, operands, typ, name): - super().__init__(typ, name) - self.operands = [] - self.set_operands(operands) - - def set_operands(self, new_operands): - for operand in set(self.operands): - operand.uses.remove(self) - self.operands = new_operands - for operand in set(self.operands): - operand.uses.add(self) - - def drop_references(self): - self.set_operands([]) - - def replace_uses_of(self, value, replacement): - assert value in self.operands - - for index, operand in enumerate(self.operands): - if operand == value: - self.operands[index] = replacement - - value.uses.remove(self) - replacement.uses.add(self) - -class Instruction(User): - """ - An SSA instruction. - - :ivar loc: (:class:`pythonparser.source.Range` or None) - source location - """ - - def __init__(self, operands, typ, name=""): - assert isinstance(operands, list) - assert isinstance(typ, types.Type) - super().__init__(operands, typ, name) - self.basic_block = None - self.loc = None - - def copy(self, mapper): - self_copy = self.__class__.__new__(self.__class__) - Instruction.__init__(self_copy, list(map(mapper, self.operands)), - self.type, self.name) - self_copy.loc = self.loc - return self_copy - - def set_basic_block(self, new_basic_block): - self.basic_block = new_basic_block - if self.basic_block is not None: - self._set_function(self.basic_block.function) - else: - self._set_function(None) - - def opcode(self): - """String representation of the opcode.""" - return "???" - - def _detach(self): - self.set_basic_block(None) - - def remove_from_parent(self): - if self.basic_block is not None: - self.basic_block.remove(self) - - def erase(self): - self.remove_from_parent() - self.drop_references() - # Check this after drop_references in case this - # is a self-referencing phi. - assert all(use.is_removed for use in self.uses) - - def replace_with(self, value): - self.replace_all_uses_with(value) - if isinstance(value, Instruction): - self.basic_block.replace(self, value) - self.drop_references() - else: - self.erase() - - def _operands_as_string(self, type_printer): - return ", ".join([operand.as_operand(type_printer) for operand in self.operands]) - - def as_entity(self, type_printer): - if builtins.is_none(self.type) and len(self.uses) == 0: - prefix = "" - else: - prefix = "%{} = {} ".format(escape_name(self.name), - type_printer.name(self.type)) - - if any(self.operands): - return "{}{} {}".format(prefix, self.opcode(), - self._operands_as_string(type_printer)) - else: - return "{}{}".format(prefix, self.opcode()) - -class Phi(Instruction): - """ - An SSA instruction that joins data flow. - - Use :meth:`incoming` and :meth:`add_incoming` instead of - directly reading :attr:`operands` or calling :meth:`set_operands`. - """ - - def __init__(self, typ, name=""): - super().__init__([], typ, name) - - def opcode(self): - return "phi" - - def incoming(self): - operand_iter = iter(self.operands) - while True: - try: - yield next(operand_iter), next(operand_iter) - except StopIteration: - return - - def incoming_blocks(self): - return (block for (value, block) in self.incoming()) - - def incoming_values(self): - return (value for (value, block) in self.incoming()) - - def incoming_value_for_block(self, target_block): - for (value, block) in self.incoming(): - if block == target_block: - return value - assert False - - def add_incoming(self, value, block): - assert value.type == self.type - self.operands.append(value) - value.uses.add(self) - self.operands.append(block) - block.uses.add(self) - - def remove_incoming_value(self, value): - index = self.operands.index(value) - assert index % 2 == 0 - self.operands[index].uses.remove(self) - self.operands[index + 1].uses.remove(self) - del self.operands[index:index + 2] - - def remove_incoming_block(self, block): - index = self.operands.index(block) - assert index % 2 == 1 - self.operands[index - 1].uses.remove(self) - self.operands[index].uses.remove(self) - del self.operands[index - 1:index + 1] - - def as_entity(self, type_printer): - if builtins.is_none(self.type): - prefix = "" - else: - prefix = "%{} = {} ".format(escape_name(self.name), - type_printer.name(self.type)) - - if any(self.operands): - operand_list = ["%{} => {}".format(escape_name(block.name), - value.as_operand(type_printer)) - for value, block in self.incoming()] - return "{}{} [{}]".format(prefix, self.opcode(), ", ".join(operand_list)) - else: - return "{}{} [???]".format(prefix, self.opcode()) - -class Terminator(Instruction): - """ - An SSA instruction that performs control flow. - """ - - def successors(self): - return [operand for operand in self.operands if isinstance(operand, BasicBlock)] - -class BasicBlock(NamedValue): - """ - A block of instructions with no control flow inside it. - - :ivar instructions: (list of :class:`Instruction`) - """ - _dump_loc = True - - def __init__(self, instructions, name=""): - super().__init__(TBasicBlock(), name) - self.instructions = [] - self.set_instructions(instructions) - - def set_instructions(self, new_insns): - for insn in self.instructions: - insn.detach() - self.instructions = new_insns - for insn in self.instructions: - insn.set_basic_block(self) - - def remove_from_parent(self): - if self.function is not None: - self.function.remove(self) - - def erase(self): - # self.instructions is updated while iterating - for insn in reversed(self.instructions): - insn.erase() - self.remove_from_parent() - # Check this after erasing instructions in case the block - # loops into itself. - assert all(use.is_removed for use in self.uses) - - def prepend(self, insn): - assert isinstance(insn, Instruction) - insn.set_basic_block(self) - self.instructions.insert(0, insn) - return insn - - def append(self, insn): - assert isinstance(insn, Instruction) - insn.set_basic_block(self) - self.instructions.append(insn) - return insn - - def index(self, insn): - return self.instructions.index(insn) - - def insert(self, insn, before): - assert isinstance(insn, Instruction) - insn.set_basic_block(self) - self.instructions.insert(self.index(before), insn) - return insn - - def remove(self, insn): - assert insn in self.instructions - insn._detach() - self.instructions.remove(insn) - return insn - - def replace(self, insn, replacement): - self.insert(replacement, before=insn) - self.remove(insn) - - def is_terminated(self): - return any(self.instructions) and isinstance(self.instructions[-1], Terminator) - - def terminator(self): - assert self.is_terminated() - return self.instructions[-1] - - def successors(self): - return self.terminator().successors() - - def predecessors(self): - return [use.basic_block for use in self.uses if isinstance(use, Terminator)] - - def as_entity(self, type_printer): - # Header - lines = ["{}:".format(escape_name(self.name))] - if self.function is not None: - lines[0] += " ; predecessors: {}".format( - ", ".join(sorted([escape_name(pred.name) for pred in self.predecessors()]))) - - # Annotated instructions - loc = None - for insn in self.instructions: - if self._dump_loc and loc != insn.loc: - loc = insn.loc - - if loc is None: - lines.append("; ") - else: - source_lines = loc.source_lines() - beg_col, end_col = loc.column(), loc.end().column() - source_lines[-1] = \ - source_lines[-1][:end_col] + "\x1b[0m" + source_lines[-1][end_col:] - source_lines[0] = \ - source_lines[0][:beg_col] + "\x1b[1;32m" + source_lines[0][beg_col:] - - line_desc = "{}:{}".format(loc.source_buffer.name, loc.line()) - lines += ["; {} {}".format(line_desc, line.rstrip("\n")) - for line in source_lines] - lines.append(" " + insn.as_entity(type_printer)) - - return "\n".join(lines) - - def __repr__(self): - return "".format(repr(self.name)) - -class Argument(NamedValue): - """ - A function argument. - - :ivar loc: (:class:`pythonparser.source.Range` or None) - source location - """ - def __init__(self, typ, name): - super().__init__(typ, name) - self.loc = None - - def as_entity(self, type_printer): - return self.as_operand(type_printer) - -class Function: - """ - A function containing SSA IR. - - :ivar loc: (:class:`pythonparser.source.Range` or None) - source location of function definition - :ivar is_internal: - (bool) if True, the function should not be accessible from outside - the module it is contained in - :ivar is_cold: - (bool) if True, the function should be considered rarely called - :ivar is_generated: - (bool) if True, the function will not appear in backtraces - :ivar flags: (set of str) Code generation flags. - Flag ``fast-math`` is the equivalent of gcc's ``-ffast-math``. - """ - - def __init__(self, typ, name, arguments, loc=None): - self.type, self.name, self.loc = typ, name, loc - self.names, self.arguments, self.basic_blocks = set(), [], [] - self.next_name = 1 - self.set_arguments(arguments) - self.is_internal = False - self.is_cold = False - self.is_generated = False - self.flags = {} - - def _remove_name(self, name): - self.names.remove(name) - - def _add_name(self, base_name): - if base_name == "": - name = "UNN.{}".format(self.next_name) - self.next_name += 1 - elif base_name in self.names: - name = "{}.{}".format(base_name, self.next_name) - self.next_name += 1 - else: - name = base_name - - self.names.add(name) - return name - - def set_arguments(self, new_arguments): - for argument in self.arguments: - argument._set_function(None) - self.arguments = new_arguments - for argument in self.arguments: - argument._set_function(self) - - def add(self, basic_block): - basic_block._set_function(self) - self.basic_blocks.append(basic_block) - - def remove(self, basic_block): - basic_block._detach() - self.basic_blocks.remove(basic_block) - - def entry(self): - assert any(self.basic_blocks) - return self.basic_blocks[0] - - def exits(self): - return [block for block in self.basic_blocks if not any(block.successors())] - - def instructions(self): - for basic_block in self.basic_blocks: - yield from iter(basic_block.instructions) - - def as_entity(self, type_printer): - postorder = [] - visited = set() - def visit(block): - visited.add(block) - for next_block in block.successors(): - if next_block not in visited: - visit(next_block) - postorder.append(block) - - visit(self.entry()) - - lines = [] - lines.append("{} {}({}) {{ ; type: {}".format( - type_printer.name(self.type.ret), self.name, - ", ".join([arg.as_operand(type_printer) for arg in self.arguments]), - type_printer.name(self.type))) - - postorder_blocks = list(reversed(postorder)) - orphan_blocks = [block for block in self.basic_blocks if block not in postorder] - for block in postorder_blocks + orphan_blocks: - lines.append(block.as_entity(type_printer)) - - lines.append("}") - return "\n".join(lines) - - def __str__(self): - return self.as_entity(types.TypePrinter()) - -# Python-specific SSA IR classes - -class TEnvironment(types.TMono): - def __init__(self, name, vars, outer=None): - assert isinstance(name, str) - self.env_name = name # for readable type names in LLVM IR - - if outer is not None: - assert isinstance(outer, TEnvironment) - env = OrderedDict({"$outer": outer}) - env.update(vars) - else: - env = OrderedDict(vars) - - super().__init__("environment", env) - - def type_of(self, name): - if name in self.params: - return self.params[name].find() - elif "$outer" in self.params: - return self.params["$outer"].type_of(name) - else: - assert False - - def outermost(self): - if "$outer" in self.params: - return self.params["$outer"].outermost() - else: - return self - - """ - Add a new binding, ensuring hygiene. - - :returns: (string) mangled name - """ - def add(self, base_name, typ): - name, counter = base_name, 1 - while name in self.params or name == "": - if base_name == "": - name = str(counter) - else: - name = "{}.{}".format(name, counter) - counter += 1 - - self.params[name] = typ.find() - return name - -def is_environment(typ): - return isinstance(typ, TEnvironment) - -class EnvironmentArgument(Argument): - """ - A function argument specifying an outer environment. - """ - - def as_operand(self, type_printer): - return "environment(...) %{}".format(escape_name(self.name)) - -class Alloc(Instruction): - """ - An instruction that allocates an object specified by - the type of the intsruction. - """ - - def __init__(self, operands, typ, name=""): - for operand in operands: assert isinstance(operand, Value) - super().__init__(operands, typ, name) - - def opcode(self): - return "alloc" - - def as_operand(self, type_printer): - if is_environment(self.type): - # Only show full environment in the instruction itself - return "%{}".format(escape_name(self.name)) - else: - return super().as_operand(type_printer) - -class GetLocal(Instruction): - """ - An intruction that loads a local variable from an environment, - possibly going through multiple levels of indirection. - - :ivar var_name: (string) variable name - """ - - """ - :param env: (:class:`Value`) local environment - :param var_name: (string) local variable name - """ - def __init__(self, env, var_name, name=""): - assert isinstance(env, Value) - assert isinstance(env.type, TEnvironment) - assert isinstance(var_name, str) - super().__init__([env], env.type.type_of(var_name), name) - self.var_name = var_name - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.var_name = self.var_name - return self_copy - - def opcode(self): - return "getlocal({})".format(repr(self.var_name)) - - def environment(self): - return self.operands[0] - -class SetLocal(Instruction): - """ - An intruction that stores a local variable into an environment, - possibly going through multiple levels of indirection. - - :ivar var_name: (string) variable name - """ - - """ - :param env: (:class:`Value`) local environment - :param var_name: (string) local variable name - :param value: (:class:`Value`) value to assign - """ - def __init__(self, env, var_name, value, name=""): - assert isinstance(env, Value) - assert isinstance(env.type, TEnvironment) - assert isinstance(var_name, str) - assert env.type.type_of(var_name) == value.type - assert isinstance(value, Value) - super().__init__([env, value], builtins.TNone(), name) - self.var_name = var_name - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.var_name = self.var_name - return self_copy - - def opcode(self): - return "setlocal({})".format(repr(self.var_name)) - - def environment(self): - return self.operands[0] - - def value(self): - return self.operands[1] - -class GetArgFromRemote(Instruction): - """ - An instruction that receives function arguments from remote - (ie. subkernel in DRTIO context) - - :ivar arg_name: (string) argument name - :ivar arg_type: argument type - """ - - """ - :param arg_name: (string) argument name - :param arg_type: argument type - """ - def __init__(self, arg_name, arg_type, name=""): - assert isinstance(arg_name, str) - super().__init__([], arg_type, name) - self.arg_name = arg_name - self.arg_type = arg_type - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.arg_name = self.arg_name - self_copy.arg_type = self.arg_type - return self_copy - - def opcode(self): - return "getargfromremote({})".format(repr(self.arg_name)) - -class GetOptArgFromRemote(GetArgFromRemote): - """ - An instruction that may or may not retrieve an optional function argument - from remote, depending on number of values received by firmware. - - :ivar rcv_count: number of received values, - determined by firmware - :ivar index: (integer) index of the current argument, - in reference to remote arguments - """ - - """ - :param rcv_count: number of received valuese - :param index: (integer) index of the current argument, - in reference to remote arguments - """ - def __init__(self, arg_name, arg_type, rcv_count, index, name=""): - super().__init__(arg_name, arg_type, name) - self.rcv_count = rcv_count - self.index = index - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.rcv_count = self.rcv_count - self_copy.index = self.index - return self_copy - - def opcode(self): - return "getoptargfromremote({})".format(repr(self.arg_name)) - -class SubkernelAwaitArgs(Instruction): - """ - A builtin instruction that takes min and max received messages as operands, - and a list of received types. - - :ivar arg_types: (list of types) types of passed arguments (including optional) - """ - - """ - :param arg_types: (list of types) types of passed arguments (including optional) - """ - - def __init__(self, operands, arg_types, name=None): - assert isinstance(arg_types, list) - self.arg_types = arg_types - super().__init__(operands, builtins.TNone(), name) - -class GetAttr(Instruction): - """ - An intruction that loads an attribute from an object, - or extracts a tuple element. - - :ivar attr: (string) variable name - """ - - """ - :param obj: (:class:`Value`) object or tuple - :param attr: (string or integer) attribute or index - """ - def __init__(self, obj, attr, name=""): - assert isinstance(obj, Value) - assert isinstance(attr, (str, int)) - if isinstance(attr, int): - assert isinstance(obj.type, types.TTuple) - typ = obj.type.elts[attr] - elif attr in obj.type.attributes: - typ = obj.type.attributes[attr] - else: - typ = obj.type.constructor.attributes[attr] - if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(typ): - typ = types.TMethod(obj.type, typ) - super().__init__([obj], typ, name) - self.attr = attr - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.attr = self.attr - return self_copy - - def opcode(self): - return "getattr({})".format(repr(self.attr)) - - def object(self): - return self.operands[0] - -class SetAttr(Instruction): - """ - An intruction that stores an attribute to an object. - - :ivar attr: (string) variable name - """ - - """ - :param obj: (:class:`Value`) object or tuple - :param attr: (string or integer) attribute - :param value: (:class:`Value`) value to store - """ - def __init__(self, obj, attr, value, name=""): - assert isinstance(obj, Value) - assert isinstance(attr, (str, int)) - assert isinstance(value, Value) - if isinstance(attr, int): - assert value.type == obj.type.elts[attr].find() - else: - assert value.type == obj.type.attributes[attr].find() - super().__init__([obj, value], builtins.TNone(), name) - self.attr = attr - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.attr = self.attr - return self_copy - - def opcode(self): - return "setattr({})".format(repr(self.attr)) - - def object(self): - return self.operands[0] - - def value(self): - return self.operands[1] - -class Offset(Instruction): - """ - An intruction that adds an offset to a pointer (indexes into a list). - - This is used to represent internally generated pointer arithmetic, and must - remain inside the same object (see :class:`GetElem` and LLVM's GetElementPtr). - """ - - """ - :param lst: (:class:`Value`) list - :param index: (:class:`Value`) index - """ - def __init__(self, base, offset, name=""): - assert isinstance(base, Value) - assert isinstance(offset, Value) - typ = types._TPointer(builtins.get_iterable_elt(base.type)) - super().__init__([base, offset], typ, name) - - def opcode(self): - return "offset" - - def base(self): - return self.operands[0] - - def index(self): - return self.operands[1] - -class GetElem(Instruction): - """ - An intruction that loads an element from a list. - """ - - """ - :param lst: (:class:`Value`) list - :param index: (:class:`Value`) index - """ - def __init__(self, lst, index, name=""): - assert isinstance(lst, Value) - assert isinstance(index, Value) - super().__init__([lst, index], builtins.get_iterable_elt(lst.type), name) - - def opcode(self): - return "getelem" - - def base(self): - return self.operands[0] - - def index(self): - return self.operands[1] - -class SetElem(Instruction): - """ - An intruction that stores an element into a list. - """ - - """ - :param lst: (:class:`Value`) list - :param index: (:class:`Value`) index - :param value: (:class:`Value`) value to store - """ - def __init__(self, lst, index, value, name=""): - assert isinstance(lst, Value) - assert isinstance(index, Value) - assert isinstance(value, Value) - assert builtins.get_iterable_elt(lst.type) == value.type.find() - super().__init__([lst, index, value], builtins.TNone(), name) - - def opcode(self): - return "setelem" - - def base(self): - return self.operands[0] - - def index(self): - return self.operands[1] - - def value(self): - return self.operands[2] - -class Coerce(Instruction): - """ - A coercion operation for numbers. - """ - - def __init__(self, value, typ, name=""): - assert isinstance(value, Value) - assert isinstance(typ, types.Type) - super().__init__([value], typ, name) - - def opcode(self): - return "coerce" - - def value(self): - return self.operands[0] - -class Arith(Instruction): - """ - An arithmetic operation on numbers. - - :ivar op: (:class:`pythonparser.ast.operator`) operation - """ - - """ - :param op: (:class:`pythonparser.ast.operator`) operation - :param lhs: (:class:`Value`) left-hand operand - :param rhs: (:class:`Value`) right-hand operand - """ - def __init__(self, op, lhs, rhs, name=""): - assert isinstance(op, ast.operator) - assert isinstance(lhs, Value) - assert isinstance(rhs, Value) - assert lhs.type == rhs.type - super().__init__([lhs, rhs], lhs.type, name) - self.op = op - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.op = self.op - return self_copy - - def opcode(self): - return "arith({})".format(type(self.op).__name__) - - def lhs(self): - return self.operands[0] - - def rhs(self): - return self.operands[1] - - -class Compare(Instruction): - """ - A comparison operation on numbers. - - :ivar op: (:class:`pythonparser.ast.cmpop`) operation - """ - - """ - :param op: (:class:`pythonparser.ast.cmpop`) operation - :param lhs: (:class:`Value`) left-hand operand - :param rhs: (:class:`Value`) right-hand operand - """ - def __init__(self, op, lhs, rhs, name=""): - assert isinstance(op, ast.cmpop) - assert isinstance(lhs, Value) - assert isinstance(rhs, Value) - assert lhs.type == rhs.type - super().__init__([lhs, rhs], builtins.TBool(), name) - self.op = op - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.op = self.op - return self_copy - - def opcode(self): - return "compare({})".format(type(self.op).__name__) - - def lhs(self): - return self.operands[0] - - def rhs(self): - return self.operands[1] - -class Builtin(Instruction): - """ - A builtin operation. Similar to a function call that - never raises. - - :ivar op: (string) operation name - """ - - """ - :param op: (string) operation name - """ - def __init__(self, op, operands, typ, name=None): - assert isinstance(op, str) - for operand in operands: assert isinstance(operand, Value) - if name is None: - name = "BLT.{}".format(op) - super().__init__(operands, typ, name) - self.op = op - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.op = self.op - return self_copy - - def opcode(self): - return "builtin({})".format(self.op) - -class BuiltinInvoke(Terminator): - """ - A builtin operation which can raise exceptions. - - :ivar op: (string) operation name - """ - - """ - :param op: (string) operation name - :param normal: (:class:`BasicBlock`) normal target - :param exn: (:class:`BasicBlock`) exceptional target - """ - def __init__(self, op, operands, typ, normal, exn, name=None): - assert isinstance(op, str) - for operand in operands: assert isinstance(operand, Value) - assert isinstance(normal, BasicBlock) - assert isinstance(exn, BasicBlock) - if name is None: - name = "BLTINV.{}".format(op) - super().__init__(operands + [normal, exn], typ, name) - self.op = op - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.op = self.op - return self_copy - - def normal_target(self): - return self.operands[-2] - - def exception_target(self): - return self.operands[-1] - - def opcode(self): - return "builtinInvokable({})".format(self.op) - -class Closure(Instruction): - """ - A closure creation operation. - - :ivar target_function: (:class:`Function`) function to invoke - """ - - """ - :param func: (:class:`Function`) function - :param env: (:class:`Value`) outer environment - """ - def __init__(self, func, env, name=""): - assert isinstance(func, Function) - assert isinstance(env, Value) - assert is_environment(env.type) - super().__init__([env], func.type, name) - self.target_function = func - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.target_function = self.target_function - return self_copy - - def opcode(self): - return "closure({})".format(self.target_function.name) - - def environment(self): - return self.operands[0] - -class Call(Instruction): - """ - A function call operation. - - :ivar arg_exprs: (dict of str to `iodelay.Expr`) - iodelay expressions for values of arguments - :ivar static_target_function: (:class:`Function` or None) - statically resolved callee - :ivar is_cold: (bool) - the callee function is cold - """ - - """ - :param func: (:class:`Value`) function to call - :param args: (list of :class:`Value`) function arguments - :param arg_exprs: (dict of str to `iodelay.Expr`) - """ - def __init__(self, func, args, arg_exprs, name=""): - assert isinstance(func, Value) - for arg in args: assert isinstance(arg, Value) - for arg in arg_exprs: - assert isinstance(arg, str) - assert isinstance(arg_exprs[arg], iodelay.Expr) - super().__init__([func] + args, func.type.ret, name) - self.arg_exprs = arg_exprs - self.static_target_function = None - self.is_cold = False - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.arg_exprs = self.arg_exprs - self_copy.static_target_function = self.static_target_function - return self_copy - - def opcode(self): - return "call" - - def target_function(self): - return self.operands[0] - - def arguments(self): - return self.operands[1:] - - def as_entity(self, type_printer): - result = super().as_entity(type_printer) - if self.static_target_function is not None: - result += " ; calls {}".format(self.static_target_function.name) - return result - -class Select(Instruction): - """ - A conditional select instruction. - """ - - """ - :param cond: (:class:`Value`) select condition - :param if_true: (:class:`Value`) value of select if condition is truthful - :param if_false: (:class:`Value`) value of select if condition is falseful - """ - def __init__(self, cond, if_true, if_false, name=""): - assert isinstance(cond, Value) - assert builtins.is_bool(cond.type) - assert isinstance(if_true, Value) - assert isinstance(if_false, Value) - assert if_true.type == if_false.type - super().__init__([cond, if_true, if_false], if_true.type, name) - - def opcode(self): - return "select" - - def condition(self): - return self.operands[0] - - def if_true(self): - return self.operands[1] - - def if_false(self): - return self.operands[2] - -class Quote(Instruction): - """ - A quote operation. Returns a host interpreter value as a constant. - - :ivar value: (string) operation name - """ - - """ - :param value: (string) operation name - """ - def __init__(self, value, typ, name=""): - super().__init__([], typ, name) - self.value = value - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.value = self.value - return self_copy - - def opcode(self): - return "quote({})".format(repr(self.value)) - -class Branch(Terminator): - """ - An unconditional branch instruction. - """ - - """ - :param target: (:class:`BasicBlock`) branch target - """ - def __init__(self, target, name=""): - assert isinstance(target, BasicBlock) - super().__init__([target], builtins.TNone(), name) - - def opcode(self): - return "branch" - - def target(self): - return self.operands[0] - - def set_target(self, new_target): - self.operands[0].uses.remove(self) - self.operands[0] = new_target - self.operands[0].uses.add(self) - -class BranchIf(Terminator): - """ - A conditional branch instruction. - """ - - """ - :param cond: (:class:`Value`) branch condition - :param if_true: (:class:`BasicBlock`) branch target if condition is truthful - :param if_false: (:class:`BasicBlock`) branch target if condition is falseful - """ - def __init__(self, cond, if_true, if_false, name=""): - assert isinstance(cond, Value) - assert builtins.is_bool(cond.type) - assert isinstance(if_true, BasicBlock) - assert isinstance(if_false, BasicBlock) - assert if_true != if_false # use Branch instead - super().__init__([cond, if_true, if_false], builtins.TNone(), name) - - def opcode(self): - return "branchif" - - def condition(self): - return self.operands[0] - - def if_true(self): - return self.operands[1] - - def if_false(self): - return self.operands[2] - -class IndirectBranch(Terminator): - """ - An indirect branch instruction. - """ - - """ - :param target: (:class:`Value`) branch target - :param destinations: (list of :class:`BasicBlock`) all possible values of `target` - """ - def __init__(self, target, destinations, name=""): - assert isinstance(target, Value) - assert all([isinstance(dest, BasicBlock) for dest in destinations]) - super().__init__([target] + destinations, builtins.TNone(), name) - - def opcode(self): - return "indirectbranch" - - def target(self): - return self.operands[0] - - def destinations(self): - return self.operands[1:] - - def add_destination(self, destination): - destination.uses.add(self) - self.operands.append(destination) - - def _operands_as_string(self, type_printer): - return "{}, [{}]".format(self.operands[0].as_operand(type_printer), - ", ".join([dest.as_operand(type_printer) - for dest in self.operands[1:]])) - -class Return(Terminator): - """ - A return instruction. - :param remote_return: (bool) - marks a return in subkernel context, - where the return value is sent back through DRTIO - """ - - """ - :param value: (:class:`Value`) return value - """ - def __init__(self, value, remote_return=False, name=""): - assert isinstance(value, Value) - super().__init__([value], builtins.TNone(), name) - self.remote_return = remote_return - - def opcode(self): - return "return" - - def value(self): - return self.operands[0] - -class Unreachable(Terminator): - """ - An instruction used to mark unreachable branches. - """ - - """ - :param target: (:class:`BasicBlock`) branch target - """ - def __init__(self, name=""): - super().__init__([], builtins.TNone(), name) - - def opcode(self): - return "unreachable" - -class Raise(Terminator): - """ - A raise instruction. - """ - - """ - :param value: (:class:`Value`) exception value - :param exn: (:class:`BasicBlock` or None) exceptional target - """ - def __init__(self, value=None, exn=None, name=""): - assert isinstance(value, Value) - operands = [value] - if exn is not None: - assert isinstance(exn, BasicBlock) - operands.append(exn) - super().__init__(operands, builtins.TNone(), name) - - def opcode(self): - return "raise" - - def value(self): - return self.operands[0] - - def exception_target(self): - if len(self.operands) > 1: - return self.operands[1] - -class Resume(Terminator): - """ - A resume instruction. - """ - - """ - :param exn: (:class:`BasicBlock` or None) exceptional target - """ - def __init__(self, exn=None, name=""): - operands = [] - if exn is not None: - assert isinstance(exn, BasicBlock) - operands.append(exn) - super().__init__(operands, builtins.TNone(), name) - - def opcode(self): - return "resume" - - def exception_target(self): - if len(self.operands) > 0: - return self.operands[0] - -class Invoke(Terminator): - """ - A function call operation that supports exception handling. - - :ivar arg_exprs: (dict of str to `iodelay.Expr`) - iodelay expressions for values of arguments - :ivar static_target_function: (:class:`Function` or None) - statically resolved callee - :ivar is_cold: (bool) - the callee function is cold - """ - - """ - :param func: (:class:`Value`) function to call - :param args: (list of :class:`Value`) function arguments - :param normal: (:class:`BasicBlock`) normal target - :param exn: (:class:`BasicBlock`) exceptional target - :param arg_exprs: (dict of str to `iodelay.Expr`) - """ - def __init__(self, func, args, arg_exprs, normal, exn, name=""): - assert isinstance(func, Value) - for arg in args: assert isinstance(arg, Value) - assert isinstance(normal, BasicBlock) - assert isinstance(exn, BasicBlock) - for arg in arg_exprs: - assert isinstance(arg, str) - assert isinstance(arg_exprs[arg], iodelay.Expr) - super().__init__([func] + args + [normal, exn], func.type.ret, name) - self.arg_exprs = arg_exprs - self.static_target_function = None - self.is_cold = False - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.arg_exprs = self.arg_exprs - self_copy.static_target_function = self.static_target_function - return self_copy - - def opcode(self): - return "invoke" - - def target_function(self): - return self.operands[0] - - def arguments(self): - return self.operands[1:-2] - - def normal_target(self): - return self.operands[-2] - - def exception_target(self): - return self.operands[-1] - - def _operands_as_string(self, type_printer): - result = ", ".join([operand.as_operand(type_printer) for operand in self.operands[:-2]]) - result += " to {} unwind {}".format(self.operands[-2].as_operand(type_printer), - self.operands[-1].as_operand(type_printer)) - return result - - def as_entity(self, type_printer): - result = super().as_entity(type_printer) - if self.static_target_function is not None: - result += " ; calls {}".format(self.static_target_function.name) - return result - -class LandingPad(Terminator): - """ - An instruction that gives an incoming exception a name and - dispatches it according to its type. - - Once dispatched, the exception should be cast to its proper - type by calling the "exncast" builtin on the landing pad value. - - :ivar types: (a list of :class:`builtins.TException`) - exception types corresponding to the basic block operands - """ - - def __init__(self, cleanup, name=""): - super().__init__([cleanup], builtins.TException(), name) - self.types = [] - self.has_cleanup = True - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.types = list(self.types) - return self_copy - - def opcode(self): - return "landingpad" - - def cleanup(self): - return self.operands[0] - - def clauses(self): - return zip(self.operands[1:], self.types) - - def add_clause(self, target, typ): - assert isinstance(target, BasicBlock) - assert typ is None or builtins.is_exception(typ) - self.operands.append(target) - self.types.append(typ.find() if typ is not None else None) - target.uses.add(self) - - def _operands_as_string(self, type_printer): - table = [] - for target, typ in self.clauses(): - if typ is None: - table.append("... => {}".format(target.as_operand(type_printer))) - else: - table.append("{} => {}".format(type_printer.name(typ), - target.as_operand(type_printer))) - return "cleanup {}, [{}]".format(self.cleanup().as_operand(type_printer), - ", ".join(table)) - -class Delay(Terminator): - """ - A delay operation. Ties an :class:`iodelay.Expr` to SSA values so that - inlining could lead to the expression folding to a constant. - - :ivar interval: (:class:`iodelay.Expr`) expression - """ - - """ - :param interval: (:class:`iodelay.Expr`) expression - :param call: (:class:`Call` or ``Constant(None, builtins.TNone())``) - the call instruction that caused this delay, if any - :param target: (:class:`BasicBlock`) branch target - """ - def __init__(self, interval, decomposition, target, name=""): - assert isinstance(decomposition, Call) or isinstance(decomposition, Invoke) or \ - isinstance(decomposition, Builtin) and decomposition.op in ("delay", "delay_mu") - assert isinstance(target, BasicBlock) - super().__init__([decomposition, target], builtins.TNone(), name) - self.interval = interval - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.interval = self.interval - return self_copy - - def decomposition(self): - return self.operands[0] - - def set_decomposition(self, new_decomposition): - self.operands[0].uses.remove(self) - self.operands[0] = new_decomposition - self.operands[0].uses.add(self) - - def target(self): - return self.operands[1] - - def set_target(self, new_target): - self.operands[1].uses.remove(self) - self.operands[1] = new_target - self.operands[1].uses.add(self) - - def _operands_as_string(self, type_printer): - result = "decomp {}, to {}".format(self.decomposition().as_operand(type_printer), - self.target().as_operand(type_printer)) - return result - - def opcode(self): - return "delay({})".format(self.interval) - -class Loop(Terminator): - """ - A terminator for loop headers that carries metadata useful - for unrolling. It includes an :class:`iodelay.Expr` specifying - the trip count, tied to SSA values so that inlining could lead - to the expression folding to a constant. - - :ivar trip_count: (:class:`iodelay.Expr`) - expression for trip count - """ - - """ - :param trip_count: (:class:`iodelay.Expr`) expression - :param indvar: (:class:`Phi`) - phi node corresponding to the induction SSA value, - which advances from ``0`` to ``trip_count - 1`` - :param cond: (:class:`Value`) branch condition - :param if_true: (:class:`BasicBlock`) branch target if condition is truthful - :param if_false: (:class:`BasicBlock`) branch target if condition is falseful - """ - def __init__(self, trip_count, indvar, cond, if_true, if_false, name=""): - assert isinstance(indvar, Phi) - assert isinstance(cond, Value) - assert builtins.is_bool(cond.type) - assert isinstance(if_true, BasicBlock) - assert isinstance(if_false, BasicBlock) - super().__init__([indvar, cond, if_true, if_false], builtins.TNone(), name) - self.trip_count = trip_count - - def copy(self, mapper): - self_copy = super().copy(mapper) - self_copy.trip_count = self.trip_count - return self_copy - - def induction_variable(self): - return self.operands[0] - - def condition(self): - return self.operands[1] - - def if_true(self): - return self.operands[2] - - def if_false(self): - return self.operands[3] - - def _operands_as_string(self, type_printer): - result = "indvar {}, if {}, {}, {}".format( - *list(map(lambda value: value.as_operand(type_printer), self.operands))) - return result - - def opcode(self): - return "loop({} times)".format(self.trip_count) - -class Interleave(Terminator): - """ - An instruction that schedules several threads of execution - in parallel. - """ - - def __init__(self, destinations, name=""): - super().__init__(destinations, builtins.TNone(), name) - - def opcode(self): - return "interleave" - - def destinations(self): - return self.operands - - def add_destination(self, destination): - destination.uses.add(self) - self.operands.append(destination) diff --git a/artiq/compiler/kernel.ld b/artiq/compiler/kernel.ld deleted file mode 100644 index bf080c37a..000000000 --- a/artiq/compiler/kernel.ld +++ /dev/null @@ -1,70 +0,0 @@ -/* Force ld to make the ELF header as loadable. */ -PHDRS -{ - headers PT_LOAD FILEHDR PHDRS ; - text PT_LOAD ; - data PT_LOAD ; - dynamic PT_DYNAMIC ; - eh_frame PT_GNU_EH_FRAME ; -} - -SECTIONS -{ - /* Push back .text section enough so that ld.lld not complain */ - . = SIZEOF_HEADERS; - - .text : - { - *(.text .text.*) - } : text - - .rodata : - { - *(.rodata .rodata.*) - } - - .eh_frame : - { - KEEP(*(.eh_frame)) - } : text - - .eh_frame_hdr : - { - KEEP(*(.eh_frame_hdr)) - } : text : eh_frame - - .got : - { - *(.got) - } : text - - .got.plt : - { - *(.got.plt) - } : text - - .data : - { - *(.data .data.*) - } : data - - .dynamic : - { - *(.dynamic) - } : data : dynamic - - .bss (NOLOAD) : ALIGN(4) - { - __bss_start = .; - *(.sbss .sbss.* .bss .bss.*); - . = ALIGN(4); - _end = .; - } - - /* Kernel stack grows downward from end of memory, so put guard page after - * all the program contents. Note: This requires all loaded sections (at - * least those accessed) to be explicitly listed in the above! - */ - . = ALIGN(0x1000); - _sstack_guard = .; -} diff --git a/artiq/compiler/math_fns.py b/artiq/compiler/math_fns.py deleted file mode 100644 index 1a815b2dd..000000000 --- a/artiq/compiler/math_fns.py +++ /dev/null @@ -1,116 +0,0 @@ -r""" -The :mod:`math_fns` module lists math-related functions from NumPy recognized -by the ARTIQ compiler so host function objects can be :func:`match`\ ed to -the compiler type metadata describing their core device analogue. -""" - -from collections import OrderedDict -import numpy -from . import builtins, types - -# Some special mathematical functions are exposed via their scipy.special -# equivalents. Since the rest of the ARTIQ core does not depend on SciPy, -# gracefully handle it not being present, making the functions simply not -# available. -try: - import scipy.special as scipy_special -except ImportError: - scipy_special = None - -#: float -> float numpy.* math functions for which llvm.* intrinsics exist. -unary_fp_intrinsics = [(name, "llvm." + name + ".f64") for name in [ - "sin", - "cos", - "exp", - "exp2", - "log", - "log10", - "log2", - "fabs", - "floor", - "ceil", - "trunc", - "sqrt", -]] + [ - # numpy.rint() seems to (NumPy 1.19.0, Python 3.8.5, Linux x86_64) - # implement round-to-even, but unfortunately, rust-lang/libm only - # provides round(), which always rounds away from zero. - # - # As there is no equivalent of the latter in NumPy (nor any other - # basic rounding function), expose round() as numpy.rint anyway, - # even if the rounding modes don't match up, so there is some way - # to do rounding on the core device. (numpy.round() has entirely - # different semantics; it rounds to a configurable number of - # decimals.) - ("rint", "llvm.round.f64"), -] - -#: float -> float numpy.* math functions lowered to runtime calls. -unary_fp_runtime_calls = [ - ("tan", "tan"), - ("arcsin", "asin"), - ("arccos", "acos"), - ("arctan", "atan"), - ("sinh", "sinh"), - ("cosh", "cosh"), - ("tanh", "tanh"), - ("arcsinh", "asinh"), - ("arccosh", "acosh"), - ("arctanh", "atanh"), - ("expm1", "expm1"), - ("cbrt", "cbrt"), -] - -scipy_special_unary_runtime_calls = [ - ("erf", "erf"), - ("erfc", "erfc"), - ("gamma", "tgamma"), - ("gammaln", "lgamma"), - ("j0", "j0"), - ("j1", "j1"), - ("y0", "y0"), - ("y1", "y1"), -] -# Not mapped: jv/yv, libm only supports integer orders. - -#: (float, float) -> float numpy.* math functions lowered to runtime calls. -binary_fp_runtime_calls = [ - ("arctan2", "atan2"), - ("copysign", "copysign"), - ("fmax", "fmax"), - ("fmin", "fmin"), - # ("ldexp", "ldexp"), # One argument is an int; would need a bit more plumbing. - ("hypot", "hypot"), - ("nextafter", "nextafter"), -] - -#: Array handling builtins (special treatment due to allocations). -numpy_builtins = ["transpose"] - - -def fp_runtime_type(name, arity): - args = [("arg{}".format(i), builtins.TFloat()) for i in range(arity)] - return types.TExternalFunction( - OrderedDict(args), - builtins.TFloat(), - name, - # errno isn't observable from ARTIQ Python. - flags={"nounwind", "nowrite"}, - broadcast_across_arrays=True) - - -math_fn_map = { - getattr(numpy, symbol): fp_runtime_type(mangle, arity=1) - for symbol, mangle in (unary_fp_intrinsics + unary_fp_runtime_calls) -} -for symbol, mangle in binary_fp_runtime_calls: - math_fn_map[getattr(numpy, symbol)] = fp_runtime_type(mangle, arity=2) -for name in numpy_builtins: - math_fn_map[getattr(numpy, name)] = types.TBuiltinFunction("numpy." + name) -if scipy_special is not None: - for symbol, mangle in scipy_special_unary_runtime_calls: - math_fn_map[getattr(scipy_special, symbol)] = fp_runtime_type(mangle, arity=1) - - -def match(obj): - return math_fn_map.get(obj, None) diff --git a/artiq/compiler/module.py b/artiq/compiler/module.py deleted file mode 100644 index cfac4e26e..000000000 --- a/artiq/compiler/module.py +++ /dev/null @@ -1,101 +0,0 @@ -""" -The :class:`Module` class encapsulates a single Python module, -which corresponds to a single ARTIQ translation unit (one LLVM -bitcode file and one object file, unless LTO is used). -A :class:`Module` can be created from a typed AST. - -The :class:`Source` class parses a single source file or -string and infers types for it using a trivial :module:`prelude`. -""" - -import os -from pythonparser import source, diagnostic, parse_buffer -from . import prelude, types, transforms, analyses, validators, embedding - -class Source: - def __init__(self, source_buffer, engine=None): - if engine is None: - self.engine = diagnostic.Engine(all_errors_are_fatal=True) - else: - self.engine = engine - self.embedding_map = embedding.EmbeddingMap() - self.name, _ = os.path.splitext(os.path.basename(source_buffer.name)) - - asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine, - prelude=prelude.globals()) - inferencer = transforms.Inferencer(engine=engine) - - self.parsetree, self.comments = parse_buffer(source_buffer, engine=engine) - self.typedtree = asttyped_rewriter.visit(self.parsetree) - self.globals = asttyped_rewriter.globals - inferencer.visit(self.typedtree) - - @classmethod - def from_string(cls, source_string, name="input.py", first_line=1, engine=None): - return cls(source.Buffer(source_string + "\n", name, first_line), engine=engine) - - @classmethod - def from_filename(cls, filename, engine=None): - with open(filename) as f: - return cls(source.Buffer(f.read(), filename, 1), engine=engine) - -class Module: - def __init__(self, src, ref_period=1e-6, attribute_writeback=True, remarks=False): - self.attribute_writeback = attribute_writeback - self.engine = src.engine - self.embedding_map = src.embedding_map - self.name = src.name - self.globals = src.globals - - int_monomorphizer = transforms.IntMonomorphizer(engine=self.engine) - cast_monomorphizer = transforms.CastMonomorphizer(engine=self.engine) - inferencer = transforms.Inferencer(engine=self.engine) - monomorphism_validator = validators.MonomorphismValidator(engine=self.engine) - escape_validator = validators.EscapeValidator(engine=self.engine) - iodelay_estimator = transforms.IODelayEstimator(engine=self.engine, - ref_period=ref_period) - constness_validator = validators.ConstnessValidator(engine=self.engine) - artiq_ir_generator = transforms.ARTIQIRGenerator(engine=self.engine, - module_name=src.name, - ref_period=ref_period, - embedding_map=self.embedding_map) - dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine) - local_access_validator = validators.LocalAccessValidator(engine=self.engine) - local_demoter = transforms.LocalDemoter() - constant_hoister = transforms.ConstantHoister() - devirtualization = analyses.Devirtualization() - interleaver = transforms.Interleaver(engine=self.engine) - invariant_detection = analyses.InvariantDetection(engine=self.engine) - - int_monomorphizer.visit(src.typedtree) - cast_monomorphizer.visit(src.typedtree) - inferencer.visit(src.typedtree) - monomorphism_validator.visit(src.typedtree) - escape_validator.visit(src.typedtree) - iodelay_estimator.visit_fixpoint(src.typedtree) - constness_validator.visit(src.typedtree) - devirtualization.visit(src.typedtree) - self.artiq_ir = artiq_ir_generator.visit(src.typedtree) - artiq_ir_generator.annotate_calls(devirtualization) - dead_code_eliminator.process(self.artiq_ir) - interleaver.process(self.artiq_ir) - local_access_validator.process(self.artiq_ir) - local_demoter.process(self.artiq_ir) - constant_hoister.process(self.artiq_ir) - if remarks: - invariant_detection.process(self.artiq_ir) - # for subkernels: main kernel inferencer output, to be passed to further compilations - self.subkernel_arg_types = inferencer.subkernel_arg_types - - def build_llvm_ir(self, target): - """Compile the module to LLVM IR for the specified target.""" - llvm_ir_generator = transforms.LLVMIRGenerator( - engine=self.engine, module_name=self.name, target=target, - embedding_map=self.embedding_map) - return llvm_ir_generator.process(self.artiq_ir, - attribute_writeback=self.attribute_writeback) - - def __repr__(self): - printer = types.TypePrinter() - globals = ["%s: %s" % (var, printer.name(self.globals[var])) for var in self.globals] - return "" % (repr(self.name), ",\n ".join(globals)) diff --git a/artiq/compiler/prelude.py b/artiq/compiler/prelude.py deleted file mode 100644 index f96b4d0d7..000000000 --- a/artiq/compiler/prelude.py +++ /dev/null @@ -1,64 +0,0 @@ -""" -The :mod:`prelude` module contains the initial global environment -in which ARTIQ kernels are evaluated. -""" - -from . import builtins - -def globals(): - return { - # Value constructors - "bool": builtins.fn_bool(), - "int": builtins.fn_int(), - "float": builtins.fn_float(), - "str": builtins.fn_str(), - "bytes": builtins.fn_bytes(), - "bytearray": builtins.fn_bytearray(), - "list": builtins.fn_list(), - "array": builtins.fn_array(), - "range": builtins.fn_range(), - "int32": builtins.fn_int32(), - "int64": builtins.fn_int64(), - - # Exception constructors - "Exception": builtins.fn_Exception(), - "IndexError": builtins.fn_IndexError(), - "ValueError": builtins.fn_ValueError(), - "ZeroDivisionError": builtins.fn_ZeroDivisionError(), - "RuntimeError": builtins.fn_RuntimeError(), - - # Built-in Python functions - "len": builtins.fn_len(), - "round": builtins.fn_round(), - "abs": builtins.fn_abs(), - "min": builtins.fn_min(), - "max": builtins.fn_max(), - "print": builtins.fn_print(), - - # ARTIQ decorators - "kernel": builtins.fn_kernel(), - "subkernel": builtins.fn_kernel(), - "portable": builtins.fn_kernel(), - "rpc": builtins.fn_kernel(), - - # ARTIQ context managers - "parallel": builtins.obj_parallel(), - "interleave": builtins.obj_interleave(), - "sequential": builtins.obj_sequential(), - - # ARTIQ time management functions - "delay": builtins.fn_delay(), - "now_mu": builtins.fn_now_mu(), - "delay_mu": builtins.fn_delay_mu(), - "at_mu": builtins.fn_at_mu(), - - # ARTIQ utility functions - "rtio_log": builtins.fn_rtio_log(), - "core_log": builtins.fn_print(), - - # ARTIQ subkernel utility functions - "subkernel_await": builtins.fn_subkernel_await(), - "subkernel_preload": builtins.fn_subkernel_preload(), - "subkernel_send": builtins.fn_subkernel_send(), - "subkernel_recv": builtins.fn_subkernel_recv(), - } diff --git a/artiq/compiler/targets.py b/artiq/compiler/targets.py deleted file mode 100644 index 5f043eb0e..000000000 --- a/artiq/compiler/targets.py +++ /dev/null @@ -1,309 +0,0 @@ -import os, sys, tempfile, subprocess, io -from artiq.compiler import types, ir -from llvmlite import ir as ll, binding as llvm - -llvm.initialize() -llvm.initialize_all_targets() -llvm.initialize_all_asmprinters() - -class RunTool: - def __init__(self, pattern, **tempdata): - self._pattern = pattern - self._tempdata = tempdata - self._tempnames = {} - self._tempfiles = {} - - def __enter__(self): - for key, data in self._tempdata.items(): - if data is None: - fd, filename = tempfile.mkstemp() - os.close(fd) - self._tempnames[key] = filename - else: - with tempfile.NamedTemporaryFile(delete=False) as f: - f.write(data) - self._tempnames[key] = f.name - - cmdline = [] - 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, shell=windows) - stdout, stderr = process.communicate() - if process.returncode != 0: - raise Exception("{} invocation failed: {}". - format(cmdline[0], stderr)) - - self._tempfiles["__stdout__"] = io.StringIO(stdout) - for key in self._tempdata: - if self._tempdata[key] is None: - self._tempfiles[key] = open(self._tempnames[key], "rb") - return self._tempfiles - - def __exit__(self, exc_typ, exc_value, exc_trace): - for file in self._tempfiles.values(): - file.close() - for filename in self._tempnames.values(): - os.unlink(filename) - -def _dump(target, kind, suffix, content): - if target is not None: - print("====== {} DUMP ======".format(kind.upper()), file=sys.stderr) - content_value = content() - if isinstance(content_value, str): - content_value = bytes(content_value, 'utf-8') - if target == "": - file = tempfile.NamedTemporaryFile(suffix=suffix, delete=False) - else: - file = open(target + suffix, "wb") - file.write(content_value) - file.close() - print("{} dumped as {}".format(kind, file.name), file=sys.stderr) - -class Target: - """ - A description of the target environment where the binaries - generated by the ARTIQ compiler will be deployed. - - :var triple: (string) - 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) - LLVM target CPU features, e.g. ``["mul", "div", "ffl1"]`` - :var additional_linker_options: (list of string) - Linker options for the target in addition to the target-independent ones, e.g. ``["--target2=rel"]`` - :var print_function: (string) - Name of a formatted print functions (with the signature of ``printf``) - provided by the target, e.g. ``"printf"``. - :var now_pinning: (boolean) - Whether the target implements the now-pinning RTIO optimization. - """ - triple = "unknown" - data_layout = "" - features = [] - additional_linker_options = [] - print_function = "printf" - now_pinning = True - - tool_ld = "ld.lld" - tool_strip = "llvm-strip" - tool_symbolizer = "llvm-symbolizer" - tool_cxxfilt = "llvm-cxxfilt" - - def __init__(self, subkernel_id=None): - self.llcontext = ll.Context() - self.subkernel_id = subkernel_id - - def target_machine(self): - 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", - abiname="ilp32d" if isinstance(self, RV32GTarget) else "") - llmachine.set_asm_verbosity(True) - return llmachine - - def optimize(self, llmodule): - llpassmgr = llvm.create_module_pass_manager() - - # Register our alias analysis passes. - llpassmgr.add_basic_alias_analysis_pass() - llpassmgr.add_type_based_alias_analysis_pass() - - # Start by cleaning up after our codegen and exposing as much - # information to LLVM as possible. - llpassmgr.add_constant_merge_pass() - llpassmgr.add_cfg_simplification_pass() - llpassmgr.add_instruction_combining_pass() - llpassmgr.add_sroa_pass() - llpassmgr.add_dead_code_elimination_pass() - llpassmgr.add_function_attrs_pass() - llpassmgr.add_global_optimizer_pass() - - # Now, actually optimize the code. - llpassmgr.add_function_inlining_pass(275) - llpassmgr.add_ipsccp_pass() - llpassmgr.add_instruction_combining_pass() - llpassmgr.add_gvn_pass() - llpassmgr.add_cfg_simplification_pass() - llpassmgr.add_licm_pass() - - # Clean up after optimizing. - llpassmgr.add_dead_arg_elimination_pass() - llpassmgr.add_global_dce_pass() - - llpassmgr.run(llmodule) - - def compile(self, module): - """Compile the module to a relocatable object for this target.""" - - if os.getenv("ARTIQ_DUMP_SIG"): - print("====== MODULE_SIGNATURE DUMP ======", file=sys.stderr) - print(module, file=sys.stderr) - - if os.getenv("ARTIQ_IR_NO_LOC") is not None: - ir.BasicBlock._dump_loc = False - - type_printer = types.TypePrinter() - suffix = "_subkernel_{}".format(self.subkernel_id) if self.subkernel_id is not None else "" - _dump(os.getenv("ARTIQ_DUMP_IR"), "ARTIQ IR", suffix + ".txt", - lambda: "\n".join(fn.as_entity(type_printer) for fn in module.artiq_ir)) - - llmod = module.build_llvm_ir(self) - - try: - llparsedmod = llvm.parse_assembly(str(llmod)) - llparsedmod.verify() - except RuntimeError: - _dump("", "LLVM IR (broken)", ".ll", lambda: str(llmod)) - raise - - _dump(os.getenv("ARTIQ_DUMP_UNOPT_LLVM"), "LLVM IR (generated)", suffix + "_unopt.ll", - lambda: str(llparsedmod)) - - self.optimize(llparsedmod) - - _dump(os.getenv("ARTIQ_DUMP_LLVM"), "LLVM IR (optimized)", suffix + ".ll", - lambda: str(llparsedmod)) - - return llparsedmod - - def assemble(self, llmodule): - llmachine = self.target_machine() - - _dump(os.getenv("ARTIQ_DUMP_ASM"), "Assembly", ".s", - lambda: llmachine.emit_assembly(llmodule)) - - _dump(os.getenv("ARTIQ_DUMP_OBJ"), "Object file", ".o", - lambda: llmachine.emit_object(llmodule)) - - return llmachine.emit_object(llmodule) - - def link(self, objects): - """Link the relocatable objects into a shared library for this target.""" - with RunTool([self.tool_ld, "-shared", "--eh-frame-hdr"] + - self.additional_linker_options + - ["-T" + os.path.join(os.path.dirname(__file__), "kernel.ld")] + - ["{{obj{}}}".format(index) for index in range(len(objects))] + - ["-x"] + - ["-o", "{output}"], - output=None, - **{"obj{}".format(index): obj for index, obj in enumerate(objects)}) \ - as results: - library = results["output"].read() - - _dump(os.getenv("ARTIQ_DUMP_ELF"), "Shared library", ".elf", - lambda: library) - - return library - - def compile_and_link(self, modules): - return self.link([self.assemble(self.compile(module)) for module in modules]) - - def strip(self, library): - with RunTool([self.tool_strip, "--strip-debug", "{library}", "-o", "{output}"], - library=library, output=None) \ - as results: - return results["output"].read() - - def symbolize(self, library, addresses): - if addresses == []: - return [] - - # We got a list of return addresses, i.e. addresses of instructions - # just after the call. Offset them back to get an address somewhere - # inside the call instruction (or its delay slot), since that's what - # the backtrace entry should point at. - last_inlined = None - offset_addresses = [hex(addr - 1) for addr in addresses] - with RunTool([self.tool_symbolizer, "--addresses", "--functions", "--inlines", - "--demangle", "--output-style=GNU", "--exe={library}"] + offset_addresses, - library=library) \ - as results: - lines = iter(results["__stdout__"].read().rstrip().split("\n")) - backtrace = [] - while True: - try: - address_or_function = next(lines) - except StopIteration: - break - if address_or_function[:2] == "0x": - address = int(address_or_function[2:], 16) + 1 # remove offset - function = next(lines) - inlined = False - else: - address = backtrace[-1][4] # inlined - function = address_or_function - inlined = True - location = next(lines) - - filename, line = location.rsplit(":", 1) - if filename == "??" or filename == "": - continue - if line == "?": - line = -1 - else: - line = int(line) - # can't get column out of addr2line D: - if inlined: - last_inlined.append((filename, line, -1, function, address)) - else: - last_inlined = [] - backtrace.append((filename, line, -1, function, address, - last_inlined)) - return backtrace - - def demangle(self, names): - if not any(names): - return names - with RunTool([self.tool_cxxfilt] + names) as results: - return results["__stdout__"].read().rstrip().split("\n") - -class NativeTarget(Target): - def __init__(self): - super().__init__() - self.triple = llvm.get_default_triple() - self.data_layout = str(llvm.targets.Target.from_default_triple().create_target_machine().target_data) - -class RV32IMATarget(Target): - triple = "riscv32-unknown-linux" - data_layout = "e-m:e-p:32:32-i64:64-n32-S128" - features = ["m", "a"] - additional_linker_options = ["-m", "elf32lriscv"] - print_function = "core_log" - now_pinning = True - - tool_ld = "ld.lld" - tool_strip = "llvm-strip" - tool_symbolizer = "llvm-symbolizer" - tool_cxxfilt = "llvm-cxxfilt" - -class RV32GTarget(Target): - triple = "riscv32-unknown-linux" - data_layout = "e-m:e-p:32:32-i64:64-n32-S128" - features = ["m", "a", "f", "d"] - additional_linker_options = ["-m", "elf32lriscv"] - print_function = "core_log" - now_pinning = True - - tool_ld = "ld.lld" - tool_strip = "llvm-strip" - tool_symbolizer = "llvm-symbolizer" - tool_cxxfilt = "llvm-cxxfilt" - -class CortexA9Target(Target): - triple = "armv7-unknown-linux-gnueabihf" - data_layout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64" - features = ["dsp", "fp16", "neon", "vfp3"] - additional_linker_options = ["-m", "armelf_linux_eabi", "--target2=rel"] - print_function = "core_log" - now_pinning = False - - tool_ld = "ld.lld" - tool_strip = "llvm-strip" - tool_symbolizer = "llvm-symbolizer" - tool_cxxfilt = "llvm-cxxfilt" diff --git a/artiq/compiler/testbench/__init__.py b/artiq/compiler/testbench/__init__.py deleted file mode 100644 index 68ea51d7b..000000000 --- a/artiq/compiler/testbench/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -import time, cProfile as profile, pstats - -def benchmark(f, name): - profiler = profile.Profile() - profiler.enable() - - start = time.perf_counter() - end = 0 - runs = 0 - while end - start < 5 or runs < 10: - f() - runs += 1 - end = time.perf_counter() - - profiler.create_stats() - - print("{} {} runs: {:.2f}s, {:.2f}ms/run".format( - runs, name, end - start, (end - start) / runs * 1000)) - - stats = pstats.Stats(profiler) - stats.strip_dirs().sort_stats('time').print_stats(10) diff --git a/artiq/compiler/testbench/embedding.py b/artiq/compiler/testbench/embedding.py deleted file mode 100644 index c637c4b3a..000000000 --- a/artiq/compiler/testbench/embedding.py +++ /dev/null @@ -1,46 +0,0 @@ -import sys, os, tokenize - -from artiq.master.databases import DeviceDB -from artiq.master.worker_db import DeviceManager - -import artiq.coredevice.core -from artiq.coredevice.core import Core, CompileError - -def _render_diagnostic(diagnostic, colored): - return "\n".join(diagnostic.render(only_line=True)) - -artiq.coredevice.core._render_diagnostic = _render_diagnostic - -def main(): - if len(sys.argv) > 1 and sys.argv[1] == "+diag": - del sys.argv[1] - diag = True - else: - diag = False - - if len(sys.argv) > 1 and sys.argv[1] == "+compile": - del sys.argv[1] - compile_only = True - else: - compile_only = False - - ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py") - dmgr = DeviceManager(DeviceDB(ddb_path)) - - with tokenize.open(sys.argv[1]) as f: - testcase_code = compile(f.read(), f.name, "exec") - testcase_vars = {'__name__': 'testbench', 'dmgr': dmgr} - exec(testcase_code, testcase_vars) - - try: - core = dmgr.get("core") - if compile_only: - core.compile(testcase_vars["entrypoint"], (), {}) - else: - core.run(testcase_vars["entrypoint"], (), {}) - except CompileError as error: - if not diag: - exit(1) - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/inferencer.py b/artiq/compiler/testbench/inferencer.py deleted file mode 100644 index 659b3b052..000000000 --- a/artiq/compiler/testbench/inferencer.py +++ /dev/null @@ -1,103 +0,0 @@ -import sys, fileinput, os -from pythonparser import source, diagnostic, algorithm, parse_buffer -from .. import prelude, types -from ..transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer, CastMonomorphizer -from ..transforms import IODelayEstimator -from ..validators import ConstnessValidator - -class Printer(algorithm.Visitor): - """ - :class:`Printer` prints ``:`` and the node type after every typed node, - and ``->`` and the node type before the colon in a function definition. - - In almost all cases (except function definition) this does not result - in valid Python syntax. - - :ivar rewriter: (:class:`pythonparser.source.Rewriter`) rewriter instance - """ - - def __init__(self, buf): - self.rewriter = source.Rewriter(buf) - self.type_printer = types.TypePrinter() - - def rewrite(self): - return self.rewriter.rewrite() - - def visit_FunctionDefT(self, node): - super().generic_visit(node) - - self.rewriter.insert_before(node.colon_loc, - "->{}".format(self.type_printer.name(node.return_type))) - - def visit_ExceptHandlerT(self, node): - super().generic_visit(node) - - if node.name_loc: - self.rewriter.insert_after(node.name_loc, - ":{}".format(self.type_printer.name(node.name_type))) - - def visit_ForT(self, node): - super().generic_visit(node) - - if node.trip_count is not None and node.trip_interval is not None: - self.rewriter.insert_after(node.keyword_loc, - "[{} x {} mu]".format(node.trip_count.fold(), - node.trip_interval.fold())) - - def generic_visit(self, node): - super().generic_visit(node) - - if hasattr(node, "type"): - self.rewriter.insert_after(node.loc, - ":{}".format(self.type_printer.name(node.type))) - -def main(): - if len(sys.argv) > 1 and sys.argv[1] == "+mono": - del sys.argv[1] - monomorphize = True - else: - monomorphize = False - - if len(sys.argv) > 1 and sys.argv[1] == "+iodelay": - del sys.argv[1] - iodelay = True - else: - iodelay = False - - if len(sys.argv) > 1 and sys.argv[1] == "+diag": - del sys.argv[1] - def process_diagnostic(diag): - print("\n".join(diag.render(only_line=True))) - if diag.level == "fatal": - exit() - else: - def process_diagnostic(diag): - print("\n".join(diag.render())) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - buf = source.Buffer("".join(fileinput.input()).expandtabs(), - os.path.basename(fileinput.filename())) - parsed, comments = parse_buffer(buf, engine=engine) - typed = ASTTypedRewriter(engine=engine, prelude=prelude.globals()).visit(parsed) - Inferencer(engine=engine).visit(typed) - ConstnessValidator(engine=engine).visit(typed) - if monomorphize: - CastMonomorphizer(engine=engine).visit(typed) - IntMonomorphizer(engine=engine).visit(typed) - Inferencer(engine=engine).visit(typed) - if iodelay: - IODelayEstimator(engine=engine, ref_period=1e6).visit_fixpoint(typed) - - printer = Printer(buf) - printer.visit(typed) - for comment in comments: - if comment.text.find("CHECK") >= 0: - printer.rewriter.remove(comment.loc) - print(printer.rewrite().source) - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/irgen.py b/artiq/compiler/testbench/irgen.py deleted file mode 100644 index 275f11c94..000000000 --- a/artiq/compiler/testbench/irgen.py +++ /dev/null @@ -1,23 +0,0 @@ -import sys, os, fileinput -from pythonparser import diagnostic -from .. import ir -from ..module import Module, Source - -def main(): - if os.getenv("ARTIQ_IR_NO_LOC") is not None: - ir.BasicBlock._dump_loc = False - - def process_diagnostic(diag): - print("\n".join(diag.render())) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine)) - for fn in mod.artiq_ir: - print(fn) - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/jit.py b/artiq/compiler/testbench/jit.py deleted file mode 100644 index 67a43c008..000000000 --- a/artiq/compiler/testbench/jit.py +++ /dev/null @@ -1,35 +0,0 @@ -import os, sys, fileinput, ctypes -from pythonparser import diagnostic -from llvmlite import binding as llvm -from ..module import Module, Source -from ..targets import NativeTarget - -def main(): - libartiq_support = os.getenv("LIBARTIQ_SUPPORT") - if libartiq_support is not None: - llvm.load_library_permanently(libartiq_support) - - def process_diagnostic(diag): - print("\n".join(diag.render())) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - source = "".join(fileinput.input()) - source = source.replace("#ARTIQ#", "") - mod = Module(Source.from_string(source.expandtabs(), engine=engine)) - - target = NativeTarget() - llmod = mod.build_llvm_ir(target) - llparsedmod = llvm.parse_assembly(str(llmod)) - llparsedmod.verify() - - llmachine = llvm.Target.from_triple(target.triple).create_target_machine() - lljit = llvm.create_mcjit_compiler(llparsedmod, llmachine) - llmain = lljit.get_function_address(llmod.name + ".__modinit__") - ctypes.CFUNCTYPE(None)(llmain)() - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/llvmgen.py b/artiq/compiler/testbench/llvmgen.py deleted file mode 100644 index 6bcf031c9..000000000 --- a/artiq/compiler/testbench/llvmgen.py +++ /dev/null @@ -1,31 +0,0 @@ -import sys, fileinput -from pythonparser import diagnostic -from llvmlite import ir as ll -from ..module import Module, Source -from ..targets import NativeTarget - -def main(): - def process_diagnostic(diag): - print("\n".join(diag.render())) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine)) - - target = NativeTarget() - llmod = mod.build_llvm_ir(target=target) - - # Add main so that the result can be executed with lli - llmain = ll.Function(llmod, ll.FunctionType(ll.VoidType(), []), "main") - llbuilder = ll.IRBuilder(llmain.append_basic_block("entry")) - llbuilder.call(llmod.get_global(llmod.name + ".__modinit__"), [ - ll.Constant(ll.IntType(8).as_pointer(), None)]) - llbuilder.ret_void() - - print(llmod) - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/perf.py b/artiq/compiler/testbench/perf.py deleted file mode 100644 index 2d70bcf84..000000000 --- a/artiq/compiler/testbench/perf.py +++ /dev/null @@ -1,37 +0,0 @@ -import sys, os -from pythonparser import diagnostic -from ..module import Module, Source -from ..targets import RV32GTarget -from . import benchmark - -def main(): - if not len(sys.argv) == 2: - print("Expected exactly one module filename", file=sys.stderr) - exit(1) - - def process_diagnostic(diag): - print("\n".join(diag.render()), file=sys.stderr) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - # Make sure everything's valid - filename = sys.argv[1] - with open(filename) as f: - code = f.read() - source = Source.from_string(code, filename, engine=engine) - module = Module(source) - - benchmark(lambda: Source.from_string(code, filename), - "ARTIQ parsing and inference") - - benchmark(lambda: Module(source), - "ARTIQ transforms and validators") - - benchmark(lambda: RV32GTarget().compile_and_link([module]), - "LLVM optimization and linking") - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/perf_embedding.py b/artiq/compiler/testbench/perf_embedding.py deleted file mode 100644 index 5d148cb34..000000000 --- a/artiq/compiler/testbench/perf_embedding.py +++ /dev/null @@ -1,75 +0,0 @@ -import sys, os, tokenize -from pythonparser import diagnostic -from ...language.environment import ProcessArgumentManager -from ...master.databases import DeviceDB, DatasetDB -from ...master.worker_db import DeviceManager, DatasetManager -from ..module import Module -from ..embedding import Stitcher -from ..targets import RV32GTarget -from . import benchmark - - -def main(): - if not len(sys.argv) == 2: - print("Expected exactly one module filename", file=sys.stderr) - exit(1) - - def process_diagnostic(diag): - print("\n".join(diag.render()), file=sys.stderr) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - with tokenize.open(sys.argv[1]) as f: - testcase_code = compile(f.read(), f.name, "exec") - testcase_vars = {'__name__': 'testbench'} - exec(testcase_code, testcase_vars) - - device_db_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py") - device_mgr = DeviceManager(DeviceDB(device_db_path)) - - dataset_db_path = os.path.join(os.path.dirname(sys.argv[1]), "dataset_db.mdb") - dataset_db = DatasetDB(dataset_db_path) - dataset_mgr = DatasetManager() - - argument_mgr = ProcessArgumentManager({}) - - def embed(): - experiment = testcase_vars["Benchmark"]((device_mgr, dataset_mgr, argument_mgr)) - - stitcher = Stitcher(core=experiment.core, dmgr=device_mgr) - stitcher.stitch_call(experiment.run, (), {}) - stitcher.finalize() - return stitcher - - stitcher = embed() - module = Module(stitcher) - target = RV32GTarget() - llvm_ir = target.compile(module) - elf_obj = target.assemble(llvm_ir) - elf_shlib = target.link([elf_obj]) - - benchmark(lambda: embed(), - "ARTIQ embedding") - - benchmark(lambda: Module(stitcher), - "ARTIQ transforms and validators") - - benchmark(lambda: target.compile(module), - "LLVM optimizations") - - benchmark(lambda: target.assemble(llvm_ir), - "LLVM machine code emission") - - benchmark(lambda: target.link([elf_obj]), - "Linking") - - benchmark(lambda: target.strip(elf_shlib), - "Stripping debug information") - - dataset_db.close_db() - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/shlib.py b/artiq/compiler/testbench/shlib.py deleted file mode 100644 index 0e2317a5c..000000000 --- a/artiq/compiler/testbench/shlib.py +++ /dev/null @@ -1,30 +0,0 @@ -import sys, os -from pythonparser import diagnostic -from ..module import Module, Source -from ..targets import RV32GTarget - -def main(): - if not len(sys.argv) > 1: - print("Expected at least one module filename", file=sys.stderr) - exit(1) - - def process_diagnostic(diag): - print("\n".join(diag.render()), file=sys.stderr) - if diag.level in ("fatal", "error"): - exit(1) - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - modules = [] - for filename in sys.argv[1:]: - modules.append(Module(Source.from_filename(filename, engine=engine))) - - llobj = RV32GTarget().compile_and_link(modules) - - basename, ext = os.path.splitext(sys.argv[-1]) - with open(basename + ".so", "wb") as f: - f.write(llobj) - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/testbench/signature.py b/artiq/compiler/testbench/signature.py deleted file mode 100644 index 5d3ff1aa4..000000000 --- a/artiq/compiler/testbench/signature.py +++ /dev/null @@ -1,44 +0,0 @@ -import sys, fileinput -from pythonparser import diagnostic -from ..module import Module, Source -from .. import types, iodelay - -def main(): - if len(sys.argv) > 1 and sys.argv[1] == "+diag": - del sys.argv[1] - diag = True - def process_diagnostic(diag): - print("\n".join(diag.render(only_line=True))) - if diag.level == "fatal": - exit() - else: - diag = False - def process_diagnostic(diag): - print("\n".join(diag.render(colored=False))) - if diag.level in ("fatal", "error"): - exit(1) - - if len(sys.argv) > 1 and sys.argv[1] == "+delay": - del sys.argv[1] - force_delays = True - else: - force_delays = False - - engine = diagnostic.Engine() - engine.process = process_diagnostic - - try: - mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine)) - - if force_delays: - for var in mod.globals: - typ = mod.globals[var].find() - if types.is_function(typ) and types.is_indeterminate_delay(typ.delay): - process_diagnostic(typ.delay.find().cause) - - print(repr(mod)) - except: - if not diag: raise - -if __name__ == "__main__": - main() diff --git a/artiq/compiler/transforms/__init__.py b/artiq/compiler/transforms/__init__.py deleted file mode 100644 index 5696be95f..000000000 --- a/artiq/compiler/transforms/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -from .asttyped_rewriter import ASTTypedRewriter -from .inferencer import Inferencer -from .int_monomorphizer import IntMonomorphizer -from .cast_monomorphizer import CastMonomorphizer -from .iodelay_estimator import IODelayEstimator -from .artiq_ir_generator import ARTIQIRGenerator -from .dead_code_eliminator import DeadCodeEliminator -from .local_demoter import LocalDemoter -from .constant_hoister import ConstantHoister -from .interleaver import Interleaver -from .typedtree_printer import TypedtreePrinter -from .llvm_ir_generator import LLVMIRGenerator diff --git a/artiq/compiler/transforms/artiq_ir_generator.py b/artiq/compiler/transforms/artiq_ir_generator.py deleted file mode 100644 index d3d348b12..000000000 --- a/artiq/compiler/transforms/artiq_ir_generator.py +++ /dev/null @@ -1,2960 +0,0 @@ -""" -:class:`ARTIQIRGenerator` transforms typed AST into ARTIQ intermediate -representation. ARTIQ IR is designed to be low-level enough that -its operations are elementary--contain no internal branching-- -but without too much detail, such as exposing the reference/value -semantics explicitly. -""" - -from collections import OrderedDict, defaultdict -from functools import reduce -from itertools import chain -from pythonparser import algorithm, diagnostic, ast -from .. import types, builtins, asttyped, ir, iodelay - -def _readable_name(insn): - if isinstance(insn, ir.Constant): - return str(insn.value) - else: - return insn.name - -def _extract_loc(node): - if "keyword_loc" in node._locs: - return node.keyword_loc - else: - return node.loc - -# We put some effort in keeping generated IR readable, -# i.e. with a more or less linear correspondence to the source. -# This is why basic blocks sometimes seem to be produced in an odd order. -class ARTIQIRGenerator(algorithm.Visitor): - """ - :class:`ARTIQIRGenerator` contains a lot of internal state, - which is effectively maintained in a stack--with push/pop - pairs around any state updates. It is comprised of following: - - :ivar current_loc: (:class:`pythonparser.source.Range`) - source range of the node being currently visited - :ivar current_function: (:class:`ir.Function` or None) - module, def or lambda currently being translated - :ivar current_globals: (set of string) - set of variables that will be resolved in global scope - :ivar current_block: (:class:`ir.BasicBlock`) - basic block to which any new instruction will be appended - :ivar current_env: (:class:`ir.Alloc` of type :class:`ir.TEnvironment`) - the chained function environment, containing variables that - can become upvalues - :ivar current_private_env: (:class:`ir.Alloc` of type :class:`ir.TEnvironment`) - the private function environment, containing internal state - :ivar current_args: (dict of string to :class:`ir.Argument`) - the map of Python names of formal arguments to - the current function to their SSA names - :ivar current_assign: (:class:`ir.Value` or None) - the right-hand side of current assignment statement, or - a component of a composite right-hand side when visiting - a composite left-hand side, such as, in ``x, y = z``, - the 2nd tuple element when visting ``y`` - :ivar break_target: (:class:`ir.BasicBlock` or None) - the basic block to which ``break`` will transfer control - :ivar continue_target: (:class:`ir.BasicBlock` or None) - the basic block to which ``continue`` will transfer control - :ivar return_target: (:class:`ir.BasicBlock` or None) - the basic block to which ``return`` will transfer control - :ivar unwind_target: (:class:`ir.BasicBlock` or None) - the basic block to which unwinding will transfer control - :ivar catch_clauses: (list of (:class:`ir.BasicBlock`, :class:`types.Type` or None)) - a list of catch clauses that should be appended to inner try block - landingpad - - There is, additionally, some global state that is used to translate - the results of analyses on AST level to IR level: - - :ivar function_map: (map of :class:`ast.FunctionDefT` to :class:`ir.Function`) - the map from function definition nodes to IR functions - :ivar variable_map: (map of :class:`ast.NameT` to :class:`ir.GetLocal`) - the map from variable name nodes to instructions retrieving - the variable values - :ivar method_map: (map of :class:`ast.AttributeT` to :class:`ir.GetAttribute`) - the map from method resolution nodes to instructions retrieving - the called function inside a translated :class:`ast.CallT` node - - Finally, functions that implement array operations are instantiated on the fly as - necessary. They are kept track of in global dictionaries, with a mangled name - containing types and operations as key: - - :ivar array_op_funcs: the map from mangled name to implementation of - operations on/between arrays - """ - - _size_type = builtins.TInt32() - - def __init__(self, module_name, engine, ref_period, embedding_map): - self.engine = engine - self.embedding_map = embedding_map - self.functions = [] - self.name = [module_name] if module_name != "" else [] - self.ref_period = ir.Constant(ref_period, builtins.TFloat()) - self.current_loc = None - self.current_function = None - self.current_class = None - self.current_globals = set() - self.current_block = None - self.current_env = None - self.current_private_env = None - self.current_args = None - self.current_assign = None - self.current_exception = None - self.current_remote_fn = False - self.break_target = None - self.continue_target = None - self.return_target = None - self.unwind_target = None - self.catch_clauses = [] - self.function_map = dict() - self.variable_map = dict() - self.method_map = defaultdict(lambda: []) - self.array_op_funcs = dict() - self.raise_assert_func = None - - def annotate_calls(self, devirtualization): - for var_node in devirtualization.variable_map: - callee_node = devirtualization.variable_map[var_node] - if callee_node is None: - continue - callee = self.function_map[callee_node] - - call_target = self.variable_map[var_node] - for use in call_target.uses: - if isinstance(use, (ir.Call, ir.Invoke)) and \ - use.target_function() == call_target: - use.static_target_function = callee - - for type_and_method in devirtualization.method_map: - callee_node = devirtualization.method_map[type_and_method] - if callee_node is None: - continue - callee = self.function_map[callee_node] - - for call in self.method_map[type_and_method]: - assert isinstance(call, (ir.Call, ir.Invoke)) - call.static_target_function = callee - - def add_block(self, name=""): - block = ir.BasicBlock([], name) - self.current_function.add(block) - return block - - def append(self, insn, block=None, loc=None): - if loc is None: - loc = self.current_loc - if block is None: - block = self.current_block - - if insn.loc is None: - insn.loc = loc - return block.append(insn) - - def terminate(self, insn): - if not self.current_block.is_terminated(): - self.append(insn) - else: - insn.drop_references() - - def warn_unreachable(self, node): - diag = diagnostic.Diagnostic("warning", - "unreachable code", {}, - node.loc.begin()) - self.engine.process(diag) - - # Visitors - - def visit(self, obj): - if isinstance(obj, list): - for elt in obj: - if self.current_block.is_terminated(): - self.warn_unreachable(elt) - break - self.visit(elt) - elif isinstance(obj, ast.AST): - try: - old_loc, self.current_loc = self.current_loc, _extract_loc(obj) - return self._visit_one(obj) - finally: - self.current_loc = old_loc - - # Module visitor - - def visit_ModuleT(self, node): - # Treat start of module as synthesized - self.current_loc = None - - try: - typ = types.TFunction(OrderedDict(), OrderedDict(), builtins.TNone()) - func = ir.Function(typ, ".".join(self.name + ['__modinit__']), [], - loc=node.loc.begin()) - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - - env_type = ir.TEnvironment(name=func.name, vars=node.typing_env) - env = self.append(ir.Alloc([], env_type, name="env")) - old_env, self.current_env = self.current_env, env - - priv_env_type = ir.TEnvironment(name=func.name + ".priv", vars={ "$return": typ.ret }) - priv_env = self.append(ir.Alloc([], priv_env_type, name="privenv")) - old_priv_env, self.current_private_env = self.current_private_env, priv_env - - self.generic_visit(node) - self.terminate(ir.Return(ir.Constant(None, builtins.TNone()), - remote_return=self.current_remote_fn)) - - return self.functions - finally: - self.current_function = old_func - self.current_block = old_block - self.current_env = old_env - self.current_private_env = old_priv_env - - # Statement visitors - - def visit_ClassDefT(self, node): - klass = self.append(ir.Alloc([], node.constructor_type, - name="class.{}".format(node.name))) - self._set_local(node.name, klass) - - try: - old_class, self.current_class = self.current_class, klass - self.visit(node.body) - finally: - self.current_class = old_class - - def visit_function(self, node, is_lambda=False, is_internal=False, is_quoted=False, - flags={}): - if is_lambda: - name = "lambda@{}:{}".format(node.loc.line(), node.loc.column()) - typ = node.type.find() - else: - name = node.name - typ = node.signature_type.find() - - try: - defaults = [] - if not is_quoted: - for arg_name, default_node in zip(typ.optargs, node.args.defaults): - default = self.visit(default_node) - env_default_name = \ - self.current_env.type.add("$default." + arg_name, default.type) - self.append(ir.SetLocal(self.current_env, env_default_name, default)) - def codegen_default(env_default_name): - return lambda: self.append(ir.GetLocal(self.current_env, env_default_name)) - defaults.append(codegen_default(env_default_name)) - else: - for default_node in node.args.defaults: - def codegen_default(default_node): - return lambda: self.visit(default_node) - defaults.append(codegen_default(default_node)) - - old_name, self.name = self.name, self.name + [name] - - env_arg = ir.EnvironmentArgument(self.current_env.type, "ARG.ENV") - - old_args, self.current_args = self.current_args, {} - - args = [] - for arg_name in typ.args: - arg = ir.Argument(typ.args[arg_name], "ARG." + arg_name) - self.current_args[arg_name] = arg - args.append(arg) - - optargs = [] - for arg_name in typ.optargs: - arg = ir.Argument(ir.TOption(typ.optargs[arg_name]), "ARG." + arg_name) - self.current_args[arg_name] = arg - optargs.append(arg) - - for (arg, arg_node) in zip(args + optargs, node.args.args): - arg.loc = arg_node.loc - - func = ir.Function(typ, ".".join(self.name), [env_arg] + args + optargs, - loc=node.lambda_loc if is_lambda else node.keyword_loc) - func.is_internal = is_internal - func.flags = flags - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - if not is_lambda: - self.function_map[node] = func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - - old_globals, self.current_globals = self.current_globals, node.globals_in_scope - old_remote_fn = self.current_remote_fn - self.current_remote_fn = getattr(node, "remote_fn", False) - - env_without_globals = \ - {var: node.typing_env[var] - for var in node.typing_env - if var not in node.globals_in_scope} - env_type = ir.TEnvironment(name=func.name, - vars=env_without_globals, outer=self.current_env.type) - env = self.append(ir.Alloc([], env_type, name="ENV")) - old_env, self.current_env = self.current_env, env - - if not is_lambda: - priv_env_type = ir.TEnvironment(name="{}.private".format(func.name), - vars={ "$return": typ.ret }) - priv_env = self.append(ir.Alloc([], priv_env_type, name="PRV")) - old_priv_env, self.current_private_env = self.current_private_env, priv_env - - self.append(ir.SetLocal(env, "$outer", env_arg)) - for index, arg_name in enumerate(typ.args): - self.append(ir.SetLocal(env, arg_name, args[index])) - for index, (arg_name, codegen_default) in enumerate(zip(typ.optargs, defaults)): - default = codegen_default() - value = self.append(ir.Builtin("unwrap_or", [optargs[index], default], - typ.optargs[arg_name], - name="DEF.{}".format(arg_name))) - self.append(ir.SetLocal(env, arg_name, value)) - - result = self.visit(node.body) - - if is_lambda: - self.terminate(ir.Return(result)) - elif builtins.is_none(typ.ret): - if not self.current_block.is_terminated(): - self.current_block.append(ir.Return(ir.Constant(None, builtins.TNone()), - remote_return=self.current_remote_fn)) - else: - if not self.current_block.is_terminated(): - if len(self.current_block.predecessors()) != 0: - diag = diagnostic.Diagnostic("error", - "this function must return a value of type {typ} explicitly", - {"typ": types.TypePrinter().name(typ.ret)}, - node.keyword_loc) - self.engine.process(diag) - - self.current_block.append(ir.Unreachable()) - - finally: - self.name = old_name - self.current_args = old_args - self.current_function = old_func - self.current_block = old_block - self.current_globals = old_globals - self.current_env = old_env - self.current_remote_fn = old_remote_fn - if not is_lambda: - self.current_private_env = old_priv_env - - return self.append(ir.Closure(func, self.current_env)) - - def visit_FunctionDefT(self, node): - func = self.visit_function(node, is_internal=len(self.name) > 0) - if self.current_class is None: - self._set_local(node.name, func) - else: - self.append(ir.SetAttr(self.current_class, node.name, func)) - - def visit_QuotedFunctionDefT(self, node): - self.visit_function(node, is_internal=True, is_quoted=True, flags=node.flags) - - def visit_Return(self, node): - if node.value is None: - return_value = ir.Constant(None, builtins.TNone()) - else: - return_value = self.visit(node.value) - - if self.return_target is None: - self.append(ir.Return(return_value, - remote_return=self.current_remote_fn)) - else: - self.append(ir.SetLocal(self.current_private_env, "$return", return_value)) - self.append(ir.Branch(self.return_target)) - - def visit_Expr(self, node): - # Ignore the value, do it for side effects. - result = self.visit(node.value) - - # See comment in visit_Pass. - if isinstance(result, ir.Constant): - self.visit_Pass(node) - - def visit_Pass(self, node): - # Insert a dummy instruction so that analyses which extract - # locations from CFG have something to use. - self.append(ir.Builtin("nop", [], builtins.TNone())) - - def visit_Assign(self, node): - try: - self.current_assign = self.visit(node.value) - assert self.current_assign is not None - for target in node.targets: - self.visit(target) - finally: - self.current_assign = None - - def visit_AugAssign(self, node): - lhs = self.visit(node.target) - rhs = self.visit(node.value) - - if builtins.is_array(lhs.type): - name = type(node.op).__name__ - def make_op(l, r): - return self.append(ir.Arith(node.op, l, r)) - self._broadcast_binop(name, make_op, lhs.type, lhs, rhs, assign_to_lhs=True) - return - - value = self.append(ir.Arith(node.op, lhs, rhs)) - try: - self.current_assign = value - self.visit(node.target) - finally: - self.current_assign = None - - def coerce_to_bool(self, insn, block=None): - if builtins.is_bool(insn.type): - return insn - elif builtins.is_int(insn.type): - return self.append(ir.Compare(ast.NotEq(loc=None), insn, ir.Constant(0, insn.type)), - block=block) - elif builtins.is_float(insn.type): - return self.append(ir.Compare(ast.NotEq(loc=None), insn, ir.Constant(0, insn.type)), - block=block) - elif builtins.is_iterable(insn.type): - length = self.iterable_len(insn) - return self.append(ir.Compare(ast.NotEq(loc=None), length, ir.Constant(0, length.type)), - block=block) - elif builtins.is_none(insn.type): - return ir.Constant(False, builtins.TBool()) - else: - note = diagnostic.Diagnostic("note", - "this expression has type {type}", - {"type": types.TypePrinter().name(insn.type)}, - insn.loc) - diag = diagnostic.Diagnostic("warning", - "this expression, which is always truthful, is coerced to bool", {}, - insn.loc, notes=[note]) - self.engine.process(diag) - return ir.Constant(True, builtins.TBool()) - - def visit_If(self, node): - cond = self.visit(node.test) - cond = self.coerce_to_bool(cond) - head = self.current_block - - if_true = self.add_block("if.body") - self.current_block = if_true - self.visit(node.body) - post_if_true = self.current_block - - if any(node.orelse): - if_false = self.add_block("if.else") - self.current_block = if_false - self.visit(node.orelse) - post_if_false = self.current_block - - tail = self.add_block("if.tail") - self.current_block = tail - if not post_if_true.is_terminated(): - post_if_true.append(ir.Branch(tail)) - - if any(node.orelse): - if not post_if_false.is_terminated(): - post_if_false.append(ir.Branch(tail)) - self.append(ir.BranchIf(cond, if_true, if_false), block=head) - else: - self.append(ir.BranchIf(cond, if_true, tail), block=head) - - def visit_While(self, node): - try: - head = self.add_block("while.head") - self.append(ir.Branch(head)) - self.current_block = head - old_continue, self.continue_target = self.continue_target, head - cond = self.visit(node.test) - cond = self.coerce_to_bool(cond) - post_head = self.current_block - - break_block = self.add_block("while.break") - old_break, self.break_target = self.break_target, break_block - - body = self.add_block("while.body") - self.current_block = body - self.visit(node.body) - post_body = self.current_block - finally: - self.break_target = old_break - self.continue_target = old_continue - - if any(node.orelse): - else_tail = self.add_block("while.else") - self.current_block = else_tail - self.visit(node.orelse) - post_else_tail = self.current_block - - tail = self.add_block("while.tail") - self.current_block = tail - - if any(node.orelse): - if not post_else_tail.is_terminated(): - post_else_tail.append(ir.Branch(tail)) - else: - else_tail = tail - - post_head.append(ir.BranchIf(cond, body, else_tail)) - if not post_body.is_terminated(): - post_body.append(ir.Branch(head)) - break_block.append(ir.Branch(tail)) - - def iterable_len(self, value, typ=_size_type): - if builtins.is_listish(value.type): - if isinstance(value, ir.Constant): - name = None - else: - name = "{}.len".format(value.name) - len = self.append(ir.Builtin("len", [value], builtins.TInt32(), - name=name)) - return self.append(ir.Coerce(len, typ)) - elif builtins.is_range(value.type): - start = self.append(ir.GetAttr(value, "start")) - stop = self.append(ir.GetAttr(value, "stop")) - step = self.append(ir.GetAttr(value, "step")) - spread = self.append(ir.Arith(ast.Sub(loc=None), stop, start)) - return self.append(ir.Arith(ast.FloorDiv(loc=None), spread, step, - name="{}.len".format(value.name))) - else: - assert False - - def iterable_get(self, value, index): - # Assuming the value is within bounds. - if builtins.is_array(value.type): - # Scalar indexing into ndarray. - num_dims = value.type.find()["num_dims"].value - if num_dims > 1: - old_shape = self.append(ir.GetAttr(value, "shape")) - lengths = [self.append(ir.GetAttr(old_shape, i)) for i in range(1, num_dims)] - new_shape = self._make_array_shape(lengths) - - stride = reduce( - lambda l, r: self.append(ir.Arith(ast.Mult(loc=None), l, r)), - lengths[1:], lengths[0]) - offset = self.append(ir.Arith(ast.Mult(loc=None), stride, index)) - old_buffer = self.append(ir.GetAttr(value, "buffer")) - new_buffer = self.append(ir.Offset(old_buffer, offset)) - - result_type = builtins.TArray(value.type.find()["elt"], - types.TValue(num_dims - 1)) - return self.append(ir.Alloc([new_buffer, new_shape], result_type)) - else: - buffer = self.append(ir.GetAttr(value, "buffer")) - return self.append(ir.GetElem(buffer, index)) - elif builtins.is_listish(value.type): - return self.append(ir.GetElem(value, index)) - elif builtins.is_range(value.type): - start = self.append(ir.GetAttr(value, "start")) - step = self.append(ir.GetAttr(value, "step")) - offset = self.append(ir.Arith(ast.Mult(loc=None), step, index)) - return self.append(ir.Arith(ast.Add(loc=None), start, offset)) - else: - assert False - - def visit_ForT(self, node): - try: - iterable = self.visit(node.iter) - length = self.iterable_len(iterable) - prehead = self.current_block - - head = self.add_block("for.head") - self.append(ir.Branch(head)) - self.current_block = head - phi = self.append(ir.Phi(length.type, name="IND")) - phi.add_incoming(ir.Constant(0, phi.type), prehead) - cond = self.append(ir.Compare(ast.Lt(loc=None), phi, length, name="CMP")) - - break_block = self.add_block("for.break") - old_break, self.break_target = self.break_target, break_block - - continue_block = self.add_block("for.continue") - old_continue, self.continue_target = self.continue_target, continue_block - self.current_block = continue_block - - updated_index = self.append(ir.Arith(ast.Add(loc=None), phi, ir.Constant(1, phi.type), - name="IND.new")) - phi.add_incoming(updated_index, continue_block) - self.append(ir.Branch(head)) - - body = self.add_block("for.body") - self.current_block = body - elt = self.iterable_get(iterable, phi) - try: - self.current_assign = elt - self.visit(node.target) - finally: - self.current_assign = None - self.visit(node.body) - post_body = self.current_block - finally: - self.break_target = old_break - self.continue_target = old_continue - - if any(node.orelse): - else_tail = self.add_block("for.else") - self.current_block = else_tail - self.visit(node.orelse) - post_else_tail = self.current_block - - tail = self.add_block("for.tail") - self.current_block = tail - - if any(node.orelse): - if not post_else_tail.is_terminated(): - post_else_tail.append(ir.Branch(tail)) - else: - else_tail = tail - - if node.trip_count is not None: - head.append(ir.Loop(node.trip_count, phi, cond, body, else_tail)) - else: - head.append(ir.BranchIf(cond, body, else_tail)) - if not post_body.is_terminated(): - post_body.append(ir.Branch(continue_block)) - break_block.append(ir.Branch(tail)) - - def visit_Break(self, node): - self.append(ir.Branch(self.break_target)) - - def visit_Continue(self, node): - self.append(ir.Branch(self.continue_target)) - - def raise_exn(self, exn=None, loc=None): - if exn is not None: - # if we need to raise the exception in a final body, we have to - # lazy-evaluate the exception object to make sure that we generate - # it in the raise_proxy block - exn = exn() - if exn is not None: - assert loc is not None - loc_file = ir.Constant(loc.source_buffer.name, builtins.TStr()) - loc_line = ir.Constant(loc.line(), builtins.TInt32()) - loc_column = ir.Constant(loc.column(), builtins.TInt32()) - loc_function = ir.Constant(".".join(self.name), builtins.TStr()) - - self.append(ir.SetAttr(exn, "#__file__", loc_file)) - self.append(ir.SetAttr(exn, "#__line__", loc_line)) - self.append(ir.SetAttr(exn, "#__col__", loc_column)) - self.append(ir.SetAttr(exn, "#__func__", loc_function)) - - if self.unwind_target is not None: - self.append(ir.Raise(exn, self.unwind_target)) - else: - self.append(ir.Raise(exn)) - else: - if self.unwind_target is not None: - self.append(ir.Resume(self.unwind_target)) - else: - self.append(ir.Resume()) - - def visit_Raise(self, node): - if node.exc is not None and types.is_exn_constructor(node.exc.type): - self.raise_exn(lambda: self.alloc_exn(node.exc.type.instance), loc=self.current_loc) - else: - self.raise_exn(lambda: self.visit(node.exc), loc=self.current_loc) - - def visit_Try(self, node): - dispatcher = self.add_block("try.dispatch") - cleanup = self.add_block('handler.cleanup') - landingpad = ir.LandingPad(cleanup) - dispatcher.append(landingpad) - - if any(node.finalbody): - # k for continuation - final_suffix = ".try@{}:{}".format(node.loc.line(), node.loc.column()) - final_env_type = ir.TEnvironment(name=self.current_function.name + final_suffix, - vars={ "$cont": ir.TBasicBlock() }) - final_state = self.append(ir.Alloc([], final_env_type)) - final_targets = [] - final_paths = [] - - def final_branch(target, block): - block.append(ir.SetLocal(final_state, "$cont", target)) - final_targets.append(target) - final_paths.append(block) - - if self.break_target is not None: - break_proxy = self.add_block("try.break") - old_break, self.break_target = self.break_target, break_proxy - final_branch(old_break, break_proxy) - - if self.continue_target is not None: - continue_proxy = self.add_block("try.continue") - old_continue, self.continue_target = self.continue_target, continue_proxy - final_branch(old_continue, continue_proxy) - - return_proxy = self.add_block("try.return") - old_return, self.return_target = self.return_target, return_proxy - if old_return is not None: - final_branch(old_return, return_proxy) - else: - return_action = self.add_block("try.doreturn") - value = return_action.append(ir.GetLocal(self.current_private_env, "$return")) - return_action.append(ir.Return(value)) - final_branch(return_action, return_proxy) - else: - landingpad.has_cleanup = self.unwind_target is not None - - # we should propagate the clauses to nested try catch blocks - # so nested try catch will jump to our clause if the inner one does not - # match - # note that the phi instruction here requires some hack, see - # llvm_ir_generator process_function for details - clauses = [] - found_catch_all = False - for handler_node in node.handlers: - if found_catch_all: - self.warn_unreachable(handler_node) - continue - exn_type = handler_node.name_type.find() - if handler_node.filter is not None and \ - not builtins.is_exception(exn_type, 'Exception'): - handler = self.add_block("handler." + exn_type.name) - phi = ir.Phi(builtins.TException(), 'exn') - handler.append(phi) - clauses.append((handler, exn_type, phi)) - else: - handler = self.add_block("handler.catchall") - phi = ir.Phi(builtins.TException(), 'exn') - handler.append(phi) - clauses.append((handler, None, phi)) - found_catch_all = True - - all_clauses = clauses[:] - for clause in self.catch_clauses: - # if the last clause is accept all, do not add further clauses - if len(all_clauses) == 0 or all_clauses[-1][1] is not None: - all_clauses.append(clause) - - body = self.add_block("try.body") - self.append(ir.Branch(body)) - self.current_block = body - - old_unwind, self.unwind_target = self.unwind_target, dispatcher - old_clauses, self.catch_clauses = self.catch_clauses, all_clauses - try: - self.visit(node.body) - finally: - self.unwind_target = old_unwind - self.catch_clauses = old_clauses - - if not self.current_block.is_terminated(): - self.visit(node.orelse) - elif any(node.orelse): - self.warn_unreachable(node.orelse[0]) - body = self.current_block - - if any(node.finalbody): - # if we have a final block, we should not append clauses to our - # landingpad or we will skip the finally block. - # when the finally block calls resume, it will unwind to the outer - # try catch block automatically - all_clauses = clauses - # reset targets - if self.break_target: - self.break_target = old_break - if self.continue_target: - self.continue_target = old_continue - self.return_target = old_return - - # create new unwind target for cleanup - final_dispatcher = self.add_block("try.final.dispatch") - final_landingpad = ir.LandingPad(cleanup) - final_dispatcher.append(final_landingpad) - - # make sure that exception clauses are unwinded to the finally block - old_unwind, self.unwind_target = self.unwind_target, final_dispatcher - - # if we have a while:try/finally continue must execute finally - # before continuing the while - redirect = final_branch - else: - redirect = lambda dest, proxy: proxy.append(ir.Branch(dest)) - - # we need to set break/continue/return to execute end_catch - if self.break_target is not None: - break_proxy = self.add_block("try.break") - break_proxy.append(ir.Builtin("end_catch", [], builtins.TNone())) - old_break, self.break_target = self.break_target, break_proxy - redirect(old_break, break_proxy) - - if self.continue_target is not None: - continue_proxy = self.add_block("try.continue") - continue_proxy.append(ir.Builtin("end_catch", [], - builtins.TNone())) - old_continue, self.continue_target = self.continue_target, continue_proxy - redirect(old_continue, continue_proxy) - - return_proxy = self.add_block("try.return") - return_proxy.append(ir.Builtin("end_catch", [], builtins.TNone())) - old_return, self.return_target = self.return_target, return_proxy - old_return_target = old_return - if old_return_target is None: - old_return_target = self.add_block("try.doreturn") - value = old_return_target.append(ir.GetLocal(self.current_private_env, "$return")) - old_return_target.append(ir.Return(value)) - redirect(old_return_target, return_proxy) - - handlers = [] - - for (handler_node, (handler, exn_type, phi)) in zip(node.handlers, clauses): - self.current_block = handler - if handler_node.name is not None: - exn = self.append(ir.Builtin("exncast", [phi], handler_node.name_type)) - self._set_local(handler_node.name, exn) - self.visit(handler_node.body) - # only need to call end_catch if the current block is not terminated - # other possible paths: break/continue/return/raise - # we will call end_catch in the first 3 cases, and we should not - # end_catch in the last case for nested exception - if not self.current_block.is_terminated(): - self.append(ir.Builtin("end_catch", [], builtins.TNone())) - post_handler = self.current_block - handlers.append(post_handler) - - # branch to all possible clauses, including those from outer try catch - # block - # if we have a finally block, all_clauses will not include those from - # the outer block - for (handler, clause, phi) in all_clauses: - phi.add_incoming(landingpad, dispatcher) - landingpad.add_clause(handler, clause) - - if self.break_target: - self.break_target = old_break - if self.continue_target: - self.continue_target = old_continue - self.return_target = old_return - - if any(node.finalbody): - # Finalize and continue after try statement. - self.unwind_target = old_unwind - # Exception path - finalizer_reraise = self.add_block("finally.resume") - self.current_block = finalizer_reraise - self.visit(node.finalbody) - self.terminate(ir.Resume(self.unwind_target)) - cleanup.append(ir.Branch(finalizer_reraise)) - - # Normal path - finalizer = self.add_block("finally") - self.current_block = finalizer - self.visit(node.finalbody) - post_finalizer = self.current_block - self.current_block = tail = self.add_block("try.tail") - final_targets.append(tail) - - # if final block is not terminated, branch to tail - if not post_finalizer.is_terminated(): - dest = post_finalizer.append(ir.GetLocal(final_state, "$cont")) - post_finalizer.append(ir.IndirectBranch(dest, final_targets)) - # make sure proxies will branch to finalizer - for block in final_paths: - if finalizer in block.predecessors(): - # avoid producing irreducible graphs - # generate a new finalizer - self.current_block = tmp_finalizer = self.add_block("finally.tmp") - self.visit(node.finalbody) - if not self.current_block.is_terminated(): - assert isinstance(block.instructions[-1], ir.SetLocal) - self.current_block.append(ir.Branch(block.instructions[-1].operands[-1])) - block.instructions[-1].erase() - block.append(ir.Branch(tmp_finalizer)) - self.current_block = tail - else: - block.append(ir.Branch(finalizer)) - # if no raise in body/handlers, branch to finalizer - for block in chain([body], handlers): - if not block.is_terminated(): - if finalizer in block.predecessors(): - # similar to the above case - self.current_block = tmp_finalizer = self.add_block("finally.tmp") - self.visit(node.finalbody) - self.terminate(ir.Branch(tail)) - block.append(ir.Branch(tmp_finalizer)) - self.current_block = tail - else: - block.append(ir.SetLocal(final_state, "$cont", tail)) - block.append(ir.Branch(finalizer)) - else: - self.current_block = tail = self.add_block("try.tail") - if not body.is_terminated(): - body.append(ir.Branch(tail)) - - cleanup.append(ir.Resume(self.unwind_target)) - - for handler in handlers: - if not handler.is_terminated(): - handler.append(ir.Branch(tail)) - - def _try_finally(self, body_gen, finally_gen, name): - dispatcher = self.add_block("{}.dispatch".format(name)) - - try: - old_unwind, self.unwind_target = self.unwind_target, dispatcher - body_gen() - finally: - self.unwind_target = old_unwind - - if not self.current_block.is_terminated(): - finally_gen() - - self.post_body = self.current_block - - self.current_block = self.add_block("{}.cleanup".format(name)) - dispatcher.append(ir.LandingPad(self.current_block)) - finally_gen() - self.terminate(ir.Resume(self.unwind_target)) - - self.current_block = self.post_body - - def visit_With(self, node): - context_expr_node = node.items[0].context_expr - optional_vars_node = node.items[0].optional_vars - - if types.is_builtin(context_expr_node.type, "sequential"): - self.visit(node.body) - return - elif types.is_builtin(context_expr_node.type, "interleave"): - interleave = self.append(ir.Interleave([])) - - heads, tails = [], [] - for stmt in node.body: - self.current_block = self.add_block("interleave.branch") - heads.append(self.current_block) - self.visit(stmt) - tails.append(self.current_block) - - for head in heads: - interleave.add_destination(head) - - self.current_block = self.add_block("interleave.tail") - for tail in tails: - if not tail.is_terminated(): - tail.append(ir.Branch(self.current_block)) - return - elif types.is_builtin(context_expr_node.type, "parallel"): - start_mu = self.append(ir.Builtin("now_mu", [], builtins.TInt64())) - end_mu = start_mu - - for stmt in node.body: - self.append(ir.Builtin("at_mu", [start_mu], builtins.TNone())) - - block = self.add_block("parallel.branch") - if self.current_block.is_terminated(): - self.warn_unreachable(stmt[0]) - else: - self.append(ir.Branch(block)) - self.current_block = block - - self.visit(stmt) - - mid_mu = self.append(ir.Builtin("now_mu", [], builtins.TInt64())) - gt_mu = self.append(ir.Compare(ast.Gt(loc=None), mid_mu, end_mu)) - end_mu = self.append(ir.Select(gt_mu, mid_mu, end_mu)) - - self.append(ir.Builtin("at_mu", [end_mu], builtins.TNone())) - return - - cleanup = [] - for item_node in node.items: - # user-defined context manager - context_expr_node = item_node.context_expr - optional_vars_node = item_node.optional_vars - context_mgr = self.visit(context_expr_node) - enter_fn = self.append(ir.GetAttr(context_mgr, '__enter__')) - exit_fn = self.append(ir.GetAttr(context_mgr, '__exit__')) - - try: - self.current_assign = self._user_call(enter_fn, [], {}) - if optional_vars_node is not None: - self.visit(optional_vars_node) - finally: - self.current_assign = None - - none = self.append(ir.Alloc([], builtins.TNone())) - cleanup.append(lambda: - self._user_call(exit_fn, [none, none, none], {})) - - self._try_finally( - body_gen=lambda: self.visit(node.body), - finally_gen=lambda: [thunk() for thunk in cleanup], - name="with") - - # Expression visitors - # These visitors return a node in addition to mutating - # the IR. - - def visit_LambdaT(self, node): - return self.visit_function(node, is_lambda=True, is_internal=True) - - def visit_IfExpT(self, node): - cond = self.visit(node.test) - head = self.current_block - - if_true = self.add_block("ifexp.body") - self.current_block = if_true - true_result = self.visit(node.body) - post_if_true = self.current_block - - if_false = self.add_block("ifexp.else") - self.current_block = if_false - false_result = self.visit(node.orelse) - post_if_false = self.current_block - - tail = self.add_block("ifexp.tail") - self.current_block = tail - - if not post_if_true.is_terminated(): - post_if_true.append(ir.Branch(tail)) - if not post_if_false.is_terminated(): - post_if_false.append(ir.Branch(tail)) - head.append(ir.BranchIf(cond, if_true, if_false)) - - phi = self.append(ir.Phi(node.type)) - phi.add_incoming(true_result, post_if_true) - phi.add_incoming(false_result, post_if_false) - return phi - - def visit_NumT(self, node): - return ir.Constant(node.n, node.type) - - def visit_StrT(self, node): - return ir.Constant(node.s, node.type) - - def visit_NameConstantT(self, node): - return ir.Constant(node.value, node.type) - - def _env_for(self, name): - if name in self.current_globals: - return self.append(ir.Builtin("globalenv", [self.current_env], - self.current_env.type.outermost())) - else: - return self.current_env - - def _get_local(self, name): - if self.current_class is not None and \ - name in self.current_class.type.attributes: - return self.append(ir.GetAttr(self.current_class, name, - name="FLD." + name)) - - return self.append(ir.GetLocal(self._env_for(name), name, - name="LOC." + name)) - - def _set_local(self, name, value): - if self.current_class is not None and \ - name in self.current_class.type.attributes: - return self.append(ir.SetAttr(self.current_class, name, value)) - - self.append(ir.SetLocal(self._env_for(name), name, value)) - - def visit_NameT(self, node): - if self.current_assign is None: - insn = self._get_local(node.id) - self.variable_map[node] = insn - return insn - else: - return self._set_local(node.id, self.current_assign) - - def visit_AttributeT(self, node): - try: - old_assign, self.current_assign = self.current_assign, None - obj = self.visit(node.value) - finally: - self.current_assign = old_assign - - if self.current_assign is None: - return self.append(ir.GetAttr(obj, node.attr, - name="{}.FLD.{}".format(_readable_name(obj), node.attr))) - else: - return self.append(ir.SetAttr(obj, node.attr, self.current_assign)) - - def _make_check(self, cond, exn_gen, loc=None, params=[]): - if loc is None: - loc = self.current_loc - - try: - name = "check:{}:{}".format(loc.line(), loc.column()) - args = [ir.EnvironmentArgument(self.current_env.type, "ARG.ENV")] + \ - [ir.Argument(param.type, "ARG.{}".format(index)) - for index, param in enumerate(params)] - typ = types.TFunction(OrderedDict([("arg{}".format(index), param.type) - for index, param in enumerate(params)]), - OrderedDict(), - builtins.TNone()) - func = ir.Function(typ, ".".join(self.name + [name]), args, loc=loc) - func.is_internal = True - func.is_cold = True - func.is_generated = True - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - - old_unwind, self.unwind_target = self.unwind_target, None - self.raise_exn(lambda: exn_gen(*args[1:]), loc=loc) - finally: - self.current_function = old_func - self.current_block = old_block - self.unwind_target = old_unwind - - # cond: bool Value, condition - # exn_gen: lambda()->exn Value, exception if condition not true - cond_block = self.current_block - - self.current_block = body_block = self.add_block("check.body") - self._invoke_raising_func(func, params, "check") - - self.current_block = tail_block = self.add_block("check.tail") - cond_block.append(ir.BranchIf(cond, tail_block, body_block)) - - def _invoke_raising_func(self, func, params, block_name): - """Emit a call/invoke instruction as appropriate to terminte the current - basic block with a call to a helper function that always raises an - exception. - - (This is done for compiler-inserted checks and assertions to keep the - generated code tight for the normal case.) - """ - closure = self.append(ir.Closure(func, - ir.Constant(None, ir.TEnvironment("raise", {})))) - if self.unwind_target is None: - insn = self.append(ir.Call(closure, params, {})) - else: - after_invoke = self.add_block(block_name + ".invoke") - insn = self.append(ir.Invoke(closure, params, {}, after_invoke, self.unwind_target)) - self.current_block = after_invoke - insn.is_cold = True - self.append(ir.Unreachable()) - - def _map_index(self, length, index, one_past_the_end=False, loc=None): - lt_0 = self.append(ir.Compare(ast.Lt(loc=None), - index, ir.Constant(0, index.type))) - from_end = self.append(ir.Arith(ast.Add(loc=None), length, index)) - mapped_index = self.append(ir.Select(lt_0, from_end, index)) - mapped_ge_0 = self.append(ir.Compare(ast.GtE(loc=None), - mapped_index, ir.Constant(0, mapped_index.type))) - end_cmpop = ast.LtE(loc=None) if one_past_the_end else ast.Lt(loc=None) - mapped_lt_len = self.append(ir.Compare(end_cmpop, mapped_index, length)) - in_bounds = self.append(ir.Select(mapped_ge_0, mapped_lt_len, - ir.Constant(False, builtins.TBool()))) - head = self.current_block - - self._make_check( - in_bounds, - lambda index, length: self.alloc_exn(builtins.TException("IndexError"), - ir.Constant("index {0} out of bounds 0:{1}", builtins.TStr()), - index, length), - params=[index, length], - loc=loc) - - return mapped_index - - def _make_loop(self, init, cond_gen, body_gen, name="loop"): - # init: 'iter Value, initial loop variable value - # cond_gen: lambda('iter Value)->bool Value, loop condition - # body_gen: lambda('iter Value)->'iter Value, loop body, - # returns next loop variable value - init_block = self.current_block - - self.current_block = head_block = self.add_block("{}.head".format(name)) - init_block.append(ir.Branch(head_block)) - phi = self.append(ir.Phi(init.type)) - phi.add_incoming(init, init_block) - cond = cond_gen(phi) - - self.current_block = body_block = self.add_block("{}.body".format(name)) - body = body_gen(phi) - self.append(ir.Branch(head_block)) - phi.add_incoming(body, self.current_block) - - self.current_block = tail_block = self.add_block("{}.tail".format(name)) - head_block.append(ir.BranchIf(cond, body_block, tail_block)) - - return head_block, body_block, tail_block - - def visit_SubscriptT(self, node): - try: - old_assign, self.current_assign = self.current_assign, None - value = self.visit(node.value) - finally: - self.current_assign = old_assign - - if types.is_tuple(node.value.type): - assert isinstance(node.slice, ast.Index), \ - "Internal compiler error: tuple index should be an Index" - assert isinstance(node.slice.value, ast.Num), \ - "Internal compiler error: tuple index should be a constant" - - if self.current_assign is not None: - diag = diagnostic.Diagnostic("error", - "cannot assign to a tuple element", - {}, node.loc) - self.engine.process(diag) - - index = node.slice.value.n - indexed = self.append( - ir.GetAttr(value, index, name="{}.e{}".format(value.name, index)), - loc=node.loc - ) - - return indexed - - elif isinstance(node.slice, ast.Index): - try: - old_assign, self.current_assign = self.current_assign, None - index = self.visit(node.slice.value) - finally: - self.current_assign = old_assign - - # For multi-dimensional indexes, just apply them sequentially. This - # works, as they are only supported for types where we do not - # immediately need to distinguish between the Get and Set cases - # (i.e. arrays, which are reference types). - if types.is_tuple(index.type): - num_idxs = len(index.type.find().elts) - indices = [ - self.append(ir.GetAttr(index, i)) for i in range(num_idxs) - ] - else: - indices = [index] - indexed = value - for i, idx in enumerate(indices): - length = self.iterable_len(indexed, idx.type) - mapped_index = self._map_index(length, idx, loc=node.begin_loc) - if self.current_assign is None or i < len(indices) - 1: - indexed = self.iterable_get(indexed, mapped_index) - indexed.set_name("{}.at.{}".format(indexed.name, - _readable_name(idx))) - else: - self.append(ir.SetElem(indexed, mapped_index, self.current_assign, - name="{}.at.{}".format(value.name, - _readable_name(index)))) - if self.current_assign is None: - return indexed - else: - # This is a slice. The endpoint checking logic is the same for both lists - # and NumPy arrays, but the actual implementations differ – while slices of - # built-in lists are always copies in Python, they are views sharing the - # same backing storage in NumPy. - length = self.iterable_len(value, node.slice.type) - - if node.slice.lower is not None: - try: - old_assign, self.current_assign = self.current_assign, None - start_index = self.visit(node.slice.lower) - finally: - self.current_assign = old_assign - else: - start_index = ir.Constant(0, node.slice.type) - mapped_start_index = self._map_index(length, start_index, - loc=node.begin_loc) - - if node.slice.upper is not None: - try: - old_assign, self.current_assign = self.current_assign, None - stop_index = self.visit(node.slice.upper) - finally: - self.current_assign = old_assign - else: - stop_index = length - mapped_stop_index = self._map_index(length, stop_index, one_past_the_end=True, - loc=node.begin_loc) - - if builtins.is_array(node.type): - # To implement strided slicing with the proper NumPy reference - # semantics, the pointer/length array representation will need to be - # extended by another field to hold a variable stride. - assert node.slice.step is None, ( - "array slices with non-trivial step " - "should have been disallowed during type inference") - - # One-dimensionally slicing an array only affects the outermost - # dimension. - shape = self.append(ir.GetAttr(value, "shape")) - lengths = [ - self.append(ir.GetAttr(shape, i)) - for i in range(len(shape.type.elts)) - ] - - # Compute outermost length – zero for "backwards" indices. - raw_len = self.append( - ir.Arith(ast.Sub(loc=None), mapped_stop_index, mapped_start_index)) - is_neg_len = self.append( - ir.Compare(ast.Lt(loc=None), raw_len, ir.Constant(0, raw_len.type))) - outer_len = self.append( - ir.Select(is_neg_len, ir.Constant(0, raw_len.type), raw_len)) - new_shape = self._make_array_shape([outer_len] + lengths[1:]) - - # Offset buffer pointer by start index (times stride for inner dims). - stride = reduce( - lambda l, r: self.append(ir.Arith(ast.Mult(loc=None), l, r)), - lengths[1:], ir.Constant(1, lengths[0].type)) - offset = self.append( - ir.Arith(ast.Mult(loc=None), stride, mapped_start_index)) - buffer = self.append(ir.GetAttr(value, "buffer")) - new_buffer = self.append(ir.Offset(buffer, offset)) - - return self.append(ir.Alloc([new_buffer, new_shape], node.type)) - else: - if node.slice.step is not None: - try: - old_assign, self.current_assign = self.current_assign, None - step = self.visit(node.slice.step) - finally: - self.current_assign = old_assign - - self._make_check( - self.append(ir.Compare(ast.NotEq(loc=None), step, ir.Constant(0, step.type))), - lambda: self.alloc_exn(builtins.TException("ValueError"), - ir.Constant("step cannot be zero", builtins.TStr())), - loc=node.slice.step.loc) - else: - step = ir.Constant(1, node.slice.type) - counting_up = self.append(ir.Compare(ast.Gt(loc=None), step, - ir.Constant(0, step.type))) - - unstepped_size = self.append(ir.Arith(ast.Sub(loc=None), - mapped_stop_index, mapped_start_index)) - slice_size_a = self.append(ir.Arith(ast.FloorDiv(loc=None), unstepped_size, step)) - slice_size_b = self.append(ir.Arith(ast.Mod(loc=None), unstepped_size, step)) - rem_not_empty = self.append(ir.Compare(ast.NotEq(loc=None), slice_size_b, - ir.Constant(0, slice_size_b.type))) - slice_size_c = self.append(ir.Arith(ast.Add(loc=None), slice_size_a, - ir.Constant(1, slice_size_a.type))) - slice_size = self.append(ir.Select(rem_not_empty, - slice_size_c, slice_size_a, - name="slice.size")) - self._make_check( - self.append(ir.Compare(ast.LtE(loc=None), slice_size, length)), - lambda slice_size, length: self.alloc_exn(builtins.TException("ValueError"), - ir.Constant("slice size {0} is larger than iterable length {1}", - builtins.TStr()), - slice_size, length), - params=[slice_size, length], - loc=node.slice.loc) - - if self.current_assign is None: - is_neg_size = self.append(ir.Compare(ast.Lt(loc=None), - slice_size, ir.Constant(0, slice_size.type))) - abs_slice_size = self.append(ir.Select(is_neg_size, - ir.Constant(0, slice_size.type), slice_size)) - other_value = self.append(ir.Alloc([abs_slice_size], value.type, - name="slice.result")) - else: - other_value = self.current_assign - - prehead = self.current_block - - head = self.current_block = self.add_block("slice.head") - prehead.append(ir.Branch(head)) - - index = self.append(ir.Phi(node.slice.type, - name="slice.index")) - index.add_incoming(mapped_start_index, prehead) - other_index = self.append(ir.Phi(node.slice.type, - name="slice.resindex")) - other_index.add_incoming(ir.Constant(0, node.slice.type), prehead) - - # Still within bounds? - bounded_up = self.append(ir.Compare(ast.Lt(loc=None), index, mapped_stop_index)) - bounded_down = self.append(ir.Compare(ast.Gt(loc=None), index, mapped_stop_index)) - within_bounds = self.append(ir.Select(counting_up, bounded_up, bounded_down)) - - body = self.current_block = self.add_block("slice.body") - - if self.current_assign is None: - elem = self.iterable_get(value, index) - self.append(ir.SetElem(other_value, other_index, elem)) - else: - elem = self.append(ir.GetElem(self.current_assign, other_index)) - self.append(ir.SetElem(value, index, elem)) - - next_index = self.append(ir.Arith(ast.Add(loc=None), index, step)) - index.add_incoming(next_index, body) - next_other_index = self.append(ir.Arith(ast.Add(loc=None), other_index, - ir.Constant(1, node.slice.type))) - other_index.add_incoming(next_other_index, body) - self.append(ir.Branch(head)) - - tail = self.current_block = self.add_block("slice.tail") - head.append(ir.BranchIf(within_bounds, body, tail)) - - if self.current_assign is None: - return other_value - - def visit_TupleT(self, node): - if self.current_assign is None: - return self.append(ir.Alloc([self.visit(elt) for elt in node.elts], node.type)) - else: - try: - old_assign = self.current_assign - for index, elt_node in enumerate(node.elts): - self.current_assign = \ - self.append(ir.GetAttr(old_assign, index, - name="{}.e{}".format(old_assign.name, index)), - loc=elt_node.loc) - self.visit(elt_node) - finally: - self.current_assign = old_assign - - def visit_ListT(self, node): - if self.current_assign is None: - elts = [self.visit(elt_node) for elt_node in node.elts] - lst = self.append(ir.Alloc([ir.Constant(len(node.elts), self._size_type)], - node.type)) - for index, elt_node in enumerate(elts): - self.append(ir.SetElem(lst, ir.Constant(index, self._size_type), elt_node)) - return lst - else: - length = self.iterable_len(self.current_assign) - self._make_check( - self.append(ir.Compare(ast.Eq(loc=None), length, - ir.Constant(len(node.elts), self._size_type))), - lambda length: self.alloc_exn(builtins.TException("ValueError"), - ir.Constant("list must be {0} elements long to decompose", builtins.TStr()), - length), - params=[length]) - - for index, elt_node in enumerate(node.elts): - elt = self.append(ir.GetElem(self.current_assign, - ir.Constant(index, self._size_type))) - try: - old_assign, self.current_assign = self.current_assign, elt - self.visit(elt_node) - finally: - self.current_assign = old_assign - - def visit_ListCompT(self, node): - assert len(node.generators) == 1 - comprehension = node.generators[0] - assert comprehension.ifs == [] - - iterable = self.visit(comprehension.iter) - length = self.iterable_len(iterable) - result = self.append(ir.Alloc([length], node.type)) - - try: - gen_suffix = ".gen@{}:{}".format(node.loc.line(), node.loc.column()) - env_type = ir.TEnvironment(name=self.current_function.name + gen_suffix, - vars=node.typing_env, outer=self.current_env.type) - env = self.append(ir.Alloc([], env_type, name="env.gen")) - old_env, self.current_env = self.current_env, env - - self.append(ir.SetLocal(env, "$outer", old_env)) - - def body_gen(index): - elt = self.iterable_get(iterable, index) - try: - old_assign, self.current_assign = self.current_assign, elt - self.visit(comprehension.target) - finally: - self.current_assign = old_assign - - mapped_elt = self.visit(node.elt) - self.append(ir.SetElem(result, index, mapped_elt)) - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, length.type))) - self._make_loop(ir.Constant(0, length.type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, length)), - body_gen) - - return result - finally: - self.current_env = old_env - - def visit_BoolOpT(self, node): - blocks = [] - for value_node in node.values: - value_head = self.current_block - value = self.visit(value_node) - value_tail = self.current_block - - blocks.append((value, value_head, value_tail)) - self.current_block = self.add_block("boolop.seq") - - tail = self.current_block - phi = self.append(ir.Phi(node.type)) - for ((value, value_head, value_tail), (next_value_head, next_value_tail)) in \ - zip(blocks, [(h,t) for (v,h,t) in blocks[1:]] + [(tail, tail)]): - phi.add_incoming(value, value_tail) - if next_value_head != tail: - cond = self.coerce_to_bool(value, block=value_tail) - if isinstance(node.op, ast.And): - value_tail.append(ir.BranchIf(cond, next_value_head, tail)) - else: - value_tail.append(ir.BranchIf(cond, tail, next_value_head)) - else: - value_tail.append(ir.Branch(tail)) - return phi - - def _make_array_unaryop(self, name, make_op, result_type, arg_type): - try: - result = ir.Argument(result_type, "result") - arg = ir.Argument(arg_type, "arg") - - # TODO: We'd like to use a "C function" here to be able to supply - # specialised implementations in a library in the future (and e.g. avoid - # passing around the context argument), but the code generator currently - # doesn't allow emitting them. - args = [result, arg] - typ = types.TFunction(args=OrderedDict([(arg.name, arg.type) - for arg in args]), - optargs=OrderedDict(), - ret=builtins.TNone()) - env_args = [ir.EnvironmentArgument(self.current_env.type, "ARG.ENV")] - - old_loc, self.current_loc = self.current_loc, None - func = ir.Function(typ, name, env_args + args) - func.is_internal = True - func.is_generated = True - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - - old_unwind, self.unwind_target = self.unwind_target, None - - shape = self.append(ir.GetAttr(arg, "shape")) - - result_buffer = self.append(ir.GetAttr(result, "buffer")) - arg_buffer = self.append(ir.GetAttr(arg, "buffer")) - num_total_elts = self._get_total_array_len(shape) - - def body_gen(index): - a = self.append(ir.GetElem(arg_buffer, index)) - self.append( - ir.SetElem(result_buffer, index, make_op(a))) - return self.append( - ir.Arith(ast.Add(loc=None), index, ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, num_total_elts)), body_gen) - - self.append(ir.Return(ir.Constant(None, builtins.TNone()))) - return func - finally: - self.current_loc = old_loc - self.current_function = old_func - self.current_block = old_block - self.unwind_target = old_unwind - - def _get_array_unaryop(self, name, make_op, result_type, arg_type): - name = "_array_{}_{}".format( - name, self._mangle_arrayop_types([result_type, arg_type])) - if name not in self.array_op_funcs: - self.array_op_funcs[name] = self._make_array_unaryop( - name, make_op, result_type, arg_type) - return self.array_op_funcs[name] - - def visit_UnaryOpT(self, node): - if isinstance(node.op, ast.Not): - cond = self.coerce_to_bool(self.visit(node.operand)) - return self.append(ir.Select(cond, - ir.Constant(False, builtins.TBool()), - ir.Constant(True, builtins.TBool()))) - elif isinstance(node.op, ast.Invert): - operand = self.visit(node.operand) - return self.append(ir.Arith(ast.BitXor(loc=None), - ir.Constant(-1, operand.type), operand)) - elif isinstance(node.op, ast.USub): - def make_sub(val): - return self.append(ir.Arith(ast.Sub(loc=None), - ir.Constant(0, val.type), val)) - operand = self.visit(node.operand) - if builtins.is_array(operand.type): - shape = self.append(ir.GetAttr(operand, "shape")) - result, _ = self._allocate_new_array(node.type.find()["elt"], shape) - func = self._get_array_unaryop("USub", make_sub, node.type, operand.type) - self._invoke_arrayop(func, [result, operand]) - return result - else: - return make_sub(operand) - elif isinstance(node.op, ast.UAdd): - # No-op. - return self.visit(node.operand) - else: - assert False - - def visit_CoerceT(self, node): - value = self.visit(node.value) - if node.type.find() == value.type: - return value - else: - if builtins.is_array(node.type): - result_elt = node.type.find()["elt"] - shape = self.append(ir.GetAttr(value, "shape")) - result, _ = self._allocate_new_array(result_elt, shape) - func = self._get_array_unaryop( - "Coerce", lambda v: self.append(ir.Coerce(v, result_elt)), - node.type, value.type) - self._invoke_arrayop(func, [result, value]) - return result - else: - return self.append( - ir.Coerce(value, - node.type, - name="{}.{}".format(_readable_name(value), - node.type.name))) - - def _get_total_array_len(self, shape): - lengths = [ - self.append(ir.GetAttr(shape, i)) for i in range(len(shape.type.elts)) - ] - return reduce(lambda l, r: self.append(ir.Arith(ast.Mult(loc=None), l, r)), - lengths[1:], lengths[0]) - - def _allocate_new_array(self, elt, shape): - total_length = self._get_total_array_len(shape) - buffer = self.append(ir.Alloc([total_length], types._TPointer(elt=elt))) - result_type = builtins.TArray(elt, types.TValue(len(shape.type.elts))) - return self.append(ir.Alloc([buffer, shape], result_type)), total_length - - def _make_array_binop(self, name, result_type, lhs_type, rhs_type, body_gen): - try: - result = ir.Argument(result_type, "result") - lhs = ir.Argument(lhs_type, "lhs") - rhs = ir.Argument(rhs_type, "rhs") - - # TODO: We'd like to use a "C function" here to be able to supply - # specialised implementations in a library in the future (and e.g. avoid - # passing around the context argument), but the code generator currently - # doesn't allow emitting them. - args = [result, lhs, rhs] - typ = types.TFunction(args=OrderedDict([(arg.name, arg.type) - for arg in args]), - optargs=OrderedDict(), - ret=builtins.TNone()) - env_args = [ir.EnvironmentArgument(self.current_env.type, "ARG.ENV")] - - old_loc, self.current_loc = self.current_loc, None - func = ir.Function(typ, name, env_args + args) - func.is_internal = True - func.is_generated = True - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - - old_unwind, self.unwind_target = self.unwind_target, None - - body_gen(result, lhs, rhs) - - self.append(ir.Return(ir.Constant(None, builtins.TNone()))) - return func - finally: - self.current_loc = old_loc - self.current_function = old_func - self.current_block = old_block - self.unwind_target = old_unwind - - def _make_array_elementwise_binop(self, name, result_type, lhs_type, - rhs_type, make_op): - def body_gen(result, lhs, rhs): - # At this point, shapes are assumed to match; could just pass buffer - # pointer for two of the three arrays as well. - result_buffer = self.append(ir.GetAttr(result, "buffer")) - shape = self.append(ir.GetAttr(result, "shape")) - num_total_elts = self._get_total_array_len(shape) - - if builtins.is_array(lhs.type): - lhs_buffer = self.append(ir.GetAttr(lhs, "buffer")) - def get_left(index): - return self.append(ir.GetElem(lhs_buffer, index)) - else: - def get_left(index): - return lhs - - if builtins.is_array(rhs.type): - rhs_buffer = self.append(ir.GetAttr(rhs, "buffer")) - def get_right(index): - return self.append(ir.GetElem(rhs_buffer, index)) - else: - def get_right(index): - return rhs - - def loop_gen(index): - l = get_left(index) - r = get_right(index) - result = make_op(l, r) - self.append(ir.SetElem(result_buffer, index, result)) - return self.append( - ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, num_total_elts)), - loop_gen) - - return self._make_array_binop(name, result_type, lhs_type, rhs_type, - body_gen) - - def _mangle_arrayop_types(self, types): - def name_error(typ): - assert False, "Internal compiler error: No RPC tag for {}".format(typ) - - def mangle_name(typ): - typ = typ.find() - # rpc_tag is used to turn element types into mangled names for no - # particularly good reason apart from not having to invent yet another - # string representation. - if builtins.is_array(typ): - return mangle_name(typ["elt"]) + str(typ["num_dims"].find().value) - return ir.rpc_tag(typ, name_error).decode() - - return "_".join(mangle_name(t) for t in types) - - def _get_array_elementwise_binop(self, name, make_op, result_type, lhs_type, rhs_type): - # Currently, we always have any type coercions resolved explicitly in the AST. - # In the future, this might no longer be true and the three types might all - # differ. - name = "_array_{}_{}".format( - name, - self._mangle_arrayop_types([result_type, lhs_type, rhs_type])) - if name not in self.array_op_funcs: - self.array_op_funcs[name] = self._make_array_elementwise_binop( - name, result_type, lhs_type, rhs_type, make_op) - return self.array_op_funcs[name] - - def _invoke_arrayop(self, func, params): - closure = self.append( - ir.Closure(func, ir.Constant(None, ir.TEnvironment("arrayop", {})))) - if self.unwind_target is None: - self.append(ir.Call(closure, params, {})) - else: - after_invoke = self.add_block("arrayop.invoke") - self.append(ir.Invoke(func, params, {}, after_invoke, self.unwind_target)) - self.current_block = after_invoke - - def _get_array_offset(self, shape, indices): - result = indices[0] - for dim, index in zip(shape[1:], indices[1:]): - result = self.append(ir.Arith(ast.Mult(loc=None), result, dim)) - result = self.append(ir.Arith(ast.Add(loc=None), result, index)) - return result - - def _get_matmult(self, result_type, lhs_type, rhs_type): - name = "_array_MatMult_" + self._mangle_arrayop_types( - [result_type, lhs_type, rhs_type]) - if name not in self.array_op_funcs: - - def body_gen(result, lhs, rhs): - assert builtins.is_array(result.type), \ - "vec @ vec should have been normalised into array result" - - # We assume result has correct shape; could just pass buffer pointer - # as well. - result_buffer = self.append(ir.GetAttr(result, "buffer")) - lhs_buffer = self.append(ir.GetAttr(lhs, "buffer")) - rhs_buffer = self.append(ir.GetAttr(rhs, "buffer")) - - num_rows, num_summands, _, num_cols = self._get_matmult_shapes(lhs, rhs) - - elt = result.type["elt"].find() - env_type = ir.TEnvironment(name + ".loop", {"$total": elt}) - env = self.append(ir.Alloc([], env_type)) - - def row_loop(row_idx): - lhs_base_offset = self.append( - ir.Arith(ast.Mult(loc=None), row_idx, num_summands)) - lhs_base = self.append(ir.Offset(lhs_buffer, lhs_base_offset)) - result_base_offset = self.append( - ir.Arith(ast.Mult(loc=None), row_idx, num_cols)) - result_base = self.append( - ir.Offset(result_buffer, result_base_offset)) - - def col_loop(col_idx): - rhs_base = self.append(ir.Offset(rhs_buffer, col_idx)) - - self.append( - ir.SetLocal(env, "$total", ir.Constant(elt.zero(), elt))) - - def sum_loop(sum_idx): - lhs_elem = self.append(ir.GetElem(lhs_base, sum_idx)) - rhs_offset = self.append( - ir.Arith(ast.Mult(loc=None), sum_idx, num_cols)) - rhs_elem = self.append(ir.GetElem(rhs_base, rhs_offset)) - product = self.append( - ir.Arith(ast.Mult(loc=None), lhs_elem, rhs_elem)) - prev_total = self.append(ir.GetLocal(env, "$total")) - total = self.append( - ir.Arith(ast.Add(loc=None), prev_total, product)) - self.append(ir.SetLocal(env, "$total", total)) - return self.append( - ir.Arith(ast.Add(loc=None), sum_idx, - ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, num_summands)), - sum_loop) - - total = self.append(ir.GetLocal(env, "$total")) - self.append(ir.SetElem(result_base, col_idx, total)) - - return self.append( - ir.Arith(ast.Add(loc=None), col_idx, - ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, num_cols)), col_loop) - return self.append( - ir.Arith(ast.Add(loc=None), row_idx, - ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, num_rows)), row_loop) - - self.array_op_funcs[name] = self._make_array_binop( - name, result_type, lhs_type, rhs_type, body_gen) - return self.array_op_funcs[name] - - def _get_matmult_shapes(self, lhs, rhs): - lhs_shape = self.append(ir.GetAttr(lhs, "shape")) - if lhs.type["num_dims"].value == 1: - lhs_shape_outer = ir.Constant(1, self._size_type) - lhs_shape_inner = self.append(ir.GetAttr(lhs_shape, 0)) - else: - lhs_shape_outer = self.append(ir.GetAttr(lhs_shape, 0)) - lhs_shape_inner = self.append(ir.GetAttr(lhs_shape, 1)) - - rhs_shape = self.append(ir.GetAttr(rhs, "shape")) - if rhs.type["num_dims"].value == 1: - rhs_shape_inner = self.append(ir.GetAttr(rhs_shape, 0)) - rhs_shape_outer = ir.Constant(1, self._size_type) - else: - rhs_shape_inner = self.append(ir.GetAttr(rhs_shape, 0)) - rhs_shape_outer = self.append(ir.GetAttr(rhs_shape, 1)) - - return lhs_shape_outer, lhs_shape_inner, rhs_shape_inner, rhs_shape_outer - - def _make_array_shape(self, dims): - return self.append(ir.Alloc(dims, types.TTuple([self._size_type] * len(dims)))) - - def _emit_matmult(self, node, left, right): - # TODO: Also expose as numpy.dot. - lhs = self.visit(left) - rhs = self.visit(right) - - num_rows, lhs_inner, rhs_inner, num_cols = self._get_matmult_shapes(lhs, rhs) - self._make_check( - self.append(ir.Compare(ast.Eq(loc=None), lhs_inner, rhs_inner)), - lambda lhs_inner, rhs_inner: self.alloc_exn( - builtins.TException("ValueError"), - ir.Constant( - "inner dimensions for matrix multiplication do not match ({0} vs. {1})", - builtins.TStr()), lhs_inner, rhs_inner), - params=[lhs_inner, rhs_inner], - loc=node.loc) - result_shape = self._make_array_shape([num_rows, num_cols]) - - final_type = node.type.find() - if not builtins.is_array(final_type): - elt = node.type - result_dims = 0 - else: - elt = final_type["elt"] - result_dims = final_type["num_dims"].value - - result, _ = self._allocate_new_array(elt, result_shape) - func = self._get_matmult(result.type, left.type, right.type) - self._invoke_arrayop(func, [result, lhs, rhs]) - - if result_dims == 2: - return result - result_buffer = self.append(ir.GetAttr(result, "buffer")) - if result_dims == 1: - shape = self._make_array_shape( - [num_cols if lhs.type["num_dims"].value == 1 else num_rows]) - return self.append(ir.Alloc([result_buffer, shape], node.type)) - return self.append(ir.GetElem(result_buffer, ir.Constant(0, self._size_type))) - - def _broadcast_binop(self, name, make_op, result_type, lhs, rhs, assign_to_lhs): - # Broadcast scalars (broadcasting higher dimensions is not yet allowed in the - # language). - broadcast = False - array_arg = lhs - if not builtins.is_array(lhs.type): - broadcast = True - array_arg = rhs - elif not builtins.is_array(rhs.type): - broadcast = True - - shape = self.append(ir.GetAttr(array_arg, "shape")) - - if not broadcast: - rhs_shape = self.append(ir.GetAttr(rhs, "shape")) - self._make_check( - self.append(ir.Compare(ast.Eq(loc=None), shape, rhs_shape)), - lambda: self.alloc_exn( - builtins.TException("ValueError"), - ir.Constant("operands could not be broadcast together", - builtins.TStr()))) - if assign_to_lhs: - result = lhs - else: - elt = result_type.find()["elt"] - result, _ = self._allocate_new_array(elt, shape) - func = self._get_array_elementwise_binop(name, make_op, result_type, lhs.type, - rhs.type) - self._invoke_arrayop(func, [result, lhs, rhs]) - return result - - def visit_BinOpT(self, node): - if isinstance(node.op, ast.MatMult): - return self._emit_matmult(node, node.left, node.right) - elif builtins.is_array(node.type): - lhs = self.visit(node.left) - rhs = self.visit(node.right) - name = type(node.op).__name__ - def make_op(l, r): - return self.append(ir.Arith(node.op, l, r)) - return self._broadcast_binop(name, make_op, node.type, lhs, rhs, - assign_to_lhs=False) - elif builtins.is_numeric(node.type): - lhs = self.visit(node.left) - rhs = self.visit(node.right) - if isinstance(node.op, (ast.LShift, ast.RShift)): - # Check for negative shift amount. - self._make_check( - self.append(ir.Compare(ast.GtE(loc=None), rhs, ir.Constant(0, rhs.type))), - lambda: self.alloc_exn(builtins.TException("ValueError"), - ir.Constant("shift amount must be nonnegative", builtins.TStr())), - loc=node.right.loc) - elif isinstance(node.op, (ast.Div, ast.FloorDiv, ast.Mod)): - self._make_check( - self.append(ir.Compare(ast.NotEq(loc=None), rhs, ir.Constant(0, rhs.type))), - lambda: self.alloc_exn(builtins.TException("ZeroDivisionError"), - ir.Constant("cannot divide by zero", builtins.TStr())), - loc=node.right.loc) - - return self.append(ir.Arith(node.op, lhs, rhs)) - elif isinstance(node.op, ast.Add): # list + list, tuple + tuple, str + str - lhs, rhs = self.visit(node.left), self.visit(node.right) - if types.is_tuple(node.left.type) and types.is_tuple(node.right.type): - elts = [] - for index, elt in enumerate(node.left.type.elts): - elts.append(self.append(ir.GetAttr(lhs, index))) - for index, elt in enumerate(node.right.type.elts): - elts.append(self.append(ir.GetAttr(rhs, index))) - return self.append(ir.Alloc(elts, node.type)) - elif builtins.is_listish(node.left.type) and builtins.is_listish(node.right.type): - lhs_length = self.iterable_len(lhs) - rhs_length = self.iterable_len(rhs) - - result_length = self.append(ir.Arith(ast.Add(loc=None), lhs_length, rhs_length)) - result = self.append(ir.Alloc([result_length], node.type)) - - # Copy lhs - def body_gen(index): - elt = self.append(ir.GetElem(lhs, index)) - self.append(ir.SetElem(result, index, elt)) - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) - self._make_loop(ir.Constant(0, self._size_type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, lhs_length)), - body_gen) - - # Copy rhs - def body_gen(index): - elt = self.append(ir.GetElem(rhs, index)) - result_index = self.append(ir.Arith(ast.Add(loc=None), index, lhs_length)) - self.append(ir.SetElem(result, result_index, elt)) - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) - self._make_loop(ir.Constant(0, self._size_type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, rhs_length)), - body_gen) - - return result - else: - assert False - elif isinstance(node.op, ast.Mult): # list * int, int * list - lhs, rhs = self.visit(node.left), self.visit(node.right) - if builtins.is_listish(lhs.type) and builtins.is_int(rhs.type): - lst, num = lhs, rhs - elif builtins.is_int(lhs.type) and builtins.is_listish(rhs.type): - lst, num = rhs, lhs - else: - assert False - - lst_length = self.iterable_len(lst) - - result_length = self.append(ir.Arith(ast.Mult(loc=None), lst_length, num)) - result = self.append(ir.Alloc([result_length], node.type)) - - # num times... - def body_gen(num_index): - # ... copy the list - def body_gen(lst_index): - elt = self.append(ir.GetElem(lst, lst_index)) - base_index = self.append(ir.Arith(ast.Mult(loc=None), - num_index, lst_length)) - result_index = self.append(ir.Arith(ast.Add(loc=None), - base_index, lst_index)) - self.append(ir.SetElem(result, base_index, elt)) - return self.append(ir.Arith(ast.Add(loc=None), lst_index, - ir.Constant(1, self._size_type))) - self._make_loop(ir.Constant(0, self._size_type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, lst_length)), - body_gen) - - return self.append(ir.Arith(ast.Add(loc=None), num_index, - ir.Constant(1, self._size_type))) - self._make_loop(ir.Constant(0, self._size_type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, num)), - body_gen) - - return result - else: - assert False - - def polymorphic_compare_pair_order(self, op, lhs, rhs): - if builtins.is_none(lhs.type) and builtins.is_none(rhs.type): - return self.append(ir.Compare(op, lhs, rhs)) - elif builtins.is_numeric(lhs.type) and builtins.is_numeric(rhs.type): - return self.append(ir.Compare(op, lhs, rhs)) - elif builtins.is_bool(lhs.type) and builtins.is_bool(rhs.type): - return self.append(ir.Compare(op, lhs, rhs)) - elif types.is_tuple(lhs.type) and types.is_tuple(rhs.type): - result = None - for index in range(len(lhs.type.elts)): - lhs_elt = self.append(ir.GetAttr(lhs, index)) - rhs_elt = self.append(ir.GetAttr(rhs, index)) - elt_result = self.polymorphic_compare_pair(op, lhs_elt, rhs_elt) - if result is None: - result = elt_result - else: - result = self.append(ir.Select(result, elt_result, - ir.Constant(False, builtins.TBool()))) - return result - elif builtins.is_listish(lhs.type) and builtins.is_listish(rhs.type): - head = self.current_block - lhs_length = self.iterable_len(lhs) - rhs_length = self.iterable_len(rhs) - compare_length = self.append(ir.Compare(op, lhs_length, rhs_length)) - eq_length = self.append(ir.Compare(ast.Eq(loc=None), lhs_length, rhs_length)) - - # If the length is the same, compare element-by-element - # and break when the comparison result is false - loop_head = self.add_block("compare.head") - self.current_block = loop_head - index_phi = self.append(ir.Phi(self._size_type)) - index_phi.add_incoming(ir.Constant(0, self._size_type), head) - loop_cond = self.append(ir.Compare(ast.Lt(loc=None), index_phi, lhs_length)) - - loop_body = self.add_block("compare.body") - self.current_block = loop_body - lhs_elt = self.append(ir.GetElem(lhs, index_phi)) - rhs_elt = self.append(ir.GetElem(rhs, index_phi)) - body_result = self.polymorphic_compare_pair(op, lhs_elt, rhs_elt) - body_end = self.current_block - - loop_body2 = self.add_block("compare.body2") - self.current_block = loop_body2 - index_next = self.append(ir.Arith(ast.Add(loc=None), index_phi, - ir.Constant(1, self._size_type))) - self.append(ir.Branch(loop_head)) - index_phi.add_incoming(index_next, loop_body2) - - tail = self.add_block("compare.tail") - self.current_block = tail - phi = self.append(ir.Phi(builtins.TBool())) - head.append(ir.BranchIf(eq_length, loop_head, tail)) - phi.add_incoming(compare_length, head) - loop_head.append(ir.BranchIf(loop_cond, loop_body, tail)) - phi.add_incoming(ir.Constant(True, builtins.TBool()), loop_head) - body_end.append(ir.BranchIf(body_result, loop_body2, tail)) - phi.add_incoming(body_result, body_end) - - if isinstance(op, ast.NotEq): - result = self.append(ir.Select(phi, - ir.Constant(False, builtins.TBool()), ir.Constant(True, builtins.TBool()))) - else: - result = phi - - return result - else: - loc = lhs.loc - loc.end = rhs.loc.end - diag = diagnostic.Diagnostic("error", - "Custom object comparison is not supported", - {}, - loc) - self.engine.process(diag) - - def polymorphic_compare_pair_inclusion(self, needle, haystack): - if builtins.is_range(haystack.type): - # Optimized range `in` operator - start = self.append(ir.GetAttr(haystack, "start")) - stop = self.append(ir.GetAttr(haystack, "stop")) - step = self.append(ir.GetAttr(haystack, "step")) - after_start = self.append(ir.Compare(ast.GtE(loc=None), needle, start)) - after_stop = self.append(ir.Compare(ast.Lt(loc=None), needle, stop)) - from_start = self.append(ir.Arith(ast.Sub(loc=None), needle, start)) - mod_step = self.append(ir.Arith(ast.Mod(loc=None), from_start, step)) - on_step = self.append(ir.Compare(ast.Eq(loc=None), mod_step, - ir.Constant(0, mod_step.type))) - result = self.append(ir.Select(after_start, after_stop, - ir.Constant(False, builtins.TBool()))) - result = self.append(ir.Select(result, on_step, - ir.Constant(False, builtins.TBool()))) - elif builtins.is_iterable(haystack.type): - length = self.iterable_len(haystack) - - cmp_result = loop_body2 = None - def body_gen(index): - nonlocal cmp_result, loop_body2 - - elt = self.iterable_get(haystack, index) - cmp_result = self.polymorphic_compare_pair(ast.Eq(loc=None), needle, elt) - - loop_body2 = self.add_block("compare.body") - self.current_block = loop_body2 - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, length.type))) - loop_head, loop_body, loop_tail = \ - self._make_loop(ir.Constant(0, length.type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, length)), - body_gen, name="compare") - - loop_body.append(ir.BranchIf(cmp_result, loop_tail, loop_body2)) - phi = loop_tail.prepend(ir.Phi(builtins.TBool())) - phi.add_incoming(ir.Constant(False, builtins.TBool()), loop_head) - phi.add_incoming(ir.Constant(True, builtins.TBool()), loop_body) - - result = phi - else: - loc = needle.loc - loc.end = haystack.loc.end - diag = diagnostic.Diagnostic("error", - "Custom object inclusion test is not supported", - {}, - loc) - self.engine.process(diag) - - return result - - def invert(self, value): - return self.append(ir.Select(value, - ir.Constant(False, builtins.TBool()), - ir.Constant(True, builtins.TBool()))) - - def polymorphic_compare_pair(self, op, lhs, rhs): - if isinstance(op, (ast.Is, ast.IsNot)): - # The backend will handle equality of aggregates. - return self.append(ir.Compare(op, lhs, rhs)) - elif isinstance(op, ast.In): - return self.polymorphic_compare_pair_inclusion(lhs, rhs) - elif isinstance(op, ast.NotIn): - result = self.polymorphic_compare_pair_inclusion(lhs, rhs) - return self.invert(result) - elif isinstance(op, (ast.Eq, ast.Lt, ast.LtE, ast.Gt, ast.GtE)): - return self.polymorphic_compare_pair_order(op, lhs, rhs) - elif isinstance(op, ast.NotEq): - result = self.polymorphic_compare_pair_order(ast.Eq(loc=op.loc), lhs, rhs) - return self.invert(result) - else: - assert False - - def visit_CompareT(self, node): - # Essentially a sequence of `and`s performed over results - # of comparisons. - blocks = [] - lhs = self.visit(node.left) - for op, rhs_node in zip(node.ops, node.comparators): - result_head = self.current_block - rhs = self.visit(rhs_node) - result = self.polymorphic_compare_pair(op, lhs, rhs) - result_tail = self.current_block - - blocks.append((result, result_head, result_tail)) - self.current_block = self.add_block("compare.seq") - lhs = rhs - - tail = self.current_block - phi = self.append(ir.Phi(node.type)) - for ((result, result_head, result_tail), (next_result_head, next_result_tail)) in \ - zip(blocks, [(h,t) for (v,h,t) in blocks[1:]] + [(tail, tail)]): - phi.add_incoming(result, result_tail) - if next_result_head != tail: - result_tail.append(ir.BranchIf(result, next_result_head, tail)) - else: - result_tail.append(ir.Branch(tail)) - return phi - - # Keep this function with builtins.TException.attributes. - def alloc_exn(self, typ, message=None, param0=None, param1=None, - param2=None, nomsgcheck=False): - typ = typ.find() - name = "{}:{}".format(typ.id, typ.name) - name_id = self.embedding_map.store_str(name) - attributes = [ - ir.Constant(name_id, builtins.TInt32()), # typeinfo - ir.Constant("", builtins.TStr()), # file - ir.Constant(0, builtins.TInt32()), # line - ir.Constant(0, builtins.TInt32()), # column - ir.Constant("", builtins.TStr()), # function - ] - - if message is None: - attributes.append(ir.Constant(typ.name, builtins.TStr())) - elif isinstance(message, ir.Constant) or nomsgcheck: - attributes.append(message) # message - else: - diag = diagnostic.Diagnostic( - "error", - "only constant exception messages are supported", - {}, - self.current_loc if message.loc is None else message.loc - ) - self.engine.process(diag) - - param_type = builtins.TInt64() - for param in [param0, param1, param2]: - if param is None: - attributes.append(ir.Constant(0, builtins.TInt64())) - else: - if param.type != param_type: - param = self.append(ir.Coerce(param, param_type)) - attributes.append(param) # paramN, N=0:2 - - return self.append(ir.Alloc(attributes, typ)) - - def visit_builtin_call(self, node): - # A builtin by any other name... Ignore node.func, just use the type. - typ = node.func.type - if types.is_builtin(typ, "bool"): - if len(node.args) == 0 and len(node.keywords) == 0: - return ir.Constant(False, builtins.TBool()) - elif len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - return self.coerce_to_bool(arg) - else: - assert False - elif types.is_builtin(typ, "int") or \ - types.is_builtin(typ, "int32") or types.is_builtin(typ, "int64"): - if len(node.args) == 0 and len(node.keywords) == 0: - return ir.Constant(0, node.type) - elif len(node.args) == 1 and \ - (len(node.keywords) == 0 or \ - len(node.keywords) == 1 and node.keywords[0].arg == 'width'): - # The width argument is purely type-level - arg = self.visit(node.args[0]) - return self.append(ir.Coerce(arg, node.type)) - else: - assert False - elif types.is_builtin(typ, "float"): - if len(node.args) == 0 and len(node.keywords) == 0: - return ir.Constant(0.0, builtins.TFloat()) - elif len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - return self.append(ir.Coerce(arg, node.type)) - else: - assert False - elif (types.is_builtin(typ, "list") or - types.is_builtin(typ, "bytearray") or types.is_builtin(typ, "bytes")): - if len(node.args) == 0 and len(node.keywords) == 0: - length = ir.Constant(0, builtins.TInt32()) - return self.append(ir.Alloc([length], node.type)) - elif len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - length = self.iterable_len(arg) - result = self.append(ir.Alloc([length], node.type)) - - def body_gen(index): - elt = self.iterable_get(arg, index) - elt = self.append(ir.Coerce(elt, builtins.get_iterable_elt(node.type))) - self.append(ir.SetElem(result, index, elt)) - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, length.type))) - self._make_loop(ir.Constant(0, length.type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, length)), - body_gen) - - return result - else: - assert False - elif types.is_builtin(typ, "array"): - if len(node.args) == 1 and len(node.keywords) in (0, 1): - result_type = node.type.find() - arg = self.visit(node.args[0]) - - result_elt = result_type["elt"].find() - num_dims = result_type["num_dims"].value - - # Derive shape from first element on each level (and fail later if the - # array is in fact jagged). - first_elt = None - lengths = [] - for dim_idx in range(num_dims): - if first_elt is None: - first_elt = arg - else: - first_elt = self.iterable_get(first_elt, - ir.Constant(0, self._size_type)) - lengths.append(self.iterable_len(first_elt)) - - shape = self.append(ir.Alloc(lengths, result_type.attributes["shape"])) - num_total_elts = self._get_total_array_len(shape) - - # Assign buffer from nested iterables. - buffer = self.append( - ir.Alloc([num_total_elts], result_type.attributes["buffer"])) - - def assign_elems(outer_indices, indexed_arg): - if len(outer_indices) == num_dims: - dest_idx = self._get_array_offset(lengths, outer_indices) - coerced = self.append(ir.Coerce(indexed_arg, result_elt)) - self.append(ir.SetElem(buffer, dest_idx, coerced)) - else: - this_level_len = self.iterable_len(indexed_arg) - dim_idx = len(outer_indices) - if dim_idx > 0: - # Check for rectangularity (outermost index is never jagged, - # by definition). - result_len = self.append(ir.GetAttr(shape, dim_idx)) - self._make_check( - self.append(ir.Compare(ast.Eq(loc=None), this_level_len, result_len)), - lambda a, b: self.alloc_exn( - builtins.TException("ValueError"), - ir.Constant( - "arrays must be rectangular (lengths were {0} vs. {1})", - builtins.TStr()), a, b), - params=[this_level_len, result_len], - loc=node.loc) - - def body_gen(index): - elem = self.iterable_get(indexed_arg, index) - assign_elems(outer_indices + [index], elem) - return self.append( - ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, this_level_len)), body_gen) - assign_elems([], arg) - return self.append(ir.Alloc([buffer, shape], node.type)) - else: - assert False - elif types.is_builtin(typ, "range"): - elt_typ = builtins.get_iterable_elt(node.type) - if len(node.args) == 1 and len(node.keywords) == 0: - max_arg = self.visit(node.args[0]) - return self.append(ir.Alloc([ - ir.Constant(elt_typ.zero(), elt_typ), - max_arg, - ir.Constant(elt_typ.one(), elt_typ), - ], node.type)) - elif len(node.args) == 2 and len(node.keywords) == 0: - min_arg = self.visit(node.args[0]) - max_arg = self.visit(node.args[1]) - return self.append(ir.Alloc([ - min_arg, - max_arg, - ir.Constant(elt_typ.one(), elt_typ), - ], node.type)) - elif len(node.args) == 3 and len(node.keywords) == 0: - min_arg = self.visit(node.args[0]) - max_arg = self.visit(node.args[1]) - step_arg = self.visit(node.args[2]) - return self.append(ir.Alloc([ - min_arg, - max_arg, - step_arg, - ], node.type)) - else: - assert False - elif types.is_builtin(typ, "len"): - if len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - return self.iterable_len(arg) - else: - assert False - elif types.is_builtin(typ, "round"): - if len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - return self.append(ir.Builtin("round", [arg], node.type)) - else: - assert False - elif types.is_builtin(typ, "abs"): - if len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - neg = self.append( - ir.Arith(ast.Sub(loc=None), ir.Constant(0, arg.type), arg)) - cond = self.append( - ir.Compare(ast.Lt(loc=None), arg, ir.Constant(0, arg.type))) - return self.append(ir.Select(cond, neg, arg)) - else: - assert False - elif types.is_builtin(typ, "min"): - if len(node.args) == 2 and len(node.keywords) == 0: - arg0, arg1 = map(self.visit, node.args) - cond = self.append(ir.Compare(ast.Lt(loc=None), arg0, arg1)) - return self.append(ir.Select(cond, arg0, arg1)) - else: - assert False - elif types.is_builtin(typ, "max"): - if len(node.args) == 2 and len(node.keywords) == 0: - arg0, arg1 = map(self.visit, node.args) - cond = self.append(ir.Compare(ast.Gt(loc=None), arg0, arg1)) - return self.append(ir.Select(cond, arg0, arg1)) - else: - assert False - elif types.is_builtin(typ, "make_array"): - if len(node.args) == 2 and len(node.keywords) == 0: - arg0, arg1 = map(self.visit, node.args) - - num_dims = node.type.find()["num_dims"].value - if types.is_tuple(arg0.type): - lens = [self.append(ir.GetAttr(arg0, i)) for i in range(num_dims)] - else: - assert num_dims == 1 - lens = [arg0] - - shape = self._make_array_shape(lens) - result, total_len = self._allocate_new_array(node.type.find()["elt"], - shape) - - def body_gen(index): - self.append(ir.SetElem(result, index, arg1)) - return self.append( - ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, self._size_type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda index: self.append( - ir.Compare(ast.Lt(loc=None), index, total_len)), body_gen) - return result - else: - assert False - elif types.is_builtin(typ, "numpy.transpose"): - if len(node.args) == 1 and len(node.keywords) == 0: - arg, = map(self.visit, node.args) - - num_dims = arg.type.find()["num_dims"].value - if num_dims == 1: - # No-op as per NumPy semantics. - return arg - assert num_dims == 2 - arg_shape = self.append(ir.GetAttr(arg, "shape")) - dim0 = self.append(ir.GetAttr(arg_shape, 0)) - dim1 = self.append(ir.GetAttr(arg_shape, 1)) - shape = self._make_array_shape([dim1, dim0]) - result, _ = self._allocate_new_array(node.type.find()["elt"], shape) - arg_buffer = self.append(ir.GetAttr(arg, "buffer")) - result_buffer = self.append(ir.GetAttr(result, "buffer")) - - def outer_gen(idx1): - arg_base = self.append(ir.Offset(arg_buffer, idx1)) - result_offset = self.append(ir.Arith(ast.Mult(loc=None), idx1, - dim0)) - result_base = self.append(ir.Offset(result_buffer, result_offset)) - - def inner_gen(idx0): - arg_offset = self.append( - ir.Arith(ast.Mult(loc=None), idx0, dim1)) - val = self.append(ir.GetElem(arg_base, arg_offset)) - self.append(ir.SetElem(result_base, idx0, val)) - return self.append( - ir.Arith(ast.Add(loc=None), idx0, ir.Constant(1, - idx0.type))) - - self._make_loop( - ir.Constant(0, self._size_type), lambda idx0: self.append( - ir.Compare(ast.Lt(loc=None), idx0, dim0)), inner_gen) - return self.append( - ir.Arith(ast.Add(loc=None), idx1, ir.Constant(1, idx1.type))) - - self._make_loop( - ir.Constant(0, self._size_type), - lambda idx1: self.append(ir.Compare(ast.Lt(loc=None), idx1, dim1)), - outer_gen) - return result - else: - assert False - elif types.is_builtin(typ, "print"): - self.polymorphic_print([self.visit(arg) for arg in node.args], - separator=" ", suffix="\n") - return ir.Constant(None, builtins.TNone()) - elif types.is_builtin(typ, "rtio_log"): - prefix, *args = node.args - self.polymorphic_print([self.visit(prefix)], - separator=" ", suffix="\x1E", as_rtio=True) - self.polymorphic_print([self.visit(arg) for arg in args], - separator=" ", suffix="\x1D", as_rtio=True) - return ir.Constant(None, builtins.TNone()) - elif types.is_builtin(typ, "delay"): - if len(node.args) == 1 and len(node.keywords) == 0: - arg = self.visit(node.args[0]) - arg_mu_float = self.append(ir.Arith(ast.Div(loc=None), arg, self.ref_period)) - arg_mu = self.append(ir.Builtin("round", [arg_mu_float], builtins.TInt64())) - return self.append(ir.Builtin("delay_mu", [arg_mu], builtins.TNone())) - else: - assert False - elif types.is_builtin(typ, "now_mu") or types.is_builtin(typ, "delay_mu") \ - or types.is_builtin(typ, "at_mu"): - return self.append(ir.Builtin(typ.name, - [self.visit(arg) for arg in node.args], node.type)) - elif types.is_builtin(typ, "subkernel_await"): - if len(node.args) == 2 and len(node.keywords) == 0: - fn = node.args[0].type - timeout = self.visit(node.args[1]) - elif len(node.args) == 1 and len(node.keywords) == 0: - fn = node.args[0].type - timeout = ir.Constant(-1, builtins.TInt64()) - else: - assert False - if types.is_method(fn): - fn = types.get_method_function(fn) - sid = ir.Constant(fn.sid, builtins.TInt32()) - if not builtins.is_none(fn.ret): - if self.unwind_target is None: - ret = self.append(ir.Builtin("subkernel_retrieve_return", [sid, timeout], fn.ret)) - else: - after_invoke = self.add_block("invoke") - ret = self.append(ir.BuiltinInvoke("subkernel_retrieve_return", [sid, timeout], - fn.ret, after_invoke, self.unwind_target)) - self.current_block = after_invoke - else: - ret = ir.Constant(None, builtins.TNone()) - if self.unwind_target is None: - self.append(ir.Builtin("subkernel_await_finish", [sid, timeout], builtins.TNone())) - else: - after_invoke = self.add_block("invoke") - self.append(ir.BuiltinInvoke("subkernel_await_finish", [sid, timeout], - builtins.TNone(), after_invoke, self.unwind_target)) - self.current_block = after_invoke - return ret - elif types.is_builtin(typ, "subkernel_preload"): - if len(node.args) == 1 and len(node.keywords) == 0: - fn = node.args[0].type - else: - assert False - if types.is_method(fn): - fn = types.get_method_function(fn) - sid = ir.Constant(fn.sid, builtins.TInt32()) - dest = ir.Constant(fn.destination, builtins.TInt32()) - return self.append(ir.Builtin("subkernel_preload", [sid, dest], builtins.TNone())) - elif types.is_builtin(typ, "subkernel_send"): - if len(node.args) == 3 and len(node.keywords) == 0: - dest = self.visit(node.args[0]) - name = node.args[1].s - value = self.visit(node.args[2]) - else: - assert False - msg_id, msg = self.embedding_map.store_subkernel_message(name, value.type, "send", node.loc) - msg_id = ir.Constant(msg_id, builtins.TInt32()) - if value.type != msg.value_type: - diag = diagnostic.Diagnostic("error", - "type mismatch for subkernel message '{name}', receiver expects {recv} while sending {send}", - {"name": name, "recv": msg.value_type, "send": value.type}, - node.loc) - self.engine.process(diag) - return self.append(ir.Builtin("subkernel_send", [msg_id, dest, value], builtins.TNone())) - elif types.is_builtin(typ, "subkernel_recv"): - if len(node.args) == 2 and len(node.keywords) == 0: - name = node.args[0].s - vartype = node.args[1].value - timeout = ir.Constant(-1, builtins.TInt64()) - elif len(node.args) == 3 and len(node.keywords) == 0: - name = node.args[0].s - vartype = node.args[1].value - timeout = self.visit(node.args[2]) - else: - assert False - msg_id, msg = self.embedding_map.store_subkernel_message(name, vartype, "recv", node.loc) - msg_id = ir.Constant(msg_id, builtins.TInt32()) - if vartype != msg.value_type: - diag = diagnostic.Diagnostic("error", - "type mismatch for subkernel message '{name}', receiver expects {recv} while sending {send}", - {"name": name, "recv": vartype, "send": msg.value_type}, - node.loc) - self.engine.process(diag) - if self.unwind_target is None: - ret = self.append(ir.Builtin("subkernel_recv", [msg_id, timeout], vartype)) - else: - after_invoke = self.add_block("invoke") - ret = self.append(ir.BuiltinInvoke("subkernel_recv", [msg_id, timeout], - vartype, after_invoke, self.unwind_target)) - self.current_block = after_invoke - return ret - elif types.is_exn_constructor(typ): - return self.alloc_exn(node.type, *[self.visit(arg_node) for arg_node in node.args]) - elif types.is_constructor(typ): - return self.append(ir.Alloc([], typ.instance)) - else: - diag = diagnostic.Diagnostic("error", - "builtin function '{name}' cannot be used in this context", - {"name": typ.find().name}, - node.loc) - self.engine.process(diag) - - def _user_call(self, callee, positional, keywords, arg_exprs={}, remote_fn=False): - if types.is_function(callee.type) or types.is_rpc(callee.type) or types.is_subkernel(callee.type): - func = callee - self_arg = None - fn_typ = callee.type - offset = 0 - elif types.is_method(callee.type): - func = self.append(ir.GetAttr(callee, "__func__", - name="{}.ENV".format(callee.name))) - self_arg = self.append(ir.GetAttr(callee, "__self__", - name="{}.SLF".format(callee.name))) - fn_typ = types.get_method_function(callee.type) - offset = 1 - else: - assert False - - if types.is_rpc(fn_typ) or types.is_subkernel(fn_typ): - if self_arg is None or types.is_subkernel(fn_typ): - # self is not passed to subkernels by remote - args = positional - elif self_arg is not None: - args = [self_arg] + positional - - for keyword in keywords: - arg = keywords[keyword] - args.append(self.append(ir.Alloc([ir.Constant(keyword, builtins.TStr()), arg], - ir.TKeyword(arg.type)))) - elif remote_fn: - assert self_arg is None - assert len(fn_typ.args) >= len(positional) - assert len(keywords) == 0 # no keyword support - args = [None] * fn_typ.arity() - index = 0 - # fill in first available args - for arg in positional: - args[index] = arg - index += 1 - - # remaining args are received through DRTIO - if index < len(args): - # min/max args received remotely (minus already filled) - offset = index - min_args = ir.Constant(len(fn_typ.args)-offset, builtins.TInt8()) - max_args = ir.Constant(fn_typ.arity()-offset, builtins.TInt8()) - - arg_types = list(fn_typ.args.items())[offset:] - arg_type_list = [a[1] for a in arg_types] + [a[1] for a in fn_typ.optargs.items()] - rcvd_count = self.append(ir.SubkernelAwaitArgs([min_args, max_args], arg_type_list)) - # obligatory arguments - for arg_name, arg_type in arg_types: - args[index] = self.append(ir.GetArgFromRemote(arg_name, arg_type, - name="ARG.{}".format(arg_name))) - index += 1 - - # optional arguments - for optarg_name, optarg_type in fn_typ.optargs.items(): - idx = ir.Constant(index-offset, builtins.TInt8()) - args[index] = \ - self.append(ir.GetOptArgFromRemote(optarg_name, optarg_type, rcvd_count, idx)) - index += 1 - - else: - args = [None] * (len(fn_typ.args) + len(fn_typ.optargs)) - - for index, arg in enumerate(positional): - if index + offset < len(fn_typ.args): - args[index + offset] = arg - else: - args[index + offset] = self.append(ir.Alloc([arg], ir.TOption(arg.type))) - - for keyword in keywords: - arg = keywords[keyword] - if keyword in fn_typ.args: - for index, arg_name in enumerate(fn_typ.args): - if keyword == arg_name: - assert args[index] is None - args[index] = arg - break - elif keyword in fn_typ.optargs: - for index, optarg_name in enumerate(fn_typ.optargs): - if keyword == optarg_name: - assert args[len(fn_typ.args) + index] is None - args[len(fn_typ.args) + index] = \ - self.append(ir.Alloc([arg], ir.TOption(arg.type))) - break - - for index, optarg_name in enumerate(fn_typ.optargs): - if args[len(fn_typ.args) + index] is None: - args[len(fn_typ.args) + index] = \ - self.append(ir.Alloc([], ir.TOption(fn_typ.optargs[optarg_name]))) - - if self_arg is not None: - assert args[0] is None - args[0] = self_arg - - assert None not in args - - if self.unwind_target is None or \ - types.is_external_function(callee.type) and "nounwind" in callee.type.flags: - insn = self.append(ir.Call(func, args, arg_exprs)) - else: - after_invoke = self.add_block("invoke") - insn = self.append(ir.Invoke(func, args, arg_exprs, - after_invoke, self.unwind_target)) - self.current_block = after_invoke - - return insn - - def visit_CallT(self, node): - if not types.is_builtin(node.func.type): - callee = self.visit(node.func) - args = [self.visit(arg_node) for arg_node in node.args] - keywords = {kw_node.arg: self.visit(kw_node.value) for kw_node in node.keywords} - - if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0): - before_delay = self.current_block - during_delay = self.add_block("delay.head") - before_delay.append(ir.Branch(during_delay)) - self.current_block = during_delay - - if types.is_builtin(node.func.type): - insn = self.visit_builtin_call(node) - elif (types.is_broadcast_across_arrays(node.func.type) and len(args) >= 1 - and any(builtins.is_array(arg.type) for arg in args)): - # The iodelay machinery set up in the surrounding code was - # deprecated/a relic from the past when array broadcasting support - # was added, so no attempt to keep the delay tracking intact is - # made. - def make_call(*args): - return self._user_call(ir.Constant(None, callee.type), args, {}, - node.arg_exprs) - # TODO: Generate more generically if non-externals are allowed. - name = node.func.type.find().name - - if len(args) == 1: - shape = self.append(ir.GetAttr(args[0], "shape")) - result, _ = self._allocate_new_array(node.type.find()["elt"], shape) - func = self._get_array_unaryop(name, make_call, node.type, args[0].type) - self._invoke_arrayop(func, [result, args[0]]) - insn = result - elif len(args) == 2: - insn = self._broadcast_binop(name, make_call, node.type, *args, - assign_to_lhs=False) - else: - assert False, "Broadcasting for {} arguments not implemented".format(len) - else: - remote_fn = getattr(node, "remote_fn", False) - insn = self._user_call(callee, args, keywords, node.arg_exprs, remote_fn) - if isinstance(node.func, asttyped.AttributeT): - attr_node = node.func - self.method_map[(attr_node.value.type.find(), - attr_node.attr)].append(insn) - - if node.iodelay is not None and not iodelay.is_const(node.iodelay, 0): - after_delay = self.add_block("delay.tail") - self.append(ir.Delay(node.iodelay, insn, after_delay)) - self.current_block = after_delay - - return insn - - def visit_QuoteT(self, node): - return self.append(ir.Quote(node.value, node.type)) - - def _get_raise_assert_func(self): - """Emit the helper function that constructs AssertionErrors and raises - them, if it does not already exist in the current module. - - A separate function is used for code size reasons. (This could also be - compiled into a stand-alone support library instead.) - """ - if self.raise_assert_func: - return self.raise_assert_func - try: - msg = ir.Argument(builtins.TStr(), "msg") - file = ir.Argument(builtins.TStr(), "file") - line = ir.Argument(builtins.TInt32(), "line") - col = ir.Argument(builtins.TInt32(), "col") - function = ir.Argument(builtins.TStr(), "function") - - args = [msg, file, line, col, function] - typ = types.TFunction(args=OrderedDict([(arg.name, arg.type) - for arg in args]), - optargs=OrderedDict(), - ret=builtins.TNone()) - env = ir.TEnvironment(name="raise", vars={}) - env_arg = ir.EnvironmentArgument(env, "ARG.ENV") - func = ir.Function(typ, "_artiq_raise_assert", [env_arg] + args) - func.is_internal = True - func.is_cold = True - func.is_generated = True - self.functions.append(func) - old_func, self.current_function = self.current_function, func - - entry = self.add_block("entry") - old_block, self.current_block = self.current_block, entry - old_unwind, self.unwind_target = self.unwind_target, None - - exn = self.alloc_exn(builtins.TException("AssertionError"), - message=msg, nomsgcheck=True) - self.append(ir.SetAttr(exn, "#__file__", file)) - self.append(ir.SetAttr(exn, "#__line__", line)) - self.append(ir.SetAttr(exn, "#__col__", col)) - self.append(ir.SetAttr(exn, "#__func__", function)) - self.append(ir.Raise(exn)) - finally: - self.current_function = old_func - self.current_block = old_block - self.unwind_target = old_unwind - - self.raise_assert_func = func - return self.raise_assert_func - - def visit_Assert(self, node): - cond = self.visit(node.test) - head = self.current_block - - if_failed = self.current_block = self.add_block("assert.fail") - text = str(node.msg.s) if node.msg else "AssertionError" - msg = ir.Constant(text, builtins.TStr()) - loc_file = ir.Constant(node.loc.source_buffer.name, builtins.TStr()) - loc_line = ir.Constant(node.loc.line(), builtins.TInt32()) - loc_column = ir.Constant(node.loc.column(), builtins.TInt32()) - loc_function = ir.Constant(".".join(self.name), builtins.TStr()) - self._invoke_raising_func(self._get_raise_assert_func(), [ - msg, loc_file, loc_line, loc_column, loc_function - ], "assert.fail") - - tail = self.current_block = self.add_block("assert.tail") - self.append(ir.BranchIf(cond, tail, if_failed), block=head) - - def polymorphic_print(self, values, separator, suffix="", as_repr=False, as_rtio=False): - def printf(format_string, *args): - format = ir.Constant(format_string, builtins.TStr()) - if as_rtio: - self.append(ir.Builtin("rtio_log", [format, *args], builtins.TNone())) - else: - self.append(ir.Builtin("printf", [format, *args], builtins.TNone())) - - format_string = "" - args = [] - def flush(): - nonlocal format_string, args - if format_string != "": - printf(format_string + "\x00", *args) - format_string = "" - args = [] - - for value in values: - if format_string != "": - format_string += separator - - if types.is_tuple(value.type): - format_string += "("; flush() - self.polymorphic_print([self.append(ir.GetAttr(value, index)) - for index in range(len(value.type.elts))], - separator=", ", as_repr=True, as_rtio=as_rtio) - if len(value.type.elts) == 1: - format_string += ",)" - else: - format_string += ")" - elif types.is_function(value.type): - format_string += "" - args.append(self.append(ir.GetAttr(value, '__code__'))) - args.append(self.append(ir.GetAttr(value, '__closure__'))) - elif builtins.is_none(value.type): - format_string += "None" - elif builtins.is_bool(value.type): - format_string += "%.*s" - args.append(self.append(ir.Select(value, - ir.Constant("True", builtins.TStr()), - ir.Constant("False", builtins.TStr())))) - elif builtins.is_int(value.type): - width = builtins.get_int_width(value.type) - if width <= 32: - format_string += "%d" - elif width <= 64: - format_string += "%lld" - else: - assert False - args.append(value) - elif builtins.is_float(value.type): - format_string += "%g" - args.append(value) - elif builtins.is_str(value.type): - if as_repr: - format_string += "\"%.*s\"" - else: - format_string += "%.*s" - args.append(value) - elif builtins.is_listish(value.type): - if builtins.is_list(value.type): - format_string += "["; flush() - elif builtins.is_bytes(value.type): - format_string += "bytes(["; flush() - elif builtins.is_bytearray(value.type): - format_string += "bytearray(["; flush() - elif builtins.is_array(value.type): - format_string += "array(["; flush() - else: - assert False - - length = self.iterable_len(value) - last = self.append(ir.Arith(ast.Sub(loc=None), length, ir.Constant(1, length.type))) - def body_gen(index): - elt = self.iterable_get(value, index) - self.polymorphic_print([elt], separator="", as_repr=True, as_rtio=as_rtio) - is_last = self.append(ir.Compare(ast.Lt(loc=None), index, last)) - head = self.current_block - - if_last = self.current_block = self.add_block("print.comma") - printf(", \x00") - - tail = self.current_block = self.add_block("print.tail") - if_last.append(ir.Branch(tail)) - head.append(ir.BranchIf(is_last, if_last, tail)) - - return self.append(ir.Arith(ast.Add(loc=None), index, - ir.Constant(1, length.type))) - self._make_loop(ir.Constant(0, length.type), - lambda index: self.append(ir.Compare(ast.Lt(loc=None), index, length)), - body_gen) - - if builtins.is_list(value.type): - format_string += "]" - elif (builtins.is_bytes(value.type) or builtins.is_bytearray(value.type) or - builtins.is_array(value.type)): - format_string += "])" - elif builtins.is_range(value.type): - format_string += "range("; flush() - - start = self.append(ir.GetAttr(value, "start")) - stop = self.append(ir.GetAttr(value, "stop")) - step = self.append(ir.GetAttr(value, "step")) - self.polymorphic_print([start, stop, step], separator=", ", as_rtio=as_rtio) - - format_string += ")" - elif builtins.is_exception(value.type): - # message may not be an actual string... - # so we cannot really print itInvoke - name = self.append(ir.GetAttr(value, "#__name__")) - param1 = self.append(ir.GetAttr(value, "#__param0__")) - param2 = self.append(ir.GetAttr(value, "#__param1__")) - param3 = self.append(ir.GetAttr(value, "#__param2__")) - - format_string += "%ld(%lld, %lld, %lld)" - args += [name, param1, param2, param3] - else: - assert False - - format_string += suffix - flush() diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py deleted file mode 100644 index 07ab9ded2..000000000 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ /dev/null @@ -1,527 +0,0 @@ -""" -:class:`ASTTypedRewriter` rewrites a parsetree (:mod:`pythonparser.ast`) -to a typedtree (:mod:`..asttyped`). -""" - -from collections import OrderedDict -from pythonparser import ast, algorithm, diagnostic -from .. import asttyped, types, builtins - -# This visitor will be called for every node with a scope, -# i.e.: class, function, comprehension, lambda -class LocalExtractor(algorithm.Visitor): - def __init__(self, env_stack, engine): - super().__init__() - self.env_stack = env_stack - self.engine = engine - - self.in_root = False - self.in_assign = False - self.typing_env = OrderedDict() - - # which names are global have to be recorded in the current scope - self.global_ = set() - - # which names are nonlocal only affects whether the current scope - # gets a new binding or not, so we throw this away - self.nonlocal_ = set() - - # parameters can't be declared as global or nonlocal - self.params = set() - - def visit_in_assign(self, node, in_assign): - try: - old_in_assign, self.in_assign = self.in_assign, in_assign - return self.visit(node) - finally: - self.in_assign = old_in_assign - - def visit_Assign(self, node): - self.visit(node.value) - self.visit_in_assign(node.targets, in_assign=True) - - def visit_For(self, node): - self.visit(node.iter) - self.visit_in_assign(node.target, in_assign=True) - self.visit(node.body) - self.visit(node.orelse) - - def visit_withitem(self, node): - self.visit(node.context_expr) - self.visit_in_assign(node.optional_vars, in_assign=True) - - def visit_comprehension(self, node): - self.visit(node.iter) - self.visit_in_assign(node.target, in_assign=True) - self.visit(node.ifs) - - def visit_generator(self, node): - if self.in_root: - return - self.in_root = True - self.visit(list(reversed(node.generators))) - self.visit(node.elt) - - visit_ListComp = visit_generator - visit_SetComp = visit_generator - visit_GeneratorExp = visit_generator - - def visit_DictComp(self, node): - if self.in_root: - return - self.in_root = True - self.visit(list(reversed(node.generators))) - self.visit(node.key) - self.visit(node.value) - - def visit_root(self, node): - if self.in_root: - return - self.in_root = True - self.generic_visit(node) - - visit_Module = visit_root # don't look at inner scopes - visit_ClassDef = visit_root - visit_Lambda = visit_root - - def visit_FunctionDef(self, node): - if self.in_root: - self._assignable(node.name) - self.visit_root(node) - - def _assignable(self, name): - assert name is not None - if name not in self.typing_env and name not in self.nonlocal_: - self.typing_env[name] = types.TVar() - - def visit_arg(self, node): - if node.arg in self.params: - diag = diagnostic.Diagnostic("error", - "duplicate parameter '{name}'", {"name": node.arg}, - node.loc) - self.engine.process(diag) - return - self._assignable(node.arg) - self.params.add(node.arg) - - def visit_Name(self, node): - if self.in_assign: - # code like: - # x = 1 - # def f(): - # x = 1 - # creates a new binding for x in f's scope - self._assignable(node.id) - - def visit_Attribute(self, node): - self.visit_in_assign(node.value, in_assign=False) - - def visit_Subscript(self, node): - self.visit_in_assign(node.value, in_assign=False) - self.visit_in_assign(node.slice, in_assign=False) - - def _check_not_in(self, name, names, curkind, newkind, loc): - if name in names: - diag = diagnostic.Diagnostic("error", - "name '{name}' cannot be {curkind} and {newkind} simultaneously", - {"name": name, "curkind": curkind, "newkind": newkind}, loc) - self.engine.process(diag) - return True - return False - - def visit_Global(self, node): - for name, loc in zip(node.names, node.name_locs): - if self._check_not_in(name, self.nonlocal_, "nonlocal", "global", loc) or \ - self._check_not_in(name, self.params, "a parameter", "global", loc): - continue - - self.global_.add(name) - if len(self.env_stack) == 1: - self._assignable(name) # already in global scope - else: - if name not in self.env_stack[1]: - self.env_stack[1][name] = types.TVar() - self.typing_env[name] = self.env_stack[1][name] - - def visit_Nonlocal(self, node): - for name, loc in zip(node.names, node.name_locs): - if self._check_not_in(name, self.global_, "global", "nonlocal", loc) or \ - self._check_not_in(name, self.params, "a parameter", "nonlocal", loc): - continue - - # nonlocal does not search prelude and global scopes - found = False - for outer_env in reversed(self.env_stack[2:]): - if name in outer_env: - found = True - break - if not found: - diag = diagnostic.Diagnostic("error", - "cannot declare name '{name}' as nonlocal: it is not bound in any outer scope", - {"name": name}, - loc, [node.keyword_loc]) - self.engine.process(diag) - continue - - self.nonlocal_.add(name) - - def visit_ExceptHandler(self, node): - self.visit(node.type) - if node.name is not None: - self._assignable(node.name) - for stmt in node.body: - self.visit(stmt) - - -class ASTTypedRewriter(algorithm.Transformer): - """ - :class:`ASTTypedRewriter` converts an untyped AST to a typed AST - where all type fields of non-literals are filled with fresh type variables, - and type fields of literals are filled with corresponding types. - - :class:`ASTTypedRewriter` also discovers the scope of variable bindings - via :class:`LocalExtractor`. - """ - - def __init__(self, engine, prelude): - self.engine = engine - self.globals = None - self.env_stack = [prelude] - self.in_class = None - - def _try_find_name(self, name): - if self.in_class is not None: - typ = self.in_class.constructor_type.attributes.get(name) - if typ is not None: - return typ - - for typing_env in reversed(self.env_stack): - if name in typing_env: - return typing_env[name] - - def _find_name(self, name, loc): - typ = self._try_find_name(name) - if typ is not None: - return typ - - diag = diagnostic.Diagnostic("fatal", - "undefined variable '{name}'", {"name":name}, loc) - self.engine.process(diag) - - # Visitors that replace node with a typed node - # - def visit_Module(self, node): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - node = asttyped.ModuleT( - typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - body=node.body, loc=node.loc) - self.globals = node.typing_env - - try: - self.env_stack.append(node.typing_env) - return self.generic_visit(node) - finally: - self.env_stack.pop() - - def visit_FunctionDef(self, node): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - signature_type = self._find_name(node.name, node.name_loc) - - node = asttyped.FunctionDefT( - typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - signature_type=signature_type, return_type=types.TVar(), - name=node.name, args=node.args, returns=node.returns, - body=node.body, decorator_list=node.decorator_list, - keyword_loc=node.keyword_loc, name_loc=node.name_loc, - arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs, - loc=node.loc, remote_fn=False) - - try: - self.env_stack.append(node.typing_env) - return self.generic_visit(node) - finally: - self.env_stack.pop() - - def visit_ClassDef(self, node): - if any(node.bases) or any(node.keywords) or \ - node.starargs is not None or node.kwargs is not None: - diag = diagnostic.Diagnostic("error", - "inheritance is not supported", {}, - node.lparen_loc.join(node.rparen_loc)) - self.engine.process(diag) - - for child in node.body: - if isinstance(child, (ast.Assign, ast.FunctionDef, ast.Pass)): - continue - - diag = diagnostic.Diagnostic("fatal", - "class body must contain only assignments and function definitions", {}, - child.loc) - self.engine.process(diag) - - if node.name in self.env_stack[-1]: - diag = diagnostic.Diagnostic("fatal", - "variable '{name}' is already defined", {"name":node.name}, node.name_loc) - self.engine.process(diag) - - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - # Now we create two types. - # The first type is the type of instances created by the constructor. - # Its attributes are those of the class environment, but wrapped - # appropriately so that they are linked to the class from which they - # originate. - instance_type = types.TInstance(node.name, OrderedDict()) - - # The second type is the type of the constructor itself (in other words, - # the class object): it is simply a singleton type that has the class - # environment as attributes. - constructor_type = types.TConstructor(instance_type) - constructor_type.attributes = extractor.typing_env - instance_type.constructor = constructor_type - - self.env_stack[-1][node.name] = constructor_type - - node = asttyped.ClassDefT( - constructor_type=constructor_type, - name=node.name, - bases=self.visit(node.bases), keywords=self.visit(node.keywords), - starargs=self.visit(node.starargs), kwargs=self.visit(node.kwargs), - body=node.body, - decorator_list=self.visit(node.decorator_list), - keyword_loc=node.keyword_loc, name_loc=node.name_loc, - lparen_loc=node.lparen_loc, star_loc=node.star_loc, - dstar_loc=node.dstar_loc, rparen_loc=node.rparen_loc, - colon_loc=node.colon_loc, at_locs=node.at_locs, - loc=node.loc) - - try: - old_in_class, self.in_class = self.in_class, node - return self.generic_visit(node) - finally: - self.in_class = old_in_class - - def visit_arg(self, node): - if node.annotation is not None: - diag = diagnostic.Diagnostic("fatal", - "type annotations are not supported here", {}, - node.annotation.loc) - self.engine.process(diag) - - return asttyped.argT(type=self._find_name(node.arg, node.loc), - arg=node.arg, annotation=None, - arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc) - - def visit_Num(self, node): - if isinstance(node.n, int): - typ = builtins.TInt() - elif isinstance(node.n, float): - typ = builtins.TFloat() - else: - diag = diagnostic.Diagnostic("fatal", - "numeric type {type} is not supported", {"type": node.n.__class__.__name__}, - node.loc) - self.engine.process(diag) - return asttyped.NumT(type=typ, - n=node.n, loc=node.loc) - - def visit_Str(self, node): - if isinstance(node.s, str): - typ = builtins.TStr() - elif isinstance(node.s, bytes): - typ = builtins.TBytes() - else: - assert False - return asttyped.StrT(type=typ, s=node.s, - begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) - - def visit_Name(self, node): - return asttyped.NameT(type=self._find_name(node.id, node.loc), - id=node.id, ctx=node.ctx, loc=node.loc) - - def visit_NameConstant(self, node): - if node.value is True or node.value is False: - typ = builtins.TBool() - elif node.value is None: - typ = builtins.TNone() - return asttyped.NameConstantT(type=typ, value=node.value, loc=node.loc) - - def visit_Tuple(self, node): - node = self.generic_visit(node) - return asttyped.TupleT(type=types.TTuple([x.type for x in node.elts]), - elts=node.elts, ctx=node.ctx, loc=node.loc) - - def visit_List(self, node): - node = self.generic_visit(node) - node = asttyped.ListT(type=builtins.TList(), - elts=node.elts, ctx=node.ctx, - begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) - return self.visit(node) - - def visit_Attribute(self, node): - node = self.generic_visit(node) - node = asttyped.AttributeT(type=types.TVar(), - value=node.value, attr=node.attr, ctx=node.ctx, - dot_loc=node.dot_loc, attr_loc=node.attr_loc, loc=node.loc) - return self.visit(node) - - def visit_Slice(self, node): - node = self.generic_visit(node) - node = asttyped.SliceT(type=types.TVar(), - lower=node.lower, upper=node.upper, step=node.step, - bound_colon_loc=node.bound_colon_loc, - step_colon_loc=node.step_colon_loc, - loc=node.loc) - return self.visit(node) - - def visit_Subscript(self, node): - node = self.generic_visit(node) - node = asttyped.SubscriptT(type=types.TVar(), - value=node.value, slice=node.slice, ctx=node.ctx, - begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) - return self.visit(node) - - def visit_BoolOp(self, node): - node = self.generic_visit(node) - node = asttyped.BoolOpT(type=types.TVar(), - op=node.op, values=node.values, - op_locs=node.op_locs, loc=node.loc) - return self.visit(node) - - def visit_UnaryOp(self, node): - node = self.generic_visit(node) - node = asttyped.UnaryOpT(type=types.TVar(), - op=node.op, operand=node.operand, - loc=node.loc) - return self.visit(node) - - def visit_BinOp(self, node): - node = self.generic_visit(node) - node = asttyped.BinOpT(type=types.TVar(), - left=node.left, op=node.op, right=node.right, - loc=node.loc) - return self.visit(node) - - def visit_Compare(self, node): - node = self.generic_visit(node) - node = asttyped.CompareT(type=types.TVar(), - left=node.left, ops=node.ops, comparators=node.comparators, - loc=node.loc) - return self.visit(node) - - def visit_IfExp(self, node): - node = self.generic_visit(node) - node = asttyped.IfExpT(type=types.TVar(), - test=node.test, body=node.body, orelse=node.orelse, - if_loc=node.if_loc, else_loc=node.else_loc, loc=node.loc) - return self.visit(node) - - def visit_ListComp(self, node): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - node = asttyped.ListCompT( - typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - type=types.TVar(), - elt=node.elt, generators=node.generators, - begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) - - try: - self.env_stack.append(node.typing_env) - return self.generic_visit(node) - finally: - self.env_stack.pop() - - def visit_Call(self, node): - node = self.generic_visit(node) - node = asttyped.CallT(type=types.TVar(), iodelay=None, arg_exprs={}, - remote_fn=False, func=node.func, - args=node.args, keywords=node.keywords, - starargs=node.starargs, kwargs=node.kwargs, - star_loc=node.star_loc, dstar_loc=node.dstar_loc, - begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc) - return node - - def visit_Lambda(self, node): - extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine) - extractor.visit(node) - - node = asttyped.LambdaT( - typing_env=extractor.typing_env, globals_in_scope=extractor.global_, - type=types.TVar(), - args=node.args, body=node.body, - lambda_loc=node.lambda_loc, colon_loc=node.colon_loc, loc=node.loc) - - try: - self.env_stack.append(node.typing_env) - return self.generic_visit(node) - finally: - self.env_stack.pop() - - def visit_ExceptHandler(self, node): - node = self.generic_visit(node) - if node.name is not None: - name_type = self._find_name(node.name, node.name_loc) - else: - name_type = types.TVar() - node = asttyped.ExceptHandlerT( - name_type=name_type, - filter=node.type, name=node.name, body=node.body, - except_loc=node.except_loc, as_loc=node.as_loc, name_loc=node.name_loc, - colon_loc=node.colon_loc, loc=node.loc) - return node - - def visit_Raise(self, node): - node = self.generic_visit(node) - if node.cause: - diag = diagnostic.Diagnostic("error", - "'raise from' syntax is not supported", {}, - node.from_loc) - self.engine.process(diag) - return node - - def visit_For(self, node): - node = self.generic_visit(node) - node = asttyped.ForT( - target=node.target, iter=node.iter, body=node.body, orelse=node.orelse, - trip_count=None, trip_interval=None, - keyword_loc=node.keyword_loc, in_loc=node.in_loc, for_colon_loc=node.for_colon_loc, - else_loc=node.else_loc, else_colon_loc=node.else_colon_loc, loc=node.loc) - return node - - def visit_withitem(self, node): - node = self.generic_visit(node) - node = asttyped.withitemT( - context_expr=node.context_expr, optional_vars=node.optional_vars, - enter_type=types.TVar(), exit_type=types.TVar(), - as_loc=node.as_loc, loc=node.loc) - return node - - # Unsupported visitors - # - def visit_unsupported(self, node): - diag = diagnostic.Diagnostic("fatal", - "this syntax is not supported", {}, - node.loc) - self.engine.process(diag) - - # expr - visit_Dict = visit_unsupported - visit_DictComp = visit_unsupported - visit_Ellipsis = visit_unsupported - visit_GeneratorExp = visit_unsupported - # visit_Set = visit_unsupported - visit_SetComp = visit_unsupported - visit_Starred = visit_unsupported - visit_Yield = visit_unsupported - visit_YieldFrom = visit_unsupported - - # stmt - visit_Delete = visit_unsupported - visit_Import = visit_unsupported - visit_ImportFrom = visit_unsupported diff --git a/artiq/compiler/transforms/cast_monomorphizer.py b/artiq/compiler/transforms/cast_monomorphizer.py deleted file mode 100644 index c0935ff1f..000000000 --- a/artiq/compiler/transforms/cast_monomorphizer.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -:class:`CastMonomorphizer` uses explicit casts to monomorphize -expressions of undetermined integer type to either 32 or 64 bits. -""" - -from pythonparser import algorithm, diagnostic -from .. import types, builtins, asttyped - -class CastMonomorphizer(algorithm.Visitor): - def __init__(self, engine): - self.engine = engine - - def visit_CallT(self, node): - if (types.is_builtin(node.func.type, "int") or - types.is_builtin(node.func.type, "int32") or - types.is_builtin(node.func.type, "int64")): - typ = node.type.find() - if (not types.is_var(typ["width"]) and - len(node.args) == 1 and - builtins.is_int(node.args[0].type) and - types.is_var(node.args[0].type.find()["width"])): - if isinstance(node.args[0], asttyped.BinOpT): - # Binary operations are a bit special: they can widen, and so their - # return type is indeterminate until both argument types are fully known. - # In case we first monomorphize the return type, and then some argument type, - # and argument type is wider than return type, we'll introduce a conflict. - return - - node.args[0].type.unify(typ) - - if types.is_builtin(node.func.type, "int") or \ - types.is_builtin(node.func.type, "round"): - typ = node.type.find() - if types.is_var(typ["width"]): - typ["width"].unify(types.TValue(32)) - - self.generic_visit(node) - - def visit_CoerceT(self, node): - if isinstance(node.value, asttyped.NumT) and \ - builtins.is_int(node.type) and \ - builtins.is_int(node.value.type) and \ - not types.is_var(node.type["width"]) and \ - types.is_var(node.value.type["width"]): - node.value.type.unify(node.type) - - self.generic_visit(node) diff --git a/artiq/compiler/transforms/constant_hoister.py b/artiq/compiler/transforms/constant_hoister.py deleted file mode 100644 index ea51046aa..000000000 --- a/artiq/compiler/transforms/constant_hoister.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -:class:`ConstantHoister` is a code motion transform: -it moves any invariant loads to the earliest point where -they may be executed. -""" - -from .. import types, ir - -class ConstantHoister: - def process(self, functions): - for func in functions: - self.process_function(func) - - def process_function(self, func): - entry = func.entry() - worklist = set(func.instructions()) - moved = set() - while len(worklist) > 0: - insn = worklist.pop() - - if (isinstance(insn, ir.GetAttr) and insn not in moved and - types.is_instance(insn.object().type) and - insn.attr in insn.object().type.constant_attributes): - has_variant_operands = False - index_in_entry = 0 - for operand in insn.operands: - if isinstance(operand, ir.Argument): - pass - elif isinstance(operand, ir.Instruction) and operand.basic_block == entry: - index_in_entry = entry.index(operand) + 1 - else: - has_variant_operands = True - break - - if has_variant_operands: - continue - - insn.remove_from_parent() - entry.instructions.insert(index_in_entry, insn) - moved.add(insn) - - for use in insn.uses: - worklist.add(use) diff --git a/artiq/compiler/transforms/dead_code_eliminator.py b/artiq/compiler/transforms/dead_code_eliminator.py deleted file mode 100644 index f4862f2f9..000000000 --- a/artiq/compiler/transforms/dead_code_eliminator.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -:class:`DeadCodeEliminator` is a dead code elimination transform: -it only basic blocks with no predecessors as well as unused -instructions without side effects. -""" - -from .. import ir - -class DeadCodeEliminator: - def __init__(self, engine): - self.engine = engine - - def process(self, functions): - for func in functions: - self.process_function(func) - - def process_function(self, func): - # defer removing those blocks, so our use checks will ignore deleted blocks - preserve = [func.entry()] - work_list = [func.entry()] - while any(work_list): - block = work_list.pop() - for succ in block.successors(): - if succ not in preserve: - preserve.append(succ) - work_list.append(succ) - - to_be_removed = [] - for block in func.basic_blocks: - if block not in preserve: - block.is_removed = True - to_be_removed.append(block) - for insn in block.instructions: - insn.is_removed = True - - for block in to_be_removed: - self.remove_block(block) - - modified = True - while modified: - modified = False - for insn in func.instructions(): - # Note that GetLocal is treated as an impure operation: - # the local access validator has to observe it to emit - # a diagnostic for reads of uninitialized locals, and - # it also has to run after the interleaver, but interleaver - # doesn't like to work with IR before DCE. - if isinstance(insn, (ir.Phi, ir.Alloc, ir.GetAttr, ir.GetElem, ir.Coerce, - ir.Arith, ir.Compare, ir.Select, ir.Quote, ir.Closure, - ir.Offset)) \ - and not any(insn.uses): - insn.erase() - modified = True - - def remove_block(self, block): - # block.uses are updated while iterating - for use in set(block.uses): - if use.is_removed: - continue - if isinstance(use, ir.Phi): - use.remove_incoming_block(block) - if not any(use.operands): - self.remove_instruction(use) - elif isinstance(use, ir.SetLocal): - # setlocal %env, %block is only used for lowering finally - use.erase() - else: - assert False - - block.erase() - - def remove_instruction(self, insn): - for use in set(insn.uses): - if use.is_removed: - continue - if isinstance(use, ir.Phi): - use.remove_incoming_value(insn) - if not any(use.operands): - self.remove_instruction(use) - else: - assert False - - insn.erase() diff --git a/artiq/compiler/transforms/int_monomorphizer.py b/artiq/compiler/transforms/int_monomorphizer.py deleted file mode 100644 index 5c7d22dce..000000000 --- a/artiq/compiler/transforms/int_monomorphizer.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -:class:`IntMonomorphizer` collapses the integer literals of undetermined -width to 32 bits, assuming they fit into 32 bits, or 64 bits if they -do not. -""" - -from pythonparser import algorithm, diagnostic -from .. import types, builtins, asttyped - -class IntMonomorphizer(algorithm.Visitor): - def __init__(self, engine): - self.engine = engine - - def visit_NumT(self, node): - if builtins.is_int(node.type): - if types.is_var(node.type["width"]): - if -2**31 <= node.n <= 2**31-1: - width = 32 - elif -2**63 <= node.n <= 2**63-1: - width = 64 - else: - diag = diagnostic.Diagnostic("error", - "integer literal out of range for a signed 64-bit value", {}, - node.loc) - self.engine.process(diag) - return - - node.type["width"].unify(types.TValue(width)) diff --git a/artiq/compiler/transforms/interleaver.py b/artiq/compiler/transforms/interleaver.py deleted file mode 100644 index 1bf9cf5a5..000000000 --- a/artiq/compiler/transforms/interleaver.py +++ /dev/null @@ -1,185 +0,0 @@ -""" -:class:`Interleaver` reorders requests to the RTIO core so that -the timestamp would always monotonically nondecrease. -""" - -from pythonparser import diagnostic - -from .. import types, builtins, ir, iodelay -from ..analyses import domination -from ..algorithms import inline, unroll - -def delay_free_subgraph(root, limit): - visited = set() - queue = root.successors() - while len(queue) > 0: - block = queue.pop() - visited.add(block) - - if block is limit: - continue - - if isinstance(block.terminator(), ir.Delay): - return False - - for successor in block.successors(): - if successor not in visited: - queue.append(successor) - - return True - -def iodelay_of_block(block): - terminator = block.terminator() - if isinstance(terminator, ir.Delay): - # We should be able to fold everything without free variables. - folded_expr = terminator.interval.fold() - assert iodelay.is_const(folded_expr) - return folded_expr.value - else: - return 0 - -def is_pure_delay(insn): - return isinstance(insn, ir.Builtin) and insn.op in ("delay", "delay_mu") - -def is_impure_delay_block(block): - terminator = block.terminator() - return isinstance(terminator, ir.Delay) and \ - not is_pure_delay(terminator.decomposition()) - -class Interleaver: - def __init__(self, engine): - self.engine = engine - - def process(self, functions): - for func in functions: - self.process_function(func) - - def process_function(self, func): - for insn in func.instructions(): - if isinstance(insn, ir.Delay): - if any(insn.interval.free_vars()): - # If a function has free variables in delay expressions, - # that means its IO delay depends on arguments. - # Do not change such functions in any way so that it will - # be successfully inlined and then removed by DCE. - return - - postdom_tree = None - for insn in func.instructions(): - if not isinstance(insn, ir.Interleave): - continue - - # Lazily compute dominators. - if postdom_tree is None: - postdom_tree = domination.PostDominatorTree(func) - - target_block = insn.basic_block - target_time = 0 - source_blocks = insn.basic_block.successors() - source_times = [0 for _ in source_blocks] - - if len(source_blocks) == 1: - # Immediate dominator for a interleave instruction with one successor - # is the first instruction in the body of the statement which created - # it, but below we expect that it would be the first instruction after - # the statement itself. - insn.replace_with(ir.Branch(source_blocks[0])) - continue - - interleave_until = postdom_tree.immediate_dominator(insn.basic_block) - assert interleave_until is not None # no nonlocal flow in `with interleave` - assert interleave_until not in source_blocks - - while len(source_blocks) > 0: - def time_after_block(pair): - index, block = pair - return source_times[index] + iodelay_of_block(block) - - # Always prefer impure blocks (with calls) to pure blocks, because - # impure blocks may expand with smaller delays appearing, and in - # case of a tie, if a pure block is preferred, this would violate - # the timeline monotonicity. - available_source_blocks = list(filter(is_impure_delay_block, source_blocks)) - if not any(available_source_blocks): - available_source_blocks = source_blocks - - index, source_block = min(enumerate(available_source_blocks), key=time_after_block) - source_block_delay = iodelay_of_block(source_block) - - new_target_time = source_times[index] + source_block_delay - target_time_delta = new_target_time - target_time - assert target_time_delta >= 0 - - target_terminator = target_block.terminator() - if isinstance(target_terminator, ir.Interleave): - target_terminator.replace_with(ir.Branch(source_block)) - elif isinstance(target_terminator, (ir.Delay, ir.Branch)): - target_terminator.set_target(source_block) - else: - assert False - - source_terminator = source_block.terminator() - if isinstance(source_terminator, ir.Interleave): - source_terminator.replace_with(ir.Branch(source_terminator.target())) - elif isinstance(source_terminator, ir.Branch): - pass - elif isinstance(source_terminator, ir.BranchIf): - # Skip a delay-free loop/conditional - source_block = postdom_tree.immediate_dominator(source_block) - assert (source_block is not None) - elif isinstance(source_terminator, ir.Return): - break - elif isinstance(source_terminator, ir.Delay): - old_decomp = source_terminator.decomposition() - if is_pure_delay(old_decomp): - if target_time_delta > 0: - new_decomp_expr = ir.Constant(int(target_time_delta), builtins.TInt64()) - new_decomp = ir.Builtin("delay_mu", [new_decomp_expr], builtins.TNone()) - new_decomp.loc = old_decomp.loc - - source_terminator.basic_block.insert(new_decomp, before=source_terminator) - source_terminator.interval = iodelay.Const(target_time_delta) - source_terminator.set_decomposition(new_decomp) - else: - source_terminator.replace_with(ir.Branch(source_terminator.target())) - old_decomp.erase() - else: # It's a call. - need_to_inline = len(source_blocks) > 1 - if need_to_inline: - if old_decomp.static_target_function is None: - diag = diagnostic.Diagnostic("fatal", - "it is not possible to interleave this function call within " - "a 'with interleave:' statement because the compiler could not " - "prove that the same function would always be called", {}, - old_decomp.loc) - self.engine.process(diag) - - inline(old_decomp) - postdom_tree = domination.PostDominatorTree(func) - continue - elif target_time_delta > 0: - source_terminator.interval = iodelay.Const(target_time_delta) - else: - source_terminator.replace_with(ir.Branch(source_terminator.target())) - elif isinstance(source_terminator, ir.Loop): - unroll(source_terminator) - - postdom_tree = domination.PostDominatorTree(func) - continue - else: - assert False - - target_block = source_block - target_time = new_target_time - - new_source_block = postdom_tree.immediate_dominator(source_block) - assert (new_source_block is not None) - assert delay_free_subgraph(source_block, new_source_block) - - if new_source_block == interleave_until: - # We're finished with this branch. - del source_blocks[index] - del source_times[index] - else: - source_blocks[index] = new_source_block - source_times[index] = new_target_time diff --git a/artiq/compiler/transforms/iodelay_estimator.py b/artiq/compiler/transforms/iodelay_estimator.py deleted file mode 100644 index fcee126cf..000000000 --- a/artiq/compiler/transforms/iodelay_estimator.py +++ /dev/null @@ -1,334 +0,0 @@ -""" -:class:`IODelayEstimator` calculates the amount of time -elapsed from the point of view of the RTIO core for -every function. -""" - -from pythonparser import ast, algorithm, diagnostic -from .. import types, iodelay, builtins, asttyped - -class _UnknownDelay(Exception): - pass - -class _IndeterminateDelay(Exception): - def __init__(self, cause): - self.cause = cause - -class IODelayEstimator(algorithm.Visitor): - def __init__(self, engine, ref_period): - self.engine = engine - self.ref_period = ref_period - self.changed = False - self.current_delay = iodelay.Const(0) - self.current_args = None - self.current_goto = None - self.current_return = None - - def evaluate(self, node, abort, context): - if isinstance(node, asttyped.NumT): - return iodelay.Const(node.n) - elif isinstance(node, asttyped.CoerceT): - return self.evaluate(node.value, abort, context) - elif isinstance(node, asttyped.NameT): - if self.current_args is None: - note = diagnostic.Diagnostic("note", - "this variable is not an argument", {}, - node.loc) - abort([note]) - elif node.id in [arg.arg for arg in self.current_args.args]: - return iodelay.Var(node.id) - else: - notes = [ - diagnostic.Diagnostic("note", - "this variable is not an argument of the innermost function", {}, - node.loc), - diagnostic.Diagnostic("note", - "only these arguments are in scope of analysis", {}, - self.current_args.loc) - ] - abort(notes) - elif isinstance(node, asttyped.BinOpT): - lhs = self.evaluate(node.left, abort, context) - rhs = self.evaluate(node.right, abort, context) - if isinstance(node.op, ast.Add): - return lhs + rhs - elif isinstance(node.op, ast.Sub): - return lhs - rhs - elif isinstance(node.op, ast.Mult): - return lhs * rhs - elif isinstance(node.op, ast.Div): - return lhs / rhs - elif isinstance(node.op, ast.FloorDiv): - return lhs // rhs - else: - note = diagnostic.Diagnostic("note", - "this operator is not supported {context}", - {"context": context}, - node.op.loc) - abort([note]) - else: - note = diagnostic.Diagnostic("note", - "this expression is not supported {context}", - {"context": context}, - node.loc) - abort([note]) - - def abort(self, message, loc, notes=[]): - diag = diagnostic.Diagnostic("error", message, {}, loc, notes=notes) - raise _IndeterminateDelay(diag) - - def visit_fixpoint(self, node): - while True: - self.changed = False - self.visit(node) - if not self.changed: - return - - def visit_ModuleT(self, node): - try: - for stmt in node.body: - try: - self.visit(stmt) - except _UnknownDelay: - pass # more luck next time? - except _IndeterminateDelay: - pass # we don't care; module-level code is never interleaved - - def visit_function(self, args, body, typ, loc): - old_args, self.current_args = self.current_args, args - old_return, self.current_return = self.current_return, None - old_delay, self.current_delay = self.current_delay, iodelay.Const(0) - try: - self.visit(body) - if not iodelay.is_zero(self.current_delay) and self.current_return is not None: - self.abort("only return statement at the end of the function " - "can be interleaved", self.current_return.loc) - - delay = types.TFixedDelay(self.current_delay.fold()) - except _IndeterminateDelay as error: - delay = types.TIndeterminateDelay(error.cause) - self.current_delay = old_delay - self.current_return = old_return - self.current_args = old_args - - if types.is_indeterminate_delay(delay) and types.is_indeterminate_delay(typ.delay): - # Both delays indeterminate; no point in unifying since that will - # replace the lazy and more specific error with an eager and more generic - # error (unification error of delay(?) with delay(?), which is useless). - return - - try: - old_delay = typ.delay.find() - typ.delay.unify(delay) - if typ.delay.find() != old_delay: - self.changed = True - except types.UnificationError as e: - printer = types.TypePrinter() - diag = diagnostic.Diagnostic("fatal", - "delay {delaya} was inferred for this function, but its delay is already " - "constrained externally to {delayb}", - {"delaya": printer.name(delay), "delayb": printer.name(typ.delay)}, - loc) - self.engine.process(diag) - - def visit_FunctionDefT(self, node): - self.visit(node.args.defaults) - self.visit(node.args.kw_defaults) - - # We can only handle return in tail position. - if isinstance(node.body[-1], ast.Return): - body = node.body[:-1] - else: - body = node.body - self.visit_function(node.args, body, node.signature_type.find(), node.loc) - - visit_QuotedFunctionDefT = visit_FunctionDefT - - def visit_LambdaT(self, node): - self.visit_function(node.args, node.body, node.type.find(), node.loc) - - def get_iterable_length(self, node, context): - def abort(notes): - self.abort("for statement cannot be interleaved because " - "iteration count is indeterminate", - node.loc, notes) - - def evaluate(node): - return self.evaluate(node, abort, context) - - if isinstance(node, asttyped.CallT) and types.is_builtin(node.func.type, "range"): - range_min, range_max, range_step = iodelay.Const(0), None, iodelay.Const(1) - if len(node.args) == 3: - range_min, range_max, range_step = map(evaluate, node.args) - elif len(node.args) == 2: - range_min, range_max = map(evaluate, node.args) - elif len(node.args) == 1: - range_max, = map(evaluate, node.args) - return (range_max - range_min) // range_step - else: - note = diagnostic.Diagnostic("note", - "this value is not a constant range literal", {}, - node.loc) - abort([note]) - - def visit_ForT(self, node): - self.visit(node.iter) - - old_goto, self.current_goto = self.current_goto, None - old_delay, self.current_delay = self.current_delay, iodelay.Const(0) - self.visit(node.body) - if iodelay.is_zero(self.current_delay): - self.current_delay = old_delay - else: - if self.current_goto is not None: - self.abort("loop iteration count is indeterminate because of control flow", - self.current_goto.loc) - - context = "in an iterable used in a for loop that is being interleaved" - node.trip_count = self.get_iterable_length(node.iter, context).fold() - node.trip_interval = self.current_delay.fold() - self.current_delay = old_delay + node.trip_interval * node.trip_count - self.current_goto = old_goto - - self.visit(node.orelse) - - def visit_goto(self, node): - self.current_goto = node - - visit_Break = visit_goto - visit_Continue = visit_goto - - def visit_control_flow(self, kind, node): - old_delay, self.current_delay = self.current_delay, iodelay.Const(0) - self.generic_visit(node) - if not iodelay.is_zero(self.current_delay): - self.abort("{} cannot be interleaved".format(kind), node.loc) - self.current_delay = old_delay - - visit_If = lambda self, node: self.visit_control_flow("if statement", node) - visit_IfExpT = lambda self, node: self.visit_control_flow("if expression", node) - visit_Try = lambda self, node: self.visit_control_flow("try statement", node) - - def visit_While(self, node): - old_goto, self.current_goto = self.current_goto, None - self.visit_control_flow("while statement", node) - self.current_goto = old_goto - - def visit_Return(self, node): - self.current_return = node - - def visit_With(self, node): - self.visit(node.items) - - context_expr = node.items[0].context_expr - if len(node.items) == 1 and types.is_builtin(context_expr.type, "interleave"): - try: - delays = [] - for stmt in node.body: - old_delay, self.current_delay = self.current_delay, iodelay.Const(0) - self.visit(stmt) - delays.append(self.current_delay) - self.current_delay = old_delay - - if any(delays): - self.current_delay += iodelay.Max(delays) - except _IndeterminateDelay as error: - # Interleave failures inside `with` statements are hard failures, - # since there's no chance that the code will never actually execute - # inside a `with` statement after all. - note = diagnostic.Diagnostic("note", - "while interleaving this 'with interleave:' statement", {}, - node.loc) - error.cause.notes += [note] - self.engine.process(error.cause) - - flow_stmt = None - if self.current_goto is not None: - flow_stmt = self.current_goto - elif self.current_return is not None: - flow_stmt = self.current_return - - if flow_stmt is not None: - note = diagnostic.Diagnostic("note", - "this '{kind}' statement transfers control out of " - "the 'with interleave:' statement", - {"kind": flow_stmt.keyword_loc.source()}, - flow_stmt.loc) - diag = diagnostic.Diagnostic("error", - "cannot interleave this 'with interleave:' statement", {}, - node.keyword_loc.join(node.colon_loc), notes=[note]) - self.engine.process(diag) - - elif len(node.items) == 1 and types.is_builtin(context_expr.type, "sequential"): - self.visit(node.body) - else: - self.abort("with statement cannot be interleaved", node.loc) - - def visit_CallT(self, node): - typ = node.func.type.find() - def abort(notes): - self.abort("call cannot be interleaved because " - "an argument cannot be statically evaluated", - node.loc, notes) - - if types.is_builtin(typ, "delay"): - value = self.evaluate(node.args[0], abort=abort, - context="as an argument for delay()") - call_delay = iodelay.SToMU(value, ref_period=self.ref_period) - elif types.is_builtin(typ, "delay_mu"): - value = self.evaluate(node.args[0], abort=abort, - context="as an argument for delay_mu()") - call_delay = value - elif not types.is_builtin(typ): - if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(typ): - offset = 0 - elif types.is_method(typ): - offset = 1 - typ = types.get_method_function(typ) - else: - assert False - - if types.is_rpc(typ) or types.is_subkernel(typ): - call_delay = iodelay.Const(0) - else: - delay = typ.find().delay.find() - if types.is_var(delay): - raise _UnknownDelay() - elif delay.is_indeterminate(): - note = diagnostic.Diagnostic("note", - "function called here", {}, - node.loc) - cause = delay.cause - cause = diagnostic.Diagnostic(cause.level, cause.reason, cause.arguments, - cause.location, cause.highlights, - cause.notes + [note]) - raise _IndeterminateDelay(cause) - elif delay.is_fixed(): - args = {} - for kw_node in node.keywords: - args[kw_node.arg] = kw_node.value - for arg_name, arg_node in zip(list(typ.args)[offset:], node.args): - args[arg_name] = arg_node - - free_vars = delay.duration.free_vars() - try: - node.arg_exprs = { - arg: self.evaluate(args[arg], abort=abort, - context="in the expression for argument '{}' " - "that affects I/O delay".format(arg)) - for arg in free_vars - } - call_delay = delay.duration.fold(node.arg_exprs) - except KeyError as e: - if getattr(node, "remote_fn", False): - note = diagnostic.Diagnostic("note", - "function called here", {}, - node.loc) - self.abort("due to arguments passed remotely", node.loc, note) - else: - assert False - else: - call_delay = iodelay.Const(0) - - self.current_delay += call_delay - node.iodelay = call_delay diff --git a/artiq/compiler/transforms/local_demoter.py b/artiq/compiler/transforms/local_demoter.py deleted file mode 100644 index 4701e7a7c..000000000 --- a/artiq/compiler/transforms/local_demoter.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -:class:`LocalDemoter` is a constant propagation transform: -it replaces reads of any local variable with only one write -in a function without closures with the value that was written. - -:class:`LocalAccessValidator` must be run before this transform -to ensure that the transformation it performs is sound. -""" - -from collections import defaultdict -from .. import ir - -class LocalDemoter: - def process(self, functions): - for func in functions: - self.process_function(func) - - def process_function(self, func): - env_safe = {} - env_gets = defaultdict(lambda: set()) - env_sets = defaultdict(lambda: set()) - - for insn in func.instructions(): - if isinstance(insn, (ir.GetLocal, ir.SetLocal)): - if "$" in insn.var_name: - continue - - env = insn.environment() - - if env not in env_safe: - for use in env.uses: - if not isinstance(use, (ir.GetLocal, ir.SetLocal)): - env_safe[env] = False - break - else: - env_safe[env] = True - - if not env_safe[env]: - continue - - if isinstance(insn, ir.SetLocal): - env_sets[(env, insn.var_name)].add(insn) - else: - env_gets[(env, insn.var_name)].add(insn) - - for (env, var_name) in env_sets: - if len(env_sets[(env, var_name)]) == 1: - set_insn = next(iter(env_sets[(env, var_name)])) - for get_insn in env_gets[(env, var_name)]: - get_insn.replace_all_uses_with(set_insn.value()) - get_insn.erase() diff --git a/artiq/compiler/transforms/typedtree_printer.py b/artiq/compiler/transforms/typedtree_printer.py deleted file mode 100644 index e0c296f42..000000000 --- a/artiq/compiler/transforms/typedtree_printer.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -:class:`TypedtreePrinter` prints a human-readable representation of typedtrees. -""" - -from pythonparser import algorithm, ast -from .. import types, asttyped - -class TypedtreePrinter(algorithm.Visitor): - def __init__(self): - self.str = None - self.level = None - self.last_nl = None - self.type_printer = None - - def print(self, node): - try: - self.str = "" - self.level = 0 - self.last_nl = 0 - self.type_printer = types.TypePrinter() - self.visit(node) - self._nl() - return self.str - finally: - self.str = None - self.level = None - self.last_nl = 0 - self.type_printer = None - - def _nl(self): - # self.str += "·" - if len(self.str) != self.last_nl: - self.str += "\n" + (" " * self.level) - self.last_nl = len(self.str) - - def _indent(self): - self.level += 1 - self._nl() - - def _dedent(self): - self._nl() - self.level -= 1 - self.str = self.str[:-2] - self.last_nl -= 2 - - def visit(self, obj): - if isinstance(obj, ast.AST): - attrs = set(obj._fields) - {'ctx'} - if isinstance(obj, asttyped.commontyped): - attrs.update(set(obj._types)) - - for attr in set(attrs): - if not getattr(obj, attr): - attrs.remove(attr) # omit falsey stuff - - self.str += obj.__class__.__name__ + "(" - if len(attrs) > 1: - self._indent() - - for attr in attrs: - if len(attrs) > 1: - self._nl() - self.str += attr + "=" - self.visit(getattr(obj, attr)) - if len(attrs) > 1: - self._nl() - - if len(attrs) > 1: - self._dedent() - self.str += ")" - elif isinstance(obj, types.Type): - self.str += self.type_printer.name(obj, max_depth=0) - elif isinstance(obj, list): - self.str += "[" - if len(obj) > 1: - self._indent() - - for elem in obj: - if len(obj) > 1: - self._nl() - self.visit(elem) - if len(obj) > 1: - self._nl() - - if len(obj) > 1: - self._dedent() - self.str += "]" - else: - self.str += repr(obj) diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py deleted file mode 100644 index 7f397d308..000000000 --- a/artiq/compiler/types.py +++ /dev/null @@ -1,890 +0,0 @@ -""" -The :mod:`types` module contains the classes describing the types -in :mod:`asttyped`. -""" - -import builtins -import string -from collections import OrderedDict -from . import iodelay - - -class UnificationError(Exception): - def __init__(self, typea, typeb): - self.typea, self.typeb = typea, typeb - - -def genalnum(): - ident = ["a"] - while True: - yield "".join(ident) - pos = len(ident) - 1 - while pos >= 0: - cur_n = string.ascii_lowercase.index(ident[pos]) - if cur_n < 25: - ident[pos] = string.ascii_lowercase[cur_n + 1] - break - else: - ident[pos] = "a" - pos -= 1 - if pos < 0: - ident = ["a"] + ident - -def _freeze(dict_): - return tuple((key, dict_[key]) for key in dict_) - -def _map_find(elts): - if isinstance(elts, list): - return [x.find() for x in elts] - elif isinstance(elts, dict): - return {k: elts[k].find() for k in elts} - else: - assert False - - -class Type(object): - def __str__(self): - return TypePrinter().name(self) - -class TVar(Type): - """ - A type variable. - - In effect, the classic union-find data structure is intrusively - folded into this class. - """ - - def __init__(self): - self.parent = self - self.rank = 0 - - def find(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 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 - 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: - y.unify(x) - - def fold(self, accum, fn): - if self.parent is self: - return fn(accum, self) - else: - 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: - return repr(self.find()) - - # __eq__ and __hash__ are not overridden and default to - # comparison by identity. Use .find() explicitly before - # any lookups or comparisons. - -class TMono(Type): - """ - A monomorphic type, possibly parametric. - - :class:`TMono` is supposed to be subclassed by builtin types, - unlike all other :class:`Type` descendants. Similarly, - instances of :class:`TMono` should never be allocated directly, - as that will break the type-sniffing code in :mod:`builtins`. - """ - - attributes = OrderedDict() - - def __init__(self, name, params={}): - assert isinstance(params, (dict, OrderedDict)) - self.name, self.params = name, OrderedDict(sorted(params.items())) - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if isinstance(other, TMono) and self.name == other.name: - assert self.params.keys() == other.params.keys() - for param in self.params: - self.params[param].unify(other.params[param]) - elif isinstance(other, TVar): - other.unify(self) - else: - raise UnificationError(self, other) - - def fold(self, accum, fn): - for param in self.params: - accum = self.params[param].fold(accum, fn) - 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): - return self.params[param] - - def __eq__(self, other): - return isinstance(other, TMono) and \ - self.name == other.name and \ - _map_find(self.params) == _map_find(other.params) - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash((self.name, _freeze(self.params))) - -class TTuple(Type): - """ - A tuple type. - - :ivar elts: (list of :class:`Type`) elements - """ - - attributes = OrderedDict() - - def __init__(self, elts=[]): - self.elts = elts - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if isinstance(other, TTuple) and len(self.elts) == len(other.elts): - for selfelt, otherelt in zip(self.elts, other.elts): - selfelt.unify(otherelt) - elif isinstance(other, TVar): - other.unify(self) - else: - raise UnificationError(self, other) - - def fold(self, accum, fn): - for elt in self.elts: - accum = elt.fold(accum, fn) - 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): - return isinstance(other, TTuple) and \ - _map_find(self.elts) == _map_find(other.elts) - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash(tuple(self.elts)) - -class _TPointer(TMono): - def __init__(self, elt=None): - if elt is None: - elt = TMono("int", {"width": 8}) # i8* - super().__init__("pointer", params={"elt": elt}) - -class TFunction(Type): - """ - A function type. - - :ivar args: (:class:`collections.OrderedDict` of string to :class:`Type`) - mandatory arguments - :ivar optargs: (:class:`collections.OrderedDict` of string to :class:`Type`) - optional arguments - :ivar ret: (:class:`Type`) - return type - :ivar delay: (:class:`Type`) - RTIO delay - """ - - attributes = OrderedDict([ - ('__closure__', _TPointer()), - ('__code__', _TPointer()), - ]) - - def __init__(self, args, optargs, ret): - assert isinstance(args, OrderedDict) - assert isinstance(optargs, OrderedDict) - assert isinstance(ret, Type) - self.args, self.optargs, self.ret = args, optargs, ret - self.delay = TVar() - - def arity(self): - return len(self.args) + len(self.optargs) - - def arg_names(self): - return list(self.args.keys()) + list(self.optargs.keys()) - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if isinstance(other, TFunction) and \ - self.args.keys() == other.args.keys() and \ - self.optargs.keys() == other.optargs.keys(): - for selfarg, otherarg in zip(list(self.args.values()) + list(self.optargs.values()), - list(other.args.values()) + list(other.optargs.values())): - selfarg.unify(otherarg) - self.ret.unify(other.ret) - self.delay.unify(other.delay) - elif isinstance(other, TVar): - other.unify(self) - else: - raise UnificationError(self, other) - - def fold(self, accum, fn): - for arg in self.args: - accum = self.args[arg].fold(accum, fn) - for optarg in self.optargs: - accum = self.optargs[optarg].fold(accum, fn) - accum = self.ret.fold(accum, fn) - 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)) - - def __eq__(self, other): - return isinstance(other, TFunction) and \ - _map_find(self.args) == _map_find(other.args) and \ - _map_find(self.optargs) == _map_find(other.optargs) - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash((_freeze(self.args), _freeze(self.optargs), self.ret)) - -class TExternalFunction(TFunction): - """ - A type of an externally-provided function. - - This can be any function following the C ABI, such as provided by the - C/Rust runtime, or a compiler backend intrinsic. The mangled name to link - against is encoded as part of the type. - - :ivar name: (str) external symbol name. - This will be the symbol linked against (following any extra C name - mangling rules). - :ivar flags: (set of str) function flags. - Flag ``nounwind`` means the function never raises an exception. - Flag ``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 - with TArray arguments. - """ - - attributes = OrderedDict() - - def __init__(self, args, ret, name, flags=set(), broadcast_across_arrays=False): - assert isinstance(flags, set) - for flag in flags: - assert flag in {'nounwind', 'nowrite'} - super().__init__(args, OrderedDict(), ret) - self.name = name - self.delay = TFixedDelay(iodelay.Const(0)) - self.flags = flags - self.broadcast_across_arrays = broadcast_across_arrays - - def unify(self, other): - if other is self: - return - if isinstance(other, TExternalFunction) and \ - self.name == other.name: - super().unify(other) - elif isinstance(other, TVar): - other.unify(self) - else: - raise UnificationError(self, other) - -class TRPC(Type): - """ - A type of a remote call. - - :ivar ret: (:class:`Type`) - return type - :ivar service: (int) RPC service number - :ivar is_async: (bool) whether the RPC blocks until return - """ - - attributes = OrderedDict() - - def __init__(self, ret, service, is_async=False): - assert isinstance(ret, Type) - self.ret, self.service, self.is_async = ret, service, is_async - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if isinstance(other, TRPC) and \ - self.service == other.service and \ - self.is_async == other.is_async: - self.ret.unify(other.ret) - elif isinstance(other, TVar): - other.unify(self) - else: - raise UnificationError(self, other) - - def fold(self, accum, fn): - accum = self.ret.fold(accum, fn) - 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): - return isinstance(other, TRPC) and \ - self.service == other.service and \ - self.is_async == other.is_async - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash(self.service) - -class TSubkernel(TFunction): - """ - A kernel to be run on a satellite. - - :ivar args: (:class:`collections.OrderedDict` of string to :class:`Type`) - function arguments - :ivar ret: (:class:`Type`) - return type - :ivar sid: (int) subkernel ID number - :ivar destination: (int) satellite destination number - """ - - attributes = OrderedDict() - - def __init__(self, args, optargs, ret, sid, destination): - assert isinstance(ret, Type) - super().__init__(args, optargs, ret) - self.sid, self.destination = sid, destination - self.delay = TFixedDelay(iodelay.Const(0)) - - def unify(self, other): - if other is self: - return - if isinstance(other, TSubkernel) and \ - self.sid == other.sid and \ - self.destination == other.destination: - self.ret.unify(other.ret) - elif isinstance(other, TVar): - other.unify(self) - else: - raise UnificationError(self, other) - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - return "artiq.compiler.types.TSubkernel({})".format(repr(self.ret)) - - def __eq__(self, other): - return isinstance(other, TSubkernel) and \ - self.sid == other.sid - - def __hash__(self): - return hash(self.sid) - -class TBuiltin(Type): - """ - An instance of builtin type. Every instance of a builtin - type is treated specially according to its name. - """ - - def __init__(self, name): - assert isinstance(name, str) - self.name = name - self.attributes = OrderedDict() - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if self != other: - raise UnificationError(self, other) - - def fold(self, accum, fn): - 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): - return isinstance(other, TBuiltin) and \ - self.name == other.name - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash(self.name) - -class TBuiltinFunction(TBuiltin): - """ - A type of a builtin function. - - Builtin functions are treated specially throughout all stages of the - compilation process according to their name (e.g. calls may not actually - lower to a function call). See :class:`TExternalFunction` for externally - defined functions that are otherwise regular. - """ - -class TConstructor(TBuiltin): - """ - A type of a constructor of a class, e.g. ``list``. - Note that this is not the same as the type of an instance of - the class, which is ``TMono("list", ...)`` (or a descendant). - - :ivar instance: (:class:`Type`) - the type of the instance created by this constructor - """ - - def __init__(self, instance): - assert isinstance(instance, TMono) - super().__init__(instance.name) - self.instance = instance - -class TExceptionConstructor(TConstructor): - """ - A type of a constructor of an exception, e.g. ``Exception``. - Note that this is not the same as the type of an instance of - the class, which is ``TMono("Exception", ...)``. - """ - -class TInstance(TMono): - """ - A type of an instance of a user-defined class. - - :ivar constructor: (:class:`TConstructor`) - the type of the constructor with which this instance - was created - """ - - def __init__(self, name, attributes): - assert isinstance(attributes, OrderedDict) - super().__init__(name) - self.attributes = attributes - self.constant_attributes = set() - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - return "artiq.compiler.types.TInstance({}, {})".format( - repr(self.name), repr(self.attributes)) - -class TModule(TMono): - """ - A type of a module. - """ - - def __init__(self, name, attributes): - assert isinstance(attributes, OrderedDict) - super().__init__(name) - self.attributes = attributes - self.constant_attributes = set() - - def __repr__(self): - if getattr(builtins, "__in_sphinx__", False): - return str(self) - return "artiq.compiler.types.TModule({}, {})".format( - repr(self.name), repr(self.attributes)) - -class TMethod(TMono): - """ - A type of a method. - """ - - def __init__(self, self_type, function_type): - super().__init__("method", {"self": self_type, "fn": function_type}) - self.attributes = OrderedDict([ - ("__func__", function_type), - ("__self__", self_type), - ]) - -class TValue(Type): - """ - A type-level value (such as the integer denoting width of - a generic integer type. - """ - - def __init__(self, value): - self.value = value - - def find(self): - return self - - def unify(self, other): - if other is self: - return - if isinstance(other, TVar): - other.unify(self) - elif self != other: - raise UnificationError(self, other) - - def fold(self, accum, fn): - 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): - return isinstance(other, TValue) and \ - self.value == other.value - - def __ne__(self, other): - return not (self == other) - - def __hash__(self): - return hash(self.value) - -class TDelay(Type): - """ - The type-level representation of IO delay. - """ - - def __init__(self, duration, cause): - # Avoid pulling in too many dependencies with `artiq.language`. - from pythonparser import diagnostic - assert duration is None or isinstance(duration, iodelay.Expr) - assert cause is None or isinstance(cause, diagnostic.Diagnostic) - assert (not (duration and cause)) and (duration or cause) - self.duration, self.cause = duration, cause - - def is_fixed(self): - return self.duration is not None - - def is_indeterminate(self): - return self.cause is not None - - def find(self): - return self - - def unify(self, other): - other = other.find() - - if isinstance(other, TVar): - other.unify(self) - elif self.is_fixed() and other.is_fixed() and \ - self.duration.fold() == other.duration.fold(): - pass - elif self is not other: - raise UnificationError(self, other) - - def fold(self, accum, fn): - # delay types do not participate in folding - pass - - def __eq__(self, other): - return isinstance(other, TDelay) and \ - (self.duration == other.duration and \ - self.cause == other.cause) - - def __ne__(self, other): - 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: - return "{}.TFixedDelay({})".format(__name__, self.duration) - else: - assert False - -def TIndeterminateDelay(cause): - return TDelay(None, cause) - -def TFixedDelay(duration): - return TDelay(duration, None) - - -def is_var(typ): - return isinstance(typ.find(), TVar) - -def is_mono(typ, name=None, **params): - typ = typ.find() - - if not isinstance(typ, TMono): - return False - - if name is not None and typ.name != name: - return False - - for param in params: - if param not in typ.params: - return False - if typ.params[param].find() != params[param].find(): - return False - return True - -def is_polymorphic(typ): - return typ.fold(False, lambda accum, typ: accum or is_var(typ)) - -def is_tuple(typ, elts=None): - typ = typ.find() - if elts: - return isinstance(typ, TTuple) and \ - elts == typ.elts - else: - return isinstance(typ, TTuple) - -def _is_pointer(typ): - return isinstance(typ.find(), _TPointer) - -def is_function(typ): - return isinstance(typ.find(), TFunction) - -def is_rpc(typ): - return isinstance(typ.find(), TRPC) - -def is_subkernel(typ): - return isinstance(typ.find(), TSubkernel) - -def is_external_function(typ, name=None): - typ = typ.find() - if name is None: - return isinstance(typ, TExternalFunction) - else: - return isinstance(typ, TExternalFunction) and \ - typ.name == name - -def is_builtin(typ, name=None): - typ = typ.find() - if name is None: - return isinstance(typ, TBuiltin) - else: - return isinstance(typ, TBuiltin) and \ - typ.name == name - -def is_builtin_function(typ, name=None): - typ = typ.find() - if name is None: - return isinstance(typ, TBuiltinFunction) - else: - return isinstance(typ, TBuiltinFunction) and \ - typ.name == name - -def is_broadcast_across_arrays(typ): - # For now, broadcasting is only exposed to predefined external functions, and - # statically selected. Might be extended to user-defined functions if the design - # pans out. - typ = typ.find() - if not isinstance(typ, TExternalFunction): - return False - return typ.broadcast_across_arrays - -def is_constructor(typ, name=None): - typ = typ.find() - if name is not None: - return isinstance(typ, TConstructor) and \ - typ.name == name - else: - return isinstance(typ, TConstructor) - -def is_exn_constructor(typ, name=None): - typ = typ.find() - if name is not None: - return isinstance(typ, TExceptionConstructor) and \ - typ.name == name - else: - return isinstance(typ, TExceptionConstructor) - -def is_instance(typ, name=None): - typ = typ.find() - if name is not None: - return isinstance(typ, TInstance) and \ - typ.name == name - else: - return isinstance(typ, TInstance) - -def is_module(typ, name=None): - typ = typ.find() - if name is not None: - return isinstance(typ, TModule) and \ - typ.name == name - else: - return isinstance(typ, TModule) - -def is_method(typ): - return isinstance(typ.find(), TMethod) - -def get_method_self(typ): - if is_method(typ): - return typ.find().params["self"].find() - -def get_method_function(typ): - if is_method(typ): - return typ.find().params["fn"].find() - -def is_value(typ): - return isinstance(typ.find(), TValue) - -def get_value(typ): - typ = typ.find() - if isinstance(typ, TVar): - return None - elif isinstance(typ, TValue): - return typ.value - else: - assert False - -def is_delay(typ): - return isinstance(typ.find(), TDelay) - -def is_fixed_delay(typ): - return is_delay(typ) and typ.find().is_fixed() - -def is_indeterminate_delay(typ): - return is_delay(typ) and typ.find().is_indeterminate() - - -class TypePrinter(object): - """ - A class that prints types using Python-like syntax and gives - type variables sequential alphabetic names. - """ - - custom_printers = {} - - def __init__(self): - self.gen = genalnum() - self.map = {} - self.recurse_guard = set() - - def name(self, typ, depth=0, max_depth=1): - typ = typ.find() - if isinstance(typ, TVar): - if typ not in self.map: - self.map[typ] = "'%s" % next(self.gen) - return self.map[typ] - elif isinstance(typ, TInstance): - if typ in self.recurse_guard or depth >= max_depth: - return "".format(typ.name) - elif len(typ.attributes) > 0: - self.recurse_guard.add(typ) - attrs = ",\n\t\t".join(["{}: {}".format(attr, self.name(typ.attributes[attr], - depth + 1)) - for attr in typ.attributes]) - return "".format(typ.name, attrs) - else: - self.recurse_guard.add(typ) - return "".format(typ.name) - elif isinstance(typ, TMono): - if typ.name in self.custom_printers: - return self.custom_printers[typ.name](typ, self, depth + 1, max_depth) - elif typ.params == {}: - return typ.name - else: - return "%s(%s)" % (typ.name, ", ".join( - ["%s=%s" % (k, self.name(typ.params[k], depth + 1)) for k in typ.params])) - elif isinstance(typ, _TPointer): - return "{}*".format(self.name(typ["elt"], depth + 1)) - elif isinstance(typ, TTuple): - if len(typ.elts) == 1: - return "(%s,)" % self.name(typ.elts[0], depth + 1) - else: - return "(%s)" % ", ".join([self.name(typ, depth + 1) for typ in typ.elts]) - elif isinstance(typ, (TFunction, TExternalFunction)): - args = [] - args += [ "%s:%s" % (arg, self.name(typ.args[arg], depth + 1)) - for arg in typ.args] - args += ["?%s:%s" % (arg, self.name(typ.optargs[arg], depth + 1)) - for arg in typ.optargs] - signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret, depth + 1)) - - delay = typ.delay.find() - if isinstance(delay, TVar): - signature += " delay({})".format(self.name(delay, depth + 1)) - elif not (delay.is_fixed() and iodelay.is_zero(delay.duration)): - signature += " " + self.name(delay, depth + 1) - - if isinstance(typ, TExternalFunction): - return "[ffi {}]{}".format(repr(typ.name), signature) - elif isinstance(typ, TFunction): - return signature - elif isinstance(typ, TRPC): - return "[rpc{} #{}](...)->{}".format(typ.service, - " async" if typ.is_async else "", - self.name(typ.ret, depth + 1)) - elif isinstance(typ, TSubkernel): - return "->{}".format(typ.sid, - typ.destination, - self.name(typ.ret, depth + 1)) - elif isinstance(typ, TBuiltinFunction): - return "".format(typ.name) - elif isinstance(typ, (TConstructor, TExceptionConstructor)): - if typ in self.recurse_guard or depth >= max_depth: - return "".format(typ.name) - elif len(typ.attributes) > 0: - self.recurse_guard.add(typ) - attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr], - depth + 1)) - for attr in typ.attributes]) - return "".format(typ.name, attrs) - else: - self.recurse_guard.add(typ) - return "".format(typ.name) - elif isinstance(typ, TBuiltin): - return "".format(typ.name) - elif isinstance(typ, TValue): - return repr(typ.value) - elif isinstance(typ, TDelay): - if typ.is_fixed(): - return "delay({} mu)".format(typ.duration) - elif typ.is_indeterminate(): - return "delay(?)" - else: - assert False - else: - assert False diff --git a/artiq/compiler/validators/__init__.py b/artiq/compiler/validators/__init__.py deleted file mode 100644 index ccbaa8827..000000000 --- a/artiq/compiler/validators/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -from .monomorphism import MonomorphismValidator -from .escape import EscapeValidator -from .local_access import LocalAccessValidator -from .constness import ConstnessValidator diff --git a/artiq/compiler/validators/constness.py b/artiq/compiler/validators/constness.py deleted file mode 100644 index fb1123c49..000000000 --- a/artiq/compiler/validators/constness.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -:class:`ConstnessValidator` checks that no attribute marked -as constant is ever set. -""" - -from pythonparser import algorithm, diagnostic -from .. import types, builtins - -class ConstnessValidator(algorithm.Visitor): - def __init__(self, engine): - self.engine = engine - self.in_assign = False - - def visit_Assign(self, node): - self.visit(node.value) - self.in_assign = True - self.visit(node.targets) - self.in_assign = False - - def visit_AugAssign(self, node): - self.visit(node.value) - self.in_assign = True - self.visit(node.target) - self.in_assign = False - - def visit_SubscriptT(self, node): - old_in_assign, self.in_assign = self.in_assign, False - self.visit(node.value) - self.visit(node.slice) - self.in_assign = old_in_assign - - if self.in_assign and builtins.is_bytes(node.value.type): - diag = diagnostic.Diagnostic("error", - "type {typ} is not mutable", - {"typ": "bytes"}, - node.loc) - self.engine.process(diag) - - def visit_AttributeT(self, node): - old_in_assign, self.in_assign = self.in_assign, False - self.visit(node.value) - self.in_assign = old_in_assign - - if self.in_assign: - typ = node.value.type.find() - if types.is_instance(typ) and node.attr in typ.constant_attributes: - diag = diagnostic.Diagnostic("error", - "cannot assign to constant attribute '{attr}' of class '{class}'", - {"attr": node.attr, "class": typ.name}, - node.loc) - self.engine.process(diag) - return - if builtins.is_array(typ): - diag = diagnostic.Diagnostic("error", - "array attributes cannot be assigned to", - {}, node.loc) - self.engine.process(diag) - return diff --git a/artiq/compiler/validators/escape.py b/artiq/compiler/validators/escape.py deleted file mode 100644 index 7d2395830..000000000 --- a/artiq/compiler/validators/escape.py +++ /dev/null @@ -1,372 +0,0 @@ -""" -:class:`EscapeValidator` verifies that no mutable data escapes -the region of its allocation. -""" - -import functools -from pythonparser import algorithm, diagnostic -from .. import asttyped, types, builtins - -def has_region(typ): - return typ.fold(False, lambda accum, typ: accum or builtins.is_allocated(typ)) - -class Global: - def __repr__(self): - return "Global()" - -class Argument: - def __init__(self, loc): - self.loc = loc - - def __repr__(self): - return "Argument()" - -class Region: - """ - A last-in-first-out allocation region. Tied to lexical scoping - and is internally represented simply by a source range. - - :ivar range: (:class:`pythonparser.source.Range` or None) - """ - - def __init__(self, source_range=None): - self.range = source_range - - def present(self): - return bool(self.range) - - def includes(self, other): - assert self.range - assert self.range.source_buffer == other.range.source_buffer - - return self.range.begin_pos <= other.range.begin_pos and \ - self.range.end_pos >= other.range.end_pos - - def intersects(self, other): - assert self.range - assert self.range.source_buffer == other.range.source_buffer - - return (self.range.begin_pos <= other.range.begin_pos <= self.range.end_pos and \ - other.range.end_pos > self.range.end_pos) or \ - (other.range.begin_pos <= self.range.begin_pos <= other.range.end_pos and \ - self.range.end_pos > other.range.end_pos) - - def outlives(lhs, rhs): - if not isinstance(lhs, Region): # lhs lives nonlexically - return True - elif not isinstance(rhs, Region): # rhs lives nonlexically, lhs does not - return False - else: - assert not lhs.intersects(rhs) - return lhs.includes(rhs) - - def __repr__(self): - return "Region({})".format(repr(self.range)) - -class RegionOf(algorithm.Visitor): - """ - Visit an expression and return the region that must be alive for the - expression to execute. - - For expressions involving multiple regions, the shortest-lived one is - returned. - """ - - def __init__(self, env_stack, youngest_region): - self.env_stack, self.youngest_region = env_stack, youngest_region - - # Liveness determined by assignments - def visit_NameT(self, node): - # First, look at stack regions - for region in reversed(self.env_stack[1:]): - if node.id in region: - return region[node.id] - - # Then, look at the global region of this module - if node.id in self.env_stack[0]: - return Global() - - assert False - - # Value lives as long as the current scope, if it's mutable, - # or else forever - def visit_sometimes_allocating(self, node): - if has_region(node.type): - return self.youngest_region - else: - return Global() - - visit_BinOpT = visit_sometimes_allocating - - def visit_CallT(self, node): - if types.is_external_function(node.func.type, "cache_get"): - # The cache is borrow checked dynamically - return Global() - - if (types.is_builtin_function(node.func.type, "array") - or types.is_builtin_function(node.func.type, "make_array") - or types.is_builtin_function(node.func.type, "numpy.transpose")): - # While lifetime tracking across function calls in general is currently - # broken (see below), these special builtins that allocate an array on - # the stack of the caller _always_ allocate regardless of the parameters, - # and we can thus handle them without running into the precision issue - # mentioned in commit ae999db. - return self.visit_allocating(node) - - # FIXME: Return statement missing here, but see m-labs/artiq#1497 and - # commit ae999db. - self.visit_sometimes_allocating(node) - - # Value lives as long as the object/container, if it's mutable, - # or else forever - def visit_accessor(self, node): - if has_region(node.type): - return self.visit(node.value) - else: - return Global() - - visit_AttributeT = visit_accessor - visit_SubscriptT = visit_accessor - - # Value lives as long as the shortest living operand - def visit_selecting(self, nodes): - regions = [self.visit(node) for node in nodes] - regions = list(filter(lambda x: x, regions)) - if any(regions): - regions.sort(key=functools.cmp_to_key(Region.outlives), reverse=True) - return regions[0] - else: - return Global() - - def visit_BoolOpT(self, node): - return self.visit_selecting(node.values) - - def visit_IfExpT(self, node): - return self.visit_selecting([node.body, node.orelse]) - - def visit_TupleT(self, node): - return self.visit_selecting(node.elts) - - # Value lives as long as the current scope - def visit_allocating(self, node): - return self.youngest_region - - visit_DictT = visit_allocating - visit_DictCompT = visit_allocating - visit_GeneratorExpT = visit_allocating - visit_LambdaT = visit_allocating - visit_ListT = visit_allocating - visit_ListCompT = visit_allocating - visit_SetT = visit_allocating - visit_SetCompT = visit_allocating - - # Value lives forever - def visit_immutable(self, node): - assert not has_region(node.type) - return Global() - - visit_NameConstantT = visit_immutable - visit_NumT = visit_immutable - visit_EllipsisT = visit_immutable - visit_UnaryOpT = visit_sometimes_allocating # possibly array op - visit_CompareT = visit_immutable - - # Value lives forever - def visit_global(self, node): - return Global() - - visit_StrT = visit_global - visit_QuoteT = visit_global - - # Not implemented - def visit_unimplemented(self, node): - assert False - - visit_StarredT = visit_unimplemented - visit_YieldT = visit_unimplemented - visit_YieldFromT = visit_unimplemented - - -class AssignedNamesOf(algorithm.Visitor): - """ - Visit an expression and return the list of names that appear - on the lhs of assignment, directly or through an accessor. - """ - - def visit_name(self, node): - return [node] - - visit_NameT = visit_name - visit_QuoteT = visit_name - - def visit_accessor(self, node): - return self.visit(node.value) - - visit_AttributeT = visit_accessor - visit_SubscriptT = visit_accessor - - def visit_sequence(self, node): - return functools.reduce(list.__add__, map(self.visit, node.elts)) - - visit_TupleT = visit_sequence - visit_ListT = visit_sequence - - def visit_StarredT(self, node): - assert False - - -class EscapeValidator(algorithm.Visitor): - def __init__(self, engine): - self.engine = engine - self.youngest_region = Global() - self.env_stack = [] - self.youngest_env = None - - def _region_of(self, expr): - return RegionOf(self.env_stack, self.youngest_region).visit(expr) - - def _names_of(self, expr): - return AssignedNamesOf().visit(expr) - - def _diagnostics_for(self, region, loc, descr="the value of the expression"): - if isinstance(region, Region): - return [ - diagnostic.Diagnostic("note", - "{descr} is alive from this point...", {"descr": descr}, - region.range.begin()), - diagnostic.Diagnostic("note", - "... to this point", {}, - region.range.end()) - ] - elif isinstance(region, Global): - return [ - diagnostic.Diagnostic("note", - "{descr} is alive forever", {"descr": descr}, - loc) - ] - elif isinstance(region, Argument): - return [ - diagnostic.Diagnostic("note", - "{descr} is still alive after this function returns", {"descr": descr}, - loc), - diagnostic.Diagnostic("note", - "{descr} is introduced here as a formal argument", {"descr": descr}, - region.loc) - ] - else: - assert False - - def visit_in_region(self, node, region, typing_env, args=[]): - try: - old_youngest_region = self.youngest_region - self.youngest_region = region - - old_youngest_env = self.youngest_env - self.youngest_env = {} - - for name in typing_env: - if has_region(typing_env[name]): - if name in args: - self.youngest_env[name] = args[name] - else: - self.youngest_env[name] = Region(None) # not yet known - else: - self.youngest_env[name] = Global() - self.env_stack.append(self.youngest_env) - - self.generic_visit(node) - finally: - self.env_stack.pop() - self.youngest_env = old_youngest_env - self.youngest_region = old_youngest_region - - def visit_ModuleT(self, node): - self.visit_in_region(node, None, node.typing_env) - - def visit_FunctionDefT(self, node): - self.youngest_env[node.name] = self.youngest_region - self.visit_in_region(node, Region(node.loc), node.typing_env, - args={ arg.arg: Argument(arg.loc) for arg in node.args.args }) - - visit_QuotedFunctionDefT = visit_FunctionDefT - - def visit_ClassDefT(self, node): - self.youngest_env[node.name] = self.youngest_region - self.visit_in_region(node, Region(node.loc), node.constructor_type.attributes) - - # Only three ways for a pointer to escape: - # * Assigning or op-assigning it (we ensure an outlives relationship) - # * Returning it (we only allow returning values that live forever) - # * Raising it (we forbid allocating exceptions that refer to mutable data)¹ - # - # Literals doesn't count: a constructed object is always - # outlived by all its constituents. - # Closures don't count: see above. - # Calling functions doesn't count: arguments never outlive - # the function body. - # - # ¹Strings are currently never allocated with a limited lifetime, - # and exceptions can only refer to strings, so we don't actually check - # this property. But we will need to, if string operations are ever added. - - def visit_assignment(self, target, value): - value_region = self._region_of(value) - - # If we assign to an attribute of a quoted value, there will be no names - # in the assignment lhs. - target_names = self._names_of(target) or [] - - # Adopt the value region for any variables declared on the lhs. - for name in target_names: - region = self._region_of(name) - if isinstance(region, Region) and not region.present(): - # Find the name's environment to overwrite the region. - for env in self.env_stack[::-1]: - if name.id in env: - env[name.id] = value_region - break - - # The assigned value should outlive the assignee - target_regions = [self._region_of(name) for name in target_names] - for target_region in target_regions: - if not Region.outlives(value_region, target_region): - diag = diagnostic.Diagnostic("error", - "the assigned value does not outlive the assignment target", {}, - value.loc, [target.loc], - notes=self._diagnostics_for(target_region, target.loc, - "the assignment target") + - self._diagnostics_for(value_region, value.loc, - "the assigned value")) - self.engine.process(diag) - - def visit_Assign(self, node): - for target in node.targets: - self.visit_assignment(target, node.value) - - def visit_AugAssign(self, node): - if builtins.is_list(node.target.type): - note = diagnostic.Diagnostic("note", - "try using `{lhs} = {lhs} {op} {rhs}` instead", - {"lhs": node.target.loc.source(), - "rhs": node.value.loc.source(), - "op": node.op.loc.source()[:-1]}, - node.loc) - diag = diagnostic.Diagnostic("error", - "lists cannot be mutated in-place", {}, - node.op.loc, [node.target.loc], - notes=[note]) - self.engine.process(diag) - - self.visit_assignment(node.target, node.value) - - def visit_Return(self, node): - region = self._region_of(node.value) - if isinstance(region, Region): - note = diagnostic.Diagnostic("note", - "this expression has type {type}", - {"type": types.TypePrinter().name(node.value.type)}, - node.value.loc) - diag = diagnostic.Diagnostic("error", - "cannot return an allocated value that does not live forever", {}, - node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note]) - self.engine.process(diag) diff --git a/artiq/compiler/validators/local_access.py b/artiq/compiler/validators/local_access.py deleted file mode 100644 index 55ed7bd0d..000000000 --- a/artiq/compiler/validators/local_access.py +++ /dev/null @@ -1,187 +0,0 @@ -""" -:class:`LocalAccessValidator` verifies that local variables -are not accessed before being used. -""" - -from functools import reduce -from pythonparser import diagnostic -from .. import ir, analyses - -def is_special_variable(name): - return "$" in name - -class LocalAccessValidator: - def __init__(self, engine): - self.engine = engine - - def process(self, functions): - for func in functions: - self.process_function(func) - - def process_function(self, func): - # Find all environments and closures allocated in this func. - environments, closures = [], [] - for insn in func.instructions(): - if isinstance(insn, ir.Alloc) and ir.is_environment(insn.type): - environments.append(insn) - elif isinstance(insn, ir.Closure): - closures.append(insn) - - # Compute initial state of interesting environments. - # Environments consisting only of internal variables (containing a ".") - # are ignored. - initial_state = {} - for env in environments: - env_state = {var: False for var in env.type.params if "." not in var} - if any(env_state): - initial_state[env] = env_state - - # Traverse the acyclic graph made of basic blocks and forward edges only, - # while updating the environment state. - domtree = analyses.DominatorTree(func) - state = {} - def traverse(block): - # Have we computed the state of this block already? - if block in state: - return state[block] - - # No! Which forward edges lead to this block? - # If we dominate a predecessor, it's a back edge instead. - forward_edge_preds = [pred for pred in block.predecessors() - if block not in domtree.dominators(pred)] - - # Figure out what the state is before the leader - # instruction of this block. - pred_states = [traverse(pred) for pred in forward_edge_preds] - block_state = {} - if len(pred_states) > 1: - for env in initial_state: - # The variable has to be initialized in all predecessors - # in order to be initialized in this block. - def merge_state(a, b): - return {var: a[var] and b[var] for var in a} - block_state[env] = reduce(merge_state, - [state[env] for state in pred_states]) - elif len(pred_states) == 1: - # The state is the same as at the terminator of predecessor. - # We'll mutate it, so copy. - pred_state = pred_states[0] - for env in initial_state: - env_state = pred_state[env] - block_state[env] = {var: env_state[var] for var in env_state} - else: - # This is the entry block. - for env in initial_state: - env_state = initial_state[env] - block_state[env] = {var: env_state[var] for var in env_state} - - # Update the state based on block contents, while validating - # that no access to uninitialized variables will be done. - for insn in block.instructions: - def pred_at_fault(env, var_name): - # Find out where the uninitialized state comes from. - for pred, pred_state in zip(forward_edge_preds, pred_states): - if not pred_state[env][var_name]: - return pred - - # It's the entry block and it was never initialized. - return None - - set_local_in_this_frame = False - if (isinstance(insn, (ir.SetLocal, ir.GetLocal)) and - not is_special_variable(insn.var_name)): - env, var_name = insn.environment(), insn.var_name - - # Make sure that the variable is defined in the scope of this function. - if env in block_state and var_name in block_state[env]: - if isinstance(insn, ir.SetLocal): - # We've just initialized it. - block_state[env][var_name] = True - set_local_in_this_frame = True - else: # isinstance(insn, ir.GetLocal) - if not block_state[env][var_name]: - # Oops, accessing it uninitialized. - self._uninitialized_access(insn, var_name, - pred_at_fault(env, var_name)) - - closures_to_check = [] - - if (isinstance(insn, (ir.SetLocal, ir.SetAttr, ir.SetElem)) and - not set_local_in_this_frame): - # Closures may escape via these mechanisms and be invoked elsewhere. - if isinstance(insn.value(), ir.Closure): - closures_to_check.append(insn.value()) - - if isinstance(insn, (ir.Call, ir.Invoke)): - # We can't always trace the flow of closures from point of - # definition to point of call; however, we know that, by transitiveness - # of this analysis, only closures defined in this function can contain - # uninitialized variables. - # - # Thus, enumerate the closures, and check all of them during any operation - # that may eventually result in the closure being called. - closures_to_check = closures - - for closure in closures_to_check: - env = closure.environment() - # Make sure this environment has any interesting variables. - if env in block_state: - for var_name in block_state[env]: - if not block_state[env][var_name] and not is_special_variable(var_name): - # A closure would capture this variable while it is not always - # initialized. Note that this check is transitive. - self._uninitialized_access(closure, var_name, - pred_at_fault(env, var_name)) - - # Save the state. - state[block] = block_state - - return block_state - - for block in func.basic_blocks: - traverse(block) - - def _uninitialized_access(self, insn, var_name, pred_at_fault): - if pred_at_fault is not None: - visited = set() - possible_preds = [pred_at_fault] - - uninitialized_loc = None - while uninitialized_loc is None: - possible_pred = possible_preds.pop(0) - visited.add(possible_pred) - - for pred_insn in reversed(possible_pred.instructions): - if pred_insn.loc is not None: - uninitialized_loc = pred_insn.loc.begin() - break - - for block in possible_pred.predecessors(): - if block not in visited: - possible_preds.append(block) - - assert uninitialized_loc is not None - - note = diagnostic.Diagnostic("note", - "variable is not initialized when control flows from this point", {}, - uninitialized_loc) - else: - note = None - - if note is not None: - notes = [note] - else: - notes = [] - - if isinstance(insn, ir.Closure): - diag = diagnostic.Diagnostic("error", - "variable '{name}' can be captured in a closure uninitialized here", - {"name": var_name}, - insn.loc, notes=notes) - else: - diag = diagnostic.Diagnostic("error", - "variable '{name}' is not always initialized here", - {"name": var_name}, - insn.loc, notes=notes) - - self.engine.process(diag) diff --git a/artiq/compiler/validators/monomorphism.py b/artiq/compiler/validators/monomorphism.py deleted file mode 100644 index 0911deb40..000000000 --- a/artiq/compiler/validators/monomorphism.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -:class:`MonomorphismValidator` verifies that all type variables have been -elided, which is necessary for code generation. -""" - -from pythonparser import algorithm, diagnostic -from .. import asttyped, types, builtins - -class MonomorphismValidator(algorithm.Visitor): - def __init__(self, engine): - self.engine = engine - - def visit_FunctionDefT(self, node): - super().generic_visit(node) - - return_type = node.signature_type.find().ret - if types.is_polymorphic(return_type): - note = diagnostic.Diagnostic("note", - "the function has return type {type}", - {"type": types.TypePrinter().name(return_type)}, - node.name_loc) - diag = diagnostic.Diagnostic("error", - "the return type of this function cannot be fully inferred", {}, - node.name_loc, notes=[note]) - self.engine.process(diag) - - visit_QuotedFunctionDefT = visit_FunctionDefT - - def generic_visit(self, node): - super().generic_visit(node) - - if isinstance(node, asttyped.commontyped): - if types.is_polymorphic(node.type): - note = diagnostic.Diagnostic("note", - "the expression has type {type}", - {"type": types.TypePrinter().name(node.type)}, - node.loc) - diag = diagnostic.Diagnostic("error", - "the type of this expression cannot be fully inferred", {}, - node.loc, notes=[note]) - self.engine.process(diag) diff --git a/artiq/coredevice/ad53xx.py b/artiq/coredevice/ad53xx.py index f4601a45a..864ebcc22 100644 --- a/artiq/coredevice/ad53xx.py +++ b/artiq/coredevice/ad53xx.py @@ -8,17 +8,19 @@ time results in a collision error. # Designed from the data sheets and somewhat after the linux kernel # iio driver. -from numpy import int32 +from numpy import int32, int64 -from artiq.language.core import (kernel, portable, delay_mu, delay, now_mu, - at_mu) +from artiq.language.core import * from artiq.language.units import ns, us -from artiq.coredevice import spi2 as spi +from artiq.coredevice.core import Core +from artiq.coredevice.ttl import TTLOut +from artiq.coredevice.spi2 import * -SPI_AD53XX_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END | - 0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | - 0*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) + +SPI_AD53XX_CONFIG = (0*SPI_OFFLINE | 1*SPI_END | + 0*SPI_INPUT | 0*SPI_CS_POLARITY | + 0*SPI_CLK_POLARITY | 1*SPI_CLK_PHASE | + 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX) AD53XX_CMD_DATA = 3 << 22 AD53XX_CMD_OFFSET = 2 << 22 @@ -52,7 +54,7 @@ AD53XX_READ_AB3 = 0x109 << 7 @portable -def ad53xx_cmd_write_ch(channel, value, op): +def ad53xx_cmd_write_ch(channel: int32, value: int32, op: int32) -> int32: """Returns the word that must be written to the DAC to set a DAC channel register to a given value. @@ -67,7 +69,7 @@ def ad53xx_cmd_write_ch(channel, value, op): @portable -def ad53xx_cmd_read_ch(channel, op): +def ad53xx_cmd_read_ch(channel: int32, op: int32) -> int32: """Returns the word that must be written to the DAC to read a given DAC channel register. @@ -82,7 +84,7 @@ def ad53xx_cmd_read_ch(channel, op): # maintain function definition for backward compatibility @portable -def voltage_to_mu(voltage, offset_dacs=0x2000, vref=5.): +def voltage_to_mu(voltage: float, offset_dacs: int32 = 0x2000, vref: float = 5.) -> int32: """Returns the 16-bit DAC register value required to produce a given output voltage, assuming offset and gain errors have been trimmed out. @@ -100,22 +102,24 @@ def voltage_to_mu(voltage, offset_dacs=0x2000, vref=5.): :param vref: DAC reference voltage (default: 5.) :return: The 16-bit DAC register value """ - code = int(round((1 << 16) * (voltage / (4. * vref)) + offset_dacs * 0x4)) + code = round(float(1 << 16) * (voltage / (4. * vref))) + offset_dacs * 0x4 if code < 0x0 or code > 0xffff: raise ValueError("Invalid DAC voltage!") return code +@nac3 class _DummyTTL: - @portable + @kernel def on(self): pass - @portable + @kernel def off(self): pass +@nac3 class AD53xx: """Analog devices AD53[67][0123] family of multi-channel Digital to Analog Converters. @@ -137,8 +141,15 @@ class AD53xx: not transferred between experiments. :param core_device: Core device name (default: "core") """ - kernel_invariants = {"bus", "ldac", "clr", "chip_select", "div_write", - "div_read", "vref", "core"} + core: KernelInvariant[Core] + bus: KernelInvariant[SPIMaster] + ldac: KernelInvariant[TTLOut] + clr: KernelInvariant[TTLOut] + chip_select: KernelInvariant[int32] + div_write: KernelInvariant[int32] + div_read: KernelInvariant[int32] + vref: KernelInvariant[float] + offset_dacs: Kernel[int32] def __init__(self, dmgr, spi_device, ldac_device=None, clr_device=None, chip_select=1, div_write=4, div_read=16, vref=5., @@ -161,7 +172,7 @@ class AD53xx: self.core = dmgr.get(core) @kernel - def init(self, blind=False): + def init(self, blind: bool = False): """Configures the SPI bus, drives LDAC and CLR high, programmes the offset DACs, and enables overtemperature shutdown. @@ -177,22 +188,22 @@ class AD53xx: self.chip_select) self.write_offset_dacs_mu(self.offset_dacs) if not blind: - ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL) + ctrl = self.read_reg(0, AD53XX_READ_CONTROL) if ctrl == 0xffff: raise ValueError("DAC not found") - if ctrl & 0b10000: + if (ctrl & 0b10000) != 0: raise ValueError("DAC over temperature") - delay(25*us) + self.core.delay(25.*us) self.bus.write( # enable power and overtemperature shutdown (AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_CONTROL | 0b0010) << 8) if not blind: - ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL) + ctrl = self.read_reg(0, AD53XX_READ_CONTROL) if (ctrl & 0b10111) != 0b00010: raise ValueError("DAC CONTROL readback mismatch") - delay(15*us) + self.core.delay(15.*us) @kernel - def read_reg(self, channel=0, op=AD53XX_READ_X1A): + def read_reg(self, channel: int32 = 0, op: int32 = AD53XX_READ_X1A) -> int32: """Read a DAC register. This method advances the timeline by the duration of two SPI transfers @@ -205,17 +216,16 @@ class AD53xx: :return: The 16-bit register value """ self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8) - self.bus.set_config_mu(SPI_AD53XX_CONFIG | spi.SPI_INPUT, 24, + self.bus.set_config_mu(SPI_AD53XX_CONFIG | SPI_INPUT, 24, self.div_read, self.chip_select) - delay(270*ns) # t_21 min sync high in readback + self.core.delay(270.*ns) # t_21 min sync high in readback self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_NOP) << 8) self.bus.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write, self.chip_select) - # FIXME: the int32 should not be needed to resolve unification - return self.bus.read() & int32(0xffff) + return self.bus.read() & 0xffff @kernel - def write_offset_dacs_mu(self, value): + def write_offset_dacs_mu(self, value: int32): """Program the OFS0 and OFS1 offset DAC registers. Writes to the offset DACs take effect immediately without requiring @@ -230,7 +240,7 @@ class AD53xx: self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_OFS1 | value) << 8) @kernel - def write_gain_mu(self, channel, gain=0xffff): + def write_gain_mu(self, channel: int32, gain: int32 = 0xffff): """Program the gain register for a DAC channel. The DAC output is not updated until LDAC is pulsed (see :meth:`load`). @@ -242,7 +252,7 @@ class AD53xx: ad53xx_cmd_write_ch(channel, gain, AD53XX_CMD_GAIN) << 8) @kernel - def write_offset_mu(self, channel, offset=0x8000): + def write_offset_mu(self, channel: int32, offset: int32 = 0x8000): """Program the offset register for a DAC channel. The DAC output is not updated until LDAC is pulsed (see :meth:`load`). @@ -254,7 +264,7 @@ class AD53xx: ad53xx_cmd_write_ch(channel, offset, AD53XX_CMD_OFFSET) << 8) @kernel - def write_offset(self, channel, voltage): + def write_offset(self, channel: int32, voltage: float): """Program the DAC offset voltage for a channel. An offset of +V can be used to trim out a DAC offset error of -V. @@ -267,7 +277,7 @@ class AD53xx: self.vref)) @kernel - def write_dac_mu(self, channel, value): + def write_dac_mu(self, channel: int32, value: int32): """Program the DAC input register for a channel. The DAC output is not updated until LDAC is pulsed (see :meth:`load`). @@ -277,7 +287,7 @@ class AD53xx: ad53xx_cmd_write_ch(channel, value, AD53XX_CMD_DATA) << 8) @kernel - def write_dac(self, channel, voltage): + def write_dac(self, channel: int32, voltage: float): """Program the DAC output voltage for a channel. The DAC output is not updated until LDAC is pulsed (see :meth:`load`). @@ -299,11 +309,11 @@ class AD53xx: This method advances the timeline by two RTIO clock periods. """ self.ldac.off() - delay_mu(2*self.bus.ref_period_mu) # t13 = 10ns ldac pulse width low + delay_mu(int64(2)*self.bus.ref_period_mu) # t13 = 10ns ldac pulse width low self.ldac.on() @kernel - def set_dac_mu(self, values, channels=list(range(40))): + def set_dac_mu(self, values: list[int32], channels: Option[list[int32]] = none): """Program multiple DAC channels and pulse LDAC to update the DAC outputs. @@ -322,17 +332,18 @@ class AD53xx: t0 = now_mu() # t10: max busy period after writing to DAC registers - t_10 = self.core.seconds_to_mu(1500*ns) + t_10 = self.core.seconds_to_mu(1500.*ns) # compensate all delays that will be applied - delay_mu(-t_10-len(values)*self.bus.xfer_duration_mu) + delay_mu(-t_10-int64(len(values))*self.bus.xfer_duration_mu) + channels_list = channels.unwrap() if channels.is_some() else [i for i in range(40)] for i in range(len(values)): - self.write_dac_mu(channels[i], values[i]) + self.write_dac_mu(channels_list[i], values[i]) delay_mu(t_10) self.load() at_mu(t0) @kernel - def set_dac(self, voltages, channels=list(range(40))): + def set_dac(self, voltages: list[float], channels: Option[list[int32]] = none): """Program multiple DAC channels and pulse LDAC to update the DAC outputs. @@ -351,8 +362,8 @@ class AD53xx: self.set_dac_mu(values, channels) @kernel - def calibrate(self, channel, vzs, vfs): - """ Two-point calibration of a DAC channel. + def calibrate(self, channel: int32, vzs: float, vfs: float): + """Two-point calibration of a DAC channel. Programs the offset and gain register to trim out DAC errors. Does not take effect until LDAC is pulsed (see :meth:`load`). @@ -379,7 +390,7 @@ class AD53xx: self.write_gain_mu(channel, 0xffff-gain_err) @portable - def voltage_to_mu(self, voltage): + def voltage_to_mu(self, voltage: float) -> int32: """Returns the 16-bit DAC register value required to produce a given output voltage, assuming offset and gain errors have been trimmed out. diff --git a/artiq/coredevice/ad9834.py b/artiq/coredevice/ad9834.py index 6166be516..ad1f6456b 100644 --- a/artiq/coredevice/ad9834.py +++ b/artiq/coredevice/ad9834.py @@ -7,10 +7,14 @@ RTIO Driver for the Analog Devices AD9834 DDS via 3-wire SPI interface. from numpy import int32 +<<<<<<< HEAD from artiq.coredevice import spi2 as spi +======= +from artiq.coredevice.core import Core +from artiq.coredevice.spi2 import * +>>>>>>> upstream/nac3 from artiq.experiment import * from artiq.language.core import * -from artiq.language.types import * from artiq.language.units import * AD9834_B28 = 1 << 13 @@ -36,6 +40,7 @@ AD9834_PHASE_REG_1 = AD9834_PHASE_REG | (1 << 13) PHASE_REGS = [AD9834_PHASE_REG_0, AD9834_PHASE_REG_1] +@nac3 class AD9834: """ AD9834 DDS driver. @@ -53,14 +58,18 @@ class AD9834: :param core_device: Core device name (default: "core"). """ - kernel_invariants = {"core", "bus", "spi_freq", "clk_freq"} + core: KernelInvariant[Core] + bus: KernelInvariant[SPIMaster] + spi_freq: KernelInvariant[float] + clk_freq: KernelInvariant[float] + ctrl_reg: Kernel[int32] def __init__( - self, dmgr, spi_device, spi_freq=10 * MHz, clk_freq=75 * MHz, core_device="core" + self, dmgr, spi_device, spi_freq=10. * MHz, clk_freq=75. * MHz, core_device="core" ): self.core = dmgr.get(core_device) self.bus = dmgr.get(spi_device) - assert spi_freq <= 40 * MHz, "SPI frequency exceeds maximum value of 40 MHz" + assert spi_freq <= 40. * MHz, "SPI frequency exceeds maximum value of 40 MHz" self.spi_freq = spi_freq self.clk_freq = clk_freq self.ctrl_reg = 0x0000 # Reset control register @@ -80,7 +89,160 @@ class AD9834: :param data: The 16-bit word to be sent to the AD9834. """ +<<<<<<< HEAD self.bus.write(data << 16) +======= + self.bus.set_config(SPI_CLK_POLARITY | SPI_END, 16, self.spi_freq, 1) + self.enable_reset() + + @kernel + def set_frequency_reg(self, freq_reg: int32, frequency: float): + """ + Set the frequency for the specified frequency register. + + This method calculates the frequency word based on the provided frequency in Hz + and writes it to the specified frequency register. + + :param freq_reg: The frequency register to write to, must be one of + :const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`. + :param frequency: The desired frequency in Hz, which will be converted to a + frequency word suitable for the AD9834. + + The frequency word is calculated using the formula: + + ``freq_word = (frequency * (1 << 28)) / clk_freq`` + + The result is limited to the lower 28 bits for compatibility with the AD9834. + + The method first sets the control register to enable the appropriate settings, + then sends the fourteen least significant bits LSBs and fourteen most significant + bits MSBs in two consecutive writes to the specified frequency register. + """ + # NAC3TODO + #if freq_reg not in FREQ_REGS: + # raise ValueError("Invalid frequency register") + assert frequency <= 37.5 * MHz, "Frequency exceeds maximum value of 37.5 MHz" + freq_word = int32((frequency * float(1 << 28)) / self.clk_freq) & 0x0FFFFFFF + self.ctrl_reg |= AD9834_B28 + self.write(self.ctrl_reg) + lsb = freq_word & 0x3FFF + msb = (freq_word >> 14) & 0x3FFF + self.write(freq_reg | lsb) + self.write(freq_reg | msb) + + @kernel + def set_frequency_reg_msb(self, freq_reg: int32, word: int32): + """ + Set the fourteen most significant bits MSBs of the specified frequency register. + + This method updates the specified frequency register with the provided MSB value. + It configures the control register to indicate that the MSB is being set. + + :param freq_reg: The frequency register to update, must be one of + :const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`. + :param word: The value to be written to the fourteen MSBs of the frequency register. + + The method first clears the appropriate control bits, sets :const:`AD9834_HLB` to + indicate that the MSB is being sent, and then writes the updated control register + followed by the MSB value to the specified frequency register. + """ + # NAC3TODO + #if freq_reg not in FREQ_REGS: + # raise ValueError("Invalid frequency register") + self.ctrl_reg &= ~AD9834_B28 + self.ctrl_reg |= AD9834_HLB + self.write(self.ctrl_reg) + self.write(freq_reg | (word & 0x3FFF)) + + @kernel + def set_frequency_reg_lsb(self, freq_reg: int32, word: int32): + """ + Set the fourteen least significant bits LSBs of the specified frequency register. + + This method updates the specified frequency register with the provided LSB value. + It configures the control register to indicate that the LSB is being set. + + :param freq_reg: The frequency register to update, must be one of + :const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`. + :param word: The value to be written to the fourteen LSBs of the frequency register. + + The method first clears the appropriate control bits and writes the updated control + register followed by the LSB value to the specified frequency register. + """ + # NAC3TODO + #if freq_reg not in FREQ_REGS: + # raise ValueError("Invalid frequency register") + self.ctrl_reg &= ~AD9834_B28 + self.ctrl_reg &= ~AD9834_HLB + self.write(self.ctrl_reg) + self.write(freq_reg | (word & 0x3FFF)) + + @kernel + def select_frequency_reg(self, freq_reg: int32): + """ + Select the active frequency register for the phase accumulator. + + This method chooses between the two available frequency registers in the AD9834 to + control the frequency of the output waveform. The control register is updated + to reflect the selected frequency register. + + :param freq_reg: The frequency register to select. Must be one of + :const:`AD9834_FREQ_REG_0` or :const:`AD9834_FREQ_REG_1`. + """ + # NAC3TODO + #if freq_reg not in FREQ_REGS: + # raise ValueError("Invalid frequency register") + if freq_reg == FREQ_REGS[0]: + self.ctrl_reg &= ~AD9834_FSEL + else: + self.ctrl_reg |= AD9834_FSEL + + self.ctrl_reg &= ~AD9834_PIN_SW + self.write(self.ctrl_reg) + + @kernel + def set_phase_reg(self, phase_reg: int32, phase: int32): + """ + Set the phase for the specified phase register. + + This method updates the specified phase register with the provided phase value. + + :param phase_reg: The phase register to update, must be one of + :const:`AD9834_PHASE_REG_0` or :const:`AD9834_PHASE_REG_1`. + :param phase: The value to be written to the phase register. + + The method masks the phase value to ensure it fits within the 12-bit limit + and writes it to the specified phase register. + """ + # NAC3TODO + #if phase_reg not in PHASE_REGS: + # raise ValueError("Invalid phase register") + phase_word = phase & 0x0FFF + self.write(phase_reg | phase_word) + + @kernel + def select_phase_reg(self, phase_reg: int32): + """ + Select the active phase register for the phase accumulator. + + This method chooses between the two available phase registers in the AD9834 to + control the phase of the output waveform. The control register is updated + to reflect the selected phase register. + + :param phase_reg: The phase register to select. Must be one of + :const:`AD9834_PHASE_REG_0` or :const:`AD9834_PHASE_REG_1`. + """ + # NAC3TODO + #if phase_reg not in PHASE_REGS: + # raise ValueError("Invalid phase register") + if phase_reg == PHASE_REGS[0]: + self.ctrl_reg &= ~AD9834_PSEL + else: + self.ctrl_reg |= AD9834_PSEL + + self.ctrl_reg &= ~AD9834_PIN_SW + self.write(self.ctrl_reg) +>>>>>>> upstream/nac3 @kernel def enable_reset(self): @@ -382,6 +544,7 @@ class AD9834: self.write(self.ctrl_reg) @kernel +<<<<<<< HEAD def set_mu( self, freq_word: TInt32 = 0, @@ -389,6 +552,9 @@ class AD9834: freq_reg: TInt32 = 0, phase_reg: TInt32 = 0, ): +======= + def write(self, data: int32): +>>>>>>> upstream/nac3 """ Set DDS frequency and phase in machine units. diff --git a/artiq/coredevice/ad9910.py b/artiq/coredevice/ad9910.py index fa0640321..2ef0e240e 100644 --- a/artiq/coredevice/ad9910.py +++ b/artiq/coredevice/ad9910.py @@ -1,17 +1,13 @@ from numpy import int32, int64 -from artiq.language.core import ( - kernel, delay, portable, delay_mu, now_mu, at_mu) +from artiq.language.core import * from artiq.language.units import us, ms -from artiq.language.types import TBool, TInt32, TInt64, TFloat, TList, TTuple -from artiq.coredevice import spi2 as spi -from artiq.coredevice import urukul -from artiq.coredevice.urukul import DEFAULT_PROFILE +from artiq.coredevice.spi2 import * +from artiq.coredevice.urukul import * +from artiq.coredevice.ttl import TTLOut +from artiq.coredevice.kasli_i2c import KasliEEPROM # NAC3TODO -# Work around ARTIQ-Python import machinery -urukul_sta_pll_lock = urukul.urukul_sta_pll_lock -urukul_sta_smp_err = urukul.urukul_sta_smp_err __all__ = [ "AD9910", @@ -64,8 +60,12 @@ RAM_MODE_CONT_RAMPUP = 4 # Default profile for RAM mode _DEFAULT_PROFILE_RAM = 0 - +@nac3 class SyncDataUser: + core: KernelInvariant[Core] + sync_delay_seed: Kernel[int32] + io_update_delay: Kernel[int32] + def __init__(self, core, sync_delay_seed, io_update_delay): self.core = core self.sync_delay_seed = sync_delay_seed @@ -76,7 +76,14 @@ class SyncDataUser: pass +@nac3 class SyncDataEeprom: + core: KernelInvariant[Core] + eeprom_device: KernelInvariant[KasliEEPROM] # NAC3TODO support generic EEPROM driver + eeprom_offset: KernelInvariant[int32] + sync_delay_seed: Kernel[int32] + io_update_delay: Kernel[int32] + def __init__(self, dmgr, core, eeprom_str): self.core = core @@ -102,6 +109,7 @@ class SyncDataEeprom: self.io_update_delay = int32(io_update_delay) +@nac3 class AD9910: """ AD9910 DDS channel on Urukul. @@ -119,7 +127,7 @@ class AD9910: ``f_ref / clk_div * pll_n`` where ``f_ref`` is the reference frequency and ``clk_div`` is the reference clock divider (both set in the parent Urukul CPLD instance). - :param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1). + :param pll_en: PLL enable bit, set to False to bypass PLL (default: True). Note that when bypassing the PLL the red front panel LED may remain on. :param pll_cp: DDS PLL charge pump setting. :param pll_vco: DDS PLL VCO range selection. @@ -138,9 +146,24 @@ class AD9910: to the same string value. """ + core: KernelInvariant[Core] + cpld: KernelInvariant[CPLD] + bus: KernelInvariant[SPIMaster] + chip_select: KernelInvariant[int32] + pll_en: KernelInvariant[bool] + pll_n: KernelInvariant[int32] + pll_vco: KernelInvariant[int32] + pll_cp: KernelInvariant[int32] + ftw_per_hz: KernelInvariant[float] + sysclk_per_mu: KernelInvariant[int32] + sysclk: KernelInvariant[float] + sw: KernelInvariant[Option[TTLOut]] + sync_data: KernelInvariant[SyncDataUser] + phase_mode: Kernel[int32] + def __init__(self, dmgr, chip_select, cpld_device, sw_device=None, pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1, - io_update_delay=0, pll_en=1): + io_update_delay=0, pll_en=True): self.kernel_invariants = {"cpld", "core", "bus", "chip_select", "pll_en", "pll_n", "pll_vco", "pll_cp", "ftw_per_hz", "sysclk_per_mu", "sysclk", @@ -151,8 +174,9 @@ class AD9910: assert 3 <= chip_select <= 7 self.chip_select = chip_select if sw_device: - self.sw = dmgr.get(sw_device) - self.kernel_invariants.add("sw") + self.sw = Some(dmgr.get(sw_device)) + else: + self.sw = none clk = self.cpld.refclk / [4, 1, 2, 4][self.cpld.clk_div] self.pll_en = pll_en self.pll_n = pll_n @@ -174,6 +198,7 @@ class AD9910: self.sysclk_per_mu = int(round(sysclk * self.core.ref_period)) self.sysclk = sysclk + # NAC3TODO if isinstance(sync_delay_seed, str) or isinstance(io_update_delay, str): if sync_delay_seed != io_update_delay: @@ -187,7 +212,7 @@ class AD9910: self.phase_mode = PHASE_MODE_CONTINUOUS @kernel - def set_phase_mode(self, phase_mode: TInt32): + def set_phase_mode(self, phase_mode: int32): r"""Set the default phase mode for future calls to :meth:`set` and :meth:`set_mu`. Supported phase modes are: @@ -230,103 +255,103 @@ class AD9910: self.phase_mode = phase_mode @kernel - def write16(self, addr: TInt32, data: TInt32): + def write16(self, addr: int32, data: int32): """Write to 16-bit register. :param addr: Register address :param data: Data to be written """ - self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 24, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24, + SPIT_DDS_WR, self.chip_select) self.bus.write((addr << 24) | ((data & 0xffff) << 8)) @kernel - def write32(self, addr: TInt32, data: TInt32): + def write32(self, addr: int32, data: int32): """Write to 32-bit register. :param addr: Register address :param data: Data to be written """ - self.bus.set_config_mu(urukul.SPI_CONFIG, 8, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG, 8, + SPIT_DDS_WR, self.chip_select) self.bus.write(addr << 24) - self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32, + SPIT_DDS_WR, self.chip_select) self.bus.write(data) @kernel - def read16(self, addr: TInt32) -> TInt32: + def read16(self, addr: int32) -> int32: """Read from 16-bit register. :param addr: Register address """ - self.bus.set_config_mu(urukul.SPI_CONFIG, 8, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG, 8, + SPIT_DDS_WR, self.chip_select) self.bus.write((addr | 0x80) << 24) self.bus.set_config_mu( - urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, - 16, urukul.SPIT_DDS_RD, self.chip_select) + SPI_CONFIG | SPI_END | SPI_INPUT, + 16, SPIT_DDS_RD, self.chip_select) self.bus.write(0) return self.bus.read() @kernel - def read32(self, addr: TInt32) -> TInt32: + def read32(self, addr: int32) -> int32: """Read from 32-bit register. :param addr: Register address """ - self.bus.set_config_mu(urukul.SPI_CONFIG, 8, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG, 8, + SPIT_DDS_WR, self.chip_select) self.bus.write((addr | 0x80) << 24) self.bus.set_config_mu( - urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, - 32, urukul.SPIT_DDS_RD, self.chip_select) + SPI_CONFIG | SPI_END | SPI_INPUT, + 32, SPIT_DDS_RD, self.chip_select) self.bus.write(0) return self.bus.read() @kernel - def read64(self, addr: TInt32) -> TInt64: + def read64(self, addr: int32) -> int64: """Read from 64-bit register. :param addr: Register address :return: 64-bit integer register value """ self.bus.set_config_mu( - urukul.SPI_CONFIG, 8, - urukul.SPIT_DDS_WR, self.chip_select) + SPI_CONFIG, 8, + SPIT_DDS_WR, self.chip_select) self.bus.write((addr | 0x80) << 24) self.bus.set_config_mu( - urukul.SPI_CONFIG | spi.SPI_INPUT, 32, - urukul.SPIT_DDS_RD, self.chip_select) + SPI_CONFIG | SPI_INPUT, 32, + SPIT_DDS_RD, self.chip_select) self.bus.write(0) self.bus.set_config_mu( - urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 32, - urukul.SPIT_DDS_RD, self.chip_select) + SPI_CONFIG | SPI_END | SPI_INPUT, 32, + SPIT_DDS_RD, self.chip_select) self.bus.write(0) hi = self.bus.read() lo = self.bus.read() - return (int64(hi) << 32) | lo + return (int64(hi) << 32) | int64(lo) @kernel - def write64(self, addr: TInt32, data_high: TInt32, data_low: TInt32): + def write64(self, addr: int32, data_high: int32, data_low: int32): """Write to 64-bit register. :param addr: Register address :param data_high: High (MSB) 32 data bits :param data_low: Low (LSB) 32 data bits """ - self.bus.set_config_mu(urukul.SPI_CONFIG, 8, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG, 8, + SPIT_DDS_WR, self.chip_select) self.bus.write(addr << 24) - self.bus.set_config_mu(urukul.SPI_CONFIG, 32, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG, 32, + SPIT_DDS_WR, self.chip_select) self.bus.write(data_high) - self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32, + SPIT_DDS_WR, self.chip_select) self.bus.write(data_low) @kernel - def write_ram(self, data: TList(TInt32)): + def write_ram(self, data: list[int32]): """Write data to RAM. The profile to write to and the step, start, and end address @@ -336,19 +361,19 @@ class AD9910: :param data: Data to be written to RAM. """ - self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, + self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_DDS_WR, self.chip_select) self.bus.write(_AD9910_REG_RAM << 24) - self.bus.set_config_mu(urukul.SPI_CONFIG, 32, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG, 32, + SPIT_DDS_WR, self.chip_select) for i in range(len(data) - 1): self.bus.write(data[i]) - self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32, + SPIT_DDS_WR, self.chip_select) self.bus.write(data[len(data) - 1]) @kernel - def read_ram(self, data: TList(TInt32)): + def read_ram(self, data: list[int32]): """Read data from RAM. The profile to read from and the step, start, and end address @@ -358,38 +383,38 @@ class AD9910: :param data: List to be filled with data read from RAM. """ - self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR, + self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_DDS_WR, self.chip_select) self.bus.write((_AD9910_REG_RAM | 0x80) << 24) n = len(data) - 1 if n > 0: - self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_INPUT, 32, - urukul.SPIT_DDS_RD, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG | SPI_INPUT, 32, + SPIT_DDS_RD, self.chip_select) preload = min(n, 8) for i in range(n): self.bus.write(0) if i >= preload: data[i - preload] = self.bus.read() self.bus.set_config_mu( - urukul.SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 32, - urukul.SPIT_DDS_RD, self.chip_select) + SPI_CONFIG | SPI_INPUT | SPI_END, 32, + SPIT_DDS_RD, self.chip_select) self.bus.write(0) for i in range(preload + 1): data[(n - preload) + i] = self.bus.read() @kernel def set_cfr1(self, - power_down: TInt32 = 0b0000, - phase_autoclear: 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): + power_down: int32 = 0b0000, + phase_autoclear: int32 = 0, + drg_load_lrr: int32 = 0, + drg_autoclear: int32 = 0, + phase_clear: int32 = 0, + internal_profile: int32 = 0, + ram_destination: int32 = 0, + ram_enable: int32 = 0, + manual_osk_external: int32 = 0, + osk_enable: int32 = 0, + select_auto_osk: int32 = 0): """Set CFR1. See the AD9910 datasheet for parameter meanings and sizes. This method does not pulse ``IO_UPDATE.`` @@ -424,11 +449,11 @@ class AD9910: @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): + asf_profile_enable: int32 = 1, + drg_enable: int32 = 0, + effective_ftw: int32 = 1, + sync_validation_disable: int32 = 0, + matched_latency_enable: int32 = 0): """Set CFR2. See the AD9910 datasheet for parameter meanings and sizes. This method does not pulse ``IO_UPDATE``. @@ -452,7 +477,7 @@ class AD9910: (sync_validation_disable << 5)) @kernel - def init(self, blind: TBool = False): + def init(self, blind: bool = False): """Initialize and configure the DDS. Sets up SPI mode, confirms chip presence, powers down unused blocks, @@ -465,66 +490,66 @@ class AD9910: if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div: raise ValueError("parent CPLD does not drive SYNC") if self.sync_data.sync_delay_seed >= 0: - if self.sysclk_per_mu != self.sysclk * self.core.ref_period: + if float(self.sysclk_per_mu) != self.sysclk * self.core.ref_period: raise ValueError("incorrect clock ratio for synchronization") - delay(50 * ms) # slack + self.core.delay(50. * ms) # slack # Set SPI mode self.set_cfr1() - self.cpld.io_update.pulse(1 * us) - delay(1 * ms) + self.cpld.io_update.pulse(1. * us) + self.core.delay(1. * ms) if not blind: # Use the AUX DAC setting to identify and confirm presence aux_dac = self.read32(_AD9910_REG_AUX_DAC) if aux_dac & 0xff != 0x7f: raise ValueError("Urukul AD9910 AUX_DAC mismatch") - delay(50 * us) # slack + self.core.delay(50. * us) # slack # Configure PLL settings and bring up PLL # enable amplitude scale from profiles # read effective FTW # sync timing validation disable (enabled later) self.set_cfr2(sync_validation_disable=1) - self.cpld.io_update.pulse(1 * us) + self.cpld.io_update.pulse(1. * us) cfr3 = (0x0807c000 | (self.pll_vco << 24) | - (self.pll_cp << 19) | (self.pll_en << 8) | - (self.pll_n << 1)) + (self.pll_cp << 19) | (int32(self.pll_en) << 8) | + (int32(self.pll_n) << 1)) self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset - self.cpld.io_update.pulse(1 * us) + self.cpld.io_update.pulse(1. * us) if self.pll_en: self.write32(_AD9910_REG_CFR3, cfr3) - self.cpld.io_update.pulse(1 * us) + self.cpld.io_update.pulse(1. * us) if blind: - delay(100 * ms) + self.core.delay(100. * ms) else: # Wait for PLL lock, up to 100 ms for i in range(100): sta = self.cpld.sta_read() lock = urukul_sta_pll_lock(sta) - delay(1 * ms) - if lock & (1 << self.chip_select - 4): + self.core.delay(1. * ms) + if lock & (1 << self.chip_select - 4) != 0: break if i >= 100 - 1: raise ValueError("PLL lock timeout") - delay(10 * us) # slack + self.core.delay(10. * us) # slack if self.sync_data.sync_delay_seed >= 0 and not blind: self.tune_sync_delay(self.sync_data.sync_delay_seed) - delay(1 * ms) + self.core.delay(1. * ms) @kernel - def power_down(self, bits: TInt32 = 0b1111): + def power_down(self, bits: int32 = 0b1111): """Power down DDS. :param bits: Power-down bits, see datasheet """ self.set_cfr1(power_down=bits) - self.cpld.io_update.pulse(1 * us) + self.cpld.io_update.pulse(1. * us) @kernel - def set_mu(self, ftw: TInt32 = 0, pow_: TInt32 = 0, asf: TInt32 = 0x3fff, - phase_mode: TInt32 = _PHASE_MODE_DEFAULT, - ref_time_mu: TInt64 = int64(-1), - profile: TInt32 = DEFAULT_PROFILE, - ram_destination: TInt32 = -1) -> TInt32: + def set_mu(self, ftw: int32 = 0, pow_: int32 = 0, asf: int32 = 0x3fff, + phase_mode: int32 = _PHASE_MODE_DEFAULT, + ref_time_mu: int64 = int64(-1), + profile: int32 = DEFAULT_PROFILE, + ram_destination: int32 = -1) -> int32: """Set DDS data in machine units. This uses machine units (FTW, POW, ASF). The frequency tuning word @@ -567,15 +592,15 @@ class AD9910: phase_mode = self.phase_mode # Align to coarse RTIO which aligns SYNC_CLK. I.e. clear fine TSC # This will not cause a collision or sequence error. - at_mu(now_mu() & ~7) + at_mu(now_mu() & ~int64(7)) if phase_mode != PHASE_MODE_CONTINUOUS: # Auto-clear phase accumulator on IO_UPDATE. # This is active already for the next IO_UPDATE self.set_cfr1(phase_autoclear=1) - if phase_mode == PHASE_MODE_TRACKING and ref_time_mu < 0: + if phase_mode == PHASE_MODE_TRACKING and ref_time_mu < int64(0): # set default fiducial time stamp - ref_time_mu = 0 - if ref_time_mu >= 0: + ref_time_mu = int64(0) + if ref_time_mu >= int64(0): # 32 LSB are sufficient. # Also no need to use IO_UPDATE time as this # is equivalent to an output pipeline latency. @@ -593,16 +618,16 @@ class AD9910: if not ram_destination == RAM_DEST_POW: self.set_pow(pow_) delay_mu(int64(self.sync_data.io_update_delay)) - self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYN_CCLK - at_mu(now_mu() & ~7) # clear fine TSC again + self.cpld.io_update.pulse_mu(int64(8)) # assumes 8 mu > t_SYN_CCLK + at_mu(now_mu() & ~int64(7)) # clear fine TSC again if phase_mode != PHASE_MODE_CONTINUOUS: self.set_cfr1() # future IO_UPDATE will activate return pow_ @kernel - def get_mu(self, profile: TInt32 = DEFAULT_PROFILE - ) -> TTuple([TInt32, TInt32, TInt32]): + def get_mu(self, profile: int32 = DEFAULT_PROFILE + ) -> tuple[int32, int32, int32]: """Get the frequency tuning word, phase offset word, and amplitude scale factor. @@ -616,15 +641,15 @@ class AD9910: data = int64(self.read64(_AD9910_REG_PROFILE0 + profile)) # Extract and return fields ftw = int32(data) - pow_ = int32((data >> 32) & 0xffff) - asf = int32((data >> 48) & 0x3fff) + pow_ = int32(data >> 32) & 0xffff + asf = int32(data >> 48) & 0x3fff return ftw, pow_, asf @kernel - def set_profile_ram(self, start: TInt32, end: TInt32, step: TInt32 = 1, - profile: TInt32 = _DEFAULT_PROFILE_RAM, - nodwell_high: TInt32 = 0, zero_crossing: TInt32 = 0, - mode: TInt32 = 1): + def set_profile_ram(self, start: int32, end: int32, step: int32 = 1, + profile: int32 = _DEFAULT_PROFILE_RAM, + nodwell_high: int32 = 0, zero_crossing: int32 = 0, + mode: int32 = 1): """Set the RAM profile settings. See also AD9910 datasheet. :param start: Profile start address in RAM (10-bit). @@ -648,7 +673,7 @@ class AD9910: self.write64(_AD9910_REG_PROFILE0 + profile, hi, lo) @kernel - def set_ftw(self, ftw: TInt32): + def set_ftw(self, ftw: int32): """Set the value stored to the AD9910's frequency tuning word (FTW) register. @@ -657,7 +682,7 @@ class AD9910: self.write32(_AD9910_REG_FTW, ftw) @kernel - def set_asf(self, asf: TInt32): + def set_asf(self, asf: int32): """Set the value stored to the AD9910's amplitude scale factor (ASF) register. @@ -666,7 +691,7 @@ class AD9910: self.write32(_AD9910_REG_ASF, asf << 2) @kernel - def set_pow(self, pow_: TInt32): + def set_pow(self, pow_: int32): """Set the value stored to the AD9910's phase offset word (POW) register. @@ -675,7 +700,7 @@ class AD9910: self.write16(_AD9910_REG_POW, pow_) @kernel - def get_ftw(self) -> TInt32: + def get_ftw(self) -> int32: """Get the value stored to the AD9910's frequency tuning word (FTW) register. @@ -684,7 +709,7 @@ class AD9910: return self.read32(_AD9910_REG_FTW) @kernel - def get_asf(self) -> TInt32: + def get_asf(self) -> int32: """Get the value stored to the AD9910's amplitude scale factor (ASF) register. @@ -693,7 +718,7 @@ class AD9910: return self.read32(_AD9910_REG_ASF) >> 2 @kernel - def get_pow(self) -> TInt32: + def get_pow(self) -> int32: """Get the value stored to the AD9910's phase offset word (POW) register. @@ -701,49 +726,49 @@ class AD9910: """ return self.read16(_AD9910_REG_POW) - @portable(flags={"fast-math"}) - def frequency_to_ftw(self, frequency: TFloat) -> TInt32: + @portable + def frequency_to_ftw(self, frequency: float) -> int32: """Return the 32-bit frequency tuning word corresponding to the given frequency. """ return int32(round(self.ftw_per_hz * frequency)) - @portable(flags={"fast-math"}) - def ftw_to_frequency(self, ftw: TInt32) -> TFloat: + @portable + def ftw_to_frequency(self, ftw: int32) -> float: """Return the frequency corresponding to the given frequency tuning word. """ - return ftw / self.ftw_per_hz + return float(ftw) / self.ftw_per_hz - @portable(flags={"fast-math"}) - def turns_to_pow(self, turns: TFloat) -> TInt32: + @portable + def turns_to_pow(self, turns: float) -> int32: """Return the 16-bit phase offset word corresponding to the given phase in turns.""" - return int32(round(turns * 0x10000)) & int32(0xffff) + return round(turns * float(0x10000)) & 0xffff - @portable(flags={"fast-math"}) - def pow_to_turns(self, pow_: TInt32) -> TFloat: + @portable + def pow_to_turns(self, pow_: int32) -> float: """Return the phase in turns corresponding to a given phase offset word.""" return pow_ / 0x10000 - @portable(flags={"fast-math"}) - def amplitude_to_asf(self, amplitude: TFloat) -> TInt32: + @portable + def amplitude_to_asf(self, amplitude: float) -> int32: """Return 14-bit amplitude scale factor corresponding to given fractional amplitude.""" - code = int32(round(amplitude * 0x3fff)) + code = round(amplitude * float(0x3fff)) if code < 0 or code > 0x3fff: raise ValueError("Invalid AD9910 fractional amplitude!") return code - @portable(flags={"fast-math"}) - def asf_to_amplitude(self, asf: TInt32) -> TFloat: + @portable + def asf_to_amplitude(self, asf: int32) -> float: """Return amplitude as a fraction of full scale corresponding to given amplitude scale factor.""" - return asf / float(0x3fff) + return float(asf) / float(0x3fff) - @portable(flags={"fast-math"}) - def frequency_to_ram(self, frequency: TList(TFloat), ram: TList(TInt32)): + @portable + def frequency_to_ram(self, frequency: list[float], ram: list[int32]): """Convert frequency values to RAM profile data. To be used with :const:`RAM_DEST_FTW`. @@ -755,8 +780,8 @@ class AD9910: for i in range(len(ram)): ram[i] = self.frequency_to_ftw(frequency[i]) - @portable(flags={"fast-math"}) - def turns_to_ram(self, turns: TList(TFloat), ram: TList(TInt32)): + @portable + def turns_to_ram(self, turns: list[float], ram: list[int32]): """Convert phase values to RAM profile data. To be used with :const:`RAM_DEST_POW`. @@ -768,8 +793,8 @@ class AD9910: for i in range(len(ram)): ram[i] = self.turns_to_pow(turns[i]) << 16 - @portable(flags={"fast-math"}) - def amplitude_to_ram(self, amplitude: TList(TFloat), ram: TList(TInt32)): + @portable + def amplitude_to_ram(self, amplitude: list[float], ram: list[int32]): """Convert amplitude values to RAM profile data. To be used with :const:`RAM_DEST_ASF`. @@ -781,9 +806,9 @@ class AD9910: for i in range(len(ram)): ram[i] = self.amplitude_to_asf(amplitude[i]) << 18 - @portable(flags={"fast-math"}) - def turns_amplitude_to_ram(self, turns: TList(TFloat), - amplitude: TList(TFloat), ram: TList(TInt32)): + @portable + def turns_amplitude_to_ram(self, turns: list[float], + amplitude: list[float], ram: list[int32]): """Convert phase and amplitude values to RAM profile data. To be used with :const:`RAM_DEST_POWASF`. @@ -798,7 +823,7 @@ class AD9910: self.amplitude_to_asf(amplitude[i]) << 2) @kernel - def set_frequency(self, frequency: TFloat): + def set_frequency(self, frequency: float): """Set the value stored to the AD9910's frequency tuning word (FTW) register. @@ -807,7 +832,7 @@ class AD9910: self.set_ftw(self.frequency_to_ftw(frequency)) @kernel - def set_amplitude(self, amplitude: TFloat): + def set_amplitude(self, amplitude: float): """Set the value stored to the AD9910's amplitude scale factor (ASF) register. @@ -816,7 +841,7 @@ class AD9910: self.set_asf(self.amplitude_to_asf(amplitude)) @kernel - def set_phase(self, turns: TFloat): + def set_phase(self, turns: float): """Set the value stored to the AD9910's phase offset word (POW) register. @@ -825,7 +850,7 @@ class AD9910: self.set_pow(self.turns_to_pow(turns)) @kernel - def get_frequency(self) -> TFloat: + def get_frequency(self) -> float: """Get the value stored to the AD9910's frequency tuning word (FTW) register. @@ -834,7 +859,7 @@ class AD9910: return self.ftw_to_frequency(self.get_ftw()) @kernel - def get_amplitude(self) -> TFloat: + def get_amplitude(self) -> float: """Get the value stored to the AD9910's amplitude scale factor (ASF) register. @@ -843,7 +868,7 @@ class AD9910: return self.asf_to_amplitude(self.get_asf()) @kernel - def get_phase(self) -> TFloat: + def get_phase(self) -> float: """Get the value stored to the AD9910's phase offset word (POW) register. @@ -852,10 +877,10 @@ class AD9910: return self.pow_to_turns(self.get_pow()) @kernel - def set(self, frequency: TFloat = 0.0, phase: TFloat = 0.0, - amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT, - ref_time_mu: TInt64 = int64(-1), profile: TInt32 = DEFAULT_PROFILE, - ram_destination: TInt32 = -1) -> TFloat: + def set(self, frequency: float = 0.0, phase: float = 0.0, + amplitude: float = 1.0, phase_mode: int32 = _PHASE_MODE_DEFAULT, + ref_time_mu: int64 = int64(-1), profile: int32 = DEFAULT_PROFILE, + ram_destination: int32 = -1) -> float: """Set DDS data in SI units. See also :meth:`AD9910.set_mu`. @@ -875,8 +900,8 @@ class AD9910: profile, ram_destination)) @kernel - def get(self, profile: TInt32 = DEFAULT_PROFILE - ) -> TTuple([TFloat, TFloat, TFloat]): + def get(self, profile: int32 = DEFAULT_PROFILE + ) -> tuple[float, float, float]: """Get the frequency, phase, and amplitude. See also :meth:`AD9910.get_mu`. @@ -892,7 +917,7 @@ class AD9910: self.asf_to_amplitude(asf)) @kernel - def set_att_mu(self, att: TInt32): + def set_att_mu(self, att: int32): """Set digital step attenuator in machine units. This method will write the attenuator settings of all four channels. See also @@ -903,7 +928,7 @@ class AD9910: self.cpld.set_att_mu(self.chip_select - 4, att) @kernel - def set_att(self, att: TFloat): + def set_att(self, att: float): """Set digital step attenuator in SI units. This method will write the attenuator settings of all four channels. See also @@ -914,7 +939,7 @@ class AD9910: self.cpld.set_att(self.chip_select - 4, att) @kernel - def get_att_mu(self) -> TInt32: + def get_att_mu(self) -> int32: """Get digital step attenuator value in machine units. See also :meth:`CPLD.get_channel_att `. @@ -923,7 +948,7 @@ class AD9910: return self.cpld.get_channel_att_mu(self.chip_select - 4) @kernel - def get_att(self) -> TFloat: + def get_att(self) -> float: """Get digital step attenuator value in SI units. See also :meth:`CPLD.get_channel_att `. @@ -932,7 +957,7 @@ class AD9910: return self.cpld.get_channel_att(self.chip_select - 4) @kernel - def cfg_sw(self, state: TBool): + def cfg_sw(self, state: bool): """Set CPLD CFG RF switch state. The RF switch is controlled by the logical or of the CPLD configuration shift register RF switch bit and the SW TTL line (if used). @@ -943,9 +968,9 @@ class AD9910: @kernel def set_sync(self, - in_delay: TInt32, - window: TInt32, - en_sync_gen: TInt32 = 0): + in_delay: int32, + window: int32, + en_sync_gen: int32 = 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 @@ -978,14 +1003,14 @@ class AD9910: Also modifies CFR2. """ self.set_cfr2(sync_validation_disable=1) # clear SMP_ERR - self.cpld.io_update.pulse(1 * us) - delay(10 * us) # slack + self.cpld.io_update.pulse(1. * us) + self.core.delay(10. * us) # slack self.set_cfr2(sync_validation_disable=0) # enable SMP_ERR - self.cpld.io_update.pulse(1 * us) + self.cpld.io_update.pulse(1. * us) @kernel def tune_sync_delay(self, - search_seed: TInt32 = 15) -> TTuple([TInt32, TInt32]): + search_seed: int32 = 15) -> tuple[int32, int32]: """Find a stable ``SYNC_IN`` delay. This method first locates a valid ``SYNC_IN`` delay at zero validation @@ -1012,7 +1037,7 @@ class AD9910: next_seed = -1 for in_delay in range(search_span - 2 * window): # alternate search direction around search_seed - if in_delay & 1: + if in_delay & 1 != 0: in_delay = -in_delay in_delay = search_seed + (in_delay >> 1) if in_delay < 0 or in_delay > 31: @@ -1020,9 +1045,9 @@ class AD9910: self.set_sync(in_delay, window) self.clear_smp_err() # integrate SMP_ERR statistics for a few hundred cycles - delay(100 * us) + self.core.delay(100. * us) err = urukul_sta_smp_err(self.cpld.sta_read()) - delay(100 * us) # slack + self.core.delay(100. * us) # slack if not (err >> (self.chip_select - 4)) & 1: next_seed = in_delay break @@ -1034,15 +1059,15 @@ class AD9910: window = max(min_window, window - 1 - margin) self.set_sync(search_seed, window) self.clear_smp_err() - delay(100 * us) # slack + self.core.delay(100. * us) # slack return search_seed, window else: break raise ValueError("no valid window/delay") @kernel - def measure_io_update_alignment(self, delay_start: TInt64, - delay_stop: TInt64) -> TInt32: + def measure_io_update_alignment(self, delay_start: int64, + delay_stop: int64) -> int32: """Use the digital ramp generator to locate the alignment between ``IO_UPDATE`` and ``SYNC_CLK``. @@ -1066,25 +1091,25 @@ class AD9910: # dFTW = 1, (work around negative slope) self.write64(_AD9910_REG_RAMP_STEP, -1, 0) # delay io_update after RTIO edge - t = now_mu() + 8 & ~7 + t = now_mu() + int64(8) & ~int64(7) at_mu(t + delay_start) # assumes a maximum t_SYNC_CLK period - self.cpld.io_update.pulse_mu(16 - delay_start) # realign + self.cpld.io_update.pulse_mu(int64(16) - delay_start) # realign # disable DRG autoclear and LRR on io_update self.set_cfr1() # stop DRG self.write64(_AD9910_REG_RAMP_STEP, 0, 0) - at_mu(t + 0x1000 + delay_stop) - self.cpld.io_update.pulse_mu(16 - delay_stop) # realign + at_mu(t + int64(0x1000) + delay_stop) + self.cpld.io_update.pulse_mu(int64(16) - delay_stop) # realign ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW - delay(100 * us) # slack + self.core.delay(100. * us) # slack # disable DRG self.set_cfr2(drg_enable=0) - self.cpld.io_update.pulse_mu(8) + self.cpld.io_update.pulse_mu(int64(8)) return ftw & 1 @kernel - def tune_io_update_delay(self) -> TInt32: + def tune_io_update_delay(self) -> int32: """Find a stable ``IO_UPDATE`` delay alignment. Scan through increasing ``IO_UPDATE`` delays until a delay is found that @@ -1106,14 +1131,14 @@ class AD9910: t = 0 # check whether the sync edge is strictly between i, i+2 for j in range(repeat): - t += self.measure_io_update_alignment(i, i + 2) + t += self.measure_io_update_alignment(int64(i), int64(i + 2)) if t != 0: # no certain edge continue # check left/right half: i,i+1 and i+1,i+2 t1 = [0, 0] for j in range(repeat): - t1[0] += self.measure_io_update_alignment(i, i + 1) - t1[1] += self.measure_io_update_alignment(i + 1, i + 2) + t1[0] += self.measure_io_update_alignment(int64(i), int64(i + 1)) + t1[1] += self.measure_io_update_alignment(int64(i + 1), int64(i + 2)) if ((t1[0] == 0 and t1[1] == 0) or (t1[0] == repeat and t1[1] == repeat)): # edge is not close to i + 1, can't interpret result diff --git a/artiq/coredevice/ad9912.py b/artiq/coredevice/ad9912.py index a6bec5440..15d6e4291 100644 --- a/artiq/coredevice/ad9912.py +++ b/artiq/coredevice/ad9912.py @@ -1,14 +1,16 @@ from numpy import int32, int64 -from artiq.language.types import TInt32, TInt64, TFloat, TTuple, TBool -from artiq.language.core import kernel, delay, portable +from artiq.language.core import * from artiq.language.units import ms, us, ns from artiq.coredevice.ad9912_reg import * -from artiq.coredevice import spi2 as spi -from artiq.coredevice import urukul +from artiq.coredevice.core import Core +from artiq.coredevice.spi2 import * +from artiq.coredevice.urukul import * +from artiq.coredevice.ttl import TTLOut +@nac3 class AD9912: """ AD9912 DDS channel on Urukul. @@ -25,22 +27,30 @@ class AD9912: ``f_ref / clk_div * pll_n`` where ``f_ref`` is the reference frequency and ``clk_div`` is the reference clock divider (both set in the parent Urukul CPLD instance). - :param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1). + :param pll_en: PLL enable bit, set to False to bypass PLL (default: True). Note that when bypassing the PLL the red front panel LED may remain on. """ + core: KernelInvariant[Core] + cpld: KernelInvariant[CPLD] + bus: KernelInvariant[SPIMaster] + chip_select: KernelInvariant[int32] + pll_n: KernelInvariant[int32] + pll_en: KernelInvariant[bool] + ftw_per_hz: KernelInvariant[float] + sw: KernelInvariant[Option[TTLOut]] + def __init__(self, dmgr, chip_select, cpld_device, sw_device=None, - pll_n=10, pll_en=1): - self.kernel_invariants = {"cpld", "core", "bus", "chip_select", - "pll_n", "pll_en", "ftw_per_hz"} + pll_n=10, pll_en=True): self.cpld = dmgr.get(cpld_device) self.core = self.cpld.core self.bus = self.cpld.bus assert 4 <= chip_select <= 7 self.chip_select = chip_select if sw_device: - self.sw = dmgr.get(sw_device) - self.kernel_invariants.add("sw") + self.sw = Some(dmgr.get(sw_device)) + else: + self.sw = none self.pll_en = pll_en self.pll_n = pll_n if pll_en: @@ -52,10 +62,10 @@ class AD9912: else: sysclk = self.cpld.refclk assert sysclk <= 1e9 - self.ftw_per_hz = 1 / sysclk * (int64(1) << 48) + self.ftw_per_hz = 1 / sysclk * (1 << 48) @kernel - def write(self, addr: TInt32, data: TInt32, length: TInt32): + def write(self, addr: int32, data: int32, length: int32): """Variable length write to a register. Up to 4 bytes. @@ -65,15 +75,15 @@ class AD9912: """ assert length > 0 assert length <= 4 - self.bus.set_config_mu(urukul.SPI_CONFIG, 16, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG, 16, + SPIT_DDS_WR, self.chip_select) self.bus.write((addr | ((length - 1) << 13)) << 16) - self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, length * 8, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG | SPI_END, length * 8, + SPIT_DDS_WR, self.chip_select) self.bus.write(data << (32 - length * 8)) @kernel - def read(self, addr: TInt32, length: TInt32) -> TInt32: + def read(self, addr: int32, length: int32) -> int32: """Variable length read from a register. Up to 4 bytes. @@ -83,12 +93,12 @@ class AD9912: """ assert length > 0 assert length <= 4 - self.bus.set_config_mu(urukul.SPI_CONFIG, 16, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG, 16, + SPIT_DDS_WR, self.chip_select) self.bus.write((addr | ((length - 1) << 13) | 0x8000) << 16) - self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END - | spi.SPI_INPUT, length * 8, - urukul.SPIT_DDS_RD, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG | SPI_END + | SPI_INPUT, length * 8, + SPIT_DDS_RD, self.chip_select) self.bus.write(0) data = self.bus.read() if length < 4: @@ -104,31 +114,31 @@ class AD9912: ``IO_UPDATE`` signal multiple times. """ # SPI mode - self.write(AD9912_SER_CONF, 0x99, length=1) - self.cpld.io_update.pulse(2 * us) + self.write(AD9912_SER_CONF, 0x99, 1) + self.cpld.io_update.pulse(2. * us) # Verify chip ID and presence - prodid = self.read(AD9912_PRODIDH, length=2) + prodid = self.read(AD9912_PRODIDH, 2) if (prodid != 0x1982) and (prodid != 0x1902): raise ValueError("Urukul AD9912 product id mismatch") - delay(50 * us) + self.core.delay(50. * us) # HSTL power down, CMOS power down - pwrcntrl1 = 0x80 | ((~self.pll_en & 1) << 4) - self.write(AD9912_PWRCNTRL1, pwrcntrl1, length=1) - self.cpld.io_update.pulse(2 * us) + pwrcntrl1 = 0x80 | (int32(not self.pll_en) << 4) + self.write(AD9912_PWRCNTRL1, pwrcntrl1, 1) + self.cpld.io_update.pulse(2. * us) if self.pll_en: - self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1) - self.cpld.io_update.pulse(2 * us) + self.write(AD9912_N_DIV, self.pll_n // 2 - 2, 1) + self.cpld.io_update.pulse(2. * us) # I_cp = 375 µA, VCO high range if self.cpld.refclk < 11e6: # enable SYSCLK PLL Doubler - self.write(AD9912_PLLCFG, 0b00001101, length=1) + self.write(AD9912_PLLCFG, 0b00001101, 1) else: - self.write(AD9912_PLLCFG, 0b00000101, length=1) - self.cpld.io_update.pulse(2 * us) - delay(1 * ms) + self.write(AD9912_PLLCFG, 0b00000101, 1) + self.cpld.io_update.pulse(2. * us) + self.core.delay(1. * ms) @kernel - def set_att_mu(self, att: TInt32): + def set_att_mu(self, att: int32): """Set digital step attenuator in machine units. This method will write the attenuator settings of all four channels. @@ -140,7 +150,7 @@ class AD9912: self.cpld.set_att_mu(self.chip_select - 4, att) @kernel - def set_att(self, att: TFloat): + def set_att(self, att: float): """Set digital step attenuator in SI units. This method will write the attenuator settings of all four channels. @@ -152,7 +162,7 @@ class AD9912: self.cpld.set_att(self.chip_select - 4, att) @kernel - def get_att_mu(self) -> TInt32: + def get_att_mu(self) -> int32: """Get digital step attenuator value in machine units. See also :meth:`~artiq.coredevice.urukul.CPLD.get_channel_att_mu`. @@ -162,7 +172,7 @@ class AD9912: return self.cpld.get_channel_att_mu(self.chip_select - 4) @kernel - def get_att(self) -> TFloat: + def get_att(self) -> float: """Get digital step attenuator value in SI units. See also :meth:`~artiq.coredevice.urukul.CPLD.get_channel_att`. @@ -172,7 +182,7 @@ class AD9912: return self.cpld.get_channel_att(self.chip_select - 4) @kernel - def set_mu(self, ftw: TInt64, pow_: TInt32 = 0): + def set_mu(self, ftw: int64, pow_: int32 = 0): """Set profile 0 data in machine units. After the SPI transfer, the shared IO update pin is pulsed to @@ -182,19 +192,19 @@ class AD9912: :param pow_: Phase tuning word: 16-bit unsigned. """ # streaming transfer of FTW and POW - self.bus.set_config_mu(urukul.SPI_CONFIG, 16, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG, 16, + SPIT_DDS_WR, self.chip_select) self.bus.write((AD9912_POW1 << 16) | (3 << 29)) - self.bus.set_config_mu(urukul.SPI_CONFIG, 32, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG, 32, + SPIT_DDS_WR, self.chip_select) self.bus.write((pow_ << 16) | (int32(ftw >> 32) & 0xffff)) - self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32, - urukul.SPIT_DDS_WR, self.chip_select) + self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32, + SPIT_DDS_WR, self.chip_select) self.bus.write(int32(ftw)) - self.cpld.io_update.pulse(10 * ns) + self.cpld.io_update.pulse(10. * ns) @kernel - def get_mu(self) -> TTuple([TInt64, TInt32]): + def get_mu(self) -> tuple[int64, int32]: """Get the frequency tuning word and phase offset word. See also :meth:`AD9912.get`. @@ -211,30 +221,30 @@ class AD9912: pow_ = (high >> 16) & 0x3fff return ftw, pow_ - @portable(flags={"fast-math"}) - def frequency_to_ftw(self, frequency: TFloat) -> TInt64: + @portable + def frequency_to_ftw(self, frequency: float) -> int64: """Returns the 48-bit frequency tuning word corresponding to the given frequency. """ - return int64(round(self.ftw_per_hz * frequency)) & ( - (int64(1) << 48) - 1) + return round64(self.ftw_per_hz * frequency) & ( + (int64(1) << 48) - int64(1)) - @portable(flags={"fast-math"}) - def ftw_to_frequency(self, ftw: TInt64) -> TFloat: + @portable + def ftw_to_frequency(self, ftw: int64) -> float: """Returns the frequency corresponding to the given frequency tuning word. """ - return ftw / self.ftw_per_hz + return float(ftw) / self.ftw_per_hz - @portable(flags={"fast-math"}) - def turns_to_pow(self, phase: TFloat) -> TInt32: + @portable + def turns_to_pow(self, phase: float) -> int32: """Returns the 16-bit phase offset word corresponding to the given phase. """ - return int32(round((1 << 14) * phase)) & 0xffff + return int32(round(float(1 << 14) * phase)) & 0xffff - @portable(flags={"fast-math"}) - def pow_to_turns(self, pow_: TInt32) -> TFloat: + @portable + def pow_to_turns(self, pow_: int32) -> float: """Return the phase in turns corresponding to a given phase offset word. @@ -244,7 +254,7 @@ class AD9912: return pow_ / (1 << 14) @kernel - def set(self, frequency: TFloat, phase: TFloat = 0.0): + def set(self, frequency: float, phase: float = 0.0): """Set profile 0 data in SI units. See also :meth:`AD9912.set_mu`. @@ -256,7 +266,7 @@ class AD9912: self.turns_to_pow(phase)) @kernel - def get(self) -> TTuple([TFloat, TFloat]): + def get(self) -> tuple[float, float]: """Get the frequency and phase. See also :meth:`AD9912.get_mu`. @@ -270,7 +280,7 @@ class AD9912: return self.ftw_to_frequency(ftw), self.pow_to_turns(pow_) @kernel - def cfg_sw(self, state: TBool): + def cfg_sw(self, state: bool): """Set CPLD CFG RF switch state. The RF switch is controlled by the logical or of the CPLD configuration shift register RF switch bit and the SW TTL line (if used). diff --git a/artiq/coredevice/ad9912_reg.py b/artiq/coredevice/ad9912_reg.py index a85c03ac5..b4555b6ff 100644 --- a/artiq/coredevice/ad9912_reg.py +++ b/artiq/coredevice/ad9912_reg.py @@ -1,78 +1,78 @@ # auto-generated, do not edit +from numpy import int32 from artiq.language.core import portable -from artiq.language.types import TInt32 AD9912_SER_CONF = 0x000 # default: 0x00, access: R/W @portable -def AD9912_SDOACTIVE_SET(x: TInt32) -> TInt32: +def AD9912_SDOACTIVE_SET(x: int32) -> int32: return (x & 0x1) << 0 @portable -def AD9912_SDOACTIVE_GET(x: TInt32) -> TInt32: +def AD9912_SDOACTIVE_GET(x: int32) -> int32: return (x >> 0) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_LSBFIRST_SET(x: TInt32) -> TInt32: +def AD9912_LSBFIRST_SET(x: int32) -> int32: return (x & 0x1) << 1 @portable -def AD9912_LSBFIRST_GET(x: TInt32) -> TInt32: +def AD9912_LSBFIRST_GET(x: int32) -> int32: return (x >> 1) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_SOFTRESET_SET(x: TInt32) -> TInt32: +def AD9912_SOFTRESET_SET(x: int32) -> int32: return (x & 0x1) << 2 @portable -def AD9912_SOFTRESET_GET(x: TInt32) -> TInt32: +def AD9912_SOFTRESET_GET(x: int32) -> int32: return (x >> 2) & 0x1 # default: 0x01, access: R/W @portable -def AD9912_LONGINSN_SET(x: TInt32) -> TInt32: +def AD9912_LONGINSN_SET(x: int32) -> int32: return (x & 0x1) << 3 @portable -def AD9912_LONGINSN_GET(x: TInt32) -> TInt32: +def AD9912_LONGINSN_GET(x: int32) -> int32: return (x >> 3) & 0x1 # default: 0x01, access: R/W @portable -def AD9912_LONGINSN_M_SET(x: TInt32) -> TInt32: +def AD9912_LONGINSN_M_SET(x: int32) -> int32: return (x & 0x1) << 4 @portable -def AD9912_LONGINSN_M_GET(x: TInt32) -> TInt32: +def AD9912_LONGINSN_M_GET(x: int32) -> int32: return (x >> 4) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_SOFTRESET_M_SET(x: TInt32) -> TInt32: +def AD9912_SOFTRESET_M_SET(x: int32) -> int32: return (x & 0x1) << 5 @portable -def AD9912_SOFTRESET_M_GET(x: TInt32) -> TInt32: +def AD9912_SOFTRESET_M_GET(x: int32) -> int32: return (x >> 5) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_LSBFIRST_M_SET(x: TInt32) -> TInt32: +def AD9912_LSBFIRST_M_SET(x: int32) -> int32: return (x & 0x1) << 6 @portable -def AD9912_LSBFIRST_M_GET(x: TInt32) -> TInt32: +def AD9912_LSBFIRST_M_GET(x: int32) -> int32: return (x >> 6) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_SDOACTIVE_M_SET(x: TInt32) -> TInt32: +def AD9912_SDOACTIVE_M_SET(x: int32) -> int32: return (x & 0x1) << 7 @portable -def AD9912_SDOACTIVE_M_GET(x: TInt32) -> TInt32: +def AD9912_SDOACTIVE_M_GET(x: int32) -> int32: return (x >> 7) & 0x1 @@ -83,118 +83,118 @@ AD9912_PRODIDH = 0x003 AD9912_SER_OPT1 = 0x004 # default: 0x00, access: R/W @portable -def AD9912_READ_BUF_SET(x: TInt32) -> TInt32: +def AD9912_READ_BUF_SET(x: int32) -> int32: return (x & 0x1) << 0 @portable -def AD9912_READ_BUF_GET(x: TInt32) -> TInt32: +def AD9912_READ_BUF_GET(x: int32) -> int32: return (x >> 0) & 0x1 AD9912_SER_OPT2 = 0x005 # default: 0x00, access: R/W @portable -def AD9912_RED_UPDATE_SET(x: TInt32) -> TInt32: +def AD9912_RED_UPDATE_SET(x: int32) -> int32: return (x & 0x1) << 0 @portable -def AD9912_RED_UPDATE_GET(x: TInt32) -> TInt32: +def AD9912_RED_UPDATE_GET(x: int32) -> int32: return (x >> 0) & 0x1 AD9912_PWRCNTRL1 = 0x010 # default: 0x00, access: R/W @portable -def AD9912_PD_DIGITAL_SET(x: TInt32) -> TInt32: +def AD9912_PD_DIGITAL_SET(x: int32) -> int32: return (x & 0x1) << 0 @portable -def AD9912_PD_DIGITAL_GET(x: TInt32) -> TInt32: +def AD9912_PD_DIGITAL_GET(x: int32) -> int32: return (x >> 0) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_PD_FULL_SET(x: TInt32) -> TInt32: +def AD9912_PD_FULL_SET(x: int32) -> int32: return (x & 0x1) << 1 @portable -def AD9912_PD_FULL_GET(x: TInt32) -> TInt32: +def AD9912_PD_FULL_GET(x: int32) -> int32: return (x >> 1) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_PD_SYSCLK_SET(x: TInt32) -> TInt32: +def AD9912_PD_SYSCLK_SET(x: int32) -> int32: return (x & 0x1) << 4 @portable -def AD9912_PD_SYSCLK_GET(x: TInt32) -> TInt32: +def AD9912_PD_SYSCLK_GET(x: int32) -> int32: return (x >> 4) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_EN_DOUBLER_SET(x: TInt32) -> TInt32: +def AD9912_EN_DOUBLER_SET(x: int32) -> int32: return (x & 0x1) << 5 @portable -def AD9912_EN_DOUBLER_GET(x: TInt32) -> TInt32: +def AD9912_EN_DOUBLER_GET(x: int32) -> int32: return (x >> 5) & 0x1 # default: 0x01, access: R/W @portable -def AD9912_EN_CMOS_SET(x: TInt32) -> TInt32: +def AD9912_EN_CMOS_SET(x: int32) -> int32: return (x & 0x1) << 6 @portable -def AD9912_EN_CMOS_GET(x: TInt32) -> TInt32: +def AD9912_EN_CMOS_GET(x: int32) -> int32: return (x >> 6) & 0x1 # default: 0x01, access: R/W @portable -def AD9912_PD_HSTL_SET(x: TInt32) -> TInt32: +def AD9912_PD_HSTL_SET(x: int32) -> int32: return (x & 0x1) << 7 @portable -def AD9912_PD_HSTL_GET(x: TInt32) -> TInt32: +def AD9912_PD_HSTL_GET(x: int32) -> int32: return (x >> 7) & 0x1 AD9912_PWRCNTRL2 = 0x012 # default: 0x00, access: R/W @portable -def AD9912_DDS_RESET_SET(x: TInt32) -> TInt32: +def AD9912_DDS_RESET_SET(x: int32) -> int32: return (x & 0x1) << 0 @portable -def AD9912_DDS_RESET_GET(x: TInt32) -> TInt32: +def AD9912_DDS_RESET_GET(x: int32) -> int32: return (x >> 0) & 0x1 AD9912_PWRCNTRL3 = 0x013 # default: 0x00, access: R/W @portable -def AD9912_S_DIV_RESET_SET(x: TInt32) -> TInt32: +def AD9912_S_DIV_RESET_SET(x: int32) -> int32: return (x & 0x1) << 1 @portable -def AD9912_S_DIV_RESET_GET(x: TInt32) -> TInt32: +def AD9912_S_DIV_RESET_GET(x: int32) -> int32: return (x >> 1) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_S_DIV2_RESET_SET(x: TInt32) -> TInt32: +def AD9912_S_DIV2_RESET_SET(x: int32) -> int32: return (x & 0x1) << 3 @portable -def AD9912_S_DIV2_RESET_GET(x: TInt32) -> TInt32: +def AD9912_S_DIV2_RESET_GET(x: int32) -> int32: return (x >> 3) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_PD_FUND_SET(x: TInt32) -> TInt32: +def AD9912_PD_FUND_SET(x: int32) -> int32: return (x & 0x1) << 7 @portable -def AD9912_PD_FUND_GET(x: TInt32) -> TInt32: +def AD9912_PD_FUND_GET(x: int32) -> int32: return (x >> 7) & 0x1 @@ -203,38 +203,38 @@ AD9912_N_DIV = 0x020 AD9912_PLLCFG = 0x022 # default: 0x00, access: R/W @portable -def AD9912_PLL_ICP_SET(x: TInt32) -> TInt32: +def AD9912_PLL_ICP_SET(x: int32) -> int32: return (x & 0x3) << 0 @portable -def AD9912_PLL_ICP_GET(x: TInt32) -> TInt32: +def AD9912_PLL_ICP_GET(x: int32) -> int32: return (x >> 0) & 0x3 # default: 0x01, access: R/W @portable -def AD9912_VCO_RANGE_SET(x: TInt32) -> TInt32: +def AD9912_VCO_RANGE_SET(x: int32) -> int32: return (x & 0x1) << 2 @portable -def AD9912_VCO_RANGE_GET(x: TInt32) -> TInt32: +def AD9912_VCO_RANGE_GET(x: int32) -> int32: return (x >> 2) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_PLL_REF2X_SET(x: TInt32) -> TInt32: +def AD9912_PLL_REF2X_SET(x: int32) -> int32: return (x & 0x1) << 3 @portable -def AD9912_PLL_REF2X_GET(x: TInt32) -> TInt32: +def AD9912_PLL_REF2X_GET(x: int32) -> int32: return (x >> 3) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_VCO_AUTO_RANGE_SET(x: TInt32) -> TInt32: +def AD9912_VCO_AUTO_RANGE_SET(x: int32) -> int32: return (x & 0x1) << 7 @portable -def AD9912_VCO_AUTO_RANGE_GET(x: TInt32) -> TInt32: +def AD9912_VCO_AUTO_RANGE_GET(x: int32) -> int32: return (x >> 7) & 0x1 @@ -245,20 +245,20 @@ AD9912_S_DIVH = 0x105 AD9912_S_DIV_CFG = 0x106 # default: 0x01, access: R/W @portable -def AD9912_S_DIV2_SET(x: TInt32) -> TInt32: +def AD9912_S_DIV2_SET(x: int32) -> int32: return (x & 0x1) << 0 @portable -def AD9912_S_DIV2_GET(x: TInt32) -> TInt32: +def AD9912_S_DIV2_GET(x: int32) -> int32: return (x >> 0) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_S_DIV_FALL_SET(x: TInt32) -> TInt32: +def AD9912_S_DIV_FALL_SET(x: int32) -> int32: return (x & 0x1) << 7 @portable -def AD9912_S_DIV_FALL_GET(x: TInt32) -> TInt32: +def AD9912_S_DIV_FALL_GET(x: int32) -> int32: return (x >> 7) & 0x1 @@ -281,31 +281,31 @@ AD9912_POW1 = 0x1ad AD9912_HSTL = 0x200 # default: 0x01, access: R/W @portable -def AD9912_HSTL_CFG_SET(x: TInt32) -> TInt32: +def AD9912_HSTL_CFG_SET(x: int32) -> int32: return (x & 0x3) << 0 @portable -def AD9912_HSTL_CFG_GET(x: TInt32) -> TInt32: +def AD9912_HSTL_CFG_GET(x: int32) -> int32: return (x >> 0) & 0x3 # default: 0x01, access: R/W @portable -def AD9912_HSTL_OPOL_SET(x: TInt32) -> TInt32: +def AD9912_HSTL_OPOL_SET(x: int32) -> int32: return (x & 0x1) << 4 @portable -def AD9912_HSTL_OPOL_GET(x: TInt32) -> TInt32: +def AD9912_HSTL_OPOL_GET(x: int32) -> int32: return (x >> 4) & 0x1 AD9912_CMOS = 0x201 # default: 0x00, access: R/W @portable -def AD9912_CMOS_MUX_SET(x: TInt32) -> TInt32: +def AD9912_CMOS_MUX_SET(x: int32) -> int32: return (x & 0x1) << 0 @portable -def AD9912_CMOS_MUX_GET(x: TInt32) -> TInt32: +def AD9912_CMOS_MUX_GET(x: int32) -> int32: return (x >> 0) & 0x1 @@ -316,29 +316,29 @@ AD9912_FSC1 = 0x40c AD9912_HSR_A_CFG = 0x500 # default: 0x00, access: R/W @portable -def AD9912_HSR_A_HARMONIC_SET(x: TInt32) -> TInt32: +def AD9912_HSR_A_HARMONIC_SET(x: int32) -> int32: return (x & 0xf) << 0 @portable -def AD9912_HSR_A_HARMONIC_GET(x: TInt32) -> TInt32: +def AD9912_HSR_A_HARMONIC_GET(x: int32) -> int32: return (x >> 0) & 0xf # default: 0x00, access: R/W @portable -def AD9912_HSR_A_MAG2X_SET(x: TInt32) -> TInt32: +def AD9912_HSR_A_MAG2X_SET(x: int32) -> int32: return (x & 0x1) << 6 @portable -def AD9912_HSR_A_MAG2X_GET(x: TInt32) -> TInt32: +def AD9912_HSR_A_MAG2X_GET(x: int32) -> int32: return (x >> 6) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_HSR_A_EN_SET(x: TInt32) -> TInt32: +def AD9912_HSR_A_EN_SET(x: int32) -> int32: return (x & 0x1) << 7 @portable -def AD9912_HSR_A_EN_GET(x: TInt32) -> TInt32: +def AD9912_HSR_A_EN_GET(x: int32) -> int32: return (x >> 7) & 0x1 @@ -351,29 +351,29 @@ AD9912_HSR_A_POW1 = 0x504 AD9912_HSR_B_CFG = 0x505 # default: 0x00, access: R/W @portable -def AD9912_HSR_B_HARMONIC_SET(x: TInt32) -> TInt32: +def AD9912_HSR_B_HARMONIC_SET(x: int32) -> int32: return (x & 0xf) << 0 @portable -def AD9912_HSR_B_HARMONIC_GET(x: TInt32) -> TInt32: +def AD9912_HSR_B_HARMONIC_GET(x: int32) -> int32: return (x >> 0) & 0xf # default: 0x00, access: R/W @portable -def AD9912_HSR_B_MAG2X_SET(x: TInt32) -> TInt32: +def AD9912_HSR_B_MAG2X_SET(x: int32) -> int32: return (x & 0x1) << 6 @portable -def AD9912_HSR_B_MAG2X_GET(x: TInt32) -> TInt32: +def AD9912_HSR_B_MAG2X_GET(x: int32) -> int32: return (x >> 6) & 0x1 # default: 0x00, access: R/W @portable -def AD9912_HSR_B_EN_SET(x: TInt32) -> TInt32: +def AD9912_HSR_B_EN_SET(x: int32) -> int32: return (x & 0x1) << 7 @portable -def AD9912_HSR_B_EN_GET(x: TInt32) -> TInt32: +def AD9912_HSR_B_EN_GET(x: int32) -> int32: return (x >> 7) & 0x1 diff --git a/artiq/coredevice/ad9914.py b/artiq/coredevice/ad9914.py index a37597b4d..bd2b5755c 100644 --- a/artiq/coredevice/ad9914.py +++ b/artiq/coredevice/ad9914.py @@ -2,13 +2,12 @@ Driver for the AD9914 DDS (with parallel bus) on RTIO. """ +from numpy import int32, int64 from artiq.language.core import * -from artiq.language.types import * from artiq.language.units import * from artiq.coredevice.rtio import rtio_output - -from numpy import int32, int64 +from artiq.coredevice.core import Core __all__ = [ @@ -43,6 +42,7 @@ AD9914_FUD = 0x80 AD9914_GPIO = 0x81 +@nac3 class AD9914: """Driver for one AD9914 DDS channel. @@ -57,10 +57,21 @@ class AD9914: :param channel: channel number (on the bus) of the DDS device to control. """ - kernel_invariants = {"core", "sysclk", "bus_channel", "channel", - "rtio_period_mu", "sysclk_per_mu", "write_duration_mu", - "dac_cal_duration_mu", "init_duration_mu", "init_sync_duration_mu", - "set_duration_mu", "set_x_duration_mu", "exit_x_duration_mu"} + core: KernelInvariant[Core] + sysclk: KernelInvariant[float] + bus_channel: KernelInvariant[int32] + channel: KernelInvariant[int32] + phase_mode: Kernel[int32] + rtio_period_mu: KernelInvariant[int64] + sysclk_per_mu: KernelInvariant[int64] + write_duration_mu: KernelInvariant[int64] + dac_cal_duration_mu: KernelInvariant[int64] + init_duration_mu: KernelInvariant[int64] + init_sync_duration_mu: KernelInvariant[int64] + set_duration_mu: KernelInvariant[int64] + set_x_duration_mu: KernelInvariant[int64] + exit_x_duration_mu: KernelInvariant[int64] + def __init__(self, dmgr, sysclk, bus_channel, channel, core_device="core"): self.core = dmgr.get(core_device) @@ -70,7 +81,7 @@ class AD9914: self.phase_mode = PHASE_MODE_CONTINUOUS self.rtio_period_mu = int64(8) - self.sysclk_per_mu = int32(self.sysclk * self.core.ref_period) + self.sysclk_per_mu = int64(self.sysclk * self.core.ref_period) self.write_duration_mu = 5 * self.rtio_period_mu self.dac_cal_duration_mu = 147000 * self.rtio_period_mu @@ -88,7 +99,7 @@ class AD9914: return [] @kernel - def write(self, addr, data): + def write(self, addr: int32, data: int32): rtio_output((self.bus_channel << 8) | addr, data) delay_mu(self.write_duration_mu) @@ -120,7 +131,7 @@ class AD9914: self.write(AD9914_FUD, 0) @kernel - def init_sync(self, sync_delay): + def init_sync(self, sync_delay: int32): """Resets and initializes the DDS channel as well as configures the AD9914 DDS for synchronisation. The synchronisation procedure follows the steps outlined in the AN-1254 application note. @@ -164,7 +175,7 @@ class AD9914: self.write(AD9914_FUD, 0) @kernel - def set_phase_mode(self, phase_mode): + def set_phase_mode(self, phase_mode: int32): """Sets the phase mode of the DDS channel. Supported phase modes are: * :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged when @@ -188,8 +199,8 @@ class AD9914: self.phase_mode = phase_mode @kernel - def set_mu(self, ftw, pow=0, phase_mode=_PHASE_MODE_DEFAULT, - asf=0x0fff, ref_time_mu=-1): + def set_mu(self, ftw: int32, pow: int32 = 0, phase_mode: int32 = _PHASE_MODE_DEFAULT, + asf: int32 = 0x0fff, ref_time_mu: int64 = int64(-1)) -> int32: """Sets the DDS channel to the specified frequency and phase. This uses machine units (FTW and POW). The frequency tuning word width @@ -212,7 +223,7 @@ class AD9914: """ if phase_mode == _PHASE_MODE_DEFAULT: phase_mode = self.phase_mode - if ref_time_mu < 0: + if ref_time_mu < int64(0): ref_time_mu = now_mu() delay_mu(-self.set_duration_mu) @@ -231,60 +242,60 @@ class AD9914: # Clear phase accumulator on FUD # Enable autoclear phase accumulator and enables OSK. self.write(AD9914_REG_CFR1L, 0x2108) - fud_time = now_mu() + 2 * self.write_duration_mu - pow -= int32((ref_time_mu - fud_time) * self.sysclk_per_mu * ftw >> (32 - 16)) + fud_time = now_mu() + int64(2) * self.write_duration_mu + pow -= int32((ref_time_mu - fud_time) * self.sysclk_per_mu * int64(ftw) >> (32 - 16)) if phase_mode == PHASE_MODE_TRACKING: - pow += int32(ref_time_mu * self.sysclk_per_mu * ftw >> (32 - 16)) + pow += int32(ref_time_mu * self.sysclk_per_mu * int64(ftw) >> (32 - 16)) self.write(AD9914_REG_POW, pow) self.write(AD9914_REG_ASF, asf) self.write(AD9914_FUD, 0) return pow - @portable(flags={"fast-math"}) - def frequency_to_ftw(self, frequency): + @portable + def frequency_to_ftw(self, frequency: float) -> int32: """Returns the 32-bit frequency tuning word corresponding to the given frequency. """ - return int32(round(float(int64(2)**32*frequency/self.sysclk))) + return round(float(int64(2)**int64(32))*frequency/self.sysclk) - @portable(flags={"fast-math"}) - def ftw_to_frequency(self, ftw): + @portable + def ftw_to_frequency(self, ftw: int32) -> float: """Returns the frequency corresponding to the given frequency tuning word. """ - return ftw*self.sysclk/int64(2)**32 + return float(ftw)*self.sysclk/float(int64(2)**int64(32)) - @portable(flags={"fast-math"}) - def turns_to_pow(self, turns): + @portable + def turns_to_pow(self, turns: float) -> int32: """Returns the 16-bit phase offset word corresponding to the given phase in turns.""" - return round(float(turns*2**16)) & 0xffff + return round(float(turns*float(2**16))) & 0xffff - @portable(flags={"fast-math"}) - def pow_to_turns(self, pow): + @portable + def pow_to_turns(self, pow: int32) -> float: """Returns the phase in turns corresponding to the given phase offset word.""" - return pow/2**16 + return float(pow)/float(2**16) - @portable(flags={"fast-math"}) - def amplitude_to_asf(self, amplitude): + @portable + def amplitude_to_asf(self, amplitude: float) -> int32: """Returns 12-bit amplitude scale factor corresponding to given amplitude.""" - code = round(float(amplitude * 0x0fff)) + code = round(float(amplitude * float(0x0fff))) if code < 0 or code > 0xfff: raise ValueError("Invalid AD9914 amplitude!") return code - @portable(flags={"fast-math"}) - def asf_to_amplitude(self, asf): + @portable + def asf_to_amplitude(self, asf: int32) -> float: """Returns the amplitude corresponding to the given amplitude scale factor.""" return asf/0x0fff @kernel - def set(self, frequency, phase=0.0, phase_mode=_PHASE_MODE_DEFAULT, - amplitude=1.0): + def set(self, frequency: float, phase: float = 0.0, phase_mode: int32 = _PHASE_MODE_DEFAULT, + amplitude: float = 1.0) -> float: """Like :meth:`set_mu`, but uses Hz and turns.""" return self.pow_to_turns( self.set_mu(self.frequency_to_ftw(frequency), @@ -293,7 +304,7 @@ class AD9914: # Extended-resolution functions @kernel - def set_x_mu(self, xftw, amplitude=0x0fff): + def set_x_mu(self, xftw: int64, amplitude: int32 = 0x0fff): """Set the DDS frequency and amplitude with an extended-resolution (63-bit) frequency tuning word. @@ -307,10 +318,10 @@ class AD9914: self.write(AD9914_GPIO, (1 << self.channel) << 1) - self.write(AD9914_REG_DRGAL, xftw & 0xffff) - self.write(AD9914_REG_DRGAH, (xftw >> 16) & 0x7fff) - self.write(AD9914_REG_DRGFL, (xftw >> 31) & 0xffff) - self.write(AD9914_REG_DRGFH, (xftw >> 47) & 0xffff) + self.write(AD9914_REG_DRGAL, int32(xftw) & 0xffff) + self.write(AD9914_REG_DRGAH, int32(xftw >> 16) & 0x7fff) + self.write(AD9914_REG_DRGFL, int32(xftw >> 31) & 0xffff) + self.write(AD9914_REG_DRGFH, int32(xftw >> 47) & 0xffff) self.write(AD9914_REG_ASF, amplitude) self.write(AD9914_FUD, 0) @@ -323,23 +334,23 @@ class AD9914: self.write(AD9914_REG_DRGAL, 0) self.write(AD9914_REG_DRGAH, 0) - @portable(flags={"fast-math"}) - def frequency_to_xftw(self, frequency): + @portable + def frequency_to_xftw(self, frequency: float) -> int64: """Returns the 63-bit frequency tuning word corresponding to the given frequency (extended resolution mode). """ - return int64(round(2.0*float(int64(2)**62)*frequency/self.sysclk)) & ( - (int64(1) << 63) - 1) + return round64(2.0*float(int64(2)**int64(62))*frequency/self.sysclk) & ( + (int64(1) << 63) - int64(1)) - @portable(flags={"fast-math"}) - def xftw_to_frequency(self, xftw): + @portable + def xftw_to_frequency(self, xftw: int64) -> float: """Returns the frequency corresponding to the given frequency tuning word (extended resolution mode). """ - return xftw*self.sysclk/(2.0*float(int64(2)**62)) + return float(xftw)*self.sysclk/(2.0*float(int64(2)**int64(62))) @kernel - def set_x(self, frequency, amplitude=1.0): + def set_x(self, frequency: float, amplitude: float = 1.0): """Like :meth:`set_x_mu`, but uses Hz and turns. Note that the precision of ``float`` is less than the precision diff --git a/artiq/coredevice/adf5356.py b/artiq/coredevice/adf5356.py index 6f6d3dbf6..3549f0674 100644 --- a/artiq/coredevice/adf5356.py +++ b/artiq/coredevice/adf5356.py @@ -8,25 +8,27 @@ on Mirny-style prefixed SPI buses. # https://www.analog.com/media/en/technical-documentation/data-sheets/ADF5355.pdf # https://www.analog.com/media/en/technical-documentation/user-guides/EV-ADF5355SD1Z-UG-1087.pdf +from numpy import int32, int64 +from math import floor, ceil -from artiq.language.core import kernel, portable, delay +from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable, round64 from artiq.language.units import us, GHz, MHz -from artiq.language.types import TInt32, TInt64 -from artiq.coredevice import spi2 as spi +from artiq.coredevice.core import Core +from artiq.coredevice.mirny import Mirny +from artiq.coredevice.ttl import TTLOut +from artiq.coredevice.spi2 import * from artiq.coredevice.adf5356_reg import * -from numpy import int32, int64, floor, ceil - SPI_CONFIG = ( - 0 * spi.SPI_OFFLINE - | 0 * spi.SPI_END - | 0 * spi.SPI_INPUT - | 1 * spi.SPI_CS_POLARITY - | 0 * spi.SPI_CLK_POLARITY - | 0 * spi.SPI_CLK_PHASE - | 0 * spi.SPI_LSB_FIRST - | 0 * spi.SPI_HALF_DUPLEX + 0 * SPI_OFFLINE + | 0 * SPI_END + | 0 * SPI_INPUT + | 1 * SPI_CS_POLARITY + | 0 * SPI_CLK_POLARITY + | 0 * SPI_CLK_PHASE + | 0 * SPI_LSB_FIRST + | 0 * SPI_HALF_DUPLEX ) @@ -38,6 +40,7 @@ ADF5356_MAX_MODULUS2 = int32(1 << 28) # FIXME: ADF5356 has 28 bits MOD2 ADF5356_MAX_R_CNT = int32(1023) +@nac3 class ADF5356: """Analog Devices AD[45]35[56] family of GHz PLLs. @@ -49,7 +52,14 @@ class ADF5356: :param core_device: Core device name (default: "core") """ - kernel_invariants = {"cpld", "sw", "channel", "core", "sysclk"} + core: KernelInvariant[Core] + cpld: KernelInvariant[Mirny] + channel: KernelInvariant[int32] + sw: KernelInvariant[TTLOut] + sysclk: KernelInvariant[float] + regs: Kernel[list[int32]] + ref_doubler: Kernel[bool] + ref_divider: Kernel[bool] def __init__( self, @@ -78,7 +88,7 @@ class ADF5356: return [] @kernel - def init(self, blind=False): + def init(self, blind: bool = False): """ Initialize and configure the PLL. @@ -89,25 +99,25 @@ class ADF5356: # MUXOUT = VDD self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 1) self.write(self.regs[4]) - delay(1000 * us) + self.core.delay(1000. * us) if not self.read_muxout(): raise ValueError("MUXOUT not high") - delay(800 * us) + self.core.delay(800. * us) # MUXOUT = DGND self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 2) self.write(self.regs[4]) - delay(1000 * us) + self.core.delay(1000. * us) if self.read_muxout(): raise ValueError("MUXOUT not low") - delay(800 * us) + self.core.delay(800. * us) # MUXOUT = digital lock-detect self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 6) self.write(self.regs[4]) @kernel - def set_att(self, att): + def set_att(self, att: float): """Set digital step attenuator in SI units. This method will write the attenuator settings of the channel. @@ -119,7 +129,7 @@ class ADF5356: self.cpld.set_att(self.channel, att) @kernel - def set_att_mu(self, att): + def set_att_mu(self, att: int32): """Set digital step attenuator in machine units. :param att: Attenuation setting, 8-bit digital. @@ -127,11 +137,11 @@ class ADF5356: self.cpld.set_att_mu(self.channel, att) @kernel - def write(self, data): + def write(self, data: int32): self.cpld.write_ext(self.channel | 4, 32, data) @kernel - def read_muxout(self): + def read_muxout(self) -> bool: """ Read the state of the MUXOUT line. @@ -140,7 +150,7 @@ class ADF5356: return bool(self.cpld.read_reg(0) & (1 << (self.channel + 8))) @kernel - def set_output_power_mu(self, n): + def set_output_power_mu(self, n: int32): """ Set the power level at output A of the PLL chip in machine units. @@ -148,13 +158,13 @@ class ADF5356: :param n: output power setting, 0, 1, 2, or 3 (see ADF5356 datasheet, fig. 44). """ - if n not in [0, 1, 2, 3]: + if not 0 <= n <= 3: raise ValueError("invalid power setting") self.regs[6] = ADF5356_REG6_RF_OUTPUT_A_POWER_UPDATE(self.regs[6], n) self.sync() @portable - def output_power_mu(self): + def output_power_mu(self) -> int32: """ Return the power level at output A of the PLL chip in machine units. """ @@ -177,13 +187,13 @@ class ADF5356: self.sync() @kernel - def set_frequency(self, f): + def set_frequency(self, f: float): """ Output given frequency on output A. :param f: 53.125 MHz <= f <= 6800 MHz """ - freq = int64(round(f)) + freq = round64(f) if freq > ADF5356_MAX_VCO_FREQ: raise ValueError("Requested too high frequency") @@ -209,7 +219,7 @@ class ADF5356: n_min, n_max = 75, 65535 # adjust reference divider to be able to match n_min constraint - while n_min * f_pfd > freq: + while int64(n_min) * f_pfd > freq: r = ADF5356_REG4_R_COUNTER_GET(self.regs[4]) self.regs[4] = ADF5356_REG4_R_COUNTER_UPDATE(self.regs[4], r + 1) f_pfd = self.f_pfd() @@ -237,10 +247,10 @@ class ADF5356: self.regs[6] = ADF5356_REG6_RF_DIVIDER_SELECT_UPDATE(self.regs[6], rf_div_sel) self.regs[6] = ADF5356_REG6_CP_BLEED_CURRENT_UPDATE( - self.regs[6], int32(floor(24 * f_pfd / (61.44 * MHz))) + self.regs[6], floor(24. * float(f_pfd) / (61.44 * MHz)) ) self.regs[9] = ADF5356_REG9_VCO_BAND_DIVISION_UPDATE( - self.regs[9], int32(ceil(f_pfd / 160e3)) + self.regs[9], ceil(float(f_pfd) / 160e3) ) # commit @@ -252,12 +262,12 @@ class ADF5356: Write all registers to the device. Attempts to lock the PLL. """ f_pfd = self.f_pfd() - delay(200 * us) # Slack + self.core.delay(200. * us) # Slack - if f_pfd <= 75.0 * MHz: + if f_pfd <= round64(75.0 * MHz): for i in range(13, 0, -1): self.write(self.regs[i]) - delay(200 * us) + self.core.delay(200. * us) self.write(self.regs[0] | ADF5356_REG0_AUTOCAL(1)) else: # AUTOCAL AT HALF PFD FREQUENCY @@ -266,7 +276,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.core.delay(200. * us) # Slack self.write( 13 @@ -289,7 +299,7 @@ class ADF5356: ) self.write(1 | ADF5356_REG1_MAIN_FRAC_VALUE(frac1)) - delay(200 * us) + self.core.delay(200. * us) self.write(ADF5356_REG0_INT_VALUE(n) | ADF5356_REG0_AUTOCAL(1)) # RELOCK AT WANTED PFD FREQUENCY @@ -301,7 +311,7 @@ class ADF5356: self.write(self.regs[0] & ~ADF5356_REG0_AUTOCAL(1)) @portable - def f_pfd(self) -> TInt64: + def f_pfd(self) -> int64: """ Return the PFD frequency for the cached set of registers. """ @@ -311,35 +321,35 @@ class ADF5356: return self._compute_pfd_frequency(r, d, t) @portable - def f_vco(self) -> TInt64: + def f_vco(self) -> int64: """ Return the VCO frequency for the cached set of registers. """ - return int64( - self.f_pfd() + return round64( + float(self.f_pfd()) * ( - self.pll_n() - + (self.pll_frac1() + self.pll_frac2() / self.pll_mod2()) - / ADF5356_MODULUS1 - ) + float(self.pll_n()) + + (float(self.pll_frac1() + self.pll_frac2()) / float(self.pll_mod2())) + / float(ADF5356_MODULUS1) + ) ) @portable - def pll_n(self) -> TInt32: + def pll_n(self) -> int32: """ Return the PLL integer value (INT) for the cached set of registers. """ return ADF5356_REG0_INT_VALUE_GET(self.regs[0]) @portable - def pll_frac1(self) -> TInt32: + def pll_frac1(self) -> int32: """ Return the main fractional value (FRAC1) for the cached set of registers. """ return ADF5356_REG1_MAIN_FRAC_VALUE_GET(self.regs[1]) @portable - def pll_frac2(self) -> TInt32: + def pll_frac2(self) -> int32: """ Return the auxiliary fractional value (FRAC2) for the cached set of registers. """ @@ -348,7 +358,7 @@ class ADF5356: ) | ADF5356_REG2_AUX_FRAC_LSB_VALUE_GET(self.regs[2]) @portable - def pll_mod2(self) -> TInt32: + def pll_mod2(self) -> int32: """ Return the auxiliary modulus value (MOD2) for the cached set of registers. """ @@ -357,14 +367,14 @@ class ADF5356: ) | ADF5356_REG2_AUX_MOD_LSB_VALUE_GET(self.regs[2]) @portable - def ref_counter(self) -> TInt32: + def ref_counter(self) -> int32: """ Return the reference counter value (R) for the cached set of registers. """ return ADF5356_REG4_R_COUNTER_GET(self.regs[4]) @portable - def output_divider(self) -> TInt32: + def output_divider(self) -> int32: """ Return the value of the output A divider. """ @@ -414,7 +424,7 @@ class ADF5356: # single-ended reference mode is recommended # for references up to 250 MHz, even if the signal is differential - if self.sysclk <= 250 * MHz: + if self.sysclk <= 250.*MHz: self.regs[4] |= ADF5356_REG4_REF_MODE(0) else: self.regs[4] |= ADF5356_REG4_REF_MODE(1) @@ -456,7 +466,7 @@ class ADF5356: # charge pump bleed current self.regs[6] |= ADF5356_REG6_CP_BLEED_CURRENT( - int32(floor(24 * self.f_pfd() / (61.44 * MHz))) + floor(24. * float(self.f_pfd()) / (61.44 * MHz)) ) # direct feedback from VCO to N counter @@ -500,7 +510,7 @@ class ADF5356: ) self.regs[9] |= ADF5356_REG9_VCO_BAND_DIVISION( - int32(ceil(self.f_pfd() / 160e3)) + ceil(float(self.f_pfd()) / 160e3) ) # REG10 @@ -529,39 +539,39 @@ class ADF5356: self.regs[12] = int32(0x15FC) @portable - def _compute_pfd_frequency(self, r, d, t) -> TInt64: + def _compute_pfd_frequency(self, r: int32, d: int32, t: int32) -> int64: """ Calculate the PFD frequency from the given reference path parameters. """ - return int64(self.sysclk * ((1 + d) / (r * (1 + t)))) + return round64(self.sysclk * (float(1 + d) / float(r * (1 + t)))) @portable - def _compute_reference_counter(self) -> TInt32: + def _compute_reference_counter(self) -> int32: """ Determine the reference counter R that maximizes the PFD frequency. """ d = ADF5356_REG4_R_DOUBLER_GET(self.regs[4]) t = ADF5356_REG4_R_DIVIDER_GET(self.regs[4]) r = 1 - while self._compute_pfd_frequency(r, d, t) > ADF5356_MAX_FREQ_PFD: + while self._compute_pfd_frequency(r, d, t) > int64(ADF5356_MAX_FREQ_PFD): r += 1 - return int32(r) + return r @portable -def gcd(a, b): - while b: +def gcd(a: int64, b: int64) -> int64: + while b != int64(0): a, b = b, a % b return a @portable -def split_msb_lsb_28b(v): - return int32((v >> 14) & 0x3FFF), int32(v & 0x3FFF) +def split_msb_lsb_28b(v: int32) -> tuple[int32, int32]: + return (v >> 14) & 0x3FFF, v & 0x3FFF @portable -def calculate_pll(f_vco: TInt64, f_pfd: TInt64): +def calculate_pll(f_vco: int64, f_pfd: int64) -> tuple[int32, int32, tuple[int32, int32], tuple[int32, int32]]: """ Calculate fractional-N PLL parameters such that @@ -575,20 +585,18 @@ def calculate_pll(f_vco: TInt64, f_pfd: TInt64): :param f_pfd: PFD frequency :return: (``n``, ``frac1``, ``(frac2_msb, frac2_lsb)``, ``(mod2_msb, mod2_lsb)``) """ - f_pfd = int64(f_pfd) - f_vco = int64(f_vco) # integral part n, r = int32(f_vco // f_pfd), f_vco % f_pfd # main fractional part - r *= ADF5356_MODULUS1 + r *= int64(ADF5356_MODULUS1) frac1, frac2 = int32(r // f_pfd), r % f_pfd # auxiliary fractional part mod2 = f_pfd - while mod2 > ADF5356_MAX_MODULUS2: + while mod2 > int64(ADF5356_MAX_MODULUS2): mod2 >>= 1 frac2 >>= 1 @@ -596,4 +604,4 @@ def calculate_pll(f_vco: TInt64, f_pfd: TInt64): mod2 //= gcd_div frac2 //= gcd_div - return n, frac1, split_msb_lsb_28b(frac2), split_msb_lsb_28b(mod2) + return n, frac1, split_msb_lsb_28b(int32(frac2)), split_msb_lsb_28b(int32(mod2)) diff --git a/artiq/coredevice/adf5356_reg.py b/artiq/coredevice/adf5356_reg.py index a61582d1a..54b4b839e 100644 --- a/artiq/coredevice/adf5356_reg.py +++ b/artiq/coredevice/adf5356_reg.py @@ -1,642 +1,643 @@ # auto-generated, do not edit -from artiq.language.core import portable -from artiq.language.types import TInt32 from numpy import int32 +from artiq.language.core import portable + + @portable -def ADF5356_REG0_AUTOCAL_GET(reg: TInt32) -> TInt32: +def ADF5356_REG0_AUTOCAL_GET(reg: int32) -> int32: return int32((reg >> 21) & 0x1) @portable -def ADF5356_REG0_AUTOCAL(x: TInt32) -> TInt32: +def ADF5356_REG0_AUTOCAL(x: int32) -> int32: return int32((x & 0x1) << 21) @portable -def ADF5356_REG0_AUTOCAL_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG0_AUTOCAL_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 21)) | ((x & 0x1) << 21)) @portable -def ADF5356_REG0_INT_VALUE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG0_INT_VALUE_GET(reg: int32) -> int32: return int32((reg >> 4) & 0xffff) @portable -def ADF5356_REG0_INT_VALUE(x: TInt32) -> TInt32: +def ADF5356_REG0_INT_VALUE(x: int32) -> int32: return int32((x & 0xffff) << 4) @portable -def ADF5356_REG0_INT_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG0_INT_VALUE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0xffff << 4)) | ((x & 0xffff) << 4)) @portable -def ADF5356_REG0_PRESCALER_GET(reg: TInt32) -> TInt32: +def ADF5356_REG0_PRESCALER_GET(reg: int32) -> int32: return int32((reg >> 20) & 0x1) @portable -def ADF5356_REG0_PRESCALER(x: TInt32) -> TInt32: +def ADF5356_REG0_PRESCALER(x: int32) -> int32: return int32((x & 0x1) << 20) @portable -def ADF5356_REG0_PRESCALER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG0_PRESCALER_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 20)) | ((x & 0x1) << 20)) @portable -def ADF5356_REG1_MAIN_FRAC_VALUE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG1_MAIN_FRAC_VALUE_GET(reg: int32) -> int32: return int32((reg >> 4) & 0xffffff) @portable -def ADF5356_REG1_MAIN_FRAC_VALUE(x: TInt32) -> TInt32: +def ADF5356_REG1_MAIN_FRAC_VALUE(x: int32) -> int32: return int32((x & 0xffffff) << 4) @portable -def ADF5356_REG1_MAIN_FRAC_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG1_MAIN_FRAC_VALUE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0xffffff << 4)) | ((x & 0xffffff) << 4)) @portable -def ADF5356_REG2_AUX_FRAC_LSB_VALUE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG2_AUX_FRAC_LSB_VALUE_GET(reg: int32) -> int32: return int32((reg >> 18) & 0x3fff) @portable -def ADF5356_REG2_AUX_FRAC_LSB_VALUE(x: TInt32) -> TInt32: +def ADF5356_REG2_AUX_FRAC_LSB_VALUE(x: int32) -> int32: return int32((x & 0x3fff) << 18) @portable -def ADF5356_REG2_AUX_FRAC_LSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG2_AUX_FRAC_LSB_VALUE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x3fff << 18)) | ((x & 0x3fff) << 18)) @portable -def ADF5356_REG2_AUX_MOD_LSB_VALUE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG2_AUX_MOD_LSB_VALUE_GET(reg: int32) -> int32: return int32((reg >> 4) & 0x3fff) @portable -def ADF5356_REG2_AUX_MOD_LSB_VALUE(x: TInt32) -> TInt32: +def ADF5356_REG2_AUX_MOD_LSB_VALUE(x: int32) -> int32: return int32((x & 0x3fff) << 4) @portable -def ADF5356_REG2_AUX_MOD_LSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG2_AUX_MOD_LSB_VALUE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x3fff << 4)) | ((x & 0x3fff) << 4)) @portable -def ADF5356_REG3_PHASE_ADJUST_GET(reg: TInt32) -> TInt32: +def ADF5356_REG3_PHASE_ADJUST_GET(reg: int32) -> int32: return int32((reg >> 28) & 0x1) @portable -def ADF5356_REG3_PHASE_ADJUST(x: TInt32) -> TInt32: +def ADF5356_REG3_PHASE_ADJUST(x: int32) -> int32: return int32((x & 0x1) << 28) @portable -def ADF5356_REG3_PHASE_ADJUST_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG3_PHASE_ADJUST_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 28)) | ((x & 0x1) << 28)) @portable -def ADF5356_REG3_PHASE_RESYNC_GET(reg: TInt32) -> TInt32: +def ADF5356_REG3_PHASE_RESYNC_GET(reg: int32) -> int32: return int32((reg >> 29) & 0x1) @portable -def ADF5356_REG3_PHASE_RESYNC(x: TInt32) -> TInt32: +def ADF5356_REG3_PHASE_RESYNC(x: int32) -> int32: return int32((x & 0x1) << 29) @portable -def ADF5356_REG3_PHASE_RESYNC_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG3_PHASE_RESYNC_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 29)) | ((x & 0x1) << 29)) @portable -def ADF5356_REG3_PHASE_VALUE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG3_PHASE_VALUE_GET(reg: int32) -> int32: return int32((reg >> 4) & 0xffffff) @portable -def ADF5356_REG3_PHASE_VALUE(x: TInt32) -> TInt32: +def ADF5356_REG3_PHASE_VALUE(x: int32) -> int32: return int32((x & 0xffffff) << 4) @portable -def ADF5356_REG3_PHASE_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG3_PHASE_VALUE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0xffffff << 4)) | ((x & 0xffffff) << 4)) @portable -def ADF5356_REG3_SD_LOAD_RESET_GET(reg: TInt32) -> TInt32: +def ADF5356_REG3_SD_LOAD_RESET_GET(reg: int32) -> int32: return int32((reg >> 30) & 0x1) @portable -def ADF5356_REG3_SD_LOAD_RESET(x: TInt32) -> TInt32: +def ADF5356_REG3_SD_LOAD_RESET(x: int32) -> int32: return int32((x & 0x1) << 30) @portable -def ADF5356_REG3_SD_LOAD_RESET_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG3_SD_LOAD_RESET_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 30)) | ((x & 0x1) << 30)) @portable -def ADF5356_REG4_COUNTER_RESET_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_COUNTER_RESET_GET(reg: int32) -> int32: return int32((reg >> 4) & 0x1) @portable -def ADF5356_REG4_COUNTER_RESET(x: TInt32) -> TInt32: +def ADF5356_REG4_COUNTER_RESET(x: int32) -> int32: return int32((x & 0x1) << 4) @portable -def ADF5356_REG4_COUNTER_RESET_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_COUNTER_RESET_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4)) @portable -def ADF5356_REG4_CP_THREE_STATE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_CP_THREE_STATE_GET(reg: int32) -> int32: return int32((reg >> 5) & 0x1) @portable -def ADF5356_REG4_CP_THREE_STATE(x: TInt32) -> TInt32: +def ADF5356_REG4_CP_THREE_STATE(x: int32) -> int32: return int32((x & 0x1) << 5) @portable -def ADF5356_REG4_CP_THREE_STATE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_CP_THREE_STATE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 5)) | ((x & 0x1) << 5)) @portable -def ADF5356_REG4_CURRENT_SETTING_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_CURRENT_SETTING_GET(reg: int32) -> int32: return int32((reg >> 10) & 0xf) @portable -def ADF5356_REG4_CURRENT_SETTING(x: TInt32) -> TInt32: +def ADF5356_REG4_CURRENT_SETTING(x: int32) -> int32: return int32((x & 0xf) << 10) @portable -def ADF5356_REG4_CURRENT_SETTING_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_CURRENT_SETTING_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0xf << 10)) | ((x & 0xf) << 10)) @portable -def ADF5356_REG4_DOUBLE_BUFF_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_DOUBLE_BUFF_GET(reg: int32) -> int32: return int32((reg >> 14) & 0x1) @portable -def ADF5356_REG4_DOUBLE_BUFF(x: TInt32) -> TInt32: +def ADF5356_REG4_DOUBLE_BUFF(x: int32) -> int32: return int32((x & 0x1) << 14) @portable -def ADF5356_REG4_DOUBLE_BUFF_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_DOUBLE_BUFF_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 14)) | ((x & 0x1) << 14)) @portable -def ADF5356_REG4_MUX_LOGIC_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_MUX_LOGIC_GET(reg: int32) -> int32: return int32((reg >> 8) & 0x1) @portable -def ADF5356_REG4_MUX_LOGIC(x: TInt32) -> TInt32: +def ADF5356_REG4_MUX_LOGIC(x: int32) -> int32: return int32((x & 0x1) << 8) @portable -def ADF5356_REG4_MUX_LOGIC_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_MUX_LOGIC_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 8)) | ((x & 0x1) << 8)) @portable -def ADF5356_REG4_MUXOUT_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_MUXOUT_GET(reg: int32) -> int32: return int32((reg >> 27) & 0x7) @portable -def ADF5356_REG4_MUXOUT(x: TInt32) -> TInt32: +def ADF5356_REG4_MUXOUT(x: int32) -> int32: return int32((x & 0x7) << 27) @portable -def ADF5356_REG4_MUXOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_MUXOUT_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x7 << 27)) | ((x & 0x7) << 27)) @portable -def ADF5356_REG4_PD_POLARITY_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_PD_POLARITY_GET(reg: int32) -> int32: return int32((reg >> 7) & 0x1) @portable -def ADF5356_REG4_PD_POLARITY(x: TInt32) -> TInt32: +def ADF5356_REG4_PD_POLARITY(x: int32) -> int32: return int32((x & 0x1) << 7) @portable -def ADF5356_REG4_PD_POLARITY_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_PD_POLARITY_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 7)) | ((x & 0x1) << 7)) @portable -def ADF5356_REG4_POWER_DOWN_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_POWER_DOWN_GET(reg: int32) -> int32: return int32((reg >> 6) & 0x1) @portable -def ADF5356_REG4_POWER_DOWN(x: TInt32) -> TInt32: +def ADF5356_REG4_POWER_DOWN(x: int32) -> int32: return int32((x & 0x1) << 6) @portable -def ADF5356_REG4_POWER_DOWN_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_POWER_DOWN_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 6)) | ((x & 0x1) << 6)) @portable -def ADF5356_REG4_R_COUNTER_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_R_COUNTER_GET(reg: int32) -> int32: return int32((reg >> 15) & 0x3ff) @portable -def ADF5356_REG4_R_COUNTER(x: TInt32) -> TInt32: +def ADF5356_REG4_R_COUNTER(x: int32) -> int32: return int32((x & 0x3ff) << 15) @portable -def ADF5356_REG4_R_COUNTER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_R_COUNTER_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x3ff << 15)) | ((x & 0x3ff) << 15)) @portable -def ADF5356_REG4_R_DIVIDER_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_R_DIVIDER_GET(reg: int32) -> int32: return int32((reg >> 25) & 0x1) @portable -def ADF5356_REG4_R_DIVIDER(x: TInt32) -> TInt32: +def ADF5356_REG4_R_DIVIDER(x: int32) -> int32: return int32((x & 0x1) << 25) @portable -def ADF5356_REG4_R_DIVIDER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_R_DIVIDER_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 25)) | ((x & 0x1) << 25)) @portable -def ADF5356_REG4_R_DOUBLER_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_R_DOUBLER_GET(reg: int32) -> int32: return int32((reg >> 26) & 0x1) @portable -def ADF5356_REG4_R_DOUBLER(x: TInt32) -> TInt32: +def ADF5356_REG4_R_DOUBLER(x: int32) -> int32: return int32((x & 0x1) << 26) @portable -def ADF5356_REG4_R_DOUBLER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_R_DOUBLER_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 26)) | ((x & 0x1) << 26)) @portable -def ADF5356_REG4_REF_MODE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG4_REF_MODE_GET(reg: int32) -> int32: return int32((reg >> 9) & 0x1) @portable -def ADF5356_REG4_REF_MODE(x: TInt32) -> TInt32: +def ADF5356_REG4_REF_MODE(x: int32) -> int32: return int32((x & 0x1) << 9) @portable -def ADF5356_REG4_REF_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG4_REF_MODE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 9)) | ((x & 0x1) << 9)) @portable -def ADF5356_REG6_BLEED_POLARITY_GET(reg: TInt32) -> TInt32: +def ADF5356_REG6_BLEED_POLARITY_GET(reg: int32) -> int32: return int32((reg >> 31) & 0x1) @portable -def ADF5356_REG6_BLEED_POLARITY(x: TInt32) -> TInt32: +def ADF5356_REG6_BLEED_POLARITY(x: int32) -> int32: return int32((x & 0x1) << 31) @portable -def ADF5356_REG6_BLEED_POLARITY_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG6_BLEED_POLARITY_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 31)) | ((x & 0x1) << 31)) @portable -def ADF5356_REG6_CP_BLEED_CURRENT_GET(reg: TInt32) -> TInt32: +def ADF5356_REG6_CP_BLEED_CURRENT_GET(reg: int32) -> int32: return int32((reg >> 13) & 0xff) @portable -def ADF5356_REG6_CP_BLEED_CURRENT(x: TInt32) -> TInt32: +def ADF5356_REG6_CP_BLEED_CURRENT(x: int32) -> int32: return int32((x & 0xff) << 13) @portable -def ADF5356_REG6_CP_BLEED_CURRENT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG6_CP_BLEED_CURRENT_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0xff << 13)) | ((x & 0xff) << 13)) @portable -def ADF5356_REG6_FB_SELECT_GET(reg: TInt32) -> TInt32: +def ADF5356_REG6_FB_SELECT_GET(reg: int32) -> int32: return int32((reg >> 24) & 0x1) @portable -def ADF5356_REG6_FB_SELECT(x: TInt32) -> TInt32: +def ADF5356_REG6_FB_SELECT(x: int32) -> int32: return int32((x & 0x1) << 24) @portable -def ADF5356_REG6_FB_SELECT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG6_FB_SELECT_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 24)) | ((x & 0x1) << 24)) @portable -def ADF5356_REG6_GATE_BLEED_GET(reg: TInt32) -> TInt32: +def ADF5356_REG6_GATE_BLEED_GET(reg: int32) -> int32: return int32((reg >> 30) & 0x1) @portable -def ADF5356_REG6_GATE_BLEED(x: TInt32) -> TInt32: +def ADF5356_REG6_GATE_BLEED(x: int32) -> int32: return int32((x & 0x1) << 30) @portable -def ADF5356_REG6_GATE_BLEED_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG6_GATE_BLEED_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 30)) | ((x & 0x1) << 30)) @portable -def ADF5356_REG6_MUTE_TILL_LD_GET(reg: TInt32) -> TInt32: +def ADF5356_REG6_MUTE_TILL_LD_GET(reg: int32) -> int32: return int32((reg >> 11) & 0x1) @portable -def ADF5356_REG6_MUTE_TILL_LD(x: TInt32) -> TInt32: +def ADF5356_REG6_MUTE_TILL_LD(x: int32) -> int32: return int32((x & 0x1) << 11) @portable -def ADF5356_REG6_MUTE_TILL_LD_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG6_MUTE_TILL_LD_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 11)) | ((x & 0x1) << 11)) @portable -def ADF5356_REG6_NEGATIVE_BLEED_GET(reg: TInt32) -> TInt32: +def ADF5356_REG6_NEGATIVE_BLEED_GET(reg: int32) -> int32: return int32((reg >> 29) & 0x1) @portable -def ADF5356_REG6_NEGATIVE_BLEED(x: TInt32) -> TInt32: +def ADF5356_REG6_NEGATIVE_BLEED(x: int32) -> int32: return int32((x & 0x1) << 29) @portable -def ADF5356_REG6_NEGATIVE_BLEED_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG6_NEGATIVE_BLEED_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 29)) | ((x & 0x1) << 29)) @portable -def ADF5356_REG6_RF_DIVIDER_SELECT_GET(reg: TInt32) -> TInt32: +def ADF5356_REG6_RF_DIVIDER_SELECT_GET(reg: int32) -> int32: return int32((reg >> 21) & 0x7) @portable -def ADF5356_REG6_RF_DIVIDER_SELECT(x: TInt32) -> TInt32: +def ADF5356_REG6_RF_DIVIDER_SELECT(x: int32) -> int32: return int32((x & 0x7) << 21) @portable -def ADF5356_REG6_RF_DIVIDER_SELECT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG6_RF_DIVIDER_SELECT_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x7 << 21)) | ((x & 0x7) << 21)) @portable -def ADF5356_REG6_RF_OUTPUT_A_ENABLE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG6_RF_OUTPUT_A_ENABLE_GET(reg: int32) -> int32: return int32((reg >> 6) & 0x1) @portable -def ADF5356_REG6_RF_OUTPUT_A_ENABLE(x: TInt32) -> TInt32: +def ADF5356_REG6_RF_OUTPUT_A_ENABLE(x: int32) -> int32: return int32((x & 0x1) << 6) @portable -def ADF5356_REG6_RF_OUTPUT_A_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG6_RF_OUTPUT_A_ENABLE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 6)) | ((x & 0x1) << 6)) @portable -def ADF5356_REG6_RF_OUTPUT_A_POWER_GET(reg: TInt32) -> TInt32: +def ADF5356_REG6_RF_OUTPUT_A_POWER_GET(reg: int32) -> int32: return int32((reg >> 4) & 0x3) @portable -def ADF5356_REG6_RF_OUTPUT_A_POWER(x: TInt32) -> TInt32: +def ADF5356_REG6_RF_OUTPUT_A_POWER(x: int32) -> int32: return int32((x & 0x3) << 4) @portable -def ADF5356_REG6_RF_OUTPUT_A_POWER_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG6_RF_OUTPUT_A_POWER_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x3 << 4)) | ((x & 0x3) << 4)) @portable -def ADF5356_REG6_RF_OUTPUT_B_ENABLE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG6_RF_OUTPUT_B_ENABLE_GET(reg: int32) -> int32: return int32((reg >> 10) & 0x1) @portable -def ADF5356_REG6_RF_OUTPUT_B_ENABLE(x: TInt32) -> TInt32: +def ADF5356_REG6_RF_OUTPUT_B_ENABLE(x: int32) -> int32: return int32((x & 0x1) << 10) @portable -def ADF5356_REG6_RF_OUTPUT_B_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG6_RF_OUTPUT_B_ENABLE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 10)) | ((x & 0x1) << 10)) @portable -def ADF5356_REG7_FRAC_N_LD_PRECISION_GET(reg: TInt32) -> TInt32: +def ADF5356_REG7_FRAC_N_LD_PRECISION_GET(reg: int32) -> int32: return int32((reg >> 5) & 0x3) @portable -def ADF5356_REG7_FRAC_N_LD_PRECISION(x: TInt32) -> TInt32: +def ADF5356_REG7_FRAC_N_LD_PRECISION(x: int32) -> int32: return int32((x & 0x3) << 5) @portable -def ADF5356_REG7_FRAC_N_LD_PRECISION_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG7_FRAC_N_LD_PRECISION_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x3 << 5)) | ((x & 0x3) << 5)) @portable -def ADF5356_REG7_LD_CYCLE_COUNT_GET(reg: TInt32) -> TInt32: +def ADF5356_REG7_LD_CYCLE_COUNT_GET(reg: int32) -> int32: return int32((reg >> 8) & 0x3) @portable -def ADF5356_REG7_LD_CYCLE_COUNT(x: TInt32) -> TInt32: +def ADF5356_REG7_LD_CYCLE_COUNT(x: int32) -> int32: return int32((x & 0x3) << 8) @portable -def ADF5356_REG7_LD_CYCLE_COUNT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG7_LD_CYCLE_COUNT_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x3 << 8)) | ((x & 0x3) << 8)) @portable -def ADF5356_REG7_LD_MODE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG7_LD_MODE_GET(reg: int32) -> int32: return int32((reg >> 4) & 0x1) @portable -def ADF5356_REG7_LD_MODE(x: TInt32) -> TInt32: +def ADF5356_REG7_LD_MODE(x: int32) -> int32: return int32((x & 0x1) << 4) @portable -def ADF5356_REG7_LD_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG7_LD_MODE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4)) @portable -def ADF5356_REG7_LE_SEL_SYNC_EDGE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG7_LE_SEL_SYNC_EDGE_GET(reg: int32) -> int32: return int32((reg >> 27) & 0x1) @portable -def ADF5356_REG7_LE_SEL_SYNC_EDGE(x: TInt32) -> TInt32: +def ADF5356_REG7_LE_SEL_SYNC_EDGE(x: int32) -> int32: return int32((x & 0x1) << 27) @portable -def ADF5356_REG7_LE_SEL_SYNC_EDGE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG7_LE_SEL_SYNC_EDGE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 27)) | ((x & 0x1) << 27)) @portable -def ADF5356_REG7_LE_SYNC_GET(reg: TInt32) -> TInt32: +def ADF5356_REG7_LE_SYNC_GET(reg: int32) -> int32: return int32((reg >> 25) & 0x1) @portable -def ADF5356_REG7_LE_SYNC(x: TInt32) -> TInt32: +def ADF5356_REG7_LE_SYNC(x: int32) -> int32: return int32((x & 0x1) << 25) @portable -def ADF5356_REG7_LE_SYNC_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG7_LE_SYNC_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 25)) | ((x & 0x1) << 25)) @portable -def ADF5356_REG7_LOL_MODE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG7_LOL_MODE_GET(reg: int32) -> int32: return int32((reg >> 7) & 0x1) @portable -def ADF5356_REG7_LOL_MODE(x: TInt32) -> TInt32: +def ADF5356_REG7_LOL_MODE(x: int32) -> int32: return int32((x & 0x1) << 7) @portable -def ADF5356_REG7_LOL_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG7_LOL_MODE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 7)) | ((x & 0x1) << 7)) @portable -def ADF5356_REG9_AUTOCAL_TIMEOUT_GET(reg: TInt32) -> TInt32: +def ADF5356_REG9_AUTOCAL_TIMEOUT_GET(reg: int32) -> int32: return int32((reg >> 9) & 0x1f) @portable -def ADF5356_REG9_AUTOCAL_TIMEOUT(x: TInt32) -> TInt32: +def ADF5356_REG9_AUTOCAL_TIMEOUT(x: int32) -> int32: return int32((x & 0x1f) << 9) @portable -def ADF5356_REG9_AUTOCAL_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG9_AUTOCAL_TIMEOUT_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1f << 9)) | ((x & 0x1f) << 9)) @portable -def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_GET(reg: TInt32) -> TInt32: +def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_GET(reg: int32) -> int32: return int32((reg >> 4) & 0x1f) @portable -def ADF5356_REG9_SYNTH_LOCK_TIMEOUT(x: TInt32) -> TInt32: +def ADF5356_REG9_SYNTH_LOCK_TIMEOUT(x: int32) -> int32: return int32((x & 0x1f) << 4) @portable -def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1f << 4)) | ((x & 0x1f) << 4)) @portable -def ADF5356_REG9_TIMEOUT_GET(reg: TInt32) -> TInt32: +def ADF5356_REG9_TIMEOUT_GET(reg: int32) -> int32: return int32((reg >> 14) & 0x3ff) @portable -def ADF5356_REG9_TIMEOUT(x: TInt32) -> TInt32: +def ADF5356_REG9_TIMEOUT(x: int32) -> int32: return int32((x & 0x3ff) << 14) @portable -def ADF5356_REG9_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG9_TIMEOUT_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x3ff << 14)) | ((x & 0x3ff) << 14)) @portable -def ADF5356_REG9_VCO_BAND_DIVISION_GET(reg: TInt32) -> TInt32: +def ADF5356_REG9_VCO_BAND_DIVISION_GET(reg: int32) -> int32: return int32((reg >> 24) & 0xff) @portable -def ADF5356_REG9_VCO_BAND_DIVISION(x: TInt32) -> TInt32: +def ADF5356_REG9_VCO_BAND_DIVISION(x: int32) -> int32: return int32((x & 0xff) << 24) @portable -def ADF5356_REG9_VCO_BAND_DIVISION_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG9_VCO_BAND_DIVISION_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0xff << 24)) | ((x & 0xff) << 24)) @portable -def ADF5356_REG10_ADC_CLK_DIV_GET(reg: TInt32) -> TInt32: +def ADF5356_REG10_ADC_CLK_DIV_GET(reg: int32) -> int32: return int32((reg >> 6) & 0xff) @portable -def ADF5356_REG10_ADC_CLK_DIV(x: TInt32) -> TInt32: +def ADF5356_REG10_ADC_CLK_DIV(x: int32) -> int32: return int32((x & 0xff) << 6) @portable -def ADF5356_REG10_ADC_CLK_DIV_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG10_ADC_CLK_DIV_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0xff << 6)) | ((x & 0xff) << 6)) @portable -def ADF5356_REG10_ADC_CONV_GET(reg: TInt32) -> TInt32: +def ADF5356_REG10_ADC_CONV_GET(reg: int32) -> int32: return int32((reg >> 5) & 0x1) @portable -def ADF5356_REG10_ADC_CONV(x: TInt32) -> TInt32: +def ADF5356_REG10_ADC_CONV(x: int32) -> int32: return int32((x & 0x1) << 5) @portable -def ADF5356_REG10_ADC_CONV_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG10_ADC_CONV_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 5)) | ((x & 0x1) << 5)) @portable -def ADF5356_REG10_ADC_ENABLE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG10_ADC_ENABLE_GET(reg: int32) -> int32: return int32((reg >> 4) & 0x1) @portable -def ADF5356_REG10_ADC_ENABLE(x: TInt32) -> TInt32: +def ADF5356_REG10_ADC_ENABLE(x: int32) -> int32: return int32((x & 0x1) << 4) @portable -def ADF5356_REG10_ADC_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG10_ADC_ENABLE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4)) @portable -def ADF5356_REG11_VCO_BAND_HOLD_GET(reg: TInt32) -> TInt32: +def ADF5356_REG11_VCO_BAND_HOLD_GET(reg: int32) -> int32: return int32((reg >> 24) & 0x1) @portable -def ADF5356_REG11_VCO_BAND_HOLD(x: TInt32) -> TInt32: +def ADF5356_REG11_VCO_BAND_HOLD(x: int32) -> int32: return int32((x & 0x1) << 24) @portable -def ADF5356_REG11_VCO_BAND_HOLD_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG11_VCO_BAND_HOLD_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x1 << 24)) | ((x & 0x1) << 24)) @portable -def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_GET(reg: int32) -> int32: return int32((reg >> 12) & 0xfffff) @portable -def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE(x: TInt32) -> TInt32: +def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE(x: int32) -> int32: return int32((x & 0xfffff) << 12) @portable -def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0xfffff << 12)) | ((x & 0xfffff) << 12)) @portable -def ADF5356_REG13_AUX_FRAC_MSB_VALUE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG13_AUX_FRAC_MSB_VALUE_GET(reg: int32) -> int32: return int32((reg >> 18) & 0x3fff) @portable -def ADF5356_REG13_AUX_FRAC_MSB_VALUE(x: TInt32) -> TInt32: +def ADF5356_REG13_AUX_FRAC_MSB_VALUE(x: int32) -> int32: return int32((x & 0x3fff) << 18) @portable -def ADF5356_REG13_AUX_FRAC_MSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG13_AUX_FRAC_MSB_VALUE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x3fff << 18)) | ((x & 0x3fff) << 18)) @portable -def ADF5356_REG13_AUX_MOD_MSB_VALUE_GET(reg: TInt32) -> TInt32: +def ADF5356_REG13_AUX_MOD_MSB_VALUE_GET(reg: int32) -> int32: return int32((reg >> 4) & 0x3fff) @portable -def ADF5356_REG13_AUX_MOD_MSB_VALUE(x: TInt32) -> TInt32: +def ADF5356_REG13_AUX_MOD_MSB_VALUE(x: int32) -> int32: return int32((x & 0x3fff) << 4) @portable -def ADF5356_REG13_AUX_MOD_MSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32: +def ADF5356_REG13_AUX_MOD_MSB_VALUE_UPDATE(reg: int32, x: int32) -> int32: return int32((reg & ~(0x3fff << 4)) | ((x & 0x3fff) << 4)) ADF5356_NUM_REGS = 14 diff --git a/artiq/coredevice/almazny.py b/artiq/coredevice/almazny.py index 9f3a8ce58..38d9d5152 100644 --- a/artiq/coredevice/almazny.py +++ b/artiq/coredevice/almazny.py @@ -1,7 +1,11 @@ -from artiq.language.core import kernel, portable, delay +from numpy import int32 + +from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable from artiq.language.units import us -from numpy import int32 +from artiq.coredevice.core import Core +from artiq.coredevice.mirny import Mirny +from artiq.coredevice.spi2 import * # almazny-specific data @@ -15,6 +19,7 @@ ALMAZNY_LEGACY_OE_SHIFT = 12 ALMAZNY_LEGACY_SPIT_WR = 32 +@nac3 class AlmaznyLegacy: """ Almazny (High-frequency mezzanine board for Mirny) @@ -25,8 +30,15 @@ class AlmaznyLegacy: :param host_mirny: :class:`~artiq.coredevice.mirny.Mirny` device Almazny is connected to """ + core: KernelInvariant[Core] + mirny_cpld: KernelInvariant[Mirny] + att_mu: Kernel[list[int32]] + channel_sw: Kernel[list[int32]] + output_enable: Kernel[bool] + def __init__(self, dmgr, host_mirny): self.mirny_cpld = dmgr.get(host_mirny) + self.core = self.mirny_cpld.core self.att_mu = [0x3f] * 4 self.channel_sw = [0] * 4 self.output_enable = False @@ -36,7 +48,7 @@ class AlmaznyLegacy: self.output_toggle(self.output_enable) @kernel - def att_to_mu(self, att): + def att_to_mu(self, att: float) -> int32: """ Convert an attenuator setting in dB to machine units. @@ -49,17 +61,17 @@ class AlmaznyLegacy: return mu @kernel - def mu_to_att(self, att_mu): + def mu_to_att(self, att_mu: int32) -> float: """ Convert a digital attenuator setting to dB. :param att_mu: attenuator setting in machine units :return: attenuator setting in dB """ - return att_mu / 2 + return float(att_mu) / 2. @kernel - def set_att(self, channel, att, rf_switch=True): + def set_att(self, channel: int32, att: float, rf_switch: bool = True): """ Sets attenuators on chosen shift register (channel). @@ -70,7 +82,7 @@ class AlmaznyLegacy: self.set_att_mu(channel, self.att_to_mu(att), rf_switch) @kernel - def set_att_mu(self, channel, att_mu, rf_switch=True): + def set_att_mu(self, channel: int32, att_mu: int32, rf_switch: bool = True): """ Sets attenuators on chosen shift register (channel). @@ -83,7 +95,7 @@ class AlmaznyLegacy: self._update_register(channel) @kernel - def output_toggle(self, oe): + def output_toggle(self, oe: bool): """ Toggles output on all shift registers on or off. @@ -92,13 +104,13 @@ class AlmaznyLegacy: self.output_enable = oe cfg_reg = self.mirny_cpld.read_reg(1) en = 1 if self.output_enable else 0 - delay(100 * us) + self.core.delay(100. * us) new_reg = (en << ALMAZNY_LEGACY_OE_SHIFT) | (cfg_reg & 0x3FF) self.mirny_cpld.write_reg(1, new_reg) - delay(100 * us) + self.core.delay(100. * us) @kernel - def _flip_mu_bits(self, mu): + def _flip_mu_bits(self, mu: int32) -> int32: # in this form MSB is actually 0.5dB attenuator # unnatural for users, so we flip the six bits return (((mu & 0x01) << 5) @@ -109,16 +121,17 @@ class AlmaznyLegacy: | ((mu & 0x20) >> 5)) @kernel - def _update_register(self, ch): + def _update_register(self, ch: int32): self.mirny_cpld.write_ext( ALMAZNY_LEGACY_REG_BASE + ch, 8, self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6), ALMAZNY_LEGACY_SPIT_WR ) - delay(100 * us) + self.core.delay(100. * us) +@nac3 class AlmaznyChannel: """ Driver for one Almazny channel. @@ -133,12 +146,17 @@ class AlmaznyChannel: :param channel: channel index (0-3) """ + core: KernelInvariant[Core] + mirny_cpld: KernelInvariant[Mirny] + channel: KernelInvariant[int32] + def __init__(self, dmgr, host_mirny, channel): - self.channel = channel self.mirny_cpld = dmgr.get(host_mirny) + self.core = self.mirny_cpld.core + self.channel = channel @portable - def to_mu(self, att, enable, led): + def to_mu(self, att: float, enable: bool, led: bool) -> int32: """ Convert an attenuation in dB, RF switch state and LED state to machine units. @@ -148,7 +166,7 @@ class AlmaznyChannel: :param led: LED state (bool) :return: channel setting in machine units """ - mu = int32(round(att * 2.)) + mu = round(att * 2.) if mu >= 64 or mu < 0: raise ValueError("Attenuation out of range") # unfortunate hardware design: bit reverse @@ -161,7 +179,7 @@ class AlmaznyChannel: return mu @kernel - def set_mu(self, mu): + def set_mu(self, mu: int32): """ Set channel state (machine units). @@ -171,7 +189,7 @@ class AlmaznyChannel: addr=0xc + self.channel, length=8, data=mu, ext_div=32) @kernel - def set(self, att, enable, led=False): + def set(self, att: float, enable: bool, led: bool = False): """ Set attenuation, RF switch, and LED state (SI units). diff --git a/artiq/coredevice/cache.py b/artiq/coredevice/cache.py index eeaf84687..96d49825f 100644 --- a/artiq/coredevice/cache.py +++ b/artiq/coredevice/cache.py @@ -1,40 +1,47 @@ -from artiq.language.core import * -from artiq.language.types import * +from numpy import int32 + +from artiq.language.core import nac3, extern, kernel, KernelInvariant +from artiq.coredevice.core import Core -@syscall(flags={"nounwind"}) -def cache_get(key: TStr) -> TList(TInt32): +@extern +def cache_get(key: str) -> list[int32]: raise NotImplementedError("syscall not simulated") -@syscall -def cache_put(key: TStr, value: TList(TInt32)) -> TNone: +@extern +def cache_put(key: str, value: list[int32]): raise NotImplementedError("syscall not simulated") +@nac3 class CoreCache: """Core device cache access""" + + core: KernelInvariant[Core] + def __init__(self, dmgr, core_device="core"): self.core = dmgr.get(core_device) - @kernel - def get(self, key): - """Extract a value from the core device cache. - After a value is extracted, it cannot be replaced with another value using - :meth:`put` until all kernel functions finish executing; attempting - to replace it will result in a :class:`~artiq.coredevice.exceptions.CacheError`. - - If the cache does not contain any value associated with `key`, an empty list - is returned. - - The value is not copied, so mutating it will change what's stored in the cache. - - :param str key: cache key - :return: a list of 32-bit integers - """ - return cache_get(key) + # NAC3TODO + # @kernel + # def get(self, key: str) -> list[int32]: + # """Extract a value from the core device cache. + # After a value is extracted, it cannot be replaced with another value using + # :meth:`put` until all kernel functions finish executing; attempting + # to replace it will result in a :class:`~artiq.coredevice.exceptions.CacheError`. + # + # If the cache does not contain any value associated with `key`, an empty list + # is returned. + # + # The value is not copied, so mutating it will change what's stored in the cache. + # + # :param str key: cache key + # :return: a list of 32-bit integers + # """ + # return cache_get(key) @kernel - def put(self, key, value): + def put(self, key: str, value: list[int32]): """Put a value into the core device cache. The value will persist until reboot. To remove a value from the cache, call :meth:`put` with an empty list. diff --git a/artiq/coredevice/comm_kernel.py b/artiq/coredevice/comm_kernel.py index 790692952..b6d807cd3 100644 --- a/artiq/coredevice/comm_kernel.py +++ b/artiq/coredevice/comm_kernel.py @@ -3,6 +3,9 @@ import logging import traceback import numpy import socket +import re +import linecache +import os import builtins from enum import Enum from fractions import Fraction @@ -10,6 +13,8 @@ from collections import namedtuple from artiq.coredevice import exceptions from artiq import __version__ as software_version +from artiq import __artiq_dir__ as artiq_dir + from sipyco.keepalive import create_connection logger = logging.getLogger(__name__) @@ -166,13 +171,116 @@ class CommKernelDummy: def run(self): pass - def serve(self, embedding_map, symbolizer, demangler): + def serve(self, embedding_map, symbolizer): pass def check_system_info(self): pass +class SourceLoader: + def __init__(self, runtime_root): + self.runtime_root = runtime_root + + def get_source(self, filename): + with open(os.path.join(self.runtime_root, filename)) as f: + return f.read() + +source_loader = SourceLoader(os.path.join(artiq_dir, "soc", "runtime")) + + +class CoreException: + """Information about an exception raised or passed through the core device.""" + def __init__(self, exceptions, exception_info, traceback, stack_pointers): + self.exceptions = exceptions + self.exception_info = exception_info + self.traceback = list(traceback) + self.stack_pointers = stack_pointers + + first_exception = exceptions[0] + name = first_exception[0] + if ':' in name: + exn_id, self.name = name.split(':', 2) + self.id = int(exn_id) + else: + self.id, self.name = 0, name + self.message = first_exception[1] + self.params = first_exception[2] + + def append_backtrace(self, record, inlined=False): + filename, line, column, function, address = record + stub_globals = {"__name__": filename, "__loader__": source_loader} + source_line = linecache.getline(filename, line, stub_globals) + indentation = re.search(r"^\s*", source_line).end() + + if address is None: + formatted_address = "" + elif inlined: + formatted_address = " (inlined)" + else: + formatted_address = " (RA=+0x{:x})".format(address) + + filename = filename.replace(artiq_dir, "") + lines = [] + if column == -1: + lines.append(" {}".format(source_line.strip() if source_line else "")) + lines.append(" File \"{file}\", line {line}, in {function}{address}". + format(file=filename, line=line, function=function, + address=formatted_address)) + else: + lines.append(" {}^".format(" " * (column - indentation))) + lines.append(" {}".format(source_line.strip() if source_line else "")) + lines.append(" File \"{file}\", line {line}, column {column}," + " in {function}{address}". + format(file=filename, line=line, column=column + 1, + function=function, address=formatted_address)) + return lines + + def single_traceback(self, exception_index): + # note that we insert in reversed order + lines = [] + last_sp = 0 + start_backtrace_index = self.exception_info[exception_index][1] + zipped = list(zip(self.traceback[start_backtrace_index:], + self.stack_pointers[start_backtrace_index:])) + exception = self.exceptions[exception_index] + name = exception[0] + message = exception[1] + params = exception[2] + if ':' in name: + exn_id, name = name.split(':', 2) + exn_id = int(exn_id) + else: + exn_id = 0 + lines.append("{}({}): {}".format(name, exn_id, message.format(*params))) + zipped.append(((exception[3], exception[4], exception[5], exception[6], + None, []), None)) + + for ((filename, line, column, function, address, inlined), sp) in zipped: + # backtrace of nested exceptions may be discontinuous + # but the stack pointer must increase monotonically + if sp is not None and sp <= last_sp: + continue + last_sp = sp + + for record in reversed(inlined): + lines += self.append_backtrace(record, True) + lines += self.append_backtrace((filename, line, column, function, + address)) + + lines.append("Traceback (most recent call first):") + + return "\n".join(reversed(lines)) + + def __str__(self): + tracebacks = [self.single_traceback(i) for i in range(len(self.exceptions))] + traceback_str = ('\n\nDuring handling of the above exception, ' + + 'another exception occurred:\n\n').join(tracebacks) + return 'Core Device Traceback:\n' +\ + traceback_str +\ + '\n\nEnd of Core Device Traceback\n' + + def incompatible_versions(v1, v2): if v1.endswith(".beta") or v2.endswith(".beta"): # Beta branches may introduce breaking changes. Check version strictly. @@ -580,9 +688,24 @@ class CommKernel: return_tags = self._read_bytes() if service_id == 0: - def service(obj, attr, value): return setattr(obj, attr, value) + def service(*values): + if embedding_map.expects_return: + embedding_map.return_value = values[0] + counter = 1 + else: + counter = 0 + + for obj in embedding_map.attributes_writeback: + old_val = obj["obj"] + if "fields" in obj: + for name in obj["fields"]: + setattr(old_val, name, values[counter]) + counter += 1 + else: + old_val[:] = values[counter] + counter += 1 else: - service = embedding_map.retrieve_object(service_id) + service = embedding_map.retrieve_function(service_id) logger.debug("rpc service: [%d]%r%s %r %r -> %s", service_id, service, (" (async)" if is_async else ""), args, kwargs, return_tags) @@ -652,7 +775,7 @@ class CommKernel: result, result, service) self._flush() - def _serve_exception(self, embedding_map, symbolizer, demangler): + def _serve_exception(self, embedding_map, symbolizer): exception_count = self._read_int32() nested_exceptions = [] @@ -676,10 +799,6 @@ class CommKernel: nested_exceptions.append([name, message, params, filename, line, column, function]) - demangled_names = demangler([ex[6] for ex in nested_exceptions]) - for i in range(exception_count): - nested_exceptions[i][6] = demangled_names[i] - exception_info = [] for _ in range(exception_count): sp = self._read_int32() @@ -696,7 +815,7 @@ class CommKernel: self._process_async_error() traceback = list(symbolizer(backtrace)) - core_exn = exceptions.CoreException(nested_exceptions, exception_info, + core_exn = CoreException(nested_exceptions, exception_info, traceback, stack_pointers) if core_exn.id == 0: @@ -730,13 +849,13 @@ class CommKernel: 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): + def serve(self, embedding_map, symbolizer): while True: self._read_header() if self._read_type == Reply.RPCRequest: self._serve_rpc(embedding_map) elif self._read_type == Reply.KernelException: - self._serve_exception(embedding_map, symbolizer, demangler) + self._serve_exception(embedding_map, symbolizer) elif self._read_type == Reply.ClockFailure: raise exceptions.ClockFailure else: diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index a7d41cc92..d74e46a10 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -1,73 +1,43 @@ -import os, sys -import numpy -from inspect import getfullargspec +import os, sys, tempfile, subprocess, io from functools import wraps +from numpy import int32, int64 -from pythonparser import diagnostic - -from artiq import __artiq_dir__ as artiq_dir +import nac3artiq from artiq.language.core import * -from artiq.language.types import * +from artiq.language.core import _ConstGenericMarker +from artiq.language import core as core_language from artiq.language.units import * - -from artiq.compiler.module import Module -from artiq.compiler.embedding import Stitcher -from artiq.compiler.targets import RV32IMATarget, RV32GTarget, CortexA9Target +from artiq.language.embedding_map import EmbeddingMap from artiq.coredevice.comm_kernel import CommKernel, CommKernelDummy -# Import for side effects (creating the exception classes). -from artiq.coredevice import exceptions -def _render_diagnostic(diagnostic, colored): - def shorten_path(path): - return path.replace(artiq_dir, "") - lines = [shorten_path(path) for path in diagnostic.render(colored=colored)] - return "\n".join(lines) - -colors_supported = os.name == "posix" -class _DiagnosticEngine(diagnostic.Engine): - def render_diagnostic(self, diagnostic): - sys.stderr.write(_render_diagnostic(diagnostic, colored=colors_supported) + "\n") - -class CompileError(Exception): - def __init__(self, diagnostic): - self.diagnostic = diagnostic - - def __str__(self): - # Prepend a newline so that the message shows up on after - # exception class name printed by Python. - return "\n" + _render_diagnostic(self.diagnostic, colored=colors_supported) - - -@syscall -def rtio_init() -> TNone: +@extern +def rtio_init(): raise NotImplementedError("syscall not simulated") -@syscall(flags={"nounwind", "nowrite"}) -def rtio_get_destination_status(linkno: TInt32) -> TBool: +@extern +def rtio_get_destination_status(destination: int32) -> bool: raise NotImplementedError("syscall not simulated") -@syscall(flags={"nounwind", "nowrite"}) -def rtio_get_counter() -> TInt64: +@extern +def rtio_get_counter() -> int64: raise NotImplementedError("syscall not simulated") -@syscall -def test_exception_id_sync(id: TInt32) -> TNone: +@extern +def test_exception_id_sync(id: int32): raise NotImplementedError("syscall not simulated") -def get_target_cls(target): - if target == "rv32g": - return RV32GTarget - elif target == "rv32ima": - return RV32IMATarget - elif target == "cortexa9": - return CortexA9Target - else: - raise ValueError("Unsupported target") +artiq_builtins = { + "none": none, + "virtual": virtual, + "_ConstGenericMarker": _ConstGenericMarker, + "Option": Option, +} +@nac3 class Core: """Core device driver. @@ -86,12 +56,11 @@ class Core: :param analyze_at_run_end: automatically trigger the core device analyzer proxy after the Experiment's run stage finishes. :param report_invariants: report variables which are not changed inside - kernels and are thus candidates for inclusion in kernel_invariants + kernels and are thus candidates for KernelInvariant annotation """ - - kernel_invariants = { - "core", "ref_period", "coarse_ref_period", "ref_multiplier", - } + ref_period: KernelInvariant[float] + ref_multiplier: KernelInvariant[int32] + coarse_ref_period: KernelInvariant[float] def __init__(self, dmgr, host, ref_period, @@ -101,22 +70,26 @@ class Core: report_invariants=False): self.ref_period = ref_period self.ref_multiplier = ref_multiplier - self.satellite_cpu_targets = satellite_cpu_targets - self.target_cls = get_target_cls(target) + self.coarse_ref_period = ref_period*ref_multiplier if host is None: self.comm = CommKernelDummy() else: self.comm = CommKernel(host) - self.analyzer_proxy_name = analyzer_proxy - self.analyze_at_run_end = analyze_at_run_end - self.report_invariants = report_invariants self.first_run = True self.dmgr = dmgr self.core = self self.comm.core = self + + self.target = target + self.analyzed = False + self.compiler = nac3artiq.NAC3(target, artiq_builtins) + + self.analyzer_proxy_name = analyzer_proxy + self.analyze_at_run_end = analyze_at_run_end self.analyzer_proxy = None + self.report_invariants = report_invariants def notify_run_end(self): if self.analyze_at_run_end: @@ -127,113 +100,53 @@ class Core: """ self.comm.close() - def compile(self, function, args, kwargs, set_result=None, - attribute_writeback=True, print_as_rpc=True, - target=None, destination=0, subkernel_arg_types=[], - old_embedding_map=None): - try: - engine = _DiagnosticEngine(all_errors_are_fatal=True) + def compile(self, method, args, kwargs, embedding_map, file_output=None, target=None): + if target is not None: + # NAC3TODO: subkernels + raise NotImplementedError - stitcher = Stitcher(engine=engine, core=self, dmgr=self.dmgr, - print_as_rpc=print_as_rpc, - destination=destination, subkernel_arg_types=subkernel_arg_types, - old_embedding_map=old_embedding_map) - stitcher.stitch_call(function, args, kwargs, set_result) - stitcher.finalize() + if not self.analyzed: + self.compiler.analyze( + core_language._registered_functions, + core_language._registered_classes, + core_language._special_ids, + core_language._registered_modules + ) + self.analyzed = True - module = Module(stitcher, - ref_period=self.ref_period, - attribute_writeback=attribute_writeback, - remarks=self.report_invariants) - target = target if target is not None else self.target_cls() + if hasattr(method, "__self__"): + obj = method.__self__ + name = method.__name__ + else: + obj = method + name = "" - library = target.compile_and_link([module]) - stripped_library = target.strip(library) + # NAC3TODO: handle self.report_invariants + if file_output is None: + return self.compiler.compile_method_to_mem(obj, name, args, embedding_map) + else: + self.compiler.compile_method_to_file(obj, name, args, file_output, embedding_map) - return stitcher.embedding_map, stripped_library, \ - lambda addresses: target.symbolize(library, addresses), \ - lambda symbols: target.demangle(symbols), \ - module.subkernel_arg_types - except diagnostic.Error as error: - raise CompileError(error.diagnostic) from error + def run(self, function, args, kwargs): + embedding_map = EmbeddingMap() + kernel_library = self.compile(function, args, kwargs, embedding_map) - def _run_compiled(self, kernel_library, embedding_map, symbolizer, demangler): + self._run_compiled(kernel_library, embedding_map) + + # set by NAC3 + if embedding_map.expects_return: + return embedding_map.return_value + + def _run_compiled(self, kernel_library, embedding_map): if self.first_run: self.comm.check_system_info() self.first_run = False + + symbolizer = lambda addresses: symbolize(kernel_library, addresses) + self.comm.load(kernel_library) self.comm.run() - self.comm.serve(embedding_map, symbolizer, demangler) - - def run(self, function, args, kwargs): - result = None - @rpc(flags={"async"}) - def set_result(new_result): - nonlocal result - result = new_result - embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \ - self.compile(function, args, kwargs, set_result) - self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types) - self._run_compiled(kernel_library, embedding_map, symbolizer, demangler) - return result - - def compile_subkernel(self, sid, subkernel_fn, embedding_map, args, subkernel_arg_types, subkernels): - # pass self to subkernels (if applicable) - # assuming the first argument is self - subkernel_args = getfullargspec(subkernel_fn.artiq_embedded.function) - self_arg = [] - if len(subkernel_args[0]) > 0: - if subkernel_args[0][0] == 'self': - self_arg = args[:1] - destination = subkernel_fn.artiq_embedded.destination - destination_tgt = self.satellite_cpu_targets[destination] - target = get_target_cls(destination_tgt)(subkernel_id=sid) - object_map, kernel_library, _, _, _ = \ - self.compile(subkernel_fn, self_arg, {}, attribute_writeback=False, - print_as_rpc=False, target=target, destination=destination, - subkernel_arg_types=subkernel_arg_types.get(sid, []), - old_embedding_map=embedding_map) - if object_map.has_rpc(): - raise ValueError("Subkernel must not use RPC") - return destination, kernel_library, object_map - - def compile_and_upload_subkernels(self, embedding_map, args, subkernel_arg_types): - subkernels = embedding_map.subkernels() - subkernels_compiled = [] - while True: - new_subkernels = {} - for sid, subkernel_fn in subkernels.items(): - if sid in subkernels_compiled: - continue - destination, kernel_library, embedding_map = \ - self.compile_subkernel(sid, subkernel_fn, embedding_map, - args, subkernel_arg_types, subkernels) - self.comm.upload_subkernel(kernel_library, sid, destination) - new_subkernels.update(embedding_map.subkernels()) - subkernels_compiled.append(sid) - if new_subkernels == subkernels: - break - subkernels.update(new_subkernels) - # check for messages without a send/recv pair - unpaired_messages = embedding_map.subkernel_messages_unpaired() - if unpaired_messages: - for unpaired_message in unpaired_messages: - engine = _DiagnosticEngine(all_errors_are_fatal=False) - # errors are non-fatal in order to display - # all unpaired message errors before raising an excption - if unpaired_message.send_loc is None: - diag = diagnostic.Diagnostic("error", - "subkernel message '{name}' only has a receiver but no sender", - {"name": unpaired_message.name}, - unpaired_message.recv_loc) - else: - diag = diagnostic.Diagnostic("error", - "subkernel message '{name}' only has a sender but no receiver", - {"name": unpaired_message.name}, - unpaired_message.send_loc) - engine.process(diag) - raise ValueError("Found subkernel message(s) without a full send/recv pair") - + self.comm.serve(embedding_map, symbolizer) def precompile(self, function, *args, **kwargs): """Precompile a kernel and return a callable that executes it on the core device @@ -251,54 +164,51 @@ class Core: Similarly, modified values are not written back, and explicit RPC should be used to modify host objects. Carefully review the source code of drivers calls used in precompiled kernels, as - they may rely on host object attributes being transferred between kernel calls. - Examples include code used to control DDS phase and Urukul RF switch control + they may rely on host object attributes being transfered between kernel calls. + Examples include code used to control DDS phase, and Urukul RF switch control via the CPLD register. The return value of the callable is the return value of the kernel, if any. The callable may be called several times. """ - if not hasattr(function, "artiq_embedded"): + if not getattr(function, "__artiq_kernel__"): raise ValueError("Argument is not a kernel") - result = None - @rpc(flags={"async"}) - def set_result(new_result): - nonlocal result - result = new_result - - embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \ - self.compile(function, args, kwargs, set_result, attribute_writeback=False) - self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types) + embedding_map = EmbeddingMap() + kernel_library = self.compile(function, args, kwargs, embedding_map) @wraps(function) def run_precompiled(): - nonlocal result - self._run_compiled(kernel_library, embedding_map, symbolizer, demangler) - return result + # NAC3TODO: support returning values + # https://git.m-labs.hk/M-Labs/nac3/issues/101 + self._run_compiled(kernel_library, embedding_map) return run_precompiled @portable - def seconds_to_mu(self, seconds): + def seconds_to_mu(self, seconds: float) -> int64: """Convert seconds to the corresponding number of machine units (fine RTIO cycles). :param seconds: time (in seconds) to convert. """ - return numpy.int64(seconds//self.ref_period) + return round64(seconds/self.ref_period) @portable - def mu_to_seconds(self, mu): + def mu_to_seconds(self, mu: int64) -> float: """Convert machine units (fine RTIO cycles) to seconds. :param mu: cycle count to convert. """ - return mu*self.ref_period + return float(mu)*self.ref_period @kernel - def get_rtio_counter_mu(self): + def delay(self, dt: float): + delay_mu(self.seconds_to_mu(dt)) + + @kernel + def get_rtio_counter_mu(self) -> int64: """Retrieve the current value of the hardware RTIO timeline counter. As the timing of kernel code executed on the CPU is inherently @@ -311,7 +221,7 @@ class Core: return rtio_get_counter() @kernel - def wait_until_mu(self, cursor_mu): + def wait_until_mu(self, cursor_mu: int64): """Block execution until the hardware RTIO counter reaches the given value (see :meth:`get_rtio_counter_mu`). @@ -322,7 +232,7 @@ class Core: pass @kernel - def get_rtio_destination_status(self, destination): + def get_rtio_destination_status(self, destination: int32) -> bool: """Returns whether the specified RTIO destination is up. This is particularly useful in startup kernels to delay startup until certain DRTIO destinations are available.""" @@ -334,7 +244,7 @@ class Core: at the current value of the hardware RTIO counter plus a margin of 125000 machine units.""" rtio_init() - at_mu(rtio_get_counter() + 125000) + at_mu(rtio_get_counter() + int64(125000)) @kernel def break_realtime(self): @@ -343,10 +253,10 @@ class Core: If the time cursor is already after that position, this function does nothing.""" - min_now = rtio_get_counter() + 125000 + min_now = rtio_get_counter() + int64(125000) if now_mu() < min_now: at_mu(min_now) - + def trigger_analyzer_proxy(self): """Causes the core analyzer proxy to retrieve a dump from the device, and distribute it to all connected clients (typically dashboards). @@ -364,3 +274,96 @@ class Core: raise IOError("No analyzer proxy configured") else: self.analyzer_proxy.trigger() + + +class RunTool: + def __init__(self, pattern, **tempdata): + self._pattern = pattern + self._tempdata = tempdata + self._tempnames = {} + self._tempfiles = {} + + def __enter__(self): + for key, data in self._tempdata.items(): + if data is None: + fd, filename = tempfile.mkstemp() + os.close(fd) + self._tempnames[key] = filename + else: + with tempfile.NamedTemporaryFile(delete=False) as f: + f.write(data) + self._tempnames[key] = f.name + + cmdline = [] + 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, shell=windows) + stdout, stderr = process.communicate() + if process.returncode != 0: + raise Exception("{} invocation failed: {}". + format(cmdline[0], stderr)) + + self._tempfiles["__stdout__"] = io.StringIO(stdout) + for key in self._tempdata: + if self._tempdata[key] is None: + self._tempfiles[key] = open(self._tempnames[key], "rb") + return self._tempfiles + + def __exit__(self, exc_typ, exc_value, exc_trace): + for file in self._tempfiles.values(): + file.close() + for filename in self._tempnames.values(): + os.unlink(filename) + + +def symbolize(library, addresses): + if addresses == []: + return [] + + # We got a list of return addresses, i.e. addresses of instructions + # just after the call. Offset them back to get an address somewhere + # inside the call instruction (or its delay slot), since that's what + # the backtrace entry should point at. + last_inlined = None + offset_addresses = [hex(addr - 1) for addr in addresses] + with RunTool(["llvm-addr2line", "--addresses", "--functions", "--inlines", + "--demangle", "--exe={library}"] + offset_addresses, + library=library) \ + as results: + lines = iter(results["__stdout__"].read().rstrip().split("\n")) + backtrace = [] + while True: + try: + address_or_function = next(lines) + except StopIteration: + break + if address_or_function[:2] == "0x": + address = int(address_or_function[2:], 16) + 1 # remove offset + function = next(lines) + inlined = False + else: + address = backtrace[-1][4] # inlined + function = address_or_function + inlined = True + location = next(lines) + + filename, line = location.rsplit(":", 1) + if filename == "??" or filename == "": + continue + if line == "?": + line = -1 + else: + line = int(line) + # can't get column out of addr2line D: + if inlined: + last_inlined.append((filename, line, -1, function, address)) + else: + last_inlined = [] + backtrace.append((filename, line, -1, function, address, + last_inlined)) + return backtrace + diff --git a/artiq/coredevice/coredevice_generic.schema.json b/artiq/coredevice/coredevice_generic.schema.json index 0558fb44c..7994f95d4 100644 --- a/artiq/coredevice/coredevice_generic.schema.json +++ b/artiq/coredevice/coredevice_generic.schema.json @@ -146,7 +146,7 @@ "properties": { "type": { "type": "string", - "enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"] + "enum": ["dio", "dio_spi", "urukul", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"] }, "board": { "type": "string" @@ -318,10 +318,8 @@ "type": "integer" }, "pll_en": { - "type": "integer", - "minimum": 0, - "maximum": 1, - "default": 1 + "type": "boolean", + "default": true }, "pll_vco": { "type": "integer" @@ -334,50 +332,6 @@ }, "required": ["ports"] } - }, { - "title": "Novogorny", - "if": { - "properties": { - "type": { - "const": "novogorny" - } - } - }, - "then": { - "properties": { - "ports": { - "type": "array", - "items": { - "type": "integer" - }, - "minItems": 1, - "maxItems": 1 - } - }, - "required": ["ports"] - } - }, { - "title": "Sampler", - "if": { - "properties": { - "type": { - "const": "sampler" - } - } - }, - "then": { - "properties": { - "ports": { - "type": "array", - "items": { - "type": "integer" - }, - "minItems": 1, - "maxItems": 2 - } - }, - "required": ["ports"] - } }, { "title": "SUServo", "if": { @@ -432,10 +386,8 @@ "default": 32 }, "pll_en": { - "type": "integer", - "minimum": 0, - "maximum": 1, - "default": 1 + "type": "boolean", + "default": true }, "pll_vco": { "type": "integer" diff --git a/artiq/coredevice/dma.py b/artiq/coredevice/dma.py index ee539ae85..0e0049089 100644 --- a/artiq/coredevice/dma.py +++ b/artiq/coredevice/dma.py @@ -5,34 +5,36 @@ the core device's SDRAM, and playing them back at higher speeds than the CPU alone could achieve. """ -from artiq.language.core import syscall, kernel -from artiq.language.types import TInt32, TInt64, TStr, TNone, TTuple, TBool +from numpy import int32, int64 + +from artiq.language.core import nac3, extern, kernel, Kernel, KernelInvariant from artiq.coredevice.exceptions import DMAError - -from numpy import int64 +from artiq.coredevice.core import Core -@syscall -def dma_record_start(name: TStr) -> TNone: + +@extern +def dma_record_start(name: str): raise NotImplementedError("syscall not simulated") -@syscall -def dma_record_stop(duration: TInt64, enable_ddma: TBool) -> TNone: +@extern +def dma_record_stop(duration: int64, enable_ddma: bool): raise NotImplementedError("syscall not simulated") -@syscall -def dma_erase(name: TStr) -> TNone: +@extern +def dma_erase(name: str): raise NotImplementedError("syscall not simulated") -@syscall -def dma_retrieve(name: TStr) -> TTuple([TInt64, TInt32, TBool]): +@extern +def dma_retrieve(name: str) -> tuple[int64, int32, bool]: raise NotImplementedError("syscall not simulated") -@syscall -def dma_playback(timestamp: TInt64, ptr: TInt32, enable_ddma: TBool) -> TNone: +@extern +def dma_playback(timestamp: int64, ptr: int32, enable_ddma: bool): raise NotImplementedError("syscall not simulated") +@nac3 class DMARecordContextManager: """Context manager returned by :meth:`CoreDMA.record()`. @@ -44,6 +46,9 @@ class DMARecordContextManager: are stored in a newly created trace, and ``now`` is restored to the value it had before the context manager was entered. """ + name: Kernel[str] + saved_now_mu: Kernel[int64] + def __init__(self): self.name = "" self.saved_now_mu = int64(0) @@ -53,21 +58,24 @@ class DMARecordContextManager: def __enter__(self): dma_record_start(self.name) # this may raise, so do it before altering now self.saved_now_mu = now_mu() - at_mu(0) + at_mu(int64(0)) @kernel - def __exit__(self, type, value, traceback): + def __exit__(self): dma_record_stop(now_mu(), self.enable_ddma) # see above at_mu(self.saved_now_mu) +@nac3 class CoreDMA: """Core device Direct Memory Access (DMA) driver. Gives access to the DMA functionality of the core device. """ - kernel_invariants = {"core", "recorder"} + core: KernelInvariant[Core] + recorder: KernelInvariant[DMARecordContextManager] + epoch: Kernel[int32] def __init__(self, dmgr, core_device="core"): self.core = dmgr.get(core_device) @@ -75,7 +83,7 @@ class CoreDMA: self.epoch = 0 @kernel - def record(self, name, enable_ddma=False): + def record(self, name: str, enable_ddma: bool = False) -> DMARecordContextManager: """Returns a context manager that will record a DMA trace called `name`. Any previously recorded trace with the same name is overwritten. The trace will persist across kernel switches. @@ -92,13 +100,13 @@ class CoreDMA: return self.recorder @kernel - def erase(self, name): + def erase(self, name: str): """Removes the DMA trace with the given name from storage.""" self.epoch += 1 dma_erase(name) @kernel - def playback(self, name): + def playback(self, name: str): """Replays a previously recorded DMA trace. This function blocks until the entire trace is submitted to the RTIO FIFOs.""" (advance_mu, ptr, uses_ddma) = dma_retrieve(name) @@ -106,14 +114,14 @@ class CoreDMA: delay_mu(advance_mu) @kernel - def get_handle(self, name): + def get_handle(self, name: str) -> tuple[int32, int64, int32]: """Returns a handle to a previously recorded DMA trace. The returned handle is only valid until the next call to :meth:`record` or :meth:`erase`.""" (advance_mu, ptr, uses_ddma) = dma_retrieve(name) return (self.epoch, advance_mu, ptr, uses_ddma) @kernel - def playback_handle(self, handle): + def playback_handle(self, handle: tuple[int32, int64, int32]): """Replays a handle obtained with :meth:`get_handle`. Using this function is much faster than :meth:`playback` for replaying a set of traces repeatedly, but offloads the overhead of managing the handles onto the programmer.""" diff --git a/artiq/coredevice/edge_counter.py b/artiq/coredevice/edge_counter.py index 28cbbf32a..438e07ee8 100644 --- a/artiq/coredevice/edge_counter.py +++ b/artiq/coredevice/edge_counter.py @@ -49,11 +49,13 @@ See the sources of :mod:`artiq.gateware.rtio.phy.edge_counter` and :meth:`artiq.gateware.eem.DIO.add_std` for the gateware components. """ +from numpy import int32, int64 + +from artiq.coredevice.core import Core from artiq.language.core import * -from artiq.language.types import * from artiq.coredevice.rtio import (rtio_output, rtio_input_data, rtio_input_timestamped_data) -from numpy import int32, int64 + CONFIG_COUNT_RISING = 0b0001 CONFIG_COUNT_FALLING = 0b0010 @@ -61,12 +63,14 @@ CONFIG_SEND_COUNT_EVENT = 0b0100 CONFIG_RESET_TO_ZERO = 0b1000 +@nac3 class CounterOverflow(Exception): """Raised when an edge counter value is read which indicates that the counter might have overflowed.""" pass +@nac3 class EdgeCounter: """RTIO TTL edge counter driver driver. @@ -82,7 +86,9 @@ class EdgeCounter: the gateware needs to be rebuilt. """ - kernel_invariants = {"core", "channel", "counter_max"} + core: KernelInvariant[Core] + channel: KernelInvariant[int32] + counter_max: KernelInvariant[int32] def __init__(self, dmgr, channel, gateware_width=31, core_device="core"): self.core = dmgr.get(core_device) @@ -94,7 +100,7 @@ class EdgeCounter: return [(channel, None)] @kernel - def gate_rising(self, duration): + def gate_rising(self, duration: float) -> int64: """Count rising edges for the given duration and request the total at the end. @@ -108,7 +114,7 @@ class EdgeCounter: return self.gate_rising_mu(self.core.seconds_to_mu(duration)) @kernel - def gate_falling(self, duration): + def gate_falling(self, duration: float) -> int64: """Count falling edges for the given duration and request the total at the end. @@ -122,7 +128,7 @@ class EdgeCounter: return self.gate_falling_mu(self.core.seconds_to_mu(duration)) @kernel - def gate_both(self, duration): + def gate_both(self, duration: float) -> int64: """Count both rising and falling edges for the given duration, and request the total at the end. @@ -136,25 +142,25 @@ class EdgeCounter: return self.gate_both_mu(self.core.seconds_to_mu(duration)) @kernel - def gate_rising_mu(self, duration_mu): + def gate_rising_mu(self, duration_mu: int64) -> int64: """See :meth:`gate_rising`.""" return self._gate_mu( duration_mu, count_rising=True, count_falling=False) @kernel - def gate_falling_mu(self, duration_mu): + def gate_falling_mu(self, duration_mu: int64) -> int64: """See :meth:`gate_falling`.""" return self._gate_mu( duration_mu, count_rising=False, count_falling=True) @kernel - def gate_both_mu(self, duration_mu): + def gate_both_mu(self, duration_mu: int64) -> int64: """See :meth:`gate_both_mu`.""" return self._gate_mu( duration_mu, count_rising=True, count_falling=True) @kernel - def _gate_mu(self, duration_mu, count_rising, count_falling): + def _gate_mu(self, duration_mu: int64, count_rising: bool, count_falling: bool) -> int64: self.set_config( count_rising=count_rising, count_falling=count_falling, @@ -169,8 +175,8 @@ class EdgeCounter: return now_mu() @kernel - def set_config(self, count_rising: TBool, count_falling: TBool, - send_count_event: TBool, reset_to_zero: TBool): + def set_config(self, count_rising: bool, count_falling: bool, + send_count_event: bool, reset_to_zero: bool): """Emit an RTIO event at the current timeline position to set the gateware configuration. @@ -195,7 +201,7 @@ class EdgeCounter: rtio_output(self.channel << 8, config) @kernel - def fetch_count(self) -> TInt32: + def fetch_count(self) -> int32: """Wait for and return count total from previously requested input event. @@ -214,7 +220,7 @@ class EdgeCounter: @kernel def fetch_timestamped_count( - self, timeout_mu=int64(-1)) -> TTuple([TInt64, TInt32]): + self, timeout_mu: int64 = int64(-1)) -> tuple[int64, int32]: """Wait for and return the timestamp and count total of a previously requested input event. diff --git a/artiq/coredevice/exceptions.py b/artiq/coredevice/exceptions.py index a4ee6ebd6..bfdbbfd6d 100644 --- a/artiq/coredevice/exceptions.py +++ b/artiq/coredevice/exceptions.py @@ -1,14 +1,9 @@ import builtins -import linecache -import re -import os from numpy.linalg import LinAlgError - -from artiq import __artiq_dir__ as artiq_dir -from artiq.coredevice.runtime import source_loader +from artiq.language.core import nac3, UnwrapNoneError """ -This file provides class definition for all the exceptions declared in `EmbeddingMap` in `artiq.compiler.embedding` +This file provides class definition for all the exceptions declared in `EmbeddingMap` in `artiq.language.embedding_map` For Python builtin exceptions, use the `builtins` module For ARTIQ specific exceptions, inherit from `Exception` class @@ -28,6 +23,7 @@ ValueError = builtins.ValueError ZeroDivisionError = builtins.ZeroDivisionError OSError = builtins.OSError +<<<<<<< HEAD class CoreException: """Information about an exception raised or passed through the core device. @@ -139,6 +135,9 @@ class CacheError(Exception): artiq_builtin = True +======= +@nac3 +>>>>>>> upstream/nac3 class RTIOUnderflow(Exception): """Raised when the CPU or DMA core fails to submit a RTIO event early enough (with respect to the event's timestamp). @@ -148,6 +147,7 @@ class RTIOUnderflow(Exception): artiq_builtin = True +@nac3 class RTIOOverflow(Exception): """Raised when at least one event could not be registered into the RTIO input FIFO because it was full (CPU not reading fast enough). @@ -159,6 +159,7 @@ class RTIOOverflow(Exception): artiq_builtin = True +@nac3 class RTIODestinationUnreachable(Exception): """Raised when a RTIO operation could not be completed due to a DRTIO link being down. @@ -166,11 +167,19 @@ class RTIODestinationUnreachable(Exception): artiq_builtin = True +@nac3 +class CacheError(Exception): + """Raised when putting a value into a cache row would violate memory safety.""" + artiq_builtin = True + + +@nac3 class DMAError(Exception): """Raised when performing an invalid DMA operation.""" artiq_builtin = True +@nac3 class SubkernelError(Exception): """Raised when an operation regarding a subkernel is invalid or cannot be completed. @@ -178,15 +187,18 @@ class SubkernelError(Exception): artiq_builtin = True +@nac3 class ClockFailure(Exception): """Raised when RTIO PLL has lost lock.""" artiq_builtin = True +@nac3 class I2CError(Exception): """Raised when a I2C transaction fails.""" artiq_builtin = True +@nac3 class SPIError(Exception): """Raised when a SPI transaction fails.""" artiq_builtin = True @@ -195,3 +207,4 @@ class SPIError(Exception): class UnwrapNoneError(Exception): """Raised when unwrapping a none Option.""" artiq_builtin = True + diff --git a/artiq/coredevice/fastino.py b/artiq/coredevice/fastino.py index cc77ec853..f4a101ed0 100644 --- a/artiq/coredevice/fastino.py +++ b/artiq/coredevice/fastino.py @@ -3,13 +3,14 @@ streaming DAC. """ from numpy import int32, int64 -from artiq.language.core import kernel, portable, delay, delay_mu +from artiq.language.core import nac3, kernel, portable, KernelInvariant from artiq.coredevice.rtio import (rtio_output, rtio_output_wide, rtio_input_data) from artiq.language.units import ns -from artiq.language.types import TInt32, TList +from artiq.coredevice.core import Core +@nac3 class Fastino: """Fastino 32-channel, 16-bit, 2.5 MS/s per channel streaming DAC @@ -41,7 +42,11 @@ class Fastino: :param log2_width: Width of DAC channel group (logarithm base 2). Value must match the corresponding value in the RTIO PHY (gateware). """ - kernel_invariants = {"core", "channel", "width", "t_frame"} + + core: KernelInvariant[Core] + channel: KernelInvariant[int32] + width: KernelInvariant[int32] + t_frame: KernelInvariant[int64] def __init__(self, dmgr, channel, core_device="core", log2_width=0): self.channel = channel << 8 @@ -74,15 +79,15 @@ class Fastino: On Fastino gateware before v0.2 this may lead to 0 voltage being emitted transiently. """ - self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=1) + self.set_cfg(reset=False, afe_power_down=False, dac_clr=False, clr_err=True) delay_mu(self.t_frame) - self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=0) + self.set_cfg(reset=False, afe_power_down=False, dac_clr=False, clr_err=False) delay_mu(self.t_frame) self.set_continuous(0) delay_mu(self.t_frame) self.stage_cic(1) delay_mu(self.t_frame) - self.apply_cic(0xffffffff) + self.apply_cic(int32(int64(0xffffffff))) delay_mu(self.t_frame) self.set_leds(0) delay_mu(self.t_frame) @@ -90,7 +95,7 @@ class Fastino: delay_mu(self.t_frame) @kernel - def write(self, addr, data): + def write(self, addr: int32, data: int32): """Write data to a Fastino register. :param addr: Address to write to. @@ -99,7 +104,7 @@ class Fastino: rtio_output(self.channel | addr, data) @kernel - def read(self, addr): + def read(self, addr: int32): """Read from Fastino register. TODO: untested @@ -112,7 +117,7 @@ class Fastino: # return rtio_input_data(self.channel >> 8) @kernel - def set_dac_mu(self, dac, data): + def set_dac_mu(self, dac: int32, data: int32): """Write DAC data in machine units. :param dac: DAC channel to write to (0-31). @@ -122,7 +127,7 @@ class Fastino: self.write(dac, data) @kernel - def set_group_mu(self, dac: TInt32, data: TList(TInt32)): + def set_group_mu(self, dac: int32, data: list[int32]): """Write a group of DAC channels in machine units. :param dac: First channel in DAC channel group (0-31). The ``log2_width`` @@ -132,24 +137,24 @@ class Fastino: If the list length is less than group size, the remaining DAC channels within the group are cleared to 0 (machine units). """ - if dac & (self.width - 1): + if dac & (self.width - 1) != 0: raise ValueError("Group index LSBs must be zero") rtio_output_wide(self.channel | dac, data) @portable - def voltage_to_mu(self, voltage): + def voltage_to_mu(self, voltage: float) -> int32: """Convert SI volts to DAC machine units. :param voltage: Voltage in SI volts. :return: DAC data word in machine units, 16-bit integer. """ - data = int32(round((0x8000/10.)*voltage)) + int32(0x8000) + data = int32(round((float(0x8000)/10.)*voltage)) + int32(0x8000) if data < 0 or data > 0xffff: raise ValueError("DAC voltage out of bounds") return data @portable - def voltage_group_to_mu(self, voltage, data): + def voltage_group_to_mu(self, voltage: list[float], data: list[int32]): """Convert SI volts to packed DAC channel group machine units. :param voltage: List of SI volt voltages. @@ -158,12 +163,12 @@ class Fastino: """ for i in range(len(voltage)): v = self.voltage_to_mu(voltage[i]) - if i & 1: + if i & 1 != 0: v = data[i // 2] | (v << 16) data[i // 2] = int32(v) @kernel - def set_dac(self, dac, voltage): + def set_dac(self, dac: int32, voltage: float): """Set DAC data to given voltage. :param dac: DAC channel (0-31). @@ -172,18 +177,18 @@ class Fastino: self.write(dac, self.voltage_to_mu(voltage)) @kernel - def set_group(self, dac, voltage): + def set_group(self, dac: int32, voltage: list[float]): """Set DAC group data to given voltage. :param dac: DAC channel (0-31). :param voltage: Desired output voltage. """ - data = [int32(0)] * (len(voltage) // 2) + data = [int32(0) for _ in range(len(voltage) // 2)] self.voltage_group_to_mu(voltage, data) self.set_group_mu(dac, data) @kernel - def update(self, update): + def update(self, update: int32): """Schedule channels for update. :param update: Bit mask of channels to update (32-bit). @@ -191,7 +196,7 @@ class Fastino: self.write(0x20, update) @kernel - def set_hold(self, hold): + def set_hold(self, hold: int32): """Set channels to manual update. :param hold: Bit mask of channels to hold (32-bit). @@ -199,7 +204,7 @@ class Fastino: self.write(0x21, hold) @kernel - def set_cfg(self, reset=0, afe_power_down=0, dac_clr=0, clr_err=0): + def set_cfg(self, reset: bool = False, afe_power_down: bool = False, dac_clr: bool = False, clr_err: bool = False): """Set configuration bits. :param reset: Reset SPI PLL and SPI clock domain. @@ -210,11 +215,11 @@ class Fastino: This clears the sticky red error LED. Must be cleared to enable error counting. """ - self.write(0x22, (reset << 0) | (afe_power_down << 1) | - (dac_clr << 2) | (clr_err << 3)) + self.write(0x22, (int32(reset) << 0) | (int32(afe_power_down) << 1) | + (int32(dac_clr) << 2) | (int32(clr_err) << 3)) @kernel - def set_leds(self, leds): + def set_leds(self, leds: int32): """Set the green user-defined LEDs. :param leds: LED status, 8-bit integer each bit corresponding to one @@ -223,14 +228,14 @@ class Fastino: self.write(0x23, leds) @kernel - def set_continuous(self, channel_mask): + def set_continuous(self, channel_mask: int32): """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): + def stage_cic_mu(self, rate_mantissa: int32, rate_exponent: int32, gain_exponent: int32): """Stage machine unit CIC interpolator configuration. """ if rate_mantissa < 0 or rate_mantissa >= 1 << 6: @@ -243,7 +248,7 @@ class Fastino: self.write(0x26, config) @kernel - def stage_cic(self, rate) -> TInt32: + def stage_cic(self, rate: int32) -> int32: """Compute and stage interpolator configuration. This method approximates the desired interpolation rate using a 10-bit @@ -279,7 +284,7 @@ class Fastino: return rate_mantissa << rate_exponent @kernel - def apply_cic(self, channel_mask): + def apply_cic(self, channel_mask: int32): """Apply the staged interpolator configuration on the specified channels. Each Fastino channel starting with gateware v0.2 includes a fourth order diff --git a/artiq/coredevice/grabber.py b/artiq/coredevice/grabber.py index 09b1af73b..f0d513c94 100644 --- a/artiq/coredevice/grabber.py +++ b/artiq/coredevice/grabber.py @@ -1,24 +1,29 @@ from numpy import int32, int64 from artiq.language.core import * -from artiq.language.types import * from artiq.coredevice.rtio import rtio_output, rtio_input_timestamped_data +from artiq.coredevice.core import Core +@nac3 class OutOfSyncException(Exception): """Raised when an incorrect number of ROI engine outputs has been retrieved from the RTIO input FIFO.""" pass +@nac3 class GrabberTimeoutException(Exception): """Raised when a timeout occurs while attempting to read Grabber RTIO input events.""" pass +@nac3 class Grabber: """Driver for the Grabber camera interface.""" - kernel_invariants = {"core", "channel_base", "sentinel"} + core: KernelInvariant[Core] + channel_base: KernelInvariant[int32] + sentinel: KernelInvariant[int32] def __init__(self, dmgr, channel_base, res_width=12, count_shift=0, core_device="core"): @@ -35,7 +40,7 @@ class Grabber: return [(channel_base, "ROI coordinates"), (channel_base + 1, "ROI mask")] @kernel - def setup_roi(self, n, x0, y0, x1, y1): + def setup_roi(self, n: int32, x0: int32, y0: int32, x1: int32, y1: int32): """ Defines the coordinates of a ROI. @@ -59,7 +64,7 @@ class Grabber: delay_mu(c) @kernel - def gate_roi(self, mask): + def gate_roi(self, mask: int32): """ Defines which ROI engines produce input events. @@ -79,15 +84,15 @@ class Grabber: rtio_output((self.channel_base + 1) << 8, mask) @kernel - def gate_roi_pulse(self, mask, dt): + def gate_roi_pulse(self, mask: int32, dt: float): """Sets a temporary mask for the specified duration (in seconds), before disabling all ROI engines.""" self.gate_roi(mask) - delay(dt) + self.core.delay(dt) self.gate_roi(0) @kernel - def input_mu(self, data, timeout_mu=-1): + def input_mu(self, data: list[int32], timeout_mu: int64 = int64(-1)): """ Retrieves the accumulated values for one frame from the ROI engines. Blocks until values are available or timeout is reached. @@ -110,7 +115,7 @@ class Grabber: channel = self.channel_base + 1 timestamp, sentinel = rtio_input_timestamped_data(timeout_mu, channel) - if timestamp == -1: + if timestamp == int64(-1): raise GrabberTimeoutException("Timeout before Grabber frame available") if sentinel != self.sentinel: raise OutOfSyncException @@ -119,7 +124,7 @@ class Grabber: timestamp, roi_output = rtio_input_timestamped_data(timeout_mu, channel) if roi_output == self.sentinel: raise OutOfSyncException - if timestamp == -1: + if timestamp == int64(-1): raise GrabberTimeoutException( "Timeout retrieving ROIs (attempting to read more ROIs than enabled?)") data[i] = roi_output diff --git a/artiq/coredevice/i2c.py b/artiq/coredevice/i2c.py index ecca4628c..244f5b21c 100644 --- a/artiq/coredevice/i2c.py +++ b/artiq/coredevice/i2c.py @@ -2,44 +2,45 @@ Non-realtime drivers for I2C chips on the core device. """ +from numpy import int32 -from artiq.language.core import syscall, kernel -from artiq.language.types import TBool, TInt32, TNone +from artiq.language.core import nac3, extern, kernel, KernelInvariant from artiq.coredevice.exceptions import I2CError +from artiq.coredevice.core import Core -@syscall(flags={"nounwind", "nowrite"}) -def i2c_start(busno: TInt32) -> TNone: +@extern +def i2c_start(busno: int32): raise NotImplementedError("syscall not simulated") -@syscall(flags={"nounwind", "nowrite"}) -def i2c_restart(busno: TInt32) -> TNone: +@extern +def i2c_restart(busno: int32): raise NotImplementedError("syscall not simulated") -@syscall(flags={"nounwind", "nowrite"}) -def i2c_stop(busno: TInt32) -> TNone: +@extern +def i2c_stop(busno: int32): raise NotImplementedError("syscall not simulated") -@syscall(flags={"nounwind", "nowrite"}) -def i2c_write(busno: TInt32, b: TInt32) -> TBool: +@extern +def i2c_write(busno: int32, b: int32) -> bool: raise NotImplementedError("syscall not simulated") -@syscall(flags={"nounwind", "nowrite"}) -def i2c_read(busno: TInt32, ack: TBool) -> TInt32: +@extern +def i2c_read(busno: int32, ack: bool) -> int32: raise NotImplementedError("syscall not simulated") -@syscall(flags={"nounwind", "nowrite"}) -def i2c_switch_select(busno: TInt32, address: TInt32, mask: TInt32) -> TNone: +@extern +def i2c_switch_select(busno: int32, address: int32, mask: int32): raise NotImplementedError("syscall not simulated") @kernel -def i2c_poll(busno, busaddr): +def i2c_poll(busno: int32, busaddr: int32) -> bool: """Poll I2C device at address. :param busno: I2C bus number @@ -53,7 +54,7 @@ def i2c_poll(busno, busaddr): @kernel -def i2c_write_byte(busno, busaddr, data, ack=True): +def i2c_write_byte(busno: int32, busaddr: int32, data: int32, ack: bool = True): """Write one byte to a device. :param busno: I2C bus number @@ -72,7 +73,7 @@ def i2c_write_byte(busno, busaddr, data, ack=True): @kernel -def i2c_read_byte(busno, busaddr): +def i2c_read_byte(busno: int32, busaddr: int32) -> int32: """Read one byte from a device. :param busno: I2C bus number @@ -91,7 +92,7 @@ def i2c_read_byte(busno, busaddr): @kernel -def i2c_write_many(busno, busaddr, addr, data, ack_last=True): +def i2c_write_many(busno: int32, busaddr: int32, addr: int32, data: list[int32], ack_last: bool = True): """Transfer multiple bytes to a device. :param busno: I2c bus number @@ -117,7 +118,7 @@ def i2c_write_many(busno, busaddr, addr, data, ack_last=True): @kernel -def i2c_read_many(busno, busaddr, addr, data): +def i2c_read_many(busno: int32, busaddr: int32, addr: int32, data: list[int32]): """Transfer multiple bytes from a device. :param busno: I2c bus number @@ -142,6 +143,7 @@ def i2c_read_many(busno, busaddr, addr, data): i2c_stop(busno) +@nac3 class I2CSwitch: """Driver for the I2C bus switch. @@ -153,13 +155,18 @@ class I2CSwitch: On the KC705, this chip is used for selecting the I2C buses on the two FMC connectors. HPC=1, LPC=2. """ + + core: KernelInvariant[Core] + busno: KernelInvariant[int32] + address: KernelInvariant[int32] + def __init__(self, dmgr, busno=0, address=0xe8, core_device="core"): self.core = dmgr.get(core_device) self.busno = busno self.address = address @kernel - def set(self, channel): + def set(self, channel: int32): """Enable one channel. :param channel: channel number (0-7) @@ -173,6 +180,7 @@ class I2CSwitch: i2c_switch_select(self.busno, self.address >> 1, 0) +@kernel class TCA6424A: """Driver for the TCA6424A I2C I/O expander. @@ -181,18 +189,23 @@ class TCA6424A: On the NIST QC2 hardware, this chip is used for switching the directions of TTL buffers.""" + + core: KernelInvariant[Core] + busno: KernelInvariant[int32] + address: KernelInvariant[int32] + def __init__(self, dmgr, busno=0, address=0x44, core_device="core"): self.core = dmgr.get(core_device) self.busno = busno self.address = address @kernel - def _write24(self, addr, value): + def _write24(self, addr: int32, value: int32): i2c_write_many(self.busno, self.address, addr, [value >> 16, value >> 8, value]) @kernel - def set(self, outputs): + def set(self, outputs: int32): """Drive all pins of the chip to the levels given by the specified 24-bit word. @@ -209,19 +222,26 @@ class TCA6424A: self._write24(0x8c, 0) # set all directions to output self._write24(0x84, outputs_le) # set levels + +@nac3 class PCF8574A: """Driver for the PCF8574 I2C remote 8-bit I/O expander. I2C transactions are not real-time, and are performed by the CPU without involving RTIO. """ + + core: KernelInvariant[Core] + busno: KernelInvariant[int32] + address: KernelInvariant[int32] + def __init__(self, dmgr, busno=0, address=0x7c, core_device="core"): self.core = dmgr.get(core_device) self.busno = busno self.address = address @kernel - def set(self, data): + def set(self, data: int32): """Drive data on the quasi-bidirectional pins. :param data: Pin data. High bits are weakly driven high @@ -237,7 +257,7 @@ class PCF8574A: i2c_stop(self.busno) @kernel - def get(self): + def get(self) -> int32: """Retrieve quasi-bidirectional pin input data. :return: Pin data diff --git a/artiq/coredevice/kasli_i2c.py b/artiq/coredevice/kasli_i2c.py index c332bb5ce..478985a92 100644 --- a/artiq/coredevice/kasli_i2c.py +++ b/artiq/coredevice/kasli_i2c.py @@ -1,7 +1,8 @@ from numpy import int32 from artiq.experiment import * -from artiq.coredevice.i2c import i2c_write_many, i2c_read_many, i2c_poll +from artiq.coredevice.core import Core +from artiq.coredevice.i2c import I2CSwitch, i2c_write_many, i2c_read_many, i2c_poll port_mapping = { @@ -24,9 +25,17 @@ port_mapping = { } +@nac3 class KasliEEPROM: + core: KernelInvariant[Core] + sw0: KernelInvariant[I2CSwitch] + sw1: KernelInvariant[I2CSwitch] + busno: KernelInvariant[int32] + port: KernelInvariant[int32] + address: KernelInvariant[int32] + def __init__(self, dmgr, port, address=0xa0, busno=0, - core_device="core", sw0_device="i2c_switch0", sw1_device="i2c_switch1"): + core_device="core", sw0_device="i2c_switch0", sw1_device="i2c_switch1"): self.core = dmgr.get(core_device) self.sw0 = dmgr.get(sw0_device) self.sw1 = dmgr.get(sw1_device) @@ -50,10 +59,10 @@ class KasliEEPROM: self.sw1.unset() @kernel - def write_i32(self, addr, value): + def write_i32(self, addr: int32, value: int32): self.select() try: - data = [0]*4 + data = [0 for _ in range(4)] for i in range(4): data[i] = (value >> 24) & 0xff value <<= 8 @@ -63,12 +72,12 @@ class KasliEEPROM: self.deselect() @kernel - def read_i32(self, addr): + def read_i32(self, addr: int32) -> int32: self.select() + value = int32(0) try: - data = [0]*4 + data = [0 for _ in range(4)] i2c_read_many(self.busno, self.address, addr, data) - value = int32(0) for i in range(4): value <<= 8 value |= data[i] diff --git a/artiq/coredevice/mirny.py b/artiq/coredevice/mirny.py index d60eca7e0..3343ea7b3 100644 --- a/artiq/coredevice/mirny.py +++ b/artiq/coredevice/mirny.py @@ -1,23 +1,24 @@ """RTIO driver for Mirny (4-channel GHz PLLs) """ -from artiq.language.core import kernel, delay, portable +from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable from artiq.language.units import us from numpy import int32 -from artiq.coredevice import spi2 as spi +from artiq.coredevice.core import Core +from artiq.coredevice.spi2 import * SPI_CONFIG = ( - 0 * spi.SPI_OFFLINE - | 0 * spi.SPI_END - | 0 * spi.SPI_INPUT - | 1 * spi.SPI_CS_POLARITY - | 0 * spi.SPI_CLK_POLARITY - | 0 * spi.SPI_CLK_PHASE - | 0 * spi.SPI_LSB_FIRST - | 0 * spi.SPI_HALF_DUPLEX + 0 * SPI_OFFLINE + | 0 * SPI_END + | 0 * SPI_INPUT + | 1 * SPI_CS_POLARITY + | 0 * SPI_CLK_POLARITY + | 0 * SPI_CLK_PHASE + | 0 * SPI_LSB_FIRST + | 0 * SPI_HALF_DUPLEX ) # SPI clock write and read dividers @@ -32,6 +33,7 @@ WE = 1 << 24 PROTO_REV_MATCH = 0x0 +@nac3 class Mirny: """ Mirny PLL-based RF generator. @@ -46,8 +48,12 @@ class Mirny: The effect depends on the hardware revision. :param core_device: Core device name (default: "core") """ - - kernel_invariants = {"bus", "core", "refclk", "clk_sel_hw_rev"} + core: KernelInvariant[Core] + bus: KernelInvariant[SPIMaster] + refclk: KernelInvariant[float] + clk_sel_hw_rev: Kernel[list[int32]] + hw_rev: Kernel[int32] + clk_sel: Kernel[int32] def __init__(self, dmgr, spi_device, refclk=100e6, clk_sel="XO", core_device="core"): self.core = dmgr.get(core_device) @@ -81,22 +87,22 @@ class Mirny: # TODO: support clk_div on v1.0 boards @kernel - def read_reg(self, addr): + def read_reg(self, addr: int32) -> int32: """Read a register.""" self.bus.set_config_mu( - SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 24, SPIT_RD, SPI_CS + SPI_CONFIG | SPI_INPUT | SPI_END, 24, SPIT_RD, SPI_CS ) self.bus.write((addr << 25)) - return self.bus.read() & int32(0xFFFF) + return self.bus.read() & 0xFFFF @kernel - def write_reg(self, addr, data): + def write_reg(self, addr: int32, data: int32): """Write a register.""" - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, SPIT_WR, SPI_CS) + self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24, SPIT_WR, SPI_CS) self.bus.write((addr << 25) | WE | ((data & 0xFFFF) << 8)) @kernel - def init(self, blind=False): + def init(self, blind: bool = False): """ Initialize and detect Mirny. @@ -111,7 +117,7 @@ class Mirny: if not blind: if (reg0 >> 2) & 0x3 != PROTO_REV_MATCH: raise ValueError("Mirny PROTO_REV mismatch") - delay(100 * us) # slack + self.core.delay(100. * us) # slack # select clock source self.clk_sel = self.clk_sel_hw_rev[self.hw_rev] @@ -120,31 +126,31 @@ class Mirny: raise ValueError("Hardware revision not supported") self.write_reg(1, (self.clk_sel << 4)) - delay(1000 * us) + self.core.delay(1000. * us) - @portable(flags={"fast-math"}) - def att_to_mu(self, att): + @portable + def att_to_mu(self, att: float) -> int32: """Convert an attenuation setting in dB to machine units. :param att: Attenuation setting in dB. :return: Digital attenuation setting. """ - code = int32(255) - int32(round(att * 8)) + code = int32(255) - int32(round(att * 8.)) if code < 0 or code > 255: raise ValueError("Invalid Mirny attenuation!") return code @kernel - def set_att_mu(self, channel, att): + def set_att_mu(self, channel: int32, att: int32): """Set digital step attenuator in machine units. :param att: Attenuation setting, 8-bit digital. """ - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 16, SPIT_WR, SPI_CS) + self.bus.set_config_mu(SPI_CONFIG | SPI_END, 16, SPIT_WR, SPI_CS) self.bus.write(((channel | 8) << 25) | (att << 16)) @kernel - def set_att(self, channel, att): + def set_att(self, channel: int32, att: float): """Set digital step attenuator in SI units. This method will write the attenuator settings of the selected channel. @@ -159,11 +165,11 @@ class Mirny: self.set_att_mu(channel, self.att_to_mu(att)) @kernel - def write_ext(self, addr, length, data, ext_div=SPIT_WR): + def write_ext(self, addr: int32, length: int32, data: int32, ext_div: int32 = SPIT_WR): """Perform SPI write to a prefixed address.""" self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_WR, SPI_CS) self.bus.write(addr << 25) - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, length, ext_div, SPI_CS) + self.bus.set_config_mu(SPI_CONFIG | SPI_END, length, ext_div, SPI_CS) if length < 32: data <<= 32 - length self.bus.write(data) diff --git a/artiq/coredevice/novogorny.py b/artiq/coredevice/novogorny.py deleted file mode 100644 index e96424065..000000000 --- a/artiq/coredevice/novogorny.py +++ /dev/null @@ -1,172 +0,0 @@ -from artiq.language.core import kernel, delay, portable -from artiq.language.units import ns - -from artiq.coredevice import spi2 as spi - - -SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END | - 0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | - 0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) - - -SPI_CS_ADC = 1 -SPI_CS_SR = 2 - - -@portable -def adc_ctrl(channel=1, softspan=0b111, valid=1): - """Build a LTC2335-16 control word.""" - return (valid << 7) | (channel << 3) | softspan - - -@portable -def adc_softspan(data): - """Return the softspan configuration index from a result packet.""" - return data & 0x7 - - -@portable -def adc_channel(data): - """Return the channel index from a result packet.""" - return (data >> 3) & 0x7 - - -@portable -def adc_data(data): - """Return the ADC value from a result packet.""" - return (data >> 8) & 0xffff - - -@portable -def adc_value(data, v_ref=5.): - """Convert a ADC result packet to SI units (volts).""" - softspan = adc_softspan(data) - data = adc_data(data) - g = 625 - if softspan & 4: - g *= 2 - if softspan & 2: - h = 1 << 15 - else: - h = 1 << 16 - data = -(data & h) + (data & ~h) - if softspan & 1: - h *= 500 - else: - h *= 512 - v_per_lsb = v_ref*g/h - return data*v_per_lsb - - -class Novogorny: - """Novogorny ADC. - - Controls the LTC2335-16 8 channel ADC with SPI interface and - the switchable gain instrumentation amplifiers using a shift - register. - - :param spi_device: SPI bus device name - :param cnv_device: CNV RTIO TTLOut channel name - :param div: SPI clock divider (default: 8) - :param gains: Initial value for PGIA gains shift register - (default: 0x0000). Knowledge of this state is not transferred - between experiments. - :param core_device: Core device name - """ - kernel_invariants = {"bus", "core", "cnv", "div", "v_ref"} - - def __init__(self, dmgr, spi_device, cnv_device, div=8, gains=0x0000, - core_device="core"): - self.bus = dmgr.get(spi_device) - self.core = dmgr.get(core_device) - self.cnv = dmgr.get(cnv_device) - self.div = div - self.gains = gains - self.v_ref = 5. # 5 Volt reference - - @kernel - def set_gain_mu(self, channel, gain): - """Set instrumentation amplifier gain of a channel. - - The four gain settings (0, 1, 2, 3) corresponds to gains of - (1, 10, 100, 1000) respectively. - - :param channel: Channel index - :param gain: Gain setting - """ - gains = self.gains - gains &= ~(0b11 << (channel*2)) - gains |= gain << (channel*2) - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, - 16, self.div, SPI_CS_SR) - self.bus.write(gains << 16) - self.gains = gains - - @kernel - def configure(self, data): - """Set up the ADC sequencer. - - :param data: List of 8-bit control words to write into the sequencer - table. - """ - if len(data) > 1: - self.bus.set_config_mu(SPI_CONFIG, - 8, self.div, SPI_CS_ADC) - for i in range(len(data) - 1): - self.bus.write(data[i] << 24) - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, - 8, self.div, SPI_CS_ADC) - self.bus.write(data[len(data) - 1] << 24) - - @kernel - def sample_mu(self, next_ctrl=0): - """Acquire a sample: - - Perform a conversion and transfer the sample. - - :param next_ctrl: ADC control word for the next sample - :return: The ADC result packet (machine units) - """ - self.cnv.pulse(40*ns) # t_CNVH - delay(560*ns) # t_CONV max - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, - 24, self.div, SPI_CS_ADC) - self.bus.write(next_ctrl << 24) - return self.bus.read() - - @kernel - def sample(self, next_ctrl=0): - """Acquire a sample. See also :meth:`Novogorny.sample_mu`. - - :param next_ctrl: ADC control word for the next sample - :return: The ADC result packet (volts) - """ - return adc_value(self.sample_mu(), self.v_ref) - - @kernel - def burst_mu(self, data, dt_mu, ctrl=0): - """Acquire a burst of samples. - - If the burst is too long and the sample rate too high, there will be - :exc:RTIOOverflow exceptions. - - High sample rates lead to gain errors since the impedance between the - instrumentation amplifier and the ADC is high. - - :param data: List of data values to write result packets into. - In machine units. - :param dt: Sample interval in machine units. - :param ctrl: ADC control word to write during each result packet - transfer. - """ - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, - 24, self.div, SPI_CS_ADC) - for i in range(len(data)): - t0 = now_mu() - self.cnv.pulse(40*ns) # t_CNVH - delay(560*ns) # t_CONV max - self.bus.write(ctrl << 24) - at_mu(t0 + dt_mu) - for i in range(len(data)): - data[i] = self.bus.read() diff --git a/artiq/coredevice/phaser.py b/artiq/coredevice/phaser.py index bae1c1122..75b31fe47 100644 --- a/artiq/coredevice/phaser.py +++ b/artiq/coredevice/phaser.py @@ -1,9 +1,10 @@ -from numpy import int32, int64 +from __future__ import annotations +from numpy import uint32, int32, int64 -from artiq.language.core import kernel, delay_mu, delay +from artiq.language.core import * +from artiq.coredevice.core import Core 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 from artiq.coredevice.trf372017 import TRF372017 @@ -82,6 +83,7 @@ SERVO_COEFF_SHIFT = 14 SERVO_T_CYCLE = (32+12+192+24+4)*ns # Must match gateware ADC parameters +@nac3 class Phaser: """Phaser 4-channel, 16-bit, 1 GS/s DAC coredevice driver. @@ -209,7 +211,7 @@ class Phaser: and buffer round trip. Tuning this might be automated later. :param tune_fifo_offset: Tune the DAC FIFO read pointer offset (default=True) - :param clk_sel: Select the external SMA clock input (1 or 0) + :param clk_sel: Select the external SMA clock input. :param sync_dly: SYNC delay with respect to ISTR. :param dac: DAC34H84 DAC settings as a dictionary. :param trf0: Channel 0 TRF372017 quadrature upconverter settings as a @@ -223,11 +225,21 @@ class Phaser: To access oscillators, digital upconverters, PLL/VCO analog quadrature upconverters and attenuators. """ - kernel_invariants = {"core", "channel_base", "t_frame", "miso_delay", - "dac_mmap"} + + core: KernelInvariant[Core] + channel_base: KernelInvariant[int32] + t_frame: KernelInvariant[int32] + miso_delay: KernelInvariant[int32] + frame_tstamp: Kernel[int64] + clk_sel: Kernel[bool] + tune_fifo_offset: Kernel[bool] + sync_dly: Kernel[int32] + gw_rev: Kernel[int32] + dac_mmap: KernelInvariant[list[int32]] + channel: Kernel[list[PhaserChannel]] def __init__(self, dmgr, channel_base, miso_delay=1, tune_fifo_offset=True, - clk_sel=0, sync_dly=0, dac=None, trf0=None, trf1=None, gw_rev=PHASER_GW_BASE, + clk_sel=False, sync_dly=0, dac=None, trf0=None, trf1=None, gw_rev=PHASER_GW_BASE, core_device="core"): self.channel_base = channel_base self.core = dmgr.get(core_device) @@ -244,6 +256,7 @@ class Phaser: self.gw_rev = gw_rev # verified in init() self.dac_mmap = DAC34H84(dac).get_mmap() + self.dac_mmap = [int32(uint32(x)) for x in self.dac_mmap] # NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/14 self.channel = [PhaserChannel(self, ch, trf) for ch, trf in enumerate([trf0, trf1])] @@ -261,7 +274,7 @@ class Phaser: raise ValueError("invalid gw_rev `{}`".format(gw_rev)) @kernel - def init(self, debug=False): + def init(self, debug: bool = False): """Initialize the board. Verifies board and chip presence, resets components, performs @@ -271,41 +284,41 @@ class Phaser: board_id = self.read8(PHASER_ADDR_BOARD_ID) if board_id != PHASER_BOARD_ID: raise ValueError("invalid board id") - delay(.1*ms) # slack + self.core.delay(.1*ms) # slack hw_rev = self.read8(PHASER_ADDR_HW_REV) - delay(.1*ms) # slack - is_baseband = hw_rev & PHASER_HW_REV_VARIANT + self.core.delay(.1*ms) # slack + is_baseband = hw_rev & PHASER_HW_REV_VARIANT != 0 gw_rev = self.read8(PHASER_ADDR_GW_REV) if debug: - print("gw_rev:", self.gw_rev) + print_rpc(("gw_rev:", self.gw_rev)) self.core.break_realtime() assert gw_rev == self.gw_rev - delay(.1*ms) # slack + self.core.delay(.1*ms) # slack # allow a few errors during startup and alignment since boot if self.get_crc_err() > 20: raise ValueError("large number of frame CRC errors") - delay(.1*ms) # slack + self.core.delay(.1*ms) # slack # determine the origin for frame-aligned timestamps self.measure_frame_timestamp() - if self.frame_tstamp < 0: + if self.frame_tstamp < int64(0): raise ValueError("frame timestamp measurement timed out") - delay(.1*ms) + self.core.delay(.1*ms) # reset - self.set_cfg(dac_resetb=0, dac_sleep=1, dac_txena=0, - trf0_ps=1, trf1_ps=1, - att0_rstn=0, att1_rstn=0) + self.set_cfg(dac_resetb=False, dac_sleep=True, dac_txena=False, + trf0_ps=True, trf1_ps=True, + att0_rstn=False, att1_rstn=False) self.set_leds(0x00) self.set_fan_mu(0) # bring dac out of reset, keep tx off - self.set_cfg(clk_sel=self.clk_sel, dac_txena=0, - trf0_ps=1, trf1_ps=1, - att0_rstn=0, att1_rstn=0) - delay(.1*ms) # slack + self.set_cfg(clk_sel=self.clk_sel, dac_txena=False, + trf0_ps=True, trf1_ps=True, + att0_rstn=False, att1_rstn=False) + self.core.delay(.1*ms) # slack # crossing dac_clk (reference) edges with sync_dly # changes the optimal fifo_offset by 4 @@ -315,25 +328,25 @@ class Phaser: self.dac_write(0x02, 0x0080) if self.dac_read(0x7f) != 0x5409: raise ValueError("DAC version readback invalid") - delay(.1*ms) + self.core.delay(.1*ms) if self.dac_read(0x00) != 0x049c: raise ValueError("DAC config0 reset readback invalid") - delay(.1*ms) + self.core.delay(.1*ms) t = self.get_dac_temperature() - delay(.1*ms) + self.core.delay(.1*ms) if t < 10 or t > 90: raise ValueError("DAC temperature out of bounds") for data in self.dac_mmap: self.dac_write(data >> 16, data) - delay(120*us) + self.core.delay(120.*us) self.dac_sync() - delay(40*us) + self.core.delay(40.*us) # pll_ndivsync_ena disable config18 = self.dac_read(0x18) - delay(.1*ms) + self.core.delay(.1*ms) self.dac_write(0x18, config18 & ~0x0800) patterns = [ @@ -346,33 +359,33 @@ class Phaser: # either side) and no need to tune at runtime. # Parity provides another level of safety. for i in range(len(patterns)): - delay(.5*ms) + self.core.delay(.5*ms) errors = self.dac_iotest(patterns[i]) - if errors: + if errors != 0: raise ValueError("DAC iotest failure") - delay(2*ms) # let it settle + self.core.delay(2.*ms) # let it settle lvolt = self.dac_read(0x18) & 7 - delay(.1*ms) + self.core.delay(.1*ms) if lvolt < 2 or lvolt > 5: raise ValueError("DAC PLL lock failed, check clocking") if self.tune_fifo_offset: fifo_offset = self.dac_tune_fifo_offset() if debug: - print("fifo_offset:", fifo_offset) + print_rpc(("fifo_offset:", fifo_offset)) self.core.break_realtime() # self.dac_write(0x20, 0x0000) # stop fifo sync # alarm = self.get_sta() & 1 - # delay(.1*ms) + # self.core.delay(.1*ms) self.clear_dac_alarms() - delay(2*ms) # let it run a bit + self.core.delay(2.*ms) # let it run a bit alarms = self.get_dac_alarms() - delay(.1*ms) # slack - if alarms & ~0x0040: # ignore PLL alarms (see DS) + self.core.delay(.1*ms) # slack + if alarms & ~0x0040 != 0: # ignore PLL alarms (see DS) if debug: - print("alarms:", alarms) + print_rpc(("alarms:", alarms)) self.core.break_realtime() # ignore alarms else: @@ -380,16 +393,16 @@ class Phaser: # avoid malformed output for: mixer_ena=1, nco_ena=0 after power up self.dac_write(self.dac_mmap[2] >> 16, self.dac_mmap[2] | (1 << 4)) - delay(40*us) + self.core.delay(40.*us) self.dac_sync() - delay(100*us) + self.core.delay(100.*us) self.dac_write(self.dac_mmap[2] >> 16, self.dac_mmap[2]) - delay(40*us) + self.core.delay(40.*us) self.dac_sync() - delay(100*us) + self.core.delay(100.*us) # power up trfs, release att reset - self.set_cfg(clk_sel=self.clk_sel, dac_txena=0) + self.set_cfg(clk_sel=self.clk_sel, dac_txena=False) for ch in range(2): channel = self.channel[ch] @@ -397,10 +410,10 @@ class Phaser: channel.set_att_mu(0x5a) if channel.get_att_mu() != 0x5a: raise ValueError("attenuator test failed") - delay(.1*ms) + self.core.delay(.1*ms) channel.set_att_mu(0x00) # maximum attenuation - channel.set_servo(profile=0, enable=0, hold=1) + channel.set_servo(profile=0, enable=False, hold=True) if self.gw_rev == PHASER_GW_BASE: # test oscillators and DUC @@ -410,18 +423,18 @@ class Phaser: if i == 0: asf = 0x7fff # 6pi/4 phase - oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=1) - delay(1*us) + oscillator.set_amplitude_phase_mu(asf=asf, pow=0xc000, clr=True) + self.core.delay(1.*us) # 3pi/4 channel.set_duc_phase_mu(0x6000) - channel.set_duc_cfg(select=0, clr=1) + channel.set_duc_cfg(select=0, clr=True) self.duc_stb() - delay(.1*ms) # settle link, pipeline and impulse response + self.core.delay(.1*ms) # settle link, pipeline and impulse response data = channel.get_dac_data() - delay(1*us) + self.core.delay(1.*us) channel.oscillator[0].set_amplitude_phase_mu(asf=0, pow=0xc000, - clr=1) - delay(.1*ms) + clr=True) + self.core.delay(.1*ms) sqrt2 = 0x5a81 # 0x7fff/sqrt(2) data_i = data & 0xffff data_q = (data >> 16) & 0xffff @@ -438,27 +451,27 @@ class Phaser: if channel.trf_read(0) & 0x7f != 0x68: raise ValueError("TRF identification failed") - delay(.1*ms) + self.core.delay(.1*ms) - delay(.2*ms) + self.core.delay(.2*ms) for data in channel.trf_mmap: channel.trf_write(data) channel.cal_trf_vco() - delay(2*ms) # lock + self.core.delay(2.*ms) # lock if not (self.get_sta() & (PHASER_STA_TRF0_LD << ch)): raise ValueError("TRF lock failure") - delay(.1*ms) - if channel.trf_read(0) & 0x1000: + self.core.delay(.1*ms) + if channel.trf_read(0) & 0x1000 != 0: raise ValueError("TRF R_SAT_ERR") - delay(.1*ms) + self.core.delay(.1*ms) channel.en_trf_out() # enable dac tx self.set_cfg(clk_sel=self.clk_sel) @kernel - def write8(self, addr, data): + def write8(self, addr: int32, data: int32): """Write data to FPGA register. :param addr: Address to write to (7-bit) @@ -468,7 +481,7 @@ class Phaser: delay_mu(int64(self.t_frame)) @kernel - def read8(self, addr) -> TInt32: + def read8(self, addr: int32) -> int32: """Read from FPGA register. :param addr: Address to read from (7-bit) @@ -479,13 +492,13 @@ class Phaser: return response >> self.miso_delay @kernel - def write16(self, addr, data: TInt32): + def write16(self, addr: int32, data: int32): """Write 16 bits to a sequence of FPGA registers.""" self.write8(addr, data >> 8) self.write8(addr + 1, data) @kernel - def write32(self, addr, data: TInt32): + def write32(self, addr: int32, data: int32): """Write 32 bits to a sequence of FPGA registers.""" for offset in range(4): byte = data >> 24 @@ -493,17 +506,17 @@ class Phaser: data <<= 8 @kernel - def read32(self, addr) -> TInt32: + def read32(self, addr: int32) -> int32: """Read 32 bits from a sequence of FPGA registers.""" data = 0 for offset in range(4): data <<= 8 data |= self.read8(addr + offset) - delay(20*us) # slack + self.core.delay(20.*us) # slack return data @kernel - def set_leds(self, leds): + def set_leds(self, leds: int32): """Set the front panel LEDs. :param leds: LED settings (6-bit) @@ -511,7 +524,7 @@ class Phaser: self.write8(PHASER_ADDR_LED, leds) @kernel - def set_fan_mu(self, pwm): + def set_fan_mu(self, pwm: int32): """Set the fan duty cycle in machine units. :param pwm: Duty cycle in machine units (8-bit) @@ -519,19 +532,19 @@ class Phaser: self.write8(PHASER_ADDR_FAN, pwm) @kernel - def set_fan(self, duty): + def set_fan(self, duty: float): """Set the fan duty cycle. :param duty: Duty cycle (0. to 1.) """ - pwm = int32(round(duty*255.)) + pwm = round(duty*255.) if pwm < 0 or pwm > 255: raise ValueError("duty cycle out of bounds") self.set_fan_mu(pwm) @kernel - def set_cfg(self, clk_sel=0, dac_resetb=1, dac_sleep=0, dac_txena=1, - trf0_ps=0, trf1_ps=0, att0_rstn=1, att1_rstn=1): + def set_cfg(self, clk_sel: bool = False, dac_resetb: bool = True, dac_sleep: bool = False, dac_txena: bool = True, + trf0_ps: bool = False, trf1_ps: bool = False, att0_rstn: bool = True, att1_rstn: bool = True): """Set the configuration register. Each flag is a single bit (0 or 1). @@ -546,13 +559,13 @@ class Phaser: :param att1_rstn: Active low attenuator 1 reset """ self.write8(PHASER_ADDR_CFG, - ((clk_sel & 1) << 0) | ((dac_resetb & 1) << 1) | - ((dac_sleep & 1) << 2) | ((dac_txena & 1) << 3) | - ((trf0_ps & 1) << 4) | ((trf1_ps & 1) << 5) | - ((att0_rstn & 1) << 6) | ((att1_rstn & 1) << 7)) + (int32(clk_sel) << 0) | (int32(dac_resetb) << 1) | + (int32(dac_sleep) << 2) | (int32(dac_txena) << 3) | + (int32(trf0_ps) << 4) | (int32(trf1_ps) << 5) | + (int32(att0_rstn) << 6) | (int32(att1_rstn) << 7)) @kernel - def get_sta(self): + def get_sta(self) -> int32: """Get the status register value. Bit flags are: @@ -569,7 +582,7 @@ class Phaser: return self.read8(PHASER_ADDR_STA) @kernel - def get_crc_err(self): + def get_crc_err(self) -> int32: """Get the frame CRC error counter. :return: The number of frames with CRC mismatches sind the reset of the @@ -585,21 +598,21 @@ class Phaser: See :meth:`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) + self.frame_tstamp = rtio_input_timestamp(now_mu() + int64(4) * int64(self.t_frame), self.channel_base) + self.core.delay(100. * us) @kernel - def get_next_frame_mu(self): + def get_next_frame_mu(self) -> int64: """Return the timestamp of the frame strictly after :meth:`~artiq.language.core.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 + n = int64((now_mu() - self.frame_tstamp) / int64(self.t_frame)) + return self.frame_tstamp + (n + int64(1)) * int64(self.t_frame) @kernel - def set_sync_dly(self, dly): + def set_sync_dly(self, dly: int32): """Set SYNC delay. :param dly: DAC SYNC delay setting (0 to 7) @@ -618,8 +631,8 @@ class Phaser: self.write8(PHASER_ADDR_DUC_STB, 0) @kernel - def spi_cfg(self, select, div, end, clk_phase=0, clk_polarity=0, - half_duplex=0, lsb_first=0, offline=0, length=8): + def spi_cfg(self, select: int32, div: int32, end: bool, clk_phase: bool = False, clk_polarity: bool = False, + half_duplex: bool = False, lsb_first: bool = False, offline: bool = False, length: int32 = 8): """Set the SPI machine configuration :param select: Chip selects to assert (DAC, TRF0, TRF1, ATT0, ATT1) @@ -639,63 +652,63 @@ class Phaser: self.write8(PHASER_ADDR_SPI_SEL, select) self.write8(PHASER_ADDR_SPI_DIVLEN, (div - 2 >> 3) | (length - 1 << 5)) self.write8(PHASER_ADDR_SPI_CFG, - ((offline & 1) << 0) | ((end & 1) << 1) | - ((clk_phase & 1) << 2) | ((clk_polarity & 1) << 3) | - ((half_duplex & 1) << 4) | ((lsb_first & 1) << 5)) + (int32(offline) << 0) | (int32(end) << 1) | + (int32(clk_phase) << 2) | (int32(clk_polarity) << 3) | + (int32(half_duplex) << 4) | (int32(lsb_first) << 5)) @kernel - def spi_write(self, data): + def spi_write(self, data: int32): """Write 8 bits into the SPI data register and start/continue the transaction.""" self.write8(PHASER_ADDR_SPI_DATW, data) @kernel - def spi_read(self): + def spi_read(self) -> int32: """Read from the SPI input data register.""" return self.read8(PHASER_ADDR_SPI_DATR) @kernel - def dac_write(self, addr, data): + def dac_write(self, addr: int32, data: int32): """Write 16 bits to a DAC register. :param addr: Register address :param data: Register data to write """ div = 34 # 100 ns min period - t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) - self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) + t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns) + self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=False) self.spi_write(addr) delay_mu(t_xfer) self.spi_write(data >> 8) delay_mu(t_xfer) - self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=1) + self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=True) self.spi_write(data) delay_mu(t_xfer) @kernel - def dac_read(self, addr, div=34) -> TInt32: + def dac_read(self, addr: int32, div: int32 = 34) -> int32: """Read from a DAC register. :param addr: Register address to read from :param div: SPI clock divider. Needs to be at least 250 (1 µs SPI clock) to read the temperature register. """ - t_xfer = self.core.seconds_to_mu((8 + 1)*div*4*ns) - self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=0) + t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns) + self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=False) self.spi_write(addr | 0x80) delay_mu(t_xfer) self.spi_write(0) delay_mu(t_xfer) data = self.spi_read() << 8 - delay(20*us) # slack - self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=1) + self.core.delay(20.*us) # slack + self.spi_cfg(select=PHASER_SEL_DAC, div=div, end=True) self.spi_write(0) delay_mu(t_xfer) data |= self.spi_read() return data @kernel - def get_dac_temperature(self) -> TInt32: + def get_dac_temperature(self) -> int32: """Read the DAC die temperature. :return: DAC temperature in degree Celsius @@ -718,12 +731,12 @@ class Phaser: .. note:: Synchronising the NCO clears the phase-accumulator. """ config1f = self.dac_read(0x1f) - delay(.4*ms) + self.core.delay(.4*ms) self.dac_write(0x1f, config1f & ~int32(1 << 1)) self.dac_write(0x1f, config1f | (1 << 1)) @kernel - def set_dac_cmix(self, fs_8_step): + def set_dac_cmix(self, fs_8_step: int32): """Set the DAC coarse mixer frequency for both channels. Use of the coarse mixer requires the DAC mixer to be enabled. The mixer @@ -741,11 +754,11 @@ class Phaser: vals = [0b0000, 0b1000, 0b0100, 0b1100, 0b0010, 0b1010, 0b0001, 0b1110] cmix = vals[fs_8_step%8] config0d = self.dac_read(0x0d) - delay(.1*ms) + self.core.delay(.1*ms) self.dac_write(0x0d, (config0d & ~(0b1111 << 12)) | (cmix << 12)) @kernel - def get_dac_alarms(self): + def get_dac_alarms(self) -> int32: """Read the DAC alarm flags. :return: DAC alarm flags (see datasheet for bit meaning) @@ -758,7 +771,7 @@ class Phaser: self.dac_write(0x05, 0x0000) @kernel - def dac_iotest(self, pattern) -> TInt32: + def dac_iotest(self, pattern: list[int32]) -> int32: """Performs a DAC IO test according to the datasheet. :param pattern: List of four int32s containing the pattern @@ -770,7 +783,7 @@ class Phaser: self.dac_write(0x25 + addr, pattern[addr]) # repeat the pattern twice self.dac_write(0x29 + addr, pattern[addr]) - delay(.1*ms) + self.core.delay(.1*ms) for ch in range(2): channel = self.channel[ch] channel.set_duc_cfg(select=1) # test @@ -779,20 +792,20 @@ class Phaser: channel.set_dac_test(data) if channel.get_dac_data() != data: raise ValueError("DAC test data readback failed") - delay(.1*ms) + self.core.delay(.1*ms) cfg = self.dac_read(0x01) - delay(.1*ms) + self.core.delay(.1*ms) self.dac_write(0x01, cfg | 0x8000) # iotest_ena self.dac_write(0x04, 0x0000) # clear iotest_result - delay(.2*ms) # let it rip + self.core.delay(.2*ms) # let it rip # no need to go through the alarm register, # just read the error mask # self.clear_dac_alarms() alarms = self.get_dac_alarms() - delay(.1*ms) # slack - if alarms & 0x0080: # alarm_from_iotest + self.core.delay(.1*ms) # slack + if alarms & 0x0080 != 0: # alarm_from_iotest errors = self.dac_read(0x04) - delay(.1*ms) # slack + self.core.delay(.1*ms) # slack else: errors = 0 self.dac_write(0x01, cfg) # clear config @@ -800,7 +813,7 @@ class Phaser: return errors @kernel - def dac_tune_fifo_offset(self): + def dac_tune_fifo_offset(self) -> int32: """Scan through ``fifo_offset`` and configure midpoint setting. :return: Optimal ``fifo_offset`` setting with maximum margin to write @@ -813,15 +826,15 @@ class Phaser: # distance 32101234 # error free x xx config9 = self.dac_read(0x09) - delay(.1*ms) + self.core.delay(.1*ms) good = 0 for o in range(8): # set new fifo_offset self.dac_write(0x09, (config9 & 0x1fff) | (o << 13)) self.clear_dac_alarms() - delay(.1*ms) # run + self.core.delay(.1*ms) # run alarms = self.get_dac_alarms() - delay(.1*ms) # slack + self.core.delay(.1*ms) # slack if (alarms >> 11) & 0x7 == 0: # any fifo alarm good |= 1 << o # if there are good offsets accross the wrap around @@ -835,7 +848,7 @@ class Phaser: sum = 0 count = 0 for o in range(8): - if good & (1 << o): + if good & (1 << o) != 0: sum += o count += 1 if count == 0: @@ -845,6 +858,7 @@ class Phaser: return best +@nac3 class PhaserChannel: """Phaser channel IQ pair. @@ -880,18 +894,26 @@ class PhaserChannel: Miqro is not affected by this, but both the oscillators and Miqro can be affected by intrinsic overshoot of the interpolator on the DAC. """ - kernel_invariants = {"index", "phaser", "trf_mmap"} + + core: KernelInvariant[Core] + phaser: KernelInvariant[Phaser] + index: KernelInvariant[int32] + trf_mmap: KernelInvariant[list[int32]] + oscillator: Kernel[list[PhaserOscillator]] + miqro: KernelInvariant[Miqro] def __init__(self, phaser, index, trf): + self.core = phaser.core self.phaser = phaser self.index = index self.trf_mmap = TRF372017(trf).get_mmap() + self.trf_mmap = [int32(uint32(x)) for x in self.trf_mmap] # NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/14 self.oscillator = [PhaserOscillator(self, osc) for osc in range(5)] self.miqro = Miqro(self) @kernel - def get_dac_data(self) -> TInt32: + def get_dac_data(self) -> int32: """Get a sample of the current DAC data. The data is split accross multiple registers and thus the data @@ -903,7 +925,7 @@ class PhaserChannel: return self.phaser.read32(PHASER_ADDR_DAC0_DATA + (self.index << 4)) @kernel - def set_dac_test(self, data: TInt32): + def set_dac_test(self, data: int32): """Set the DAC test data. :param data: 32-bit IQ test data, I/DACA/DACC in the 16 LSB, @@ -912,7 +934,7 @@ class PhaserChannel: self.phaser.write32(PHASER_ADDR_DAC0_TEST + (self.index << 4), data) @kernel - def set_duc_cfg(self, clr=0, clr_once=0, select=0): + def set_duc_cfg(self, clr: bool = False, clr_once: bool = False, select: int32 = 0): """Set the digital upconverter (DUC) and interpolator configuration. :param clr: Keep the phase accumulator cleared (persistent) @@ -921,11 +943,11 @@ class PhaserChannel: data, other values: reserved) """ self.phaser.write8(PHASER_ADDR_DUC0_CFG + (self.index << 4), - ((clr & 1) << 0) | ((clr_once & 1) << 1) | + (int32(clr) << 0) | (int32(clr_once) << 1) | ((select & 3) << 2)) @kernel - def set_duc_frequency_mu(self, ftw): + def set_duc_frequency_mu(self, ftw: int32): """Set the DUC frequency. :param ftw: DUC frequency tuning word (32-bit) @@ -933,17 +955,17 @@ class PhaserChannel: self.phaser.write32(PHASER_ADDR_DUC0_F + (self.index << 4), ftw) @kernel - def set_duc_frequency(self, frequency): + def set_duc_frequency(self, frequency: float): """Set the DUC frequency in SI units. :param frequency: DUC frequency in Hz (passband from -200 MHz to 200 MHz, wrapping around at +- 250 MHz) """ - ftw = int32(round(frequency*((1 << 30)/(125*MHz)))) + ftw = round(frequency*(float(1 << 30)/(125.*MHz))) self.set_duc_frequency_mu(ftw) @kernel - def set_duc_phase_mu(self, pow): + def set_duc_phase_mu(self, pow: int32): """Set the DUC phase offset. :param pow: DUC phase offset word (16-bit) @@ -953,16 +975,16 @@ class PhaserChannel: self.phaser.write8(addr + 1, pow) @kernel - def set_duc_phase(self, phase): + def set_duc_phase(self, phase: float): """Set the DUC phase in SI units. :param phase: DUC phase in turns """ - pow = int32(round(phase*(1 << 16))) + pow = round(phase*float(1 << 16)) self.set_duc_phase_mu(pow) @kernel - def set_nco_frequency_mu(self, ftw): + def set_nco_frequency_mu(self, ftw: int32): """Set the NCO frequency. This method stages the new NCO frequency, but does not apply it. @@ -977,7 +999,7 @@ class PhaserChannel: self.phaser.dac_write(0x14 + (self.index << 1), ftw) @kernel - def set_nco_frequency(self, frequency): + def set_nco_frequency(self, frequency: float): """Set the NCO frequency in SI units. This method stages the new NCO frequency, but does not apply it. @@ -989,11 +1011,11 @@ class PhaserChannel: :param frequency: NCO frequency in Hz (passband from -400 MHz to 400 MHz, wrapping around at +- 500 MHz) """ - ftw = int32(round(frequency*((1 << 30)/(250*MHz)))) + ftw = round(frequency*(float(1 << 30)/(250.*MHz))) self.set_nco_frequency_mu(ftw) @kernel - def set_nco_phase_mu(self, pow): + def set_nco_phase_mu(self, pow: int32): """Set the NCO phase offset. By default, the new NCO phase applies on completion of the SPI @@ -1010,7 +1032,7 @@ class PhaserChannel: self.phaser.dac_write(0x12 + self.index, pow) @kernel - def set_nco_phase(self, phase): + def set_nco_phase(self, phase: float): """Set the NCO phase in SI units. By default, the new NCO phase applies on completion of the SPI @@ -1024,36 +1046,36 @@ class PhaserChannel: :param phase: NCO phase in turns """ - pow = int32(round(phase*(1 << 16))) + pow = round(phase*float(1 << 16)) self.set_nco_phase_mu(pow) @kernel - def set_att_mu(self, data): + def set_att_mu(self, data: int32): """Set channel attenuation. :param data: Attenuator data in machine units (8-bit) """ div = 34 # 30 ns min period - t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) + t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns) self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div, - end=1) + end=True) self.phaser.spi_write(data) delay_mu(t_xfer) @kernel - def set_att(self, att): + def set_att(self, att: float): """Set channel attenuation in SI units. :param att: Attenuation in dB """ # 2 lsb are inactive, resulting in 8 LSB per dB - data = 0xff - int32(round(att*8)) + data = 0xff - round(att*8.) if data < 0 or data > 0xff: raise ValueError("attenuation out of bounds") self.set_att_mu(data) @kernel - def get_att_mu(self) -> TInt32: + def get_att_mu(self) -> int32: """Read current attenuation. The current attenuation value is read without side effects. @@ -1061,39 +1083,39 @@ class PhaserChannel: :return: Current attenuation in machine units """ div = 34 - t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) + t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns) self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div, - end=0) + end=False) self.phaser.spi_write(0) delay_mu(t_xfer) data = self.phaser.spi_read() - delay(20*us) # slack + self.core.delay(20.*us) # slack self.phaser.spi_cfg(select=PHASER_SEL_ATT0 << self.index, div=div, - end=1) + end=True) self.phaser.spi_write(data) delay_mu(t_xfer) return data @kernel - def trf_write(self, data, readback=False): + def trf_write(self, data: int32, readback: bool = False) -> int32: """Write 32 bits to quadrature upconverter register. :param data: Register data (32-bit) containing encoded address :param readback: Whether to return the read back MISO data """ div = 34 # 50 ns min period - t_xfer = self.phaser.core.seconds_to_mu((8 + 1)*div*4*ns) + t_xfer = self.core.seconds_to_mu((8. + 1.)*float(div)*4.*ns) read = 0 - end = 0 - clk_phase = 0 + end = False + clk_phase = False if readback: - clk_phase = 1 + clk_phase = True for i in range(4): if i == 0 or i == 3: if i == 3: - end = 1 + end = True self.phaser.spi_cfg(select=PHASER_SEL_TRF0 << self.index, - div=div, lsb_first=1, clk_phase=clk_phase, + div=div, lsb_first=True, clk_phase=clk_phase, end=end) self.phaser.spi_write(data) data >>= 8 @@ -1101,22 +1123,22 @@ class PhaserChannel: if readback: read >>= 8 read |= self.phaser.spi_read() << 24 - delay(20*us) # slack + self.core.delay(20.*us) # slack return read @kernel - def trf_read(self, addr, cnt_mux_sel=0) -> TInt32: + def trf_read(self, addr: int32, cnt_mux_sel: int32 = 0) -> int32: """Quadrature upconverter register read. :param addr: Register address to read (0 to 7) :param cnt_mux_sel: Report VCO counter min or max frequency :return: Register data (32-bit) """ - self.trf_write(0x80000008 | (addr << 28) | (cnt_mux_sel << 27)) + self.trf_write(int32(int64(0x80000008)) | (addr << 28) | (cnt_mux_sel << 27)) # single clk pulse with ~LE to start readback - self.phaser.spi_cfg(select=0, div=34, end=1, length=1) + self.phaser.spi_cfg(select=0, div=34, end=True, length=1) self.phaser.spi_write(0) - delay((1 + 1)*34*4*ns) + self.core.delay((1. + 1.)*34.*4.*ns) return self.trf_write(0x00000008 | (cnt_mux_sel << 27), readback=True) @@ -1129,41 +1151,41 @@ class PhaserChannel: self.trf_write(self.trf_mmap[1] | (1 << 31)) @kernel - def en_trf_out(self, rf=1, lo=0): + def en_trf_out(self, rf: bool = True, lo: bool = False): """Enable the rf/lo outputs of the upconverter (hardware variant). :param rf: 1 to enable RF output, 0 to disable :param lo: 1 to enable LO output, 0 to disable """ data = self.trf_read(0xc) - delay(0.1 * ms) + self.core.delay(0.1 * ms) # set RF and LO output bits data = data | (1 << 12) | (1 << 13) | (1 << 14) # clear to enable output - if rf == 1: + if rf: data = data ^ (1 << 14) - if lo == 1: + if lo: data = data ^ ((1 << 12) | (1 << 13)) self.trf_write(data) @kernel - def set_servo(self, profile=0, enable=0, hold=0): + def set_servo(self, profile: int32 = 0, enable: bool = False, hold: bool = False): """Set the servo configuration. - :param enable: 1 to enable servo, 0 to disable servo (default). If disabled, + :param enable: True to enable servo, False to disable servo (default). If disabled, the servo is bypassed and hold is enforced since the control loop is broken. - :param hold: 1 to hold the servo IIR filter output constant, 0 for normal operation. + :param hold: True to hold the servo IIR filter output constant, False for normal operation. :param profile: Profile index to select for channel. (0 to 3) """ if (profile < 0) or (profile > 3): raise ValueError("invalid profile index") addr = PHASER_ADDR_SERVO_CFG0 + self.index # enforce hold if the servo is disabled - data = (profile << 2) | (((hold | ~enable) & 1) << 1) | (enable & 1) + data = (profile << 2) | ((int32(hold) | int32(not enable)) << 1) | int32(enable) self.phaser.write8(addr, data) @kernel - def set_iir_mu(self, profile, b0, b1, a1, offset): + def set_iir_mu(self, profile: int32, b0: int32, b1: int32, a1: int32, offset: int32): """Load a servo profile consiting of the three filter coefficients and an output offset. Avoid setting the IIR parameters of the currently active profile. @@ -1202,7 +1224,7 @@ class PhaserChannel: addr += 2 @kernel - def set_iir(self, profile, kp, ki=0., g=0., x_offset=0., y_offset=0.): + def set_iir(self, profile: int32, kp: float, ki: float = 0., g: float = 0., x_offset: float = 0., y_offset: float = 0.): """Set servo profile IIR coefficients. Avoid setting the IIR parameters of the currently active profile. @@ -1243,23 +1265,23 @@ class PhaserChannel: COEFF_MAX = 1 << SERVO_COEFF_WIDTH - 1 DATA_MAX = 1 << SERVO_DATA_WIDTH - 1 - kp *= NORM + kp *= float(NORM) if ki == 0.: # pure P a1 = 0 b1 = 0 - b0 = int(round(kp)) + b0 = round(kp) else: # I or PI - ki *= NORM*SERVO_T_CYCLE/2. + ki *= float(NORM)*SERVO_T_CYCLE/2. if g == 0.: c = 1. a1 = NORM else: - c = 1./(1. + ki/(g*NORM)) - a1 = int(round((2.*c - 1.)*NORM)) - b0 = int(round(kp + ki*c)) - b1 = int(round(kp + (ki - 2.*kp)*c)) + c = 1./(1. + ki/(g*float(NORM))) + a1 = round((2.*c - 1.)*float(NORM)) + b0 = round(kp + ki*c) + b1 = round(kp + (ki - 2.*kp)*c) if b1 == -b0: raise ValueError("low integrator gain and/or gain limit") @@ -1268,12 +1290,13 @@ class PhaserChannel: raise ValueError("high gains") forward_gain = (b0 + b1) * (1 << SERVO_DATA_WIDTH - 1 - SERVO_COEFF_SHIFT) - effective_offset = int(round(DATA_MAX * y_offset + forward_gain * x_offset)) + effective_offset = round(float(DATA_MAX) * y_offset + float(forward_gain) * x_offset) self.set_iir_mu(profile, b0, b1, a1, effective_offset) +@nac3 class PhaserOscillator: """Phaser IQ channel oscillator (NCO/DDS). @@ -1281,15 +1304,19 @@ class PhaserOscillator: 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"} + + core: KernelInvariant[Core] + channel: KernelInvariant[PhaserChannel] + base_addr: KernelInvariant[int32] def __init__(self, channel, index): + self.core = channel.core self.channel = channel self.base_addr = ((self.channel.phaser.channel_base + 1 + 2*self.channel.index) << 8) | index @kernel - def set_frequency_mu(self, ftw): + def set_frequency_mu(self, ftw: int32): """Set Phaser MultiDDS frequency tuning word. :param ftw: Frequency tuning word (32-bit) @@ -1297,41 +1324,42 @@ class PhaserOscillator: rtio_output(self.base_addr, ftw) @kernel - def set_frequency(self, frequency): + def set_frequency(self, frequency: float): """Set Phaser MultiDDS frequency. :param frequency: Frequency in Hz (passband from -10 MHz to 10 MHz, wrapping around at +- 12.5 MHz) """ - ftw = int32(round(frequency*((1 << 30)/(6.25*MHz)))) + ftw = round(frequency*(float(1 << 30)/(6.25*MHz))) self.set_frequency_mu(ftw) @kernel - def set_amplitude_phase_mu(self, asf=0x7fff, pow=0, clr=0): + def set_amplitude_phase_mu(self, asf: int32 = 0x7fff, pow: int32 = 0, clr: bool = False): """Set Phaser MultiDDS amplitude, phase offset and accumulator clear. :param asf: Amplitude (15-bit) :param pow: Phase offset word (16-bit) :param clr: Clear the phase accumulator (persistent) """ - data = (asf & 0x7fff) | ((clr & 1) << 15) | (pow << 16) + data = (asf & 0x7fff) | (int32(clr) << 15) | (pow << 16) rtio_output(self.base_addr + (1 << 8), data) @kernel - def set_amplitude_phase(self, amplitude, phase=0., clr=0): + def set_amplitude_phase(self, amplitude: float, phase: float = 0., clr: bool = False): """Set Phaser MultiDDS amplitude and phase. :param amplitude: Amplitude in units of full scale :param phase: Phase in turns :param clr: Clear the phase accumulator (persistent) """ - asf = int32(round(amplitude*0x7fff)) + asf = round(amplitude*float(0x7fff)) if asf < 0 or asf > 0x7fff: raise ValueError("amplitude out of bounds") - pow = int32(round(phase*(1 << 16))) + pow = round(phase*float(1 << 16)) self.set_amplitude_phase_mu(asf, pow, clr) +@nac3 class Miqro: """ Miqro pulse generator. @@ -1430,7 +1458,12 @@ class Miqro: (may be increased, TBC). """ + core: KernelInvariant[Core] + channel: KernelInvariant[PhaserChannel] + base_addr: KernelInvariant[int32] + def __init__(self, channel): + self.core = channel.core self.channel = channel self.base_addr = (self.channel.phaser.channel_base + 1 + self.channel.index) << 8 @@ -1445,12 +1478,12 @@ class Miqro: """ for osc in range(16): self.set_profile_mu(osc, profile=0, ftw=0, asf=0) - delay(20*us) + self.core.delay(20.*us) self.set_window_mu(start=0, iq=[0], order=0) self.pulse(window=0, profiles=[0]) @kernel - def set_profile_mu(self, oscillator, profile, ftw, asf, pow_=0): + def set_profile_mu(self, oscillator: int32, profile: int32, ftw: int32, asf: int32, pow_: int32 = 0): """Store an oscillator profile (machine units). :param oscillator: Oscillator index (0 to 15) @@ -1471,7 +1504,7 @@ class Miqro: (asf & 0xffff) | (pow_ << 16)) @kernel - def set_profile(self, oscillator, profile, frequency, amplitude, phase=0.): + def set_profile(self, oscillator: int32, profile: int32, frequency: float, amplitude: float, phase: float = 0.) -> int32: """Store an oscillator profile. :param oscillator: Oscillator index (0 to 15) @@ -1483,16 +1516,16 @@ class Miqro: phase in this context. :return: The quantized 32-bit frequency tuning word """ - ftw = int32(round(frequency*((1 << 30)/(62.5*MHz)))) - asf = int32(round(amplitude*0xffff)) + ftw = round(frequency*(float(1 << 30)/(62.5*MHz))) + asf = round(amplitude*float(0xffff)) if asf < 0 or asf > 0xffff: raise ValueError("amplitude out of bounds") - pow_ = int32(round(phase*(1 << 16))) + pow_ = round(phase*float(1 << 16)) self.set_profile_mu(oscillator, profile, ftw, asf, pow_) return ftw @kernel - def set_window_mu(self, start, iq, rate=1, shift=0, order=3, head=1, tail=1): + def set_window_mu(self, start: int32, iq: list[int32], rate: int32 = 1, shift: int32 = 0, order: int32 = 3, head: bool = True, tail: bool = True) -> int32: """Store a window segment (machine units). :param start: Window start address (0 to 0x3ff) @@ -1530,16 +1563,16 @@ class Miqro: ((rate - 1) << 10) | (shift << 22) | (order << 28) | - ((head & 1) << 30) | - ((tail & 1) << 31) + (int32(head) << 30) | + (int32(tail) << 31) ) for iqi in iq: self.channel.phaser.write32(PHASER_ADDR_MIQRO_MEM_DATA, iqi) - delay(20*us) # slack for long windows + self.core.delay(20.*us) # slack for long windows return (start + 1 + len(iq)) & 0x3ff @kernel - def set_window(self, start, iq, period=4*ns, order=3, head=1, tail=1): + def set_window(self, start: int32, iq: list[tuple[float, float]], period: float = 4e-9, order: int32 = 3, head: bool = True, tail: bool = True) -> float: """Store a window segment. :param start: Window start address (0 to 0x3ff) @@ -1558,25 +1591,25 @@ class Miqro: to zero with the chosen interpolation. :return: Actual sample period in SI units """ - rate = int32(round(period/(4*ns))) + rate = round(period/(4.*ns)) gain = 1. for _ in range(order): - gain *= rate + gain *= float(rate) shift = 0 while gain >= 2.: shift += 1 gain *= .5 - scale = ((1 << 15) - 1)/gain + scale = float((1 << 15) - 1)/gain iq_mu = [ (int32(round(iqi[0]*scale)) & 0xffff) | (int32(round(iqi[1]*scale)) << 16) for iqi in iq ] self.set_window_mu(start, iq_mu, rate, shift, order, head, tail) - return (len(iq) + order)*rate*4*ns + return float((len(iq) + order)*rate)*4.*ns @kernel - def encode(self, window, profiles, data): + def encode(self, window: int32, profiles: list[int32], data: list[int32]) -> int32: """Encode window and profile selection. :param window: Window start address (0 to 0x3ff) @@ -1606,7 +1639,7 @@ class Miqro: return word + 1 @kernel - def pulse_mu(self, data): + def pulse_mu(self, data: list[int32]): """Emit a pulse (encoded) The pulse fiducial timing resolution is 4 ns. @@ -1615,15 +1648,15 @@ class Miqro: returned by :meth:`encode`. """ word = len(data) - delay_mu(-8*word) # back shift to align + delay_mu(int64(-8*word)) # back shift to align while word > 0: word -= 1 - delay_mu(8) + delay_mu(int64(8)) # final write sets pulse stb rtio_output(self.base_addr + word, data[word]) @kernel - def pulse(self, window, profiles): + def pulse(self, window: int32, profiles: list[int32]): """Emit a pulse This encodes the window and profiles (see :meth:`encode`) and emits them diff --git a/artiq/coredevice/rtio.py b/artiq/coredevice/rtio.py index b92769396..83c559761 100644 --- a/artiq/coredevice/rtio.py +++ b/artiq/coredevice/rtio.py @@ -1,30 +1,31 @@ -from artiq.language.core import syscall -from artiq.language.types import TInt32, TInt64, TList, TNone, TTuple +from numpy import int32, int64 + +from artiq.language.core import extern -@syscall(flags={"nowrite"}) -def rtio_output(target: TInt32, data: TInt32) -> TNone: +@extern +def rtio_output(target: int32, data: int32): raise NotImplementedError("syscall not simulated") -@syscall(flags={"nowrite"}) -def rtio_output_wide(target: TInt32, data: TList(TInt32)) -> TNone: +@extern +def rtio_output_wide(target: int32, data: list[int32]): raise NotImplementedError("syscall not simulated") -@syscall(flags={"nowrite"}) -def rtio_input_timestamp(timeout_mu: TInt64, channel: TInt32) -> TInt64: +@extern +def rtio_input_timestamp(timeout_mu: int64, channel: int32) -> int64: raise NotImplementedError("syscall not simulated") -@syscall(flags={"nowrite"}) -def rtio_input_data(channel: TInt32) -> TInt32: +@extern +def rtio_input_data(channel: int32) -> int32: raise NotImplementedError("syscall not simulated") -@syscall(flags={"nowrite"}) -def rtio_input_timestamped_data(timeout_mu: TInt64, - channel: TInt32) -> TTuple([TInt64, TInt32]): +@extern +def rtio_input_timestamped_data(timeout_mu: int64, + channel: int32) -> tuple[int64, int32]: """Wait for an input event up to ``timeout_mu`` on the given channel, and return a tuple of timestamp and attached data, or (-1, 0) if the timeout is reached.""" diff --git a/artiq/coredevice/runtime.py b/artiq/coredevice/runtime.py deleted file mode 100644 index 4d65e86d8..000000000 --- a/artiq/coredevice/runtime.py +++ /dev/null @@ -1,14 +0,0 @@ -import os - -from artiq import __artiq_dir__ as artiq_dir - - -class SourceLoader: - def __init__(self, runtime_root): - self.runtime_root = runtime_root - - def get_source(self, filename): - with open(os.path.join(self.runtime_root, filename)) as f: - return f.read() - -source_loader = SourceLoader(os.path.join(artiq_dir, "soc", "runtime")) diff --git a/artiq/coredevice/sampler.py b/artiq/coredevice/sampler.py index 096c36f68..4f9e680ef 100644 --- a/artiq/coredevice/sampler.py +++ b/artiq/coredevice/sampler.py @@ -1,13 +1,17 @@ -from artiq.language.core import kernel, delay, portable +from numpy import int32 + +from artiq.language.core import nac3, kernel, portable, Kernel, KernelInvariant from artiq.language.units import ns -from artiq.coredevice import spi2 as spi +from artiq.coredevice.core import Core +from artiq.coredevice.spi2 import * +from artiq.coredevice.ttl import TTLOut -SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END | - 0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | - 0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) +SPI_CONFIG = (0*SPI_OFFLINE | 0*SPI_END | + 0*SPI_INPUT | 0*SPI_CS_POLARITY | + 0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE | + 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX) SPI_CS_ADC = 0 # no CS, SPI_END does not matter, framing is done with CNV @@ -15,7 +19,7 @@ SPI_CS_PGIA = 1 # separate SPI bus, CS used as RCLK @portable -def adc_mu_to_volt(data, gain=0, corrected_fs=True): +def adc_mu_to_volt(data: int32, gain: int32 = 0, corrected_fs: bool = True) -> float: """Convert ADC data in machine units to volts. :param data: 16-bit signed ADC word @@ -24,19 +28,21 @@ def adc_mu_to_volt(data, gain=0, corrected_fs=True): Should be ``True`` for Sampler revisions after v2.1. ``False`` for v2.1 and earlier. :return: Voltage in volts """ + volt_per_lsb = 0. if gain == 0: - volt_per_lsb = 20.48 / (1 << 16) if corrected_fs else 20. / (1 << 16) + volt_per_lsb = 20.48 / float(1 << 16) if corrected_fs else 20. / float(1 << 16) elif gain == 1: - volt_per_lsb = 2.048 / (1 << 16) if corrected_fs else 2. / (1 << 16) + volt_per_lsb = 2.048 / float(1 << 16) if corrected_fs else 2. / float(1 << 16) elif gain == 2: - volt_per_lsb = .2048 / (1 << 16) if corrected_fs else .2 / (1 << 16) + volt_per_lsb = .2048 / float(1 << 16) if corrected_fs else .2 / float(1 << 16) elif gain == 3: - volt_per_lsb = 0.02048 / (1 << 16) if corrected_fs else .02 / (1 << 16) + volt_per_lsb = 0.02048 / float(1 << 16) if corrected_fs else .02 / float(1 << 16) else: raise ValueError("invalid gain") - return data * volt_per_lsb + return float(data)* volt_per_lsb +@nac3 class Sampler: """Sampler ADC. @@ -53,7 +59,13 @@ class Sampler: :param hw_rev: Sampler's hardware revision string (default 'v2.2') :param core_device: Core device name """ - kernel_invariants = {"bus_adc", "bus_pgia", "core", "cnv", "div", "corrected_fs"} + core: KernelInvariant[Core] + bus_adc: KernelInvariant[SPIMaster] + bus_pgia: KernelInvariant[SPIMaster] + cnv: KernelInvariant[TTLOut] + div: KernelInvariant[int32] + gains: Kernel[int32] + corrected_fs: KernelInvariant[bool] def __init__(self, dmgr, spi_adc_device, spi_pgia_device, cnv_device, div=8, gains=0x0000, hw_rev="v2.2", core_device="core"): @@ -77,13 +89,13 @@ class Sampler: Sets up SPI channels. """ - self.bus_adc.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, + self.bus_adc.set_config_mu(SPI_CONFIG | SPI_INPUT | SPI_END, 32, self.div, SPI_CS_ADC) - self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END, + self.bus_pgia.set_config_mu(SPI_CONFIG | SPI_END, 16, self.div, SPI_CS_PGIA) @kernel - def set_gain_mu(self, channel, gain): + def set_gain_mu(self, channel: int32, gain: int32): """Set instrumentation amplifier gain of a channel. The four gain settings (0, 1, 2, 3) corresponds to gains of @@ -99,21 +111,21 @@ class Sampler: self.gains = gains @kernel - def get_gains_mu(self): + def get_gains_mu(self) -> int32: """Read the PGIA gain settings of all channels. :return: The PGIA gain settings in machine units. """ - self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + self.bus_pgia.set_config_mu(SPI_CONFIG | SPI_END | SPI_INPUT, 16, self.div, SPI_CS_PGIA) self.bus_pgia.write(self.gains << 16) - self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END, + self.bus_pgia.set_config_mu(SPI_CONFIG | SPI_END, 16, self.div, SPI_CS_PGIA) self.gains = self.bus_pgia.read() & 0xffff return self.gains @kernel - def sample_mu(self, data): + def sample_mu(self, data: list[int32]): """Acquire a set of samples. Perform a conversion and transfer the samples. @@ -127,8 +139,8 @@ class Sampler: The ``data`` list will always be filled with the last item holding to the sample from channel 7. """ - self.cnv.pulse(30*ns) # t_CNVH - delay(450*ns) # t_CONV + self.cnv.pulse(30.*ns) # t_CNVH + self.core.delay(450.*ns) # t_CONV mask = 1 << 15 for i in range(len(data)//2): self.bus_adc.write(0) @@ -139,7 +151,7 @@ class Sampler: data[i - 1] = -(val & mask) + (val & ~mask) @kernel - def sample(self, data): + def sample(self, data: list[float]): """Acquire a set of samples. See also :meth:`Sampler.sample_mu`. @@ -147,7 +159,7 @@ class Sampler: :param data: List of floating point data samples to fill. """ n = len(data) - adc_data = [0]*n + adc_data = [0 for _ in range(n)] self.sample_mu(adc_data) for i in range(n): channel = i + 8 - len(data) diff --git a/artiq/coredevice/shuttler.py b/artiq/coredevice/shuttler.py index 424c62823..37877de42 100644 --- a/artiq/coredevice/shuttler.py +++ b/artiq/coredevice/shuttler.py @@ -1,18 +1,21 @@ -from artiq.language.core import * -from artiq.language.types import * +from numpy import int32, int64 + +from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable, Option, none from artiq.coredevice.rtio import rtio_output, rtio_input_data -from artiq.coredevice import spi2 as spi +from artiq.coredevice.core import Core +from artiq.coredevice.spi2 import * from artiq.language.units import us @portable -def shuttler_volt_to_mu(volt): +def shuttler_volt_to_mu(volt: float) -> int32: """Return the equivalent DAC code. Valid input range is from -10 to 10 - LSB. """ - return round((1 << 16) * (volt / 20.0)) & 0xffff + return round(float(1 << 16) * (volt / 20.0)) & 0xffff +@nac3 class Config: """Shuttler configuration registers interface. @@ -28,10 +31,13 @@ class Config: :param channel: RTIO channel number of this interface. :param core_device: Core device name. """ - kernel_invariants = { - "core", "channel", "target_base", "target_read", - "target_gain", "target_offset", "target_clr" - } + core: KernelInvariant[Core] + channel: KernelInvariant[int32] + target_base: KernelInvariant[int32] + target_read: KernelInvariant[int32] + target_gain: KernelInvariant[int32] + target_offset: KernelInvariant[int32] + target_clr: KernelInvariant[int32] def __init__(self, dmgr, channel, core_device="core"): self.core = dmgr.get(core_device) @@ -43,7 +49,7 @@ class Config: self.target_clr = 1 * (1 << 5) @kernel - def set_clr(self, clr): + def set_clr(self, clr: int32): """Set/Unset waveform phase clear bits. Each bit corresponds to a Shuttler waveform generator core. Setting a @@ -57,7 +63,7 @@ class Config: rtio_output(self.target_base | self.target_clr, clr) @kernel - def set_gain(self, channel, gain): + def set_gain(self, channel: int32, gain: int32): """Set the 16-bits pre-DAC gain register of a Shuttler Core channel. The `gain` parameter represents the decimal portion of the gain @@ -70,7 +76,7 @@ class Config: rtio_output(self.target_base | self.target_gain | channel, gain) @kernel - def get_gain(self, channel): + def get_gain(self, channel: int32) -> int32: """Return the pre-DAC gain value of a Shuttler Core channel. :param channel: The Shuttler Core channel. @@ -81,7 +87,7 @@ class Config: return rtio_input_data(self.channel) @kernel - def set_offset(self, channel, offset): + def set_offset(self, channel: int32, offset: int32): """Set the 16-bits pre-DAC offset register of a Shuttler Core channel. See also :meth:`shuttler_volt_to_mu`. @@ -92,7 +98,7 @@ class Config: rtio_output(self.target_base | self.target_offset | channel, offset) @kernel - def get_offset(self, channel): + def get_offset(self, channel: int32) -> int32: """Return the pre-DAC offset value of a Shuttler Core channel. :param channel: The Shuttler Core channel. @@ -103,6 +109,7 @@ class Config: return rtio_input_data(self.channel) +@nac3 class DCBias: """Shuttler Core cubic DC-bias spline. @@ -124,7 +131,9 @@ class DCBias: :param channel: RTIO channel number of this DC-bias spline interface. :param core_device: Core device name. """ - kernel_invariants = {"core", "channel", "target_o"} + core: KernelInvariant[Core] + channel: KernelInvariant[int32] + target_o: KernelInvariant[int32] def __init__(self, dmgr, channel, core_device="core"): self.core = dmgr.get(core_device) @@ -132,7 +141,7 @@ class DCBias: self.target_o = channel << 8 @kernel - def set_waveform(self, a0: TInt32, a1: TInt32, a2: TInt64, a3: TInt64): + def set_waveform(self, a0: int32, a1: int32, a2: int64, a3: int64): """Set the DC-bias spline waveform. Given `a(t)` as defined in :class:`DCBias`, the coefficients should be @@ -167,12 +176,12 @@ class DCBias: a0, a1, a1 >> 16, - a2 & 0xFFFF, - (a2 >> 16) & 0xFFFF, - (a2 >> 32) & 0xFFFF, - a3 & 0xFFFF, - (a3 >> 16) & 0xFFFF, - (a3 >> 32) & 0xFFFF, + int32(a2 & int64(0xFFFF)), + int32((a2 >> 16) & int64(0xFFFF)), + int32((a2 >> 32) & int64(0xFFFF)), + int32(a3 & int64(0xFFFF)), + int32((a3 >> 16) & int64(0xFFFF)), + int32((a3 >> 32) & int64(0xFFFF)), ] for i in range(len(coef_words)): @@ -180,6 +189,7 @@ class DCBias: delay_mu(int64(self.core.ref_multiplier)) +@nac3 class DDS: """Shuttler Core DDS spline. @@ -205,7 +215,9 @@ class DDS: :param channel: RTIO channel number of this DC-bias spline interface. :param core_device: Core device name. """ - kernel_invariants = {"core", "channel", "target_o"} + core: KernelInvariant[Core] + channel: KernelInvariant[int32] + target_o: KernelInvariant[int32] def __init__(self, dmgr, channel, core_device="core"): self.core = dmgr.get(core_device) @@ -213,8 +225,8 @@ class DDS: self.target_o = channel << 8 @kernel - def set_waveform(self, b0: TInt32, b1: TInt32, b2: TInt64, b3: TInt64, - c0: TInt32, c1: TInt32, c2: TInt32): + def set_waveform(self, b0: int32, b1: int32, b2: int64, b3: int64, + c0: int32, c1: int32, c2: int32): """Set the DDS spline waveform. Given `b(t)` and `c(t)` as defined in :class:`DDS`, the coefficients @@ -257,12 +269,12 @@ class DDS: b0, b1, b1 >> 16, - b2 & 0xFFFF, - (b2 >> 16) & 0xFFFF, - (b2 >> 32) & 0xFFFF, - b3 & 0xFFFF, - (b3 >> 16) & 0xFFFF, - (b3 >> 32) & 0xFFFF, + int32(b2 & int64(0xFFFF)), + int32((b2 >> 16) & int64(0xFFFF)), + int32((b2 >> 32) & int64(0xFFFF)), + int32(b3 & int64(0xFFFF)), + int32((b3 >> 16) & int64(0xFFFF)), + int32((b3 >> 32) & int64(0xFFFF)), c0, c1, c1 >> 16, @@ -275,13 +287,16 @@ class DDS: delay_mu(int64(self.core.ref_multiplier)) +@nac3 class Trigger: """Shuttler Core spline coefficients update trigger. :param channel: RTIO channel number of the trigger interface. :param core_device: Core device name. """ - kernel_invariants = {"core", "channel", "target_o"} + core: KernelInvariant[Core] + channel: KernelInvariant[int32] + target_o: KernelInvariant[int32] def __init__(self, dmgr, channel, core_device="core"): self.core = dmgr.get(core_device) @@ -289,7 +304,7 @@ class Trigger: self.target_o = channel << 8 @kernel - def trigger(self, trig_out): + def trigger(self, trig_out: int32): """Triggers coefficient update of (a) Shuttler Core channel(s). Each bit corresponds to a Shuttler waveform generator core. Setting @@ -303,15 +318,15 @@ class Trigger: rtio_output(self.target_o, trig_out) -RELAY_SPI_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END | - 0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | - 0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) +RELAY_SPI_CONFIG = (0*SPI_OFFLINE | 1*SPI_END | + 0*SPI_INPUT | 0*SPI_CS_POLARITY | + 0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE | + 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX) -ADC_SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END | - 0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | - 1*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) +ADC_SPI_CONFIG = (0*SPI_OFFLINE | 0*SPI_END | + 0*SPI_INPUT | 0*SPI_CS_POLARITY | + 1*SPI_CLK_POLARITY | 1*SPI_CLK_PHASE | + 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX) # SPI clock write and read dividers # CS should assert at least 9.5 ns after clk pulse @@ -334,6 +349,7 @@ _AD4115_REG_CH0 = 0x10 _AD4115_REG_SETUPCON0 = 0x20 +@nac3 class Relay: """Shuttler AFE relay switches. @@ -348,7 +364,8 @@ class Relay: :param spi_device: SPI bus device name. :param core_device: Core device name. """ - kernel_invariant = {"core", "bus"} + core: KernelInvariant[Core] + bus: KernelInvariant[SPIMaster] def __init__(self, dmgr, spi_device, core_device="core"): self.core = dmgr.get(core_device) @@ -365,7 +382,7 @@ class Relay: RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED) @kernel - def enable(self, en: TInt32): + def enable(self, en: int32): """Enable/disable relay switches of corresponding channels. Each bit corresponds to the relay switch of a channel. Asserting a bit @@ -378,20 +395,22 @@ class Relay: self.bus.write(en << 16) +@nac3 class ADC: """Shuttler AFE ADC (AD4115) driver. :param spi_device: SPI bus device name. :param core_device: Core device name. """ - kernel_invariant = {"core", "bus"} + core: KernelInvariant[Core] + bus: KernelInvariant[SPIMaster] def __init__(self, dmgr, spi_device, core_device="core"): self.core = dmgr.get(core_device) self.bus = dmgr.get(spi_device) @kernel - def read_id(self) -> TInt32: + def read_id(self) -> int32: """Read the product ID of the ADC. The expected return value is 0x38DX, the 4 LSbs are don't cares. @@ -413,86 +432,86 @@ class ADC: after the transfer appears to interrupt the start-up sequence. """ self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC) - self.bus.write(0xffffffff) - self.bus.write(0xffffffff) + self.bus.write(-1) + self.bus.write(-1) self.bus.set_config_mu( - ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC) - self.bus.write(0xffffffff) + ADC_SPI_CONFIG | SPI_END, 32, SPIT_ADC_WR, CS_ADC) + self.bus.write(-1) @kernel - def read8(self, addr: TInt32) -> TInt32: + def read8(self, addr: int32) -> int32: """Read from 8-bit register. :param addr: Register address. :return: Read-back register content. """ self.bus.set_config_mu( - ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + ADC_SPI_CONFIG | SPI_END | SPI_INPUT, 16, SPIT_ADC_RD, CS_ADC) self.bus.write((addr | 0x40) << 24) return self.bus.read() & 0xff @kernel - def read16(self, addr: TInt32) -> TInt32: + def read16(self, addr: int32) -> int32: """Read from 16-bit register. :param addr: Register address. :return: Read-back register content. """ self.bus.set_config_mu( - ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + ADC_SPI_CONFIG | SPI_END | SPI_INPUT, 24, SPIT_ADC_RD, CS_ADC) self.bus.write((addr | 0x40) << 24) return self.bus.read() & 0xffff @kernel - def read24(self, addr: TInt32) -> TInt32: + def read24(self, addr: int32) -> int32: """Read from 24-bit register. :param addr: Register address. :return: Read-back register content. """ self.bus.set_config_mu( - ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, + ADC_SPI_CONFIG | SPI_END | SPI_INPUT, 32, SPIT_ADC_RD, CS_ADC) self.bus.write((addr | 0x40) << 24) return self.bus.read() & 0xffffff @kernel - def write8(self, addr: TInt32, data: TInt32): + def write8(self, addr: int32, data: int32): """Write to 8-bit register. :param addr: Register address. :param data: Data to be written. """ self.bus.set_config_mu( - ADC_SPI_CONFIG | spi.SPI_END, 16, SPIT_ADC_WR, CS_ADC) + ADC_SPI_CONFIG | SPI_END, 16, SPIT_ADC_WR, CS_ADC) self.bus.write(addr << 24 | (data & 0xff) << 16) @kernel - def write16(self, addr: TInt32, data: TInt32): + def write16(self, addr: int32, data: int32): """Write to 16-bit register. :param addr: Register address. :param data: Data to be written. """ self.bus.set_config_mu( - ADC_SPI_CONFIG | spi.SPI_END, 24, SPIT_ADC_WR, CS_ADC) + ADC_SPI_CONFIG | SPI_END, 24, SPIT_ADC_WR, CS_ADC) self.bus.write(addr << 24 | (data & 0xffff) << 8) @kernel - def write24(self, addr: TInt32, data: TInt32): + def write24(self, addr: int32, data: int32): """Write to 24-bit register. :param addr: Register address. :param data: Data to be written. """ self.bus.set_config_mu( - ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC) + ADC_SPI_CONFIG | SPI_END, 32, SPIT_ADC_WR, CS_ADC) self.bus.write(addr << 24 | (data & 0xffffff)) @kernel - def read_ch(self, channel: TInt32) -> TFloat: + def read_ch(self, channel: int32) -> float: """Sample a Shuttler channel on the AFE. Performs a single conversion using profile 0 and setup 0 on the @@ -506,9 +525,9 @@ class ADC: self.write16(_AD4115_REG_SETUPCON0, 0x1300) self.single_conversion() - delay(100*us) + self.core.delay(100.*us) adc_code = self.read24(_AD4115_REG_DATA) - return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1 + return ((float(adc_code) / float(1 << 23)) - 1.) * 2.5 / 0.1 @kernel def single_conversion(self): @@ -552,10 +571,10 @@ class ADC: self.reset() # Although the datasheet claims 500 us reset wait time, only waiting # for ~500 us can result in DOUT pin stuck in high - delay(2500*us) + self.core.delay(2500.*us) @kernel - def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]): + def calibrate(self, volts: list[DCBias], trigger: Trigger, config: Config, samples: Option[list[float]] = none): """Calibrate the Shuttler waveform generator using the ADC on the AFE. Finds the average slope rate and average offset by samples, and @@ -576,33 +595,35 @@ class ADC: :param samples: A list of sample voltages for calibration. There must be at least 2 samples to perform slope rate calculation. """ - assert len(volts) == 16 - assert len(samples) > 1 + samples_l = samples.unwrap() if samples.is_some() else [-5.0, 0.0, 5.0] - measurements = [0.0] * len(samples) + assert len(volts) == 16 + assert len(samples_l) > 1 + + measurements = [0.0 for _ in range(len(samples_l))] for ch in range(16): # Find the average slope rate and offset - for i in range(len(samples)): + for i in range(len(samples_l)): self.core.break_realtime() volts[ch].set_waveform( - shuttler_volt_to_mu(samples[i]), 0, 0, 0) + shuttler_volt_to_mu(samples_l[i]), 0, int64(0), int64(0)) trigger.trigger(1 << ch) measurements[i] = self.read_ch(ch) # Find the average output slope slope_sum = 0.0 - for i in range(len(samples) - 1): - slope_sum += (measurements[i+1] - measurements[i])/(samples[i+1] - samples[i]) - slope_avg = slope_sum / (len(samples) - 1) + for i in range(len(samples_l) - 1): + slope_sum += (measurements[i+1] - measurements[i])/(samples_l[i+1] - samples_l[i]) + slope_avg = slope_sum / float(len(samples_l) - 1) - gain_code = int32(1 / slope_avg * (2 ** 16)) & 0xffff + gain_code = int32(1. / slope_avg * float(2 ** 16)) & 0xffff # Scale the measurements by 1/slope, find average offset offset_sum = 0.0 - for i in range(len(samples)): - offset_sum += (measurements[i] / slope_avg) - samples[i] - offset_avg = offset_sum / len(samples) + for i in range(len(samples_l)): + offset_sum += (measurements[i] / slope_avg) - samples_l[i] + offset_avg = offset_sum / float(len(samples_l)) offset_code = shuttler_volt_to_mu(-offset_avg) diff --git a/artiq/coredevice/spi2.py b/artiq/coredevice/spi2.py index 65ad2b961..10cb99d17 100644 --- a/artiq/coredevice/spi2.py +++ b/artiq/coredevice/spi2.py @@ -7,8 +7,10 @@ Output event replacement is not supported and issuing commands at the same time results in collision errors. """ -from artiq.language.core import syscall, kernel, portable, delay_mu -from artiq.language.types import TInt32, TNone +from numpy import int32, int64 + +from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable, extern +from artiq.coredevice.core import Core from artiq.coredevice.rtio import rtio_output, rtio_input_data @@ -33,6 +35,7 @@ SPI_LSB_FIRST = 0x40 SPI_HALF_DUPLEX = 0x80 +@nac3 class SPIMaster: """Core device Serial Peripheral Interface (SPI) bus master. @@ -62,12 +65,14 @@ class SPIMaster: :meth:`update_xfer_duration_mu` :param core_device: Core device name """ - kernel_invariants = {"core", "ref_period_mu", "channel"} + core: KernelInvariant[Core] + ref_period_mu: KernelInvariant[int64] + channel: KernelInvariant[int32] + xfer_duration_mu: Kernel[int64] def __init__(self, dmgr, channel, div=0, length=0, core_device="core"): self.core = dmgr.get(core_device) - self.ref_period_mu = self.core.seconds_to_mu( - self.core.coarse_ref_period) + self.ref_period_mu = self.core.seconds_to_mu(self.core.coarse_ref_period) assert self.ref_period_mu == self.core.ref_multiplier self.channel = channel self.update_xfer_duration_mu(div, length) @@ -77,12 +82,12 @@ class SPIMaster: return [(channel, None)] @portable - def frequency_to_div(self, f): + def frequency_to_div(self, f: float) -> int32: """Convert a SPI clock frequency to the closest SPI clock divider.""" - return int(round(1/(f*self.core.mu_to_seconds(self.ref_period_mu)))) + return round(1./(f*self.core.mu_to_seconds(self.ref_period_mu))) @kernel - def set_config(self, flags, length, freq, cs): + def set_config(self, flags: int32, length: int32, freq: float, cs: int32): """Set the configuration register. * If ``SPI_CS_POLARITY`` is cleared (``cs`` active low, the default), @@ -149,7 +154,7 @@ class SPIMaster: self.set_config_mu(flags, length, self.frequency_to_div(freq), cs) @kernel - def set_config_mu(self, flags, length, div, cs): + def set_config_mu(self, flags: int32, length: int32, div: int32, cs: int32): """Set the ``config`` register (in SPI bus machine units). See also :meth:`set_config`. @@ -175,7 +180,7 @@ class SPIMaster: delay_mu(self.ref_period_mu) @portable - def update_xfer_duration_mu(self, div, length): + def update_xfer_duration_mu(self, div: int32, length: int32): """Calculate and set the transfer duration. This method updates the SPI transfer duration which is used @@ -198,10 +203,10 @@ class SPIMaster: :param div: SPI clock divider (see: :meth:`set_config_mu`) :param length: SPI transfer length (see: :meth:`set_config_mu`) """ - self.xfer_duration_mu = ((length + 1)*div + 1)*self.ref_period_mu + self.xfer_duration_mu = int64((length + 1)*div + 1)*self.ref_period_mu @kernel - def write(self, data): + def write(self, data: int32): """Write SPI data to shift register register and start transfer. * The ``data`` register and the shift register are 32 bits wide. @@ -229,7 +234,7 @@ class SPIMaster: delay_mu(self.xfer_duration_mu) @kernel - def read(self): + def read(self) -> int32: """Read SPI data submitted by the SPI core. For bit alignment and bit ordering see :meth:`set_config`. @@ -241,21 +246,22 @@ class SPIMaster: return rtio_input_data(self.channel) -@syscall(flags={"nounwind", "nowrite"}) -def spi_set_config(busno: TInt32, flags: TInt32, length: TInt32, div: TInt32, cs: TInt32) -> TNone: +@extern +def spi_set_config(busno: int32, flags: int32, length: int32, div: int32, cs: int32): raise NotImplementedError("syscall not simulated") -@syscall(flags={"nounwind", "nowrite"}) -def spi_write(busno: TInt32, data: TInt32) -> TNone: +@extern +def spi_write(busno: int32, data: int32): raise NotImplementedError("syscall not simulated") -@syscall(flags={"nounwind", "nowrite"}) -def spi_read(busno: TInt32) -> TInt32: +@extern +def spi_read(busno: int32) -> int32: raise NotImplementedError("syscall not simulated") +@nac3 class NRTSPIMaster: """Core device non-realtime Serial Peripheral Interface (SPI) bus master. Owns one non-realtime SPI bus. @@ -268,12 +274,15 @@ class NRTSPIMaster: See :class:`SPIMaster` for a description of the methods. """ + core: KernelInvariant[Core] + busno: KernelInvariant[int32] + def __init__(self, dmgr, busno=0, core_device="core"): self.core = dmgr.get(core_device) self.busno = busno @kernel - def set_config_mu(self, flags=0, length=8, div=6, cs=1): + def set_config_mu(self, flags: int32 = 0, length: int32 = 8, div: int32 = 6, cs: int32 = 1): """Set the ``config`` register. In many cases, the SPI configuration is already set by the firmware @@ -282,9 +291,9 @@ class NRTSPIMaster: spi_set_config(self.busno, flags, length, div, cs) @kernel - def write(self, data=0): + def write(self, data: int32 = 0): spi_write(self.busno, data) @kernel - def read(self): + def read(self) -> int32: return spi_read(self.busno) diff --git a/artiq/coredevice/suservo.py b/artiq/coredevice/suservo.py index 7bb000218..c63890d10 100644 --- a/artiq/coredevice/suservo.py +++ b/artiq/coredevice/suservo.py @@ -1,8 +1,13 @@ -from artiq.language.core import kernel, delay, delay_mu, portable +from numpy import int32, int64 + +from artiq.language.core import * from artiq.language.units import us, ns +from artiq.coredevice.core import Core from artiq.coredevice.rtio import rtio_output, rtio_input_data -from artiq.coredevice import spi2 as spi -from artiq.coredevice import urukul, sampler +from artiq.coredevice.spi2 import SPI_END, SPIMaster +from artiq.coredevice.urukul import CFG_MASK_NU, CPLD +from artiq.coredevice.ad9910 import AD9910 +from artiq.coredevice.sampler import adc_mu_to_volt as sampler_adc_mu_to_volt, SPI_CONFIG as SAMPLER_SPI_CONFIG, SPI_CS_PGIA as SAMPLER_SPI_CS_PGIA COEFF_WIDTH = 18 @@ -17,20 +22,21 @@ COEFF_SHIFT = 11 @portable -def y_mu_to_full_scale(y): +def y_mu_to_full_scale(y: int32) -> float: """Convert servo Y data from machine units to units of full scale.""" - return y / Y_FULL_SCALE_MU + return float(y) / float(Y_FULL_SCALE_MU) @portable -def adc_mu_to_volts(x, gain, corrected_fs=True): +def adc_mu_to_volts(x: int32, gain: int32, corrected_fs: bool = True) -> float: """Convert servo ADC data from machine units to volts.""" val = (x >> 1) & 0xffff mask = 1 << 15 val = -(val & mask) + (val & ~mask) - return sampler.adc_mu_to_volt(val, gain, corrected_fs) + return sampler_adc_mu_to_volt(val, gain, corrected_fs) +@nac3 class SUServo: """Sampler-Urukul Servo parent and configuration device. @@ -65,8 +71,15 @@ class SUServo: :param sampler_hw_rev: Sampler's revision string :param core_device: Core device name """ - kernel_invariants = {"channel", "core", "pgia", "cplds", "ddses", - "ref_period_mu", "corrected_fs"} + + core: KernelInvariant[Core] + pgia: KernelInvariant[SPIMaster] + ddses: KernelInvariant[list[AD9910]] + cplds: KernelInvariant[list[CPLD]] + channel: KernelInvariant[int32] + gains: Kernel[int32] + ref_period_mu: KernelInvariant[int64] + corrected_fs: KernelInvariant[bool] def __init__(self, dmgr, channel, pgia_device, cpld_devices, dds_devices, @@ -102,12 +115,12 @@ class SUServo: This method does not alter the profile configuration memory or the channel controls. """ - self.set_config(enable=0) - delay(3*us) # pipeline flush + self.set_config(enable=False) + self.core.delay(3.*us) # pipeline flush self.pgia.set_config_mu( - sampler.SPI_CONFIG | spi.SPI_END, - 16, 4, sampler.SPI_CS_PGIA) + SAMPLER_SPI_CONFIG | SPI_END, + 16, 4, SAMPLER_SPI_CS_PGIA) for i in range(len(self.cplds)): cpld = self.cplds[i] @@ -115,12 +128,12 @@ class SUServo: cpld.init(blind=True) prev_cpld_cfg = cpld.cfg_reg - cpld.cfg_write(prev_cpld_cfg | (0xf << urukul.CFG_MASK_NU)) + cpld.cfg_write(prev_cpld_cfg | (0xf << CFG_MASK_NU)) dds.init(blind=True) cpld.cfg_write(prev_cpld_cfg) @kernel - def write(self, addr, value): + def write(self, addr: int32, value: int32): """Write to servo memory. This method advances the timeline by one coarse RTIO cycle. @@ -136,7 +149,7 @@ class SUServo: delay_mu(self.ref_period_mu) @kernel - def read(self, addr): + def read(self, addr: int32) -> int32: """Read from servo memory. This method does not advance the timeline but consumes all slack. @@ -149,7 +162,7 @@ class SUServo: return rtio_input_data(self.channel) @kernel - def set_config(self, enable): + def set_config(self, enable: bool): """Set SU Servo configuration. This method advances the timeline by one servo memory access. @@ -164,10 +177,10 @@ class SUServo: Disabling takes up to two servo cycles (~2.3 µs) to clear the processing pipeline. """ - self.write(CONFIG_ADDR, enable) + self.write(CONFIG_ADDR, int32(enable)) @kernel - def get_status(self): + def get_status(self) -> int32: """Get current SU Servo status. This method does not advance the timeline but consumes all slack. @@ -188,7 +201,7 @@ class SUServo: return self.read(CONFIG_ADDR) @kernel - def get_adc_mu(self, adc): + def get_adc_mu(self, adc: int32) -> int32: """Get the latest ADC reading (IIR filter input X0) in machine units. This method does not advance the timeline but consumes all slack. @@ -206,7 +219,7 @@ class SUServo: return self.read(STATE_SEL | (adc << 1) | (1 << 8)) @kernel - def set_pgia_mu(self, channel, gain): + def set_pgia_mu(self, channel: int32, gain: int32): """Set instrumentation amplifier gain of a ADC channel. The four gain settings (0, 1, 2, 3) corresponds to gains of @@ -222,7 +235,7 @@ class SUServo: self.gains = gains @kernel - def get_adc(self, channel): + def get_adc(self, channel: int32) -> float: """Get the latest ADC reading (IIR filter input X0). This method does not advance the timeline but consumes all slack. @@ -243,13 +256,19 @@ class SUServo: return adc_mu_to_volts(val, gain, self.corrected_fs) +@nac3 class Channel: """Sampler-Urukul Servo channel :param channel: RTIO channel number :param servo_device: Name of the parent SUServo device """ - kernel_invariants = {"channel", "core", "servo", "servo_channel"} + + core: KernelInvariant[Core] + servo: KernelInvariant[SUServo] + channel: KernelInvariant[int32] + servo_channel: KernelInvariant[int32] + dds: KernelInvariant[AD9910] def __init__(self, dmgr, channel, servo_device): self.servo = dmgr.get(servo_device) @@ -266,7 +285,7 @@ class Channel: return [(channel, None)] @kernel - def set(self, en_out, en_iir=0, profile=0): + def set(self, en_out: bool, en_iir: bool = False, profile: int32 = 0): """Operate channel. This method does not advance the timeline. Output RF switch setting @@ -282,10 +301,10 @@ class Channel: :param profile: Active profile (0-31) """ rtio_output(self.channel << 8, - en_out | (en_iir << 1) | (profile << 2)) + int32(en_out) | (int32(en_iir) << 1) | (profile << 2)) @kernel - def set_dds_mu(self, profile, ftw, offs, pow_=0): + def set_dds_mu(self, profile: int32, ftw: int32, offs: int32, pow_: int32 = 0): """Set profile DDS coefficients in machine units. See also :meth:`Channel.set_dds`. @@ -302,7 +321,7 @@ class Channel: self.servo.write(base + 2, pow_) @kernel - def set_dds(self, profile, frequency, offset, phase=0.): + def set_dds(self, profile: int32, frequency: float, offset: float, phase: float = 0.): """Set profile DDS coefficients. This method advances the timeline by four servo memory accesses. @@ -321,7 +340,7 @@ class Channel: self.set_dds_mu(profile, ftw, offs, pow_) @kernel - def set_dds_offset_mu(self, profile, offs): + def set_dds_offset_mu(self, profile: int32, offs: int32): """Set only IIR offset in DDS coefficient profile. See :meth:`set_dds_mu` for setting the complete DDS profile. @@ -333,7 +352,7 @@ class Channel: self.servo.write(base + 4, offs) @kernel - def set_dds_offset(self, profile, offset): + def set_dds_offset(self, profile: int32, offset: float): """Set only IIR offset in DDS coefficient profile. See :meth:`set_dds` for setting the complete DDS profile. @@ -344,7 +363,7 @@ class Channel: self.set_dds_offset_mu(profile, self.dds_offset_to_mu(offset)) @portable - def dds_offset_to_mu(self, offset): + def dds_offset_to_mu(self, offset: float) -> int32: """Convert IIR offset (negative setpoint) from units of full scale to machine units (see :meth:`set_dds_mu`, :meth:`set_dds_offset_mu`). @@ -352,10 +371,10 @@ class Channel: rounding and representation as two's complement, ``offset=1`` can not be represented while ``offset=-1`` can. """ - return int(round(offset * (1 << COEFF_WIDTH - 1))) + return round(offset * float(1 << COEFF_WIDTH - 1)) @kernel - def set_iir_mu(self, profile, adc, a1, b0, b1, dly=0): + def set_iir_mu(self, profile: int32, adc: int32, a1: int32, b0: int32, b1: int32, dly: int32 = 0): """Set profile IIR coefficients in machine units. The recurrence relation is (all data signed and MSB aligned): @@ -395,7 +414,7 @@ class Channel: self.servo.write(base + 7, b0) @kernel - def set_iir(self, profile, adc, kp, ki=0., g=0., delay=0.): + def set_iir(self, profile: int32, adc: int32, kp: float, ki: float = 0., g: float = 0., delay: float = 0.): """Set profile IIR coefficients. This method advances the timeline by four servo memory accesses. @@ -437,23 +456,23 @@ class Channel: A_NORM = 1 << COEFF_SHIFT COEFF_MAX = 1 << COEFF_WIDTH - 1 - kp *= B_NORM + kp *= float(B_NORM) if ki == 0.: # pure P a1 = 0 b1 = 0 - b0 = int(round(kp)) + b0 = round(kp) else: # I or PI - ki *= B_NORM*T_CYCLE/2. + ki *= float(B_NORM)*T_CYCLE/2. if g == 0.: c = 1. a1 = A_NORM else: - c = 1./(1. + ki/(g*B_NORM)) - a1 = int(round((2.*c - 1.)*A_NORM)) - b0 = int(round(kp + ki*c)) - b1 = int(round(kp + (ki - 2.*kp)*c)) + c = 1./(1. + ki/(g*float(B_NORM))) + a1 = round((2.*c - 1.)*float(A_NORM)) + b0 = round(kp + ki*c) + b1 = round(kp + (ki - 2.*kp)*c) if b1 == -b0: raise ValueError("low integrator gain and/or gain limit") @@ -461,11 +480,11 @@ class Channel: b1 >= COEFF_MAX or b1 < -COEFF_MAX): raise ValueError("high gains") - dly = int(round(delay/T_CYCLE)) + dly = round(delay/T_CYCLE) self.set_iir_mu(profile, adc, a1, b0, b1, dly) @kernel - def get_profile_mu(self, profile, data): + def get_profile_mu(self, profile: int32, data: list[int32]): """Retrieve profile data. Profile data is returned in the ``data`` argument in machine units @@ -483,10 +502,10 @@ class Channel: base = (self.servo_channel << 8) | (profile << 3) for i in range(len(data)): data[i] = self.servo.read(base + i) - delay(4*us) + self.core.delay(4.*us) @kernel - def get_y_mu(self, profile): + def get_y_mu(self, profile: int32) -> int32: """Get a profile's IIR state (filter output, Y0) in machine units. The IIR state is also known as the "integrator", or the DDS amplitude @@ -504,7 +523,7 @@ class Channel: return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile) @kernel - def get_y(self, profile): + def get_y(self, profile: int32) -> float: """Get a profile's IIR state (filter output, Y0). The IIR state is also known as the "integrator", or the DDS amplitude @@ -522,7 +541,7 @@ class Channel: return y_mu_to_full_scale(self.get_y_mu(profile)) @kernel - def set_y_mu(self, profile, y): + def set_y_mu(self, profile: int32, y: int32): """Set a profile's IIR state (filter output, Y0) in machine units. The IIR state is also known as the "integrator", or the DDS amplitude @@ -542,7 +561,7 @@ class Channel: self.servo.write(STATE_SEL | (self.servo_channel << 5) | profile, y) @kernel - def set_y(self, profile, y): + def set_y(self, profile: int32, y: float) -> int32: """Set a profile's IIR state (filter output, Y0). The IIR state is also known as the "integrator", or the DDS amplitude @@ -557,7 +576,7 @@ class Channel: :param profile: Profile number (0-31) :param y: IIR state in units of full scale """ - y_mu = int(round(y * Y_FULL_SCALE_MU)) + y_mu = round(y * float(Y_FULL_SCALE_MU)) if y_mu < 0 or y_mu > (1 << 17) - 1: raise ValueError("Invalid SUServo y-value!") self.set_y_mu(profile, y_mu) diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index d7af8743b..6d9b12bc3 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -6,10 +6,10 @@ replacement. For example, pulses of "zero" length (e.g. :meth:`TTLInOut.on` immediately followed by :meth:`TTLInOut.off`, without a delay) are suppressed. """ -import numpy +from numpy import int32, int64 from artiq.language.core import * -from artiq.language.types import * +from artiq.coredevice.core import Core from artiq.coredevice.rtio import (rtio_output, rtio_input_timestamp, rtio_input_data) from artiq.coredevice.exceptions import RTIOOverflow @@ -22,6 +22,7 @@ from artiq.coredevice.exceptions import RTIOOverflow # 3 Set input sensitivity and sample +@nac3 class TTLOut: """RTIO TTL output driver. @@ -29,7 +30,9 @@ class TTLOut: :param channel: Channel number """ - kernel_invariants = {"core", "channel", "target_o"} + core: KernelInvariant[Core] + channel: KernelInvariant[int32] + target_o: KernelInvariant[int32] def __init__(self, dmgr, channel, core_device="core"): self.core = dmgr.get(core_device) @@ -45,7 +48,7 @@ class TTLOut: pass @kernel - def set_o(self, o): + def set_o(self, o: bool): rtio_output(self.target_o, 1 if o else 0) @kernel @@ -65,7 +68,7 @@ class TTLOut: self.set_o(False) @kernel - def pulse_mu(self, duration): + def pulse_mu(self, duration: int64): """Pulse the output high for the specified duration (in machine units). @@ -75,16 +78,17 @@ class TTLOut: self.off() @kernel - def pulse(self, duration): + def pulse(self, duration: float): """Pulse the output high for the specified duration (in seconds). The time cursor is advanced by the specified duration.""" self.on() - delay(duration) + self.core.delay(duration) self.off() +@nac3 class TTLInOut: """RTIO TTL input/output driver. @@ -111,8 +115,13 @@ class TTLInOut: :param channel: Channel number """ - kernel_invariants = {"core", "channel", "gate_latency_mu", - "target_o", "target_oe", "target_sens", "target_sample"} + core: KernelInvariant[Core] + channel: KernelInvariant[int32] + gate_latency_mu: KernelInvariant[int32] + target_o: KernelInvariant[int32] + target_oe: KernelInvariant[int32] + target_sens: KernelInvariant[int32] + target_sample: KernelInvariant[int32] def __init__(self, dmgr, channel, gate_latency_mu=None, core_device="core"): @@ -137,7 +146,7 @@ class TTLInOut: return [(channel, None)] @kernel - def set_oe(self, oe): + def set_oe(self, oe: bool): rtio_output(self.target_oe, 1 if oe else 0) @kernel @@ -167,7 +176,7 @@ class TTLInOut: self.set_oe(False) @kernel - def set_o(self, o): + def set_o(self, o: bool): rtio_output(self.target_o, 1 if o else 0) @kernel @@ -191,7 +200,7 @@ class TTLInOut: self.set_o(False) @kernel - def pulse_mu(self, duration): + def pulse_mu(self, duration: int64): """Pulse the output high for the specified duration (in machine units). @@ -201,22 +210,22 @@ class TTLInOut: self.off() @kernel - def pulse(self, duration): + def pulse(self, duration: float): """Pulse the output high for the specified duration (in seconds). The time cursor is advanced by the specified duration.""" self.on() - delay(duration) + self.core.delay(duration) self.off() # Input API: gating @kernel - def _set_sensitivity(self, value): + def _set_sensitivity(self, value: int32): rtio_output(self.target_sens, value) @kernel - def gate_rising_mu(self, duration): + def gate_rising_mu(self, duration: int64) -> int64: """Register rising edge events for the specified duration (in machine units). @@ -231,7 +240,7 @@ class TTLInOut: return now_mu() @kernel - def gate_falling_mu(self, duration): + def gate_falling_mu(self, duration: int64) -> int64: """Register falling edge events for the specified duration (in machine units). @@ -246,7 +255,7 @@ class TTLInOut: return now_mu() @kernel - def gate_both_mu(self, duration): + def gate_both_mu(self, duration: int64) -> int64: """Register both rising and falling edge events for the specified duration (in machine units). @@ -261,7 +270,7 @@ class TTLInOut: return now_mu() @kernel - def gate_rising(self, duration): + def gate_rising(self, duration: float) -> int64: """Register rising edge events for the specified duration (in seconds). @@ -271,12 +280,12 @@ class TTLInOut: convenience when used with :meth:`count`/:meth:`timestamp_mu`. """ self._set_sensitivity(1) - delay(duration) + self.core.delay(duration) self._set_sensitivity(0) return now_mu() @kernel - def gate_falling(self, duration): + def gate_falling(self, duration: float) -> int64: """Register falling edge events for the specified duration (in seconds). @@ -287,12 +296,12 @@ class TTLInOut: """ self._set_sensitivity(2) - delay(duration) + self.core.delay(duration) self._set_sensitivity(0) return now_mu() @kernel - def gate_both(self, duration): + def gate_both(self, duration: float) -> int64: """Register both rising and falling edge events for the specified duration (in seconds). @@ -302,12 +311,12 @@ class TTLInOut: convenience when used with :meth:`count`/:meth:`timestamp_mu`. """ self._set_sensitivity(3) - delay(duration) + self.core.delay(duration) self._set_sensitivity(0) return now_mu() @kernel - def count(self, up_to_timestamp_mu): + def count(self, up_to_timestamp_mu: int64) -> int32: """Consume RTIO input events until the hardware timestamp counter has reached the specified timestamp and return the number of observed events. @@ -356,12 +365,12 @@ class TTLInOut: ttl_input.count(ttl_input.gate_rising(100 * us)) """ count = 0 - while rtio_input_timestamp(up_to_timestamp_mu + self.gate_latency_mu, self.channel) >= 0: + while rtio_input_timestamp(up_to_timestamp_mu + int64(self.gate_latency_mu), self.channel) >= int64(0): count += 1 return count @kernel - def timestamp_mu(self, up_to_timestamp_mu): + def timestamp_mu(self, up_to_timestamp_mu: int64) -> int64: """Return the timestamp of the next RTIO input event, or -1 if the hardware timestamp counter reaches the given value before an event is received. @@ -379,7 +388,7 @@ class TTLInOut: :return: The timestamp (in machine units) of the first event received; -1 on timeout. """ - return rtio_input_timestamp(up_to_timestamp_mu + self.gate_latency_mu, self.channel) + return rtio_input_timestamp(up_to_timestamp_mu + int64(self.gate_latency_mu), self.channel) # Input API: sampling @kernel @@ -391,7 +400,7 @@ class TTLInOut: rtio_output(self.target_sample, 0) @kernel - def sample_get(self): + def sample_get(self) -> int32: """Returns the value of a sample previously obtained with :meth:`sample_input`. @@ -403,7 +412,7 @@ class TTLInOut: return rtio_input_data(self.channel) @kernel - def sample_get_nonrt(self): + def sample_get_nonrt(self) -> int32: """Convenience function that obtains the value of a sample at the position of the time cursor, breaks realtime, and returns the sample value.""" @@ -414,7 +423,7 @@ class TTLInOut: # Input API: watching @kernel - def watch_stay_on(self): + def watch_stay_on(self) -> bool: """Checks that the input is at a high level at the position of the time cursor and keep checking until :meth:`watch_done` is called. @@ -429,13 +438,13 @@ class TTLInOut: return rtio_input_data(self.channel) == 1 @kernel - def watch_stay_off(self): + def watch_stay_off(self) -> bool: """Like :meth:`watch_stay_on`, but for low levels.""" rtio_output(self.target_sample, 1) # gate rising return rtio_input_data(self.channel) == 0 @kernel - def watch_done(self): + def watch_done(self) -> bool: """Stop watching the input at the position of the time cursor. Returns ``True`` if the input has not changed state while it @@ -447,13 +456,14 @@ class TTLInOut: rtio_output(self.target_sens, 0) success = True try: - while rtio_input_timestamp(now_mu() + self.gate_latency_mu, self.channel) != -1: + while rtio_input_timestamp(now_mu() + int64(self.gate_latency_mu), self.channel) != int64(-1): success = False except RTIOOverflow: success = False return success +@nac3 class TTLClockGen: """RTIO TTL clock generator driver. @@ -465,35 +475,37 @@ class TTLClockGen: :param channel: channel number :param acc_width: accumulator width in bits """ - kernel_invariants = {"core", "channel", "target", "acc_width"} + core: KernelInvariant[Core] + channel: KernelInvariant[int32] + target: KernelInvariant[int32] + acc_width: KernelInvariant[int32] def __init__(self, dmgr, channel, acc_width=24, core_device="core"): self.core = dmgr.get(core_device) self.channel = channel self.target = channel << 8 - - self.acc_width = numpy.int64(acc_width) + self.acc_width = acc_width @staticmethod def get_rtio_channels(channel, **kwargs): return [(channel, None)] @portable - def frequency_to_ftw(self, frequency): + def frequency_to_ftw(self, frequency: float) -> int32: """Returns the frequency tuning word corresponding to the given frequency. """ - return round(2**self.acc_width*frequency*self.core.coarse_ref_period) + return round(2.**float(self.acc_width)*frequency*self.core.coarse_ref_period) @portable - def ftw_to_frequency(self, ftw): + def ftw_to_frequency(self, ftw: int32) -> float: """Returns the frequency corresponding to the given frequency tuning word. """ - return ftw/self.core.coarse_ref_period/2**self.acc_width + return float(ftw)/self.core.coarse_ref_period/2.**float(self.acc_width) @kernel - def set_mu(self, frequency): + def set_mu(self, frequency: int32): """Set the frequency of the clock, in machine units, at the current position of the time cursor. @@ -513,7 +525,7 @@ class TTLClockGen: rtio_output(self.target, frequency) @kernel - def set(self, frequency): + def set(self, frequency: float): """Like :meth:`set_mu`, but using Hz.""" self.set_mu(self.frequency_to_ftw(frequency)) diff --git a/artiq/coredevice/urukul.py b/artiq/coredevice/urukul.py index cac53499b..dcf711057 100644 --- a/artiq/coredevice/urukul.py +++ b/artiq/coredevice/urukul.py @@ -1,15 +1,19 @@ +from __future__ import annotations + from numpy import int32, int64 -from artiq.language.core import kernel, delay, portable, at_mu, now_mu +from artiq.language.core import * from artiq.language.units import us, ms -from artiq.language.types import TInt32, TFloat, TBool -from artiq.coredevice import spi2 as spi +from artiq.coredevice.core import Core +from artiq.coredevice.spi2 import * +from artiq.coredevice.ttl import TTLOut, TTLClockGen -SPI_CONFIG = (0 * spi.SPI_OFFLINE | 0 * spi.SPI_END | - 0 * spi.SPI_INPUT | 1 * spi.SPI_CS_POLARITY | - 0 * spi.SPI_CLK_POLARITY | 0 * spi.SPI_CLK_PHASE | - 0 * spi.SPI_LSB_FIRST | 0 * spi.SPI_HALF_DUPLEX) + +SPI_CONFIG = (0 * SPI_OFFLINE | 0 * SPI_END | + 0 * SPI_INPUT | 1 * SPI_CS_POLARITY | + 0 * SPI_CLK_POLARITY | 0 * SPI_CLK_PHASE | + 0 * SPI_LSB_FIRST | 0 * SPI_HALF_DUPLEX) # SPI clock write and read dividers SPIT_CFG_WR = 2 @@ -57,8 +61,8 @@ DEFAULT_PROFILE = 7 @portable -def urukul_cfg(rf_sw, led, profile, io_update, mask_nu, - clk_sel, sync_sel, rst, io_rst, clk_div): +def urukul_cfg(rf_sw: int32, led: int32, profile: int32, io_update: int32, mask_nu: int32, + clk_sel: int32, sync_sel: int32, rst: int32, io_rst: int32, clk_div: int32) -> int32: """Build Urukul CPLD configuration register""" return ((rf_sw << CFG_RF_SW) | (led << CFG_LED) | @@ -74,56 +78,36 @@ def urukul_cfg(rf_sw, led, profile, io_update, mask_nu, @portable -def urukul_sta_rf_sw(sta): +def urukul_sta_rf_sw(sta: int32) -> int32: """Return the RF switch status from Urukul status register value.""" return (sta >> STA_RF_SW) & 0xf @portable -def urukul_sta_smp_err(sta): +def urukul_sta_smp_err(sta: int32) -> int32: """Return the SMP_ERR status from Urukul status register value.""" return (sta >> STA_SMP_ERR) & 0xf @portable -def urukul_sta_pll_lock(sta): +def urukul_sta_pll_lock(sta: int32) -> int32: """Return the PLL_LOCK status from Urukul status register value.""" return (sta >> STA_PLL_LOCK) & 0xf @portable -def urukul_sta_ifc_mode(sta): +def urukul_sta_ifc_mode(sta: int32) -> int32: """Return the IFC_MODE status from Urukul status register value.""" return (sta >> STA_IFC_MODE) & 0xf @portable -def urukul_sta_proto_rev(sta): +def urukul_sta_proto_rev(sta: int32) -> int32: """Return the PROTO_REV value from Urukul status register value.""" return (sta >> STA_PROTO_REV) & 0x7f -class _RegIOUpdate: - def __init__(self, cpld): - self.cpld = cpld - - @kernel - def pulse(self, t: TFloat): - cfg = self.cpld.cfg_reg - self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE)) - delay(t) - self.cpld.cfg_write(cfg) - - -class _DummySync: - def __init__(self, cpld): - self.cpld = cpld - - @kernel - def set_mu(self, ftw: TInt32): - pass - - +@nac3 class CPLD: """Urukul CPLD SPI router and configuration interface. @@ -162,7 +146,17 @@ class CPLD: front panel SMA with no clock connected), then the ``init()`` method of the DDS channels can fail with the error message ``PLL lock timeout``. """ - kernel_invariants = {"refclk", "bus", "core", "io_update", "clk_div"} + + core: KernelInvariant[Core] + refclk: KernelInvariant[float] + bus: KernelInvariant[SPIMaster] + io_update: KernelInvariant[TTLOut] + clk_div: KernelInvariant[int32] + dds_reset: KernelInvariant[Option[TTLOut]] + sync: KernelInvariant[Option[TTLClockGen]] + cfg_reg: Kernel[int32] + att_reg: Kernel[int32] + sync_div: Kernel[int32] def __init__(self, dmgr, spi_device, io_update_device=None, dds_reset_device=None, sync_device=None, @@ -179,15 +173,19 @@ class CPLD: if io_update_device is not None: self.io_update = dmgr.get(io_update_device) else: - self.io_update = _RegIOUpdate(self) + self.io_update = _RegIOUpdate(self.core, self) + # NAC3TODO + raise NotImplementedError if dds_reset_device is not None: - self.dds_reset = dmgr.get(dds_reset_device) + self.dds_reset = Some(dmgr.get(dds_reset_device)) + else: + self.dds_reset = none if sync_device is not None: - self.sync = dmgr.get(sync_device) + self.sync = Some(dmgr.get(sync_device)) if sync_div is None: sync_div = 2 else: - self.sync = _DummySync(self) + self.sync = none assert sync_div is None sync_div = 0 @@ -199,7 +197,7 @@ class CPLD: self.sync_div = sync_div @kernel - def cfg_write(self, cfg: TInt32): + def cfg_write(self, cfg: int32): """Write to the configuration register. See :func:`urukul_cfg` for possible flags. @@ -207,13 +205,13 @@ class CPLD: :param cfg: 24-bit data to be written. Will be stored at :attr:`cfg_reg`. """ - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, + self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24, SPIT_CFG_WR, CS_CFG) self.bus.write(cfg << 8) self.cfg_reg = cfg @kernel - def sta_read(self) -> TInt32: + def sta_read(self) -> int32: """Read the status register. Use any of the following functions to extract values: @@ -226,13 +224,13 @@ class CPLD: :return: The status register value. """ - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 24, + self.bus.set_config_mu(SPI_CONFIG | SPI_END | SPI_INPUT, 24, SPIT_CFG_RD, CS_CFG) self.bus.write(self.cfg_reg << 8) return self.bus.read() @kernel - def init(self, blind: TBool = False): + def init(self, blind: bool = False): """Initialize and detect Urukul. Resets the DDS I/O interface and verifies correct CPLD gateware @@ -250,12 +248,12 @@ class CPLD: proto_rev = urukul_sta_proto_rev(self.sta_read()) if proto_rev != STA_PROTO_REV_MATCH: raise ValueError("Urukul proto_rev mismatch") - delay(100 * us) # reset, slack + self.core.delay(100. * us) # reset, slack self.cfg_write(cfg) - if self.sync_div: - at_mu(now_mu() & ~0xf) # align to RTIO/2 + if self.sync_div != 0: + at_mu(now_mu() & ~int64(0xf)) # align to RTIO/2 self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16 - delay(1 * ms) # DDS wake up + self.core.delay(1. * ms) # DDS wake up @kernel def io_rst(self): @@ -264,7 +262,7 @@ class CPLD: self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST)) @kernel - def cfg_sw(self, channel: TInt32, on: TBool): + def cfg_sw(self, channel: int32, on: bool): """Configure the RF switches through the configuration register. These values are logically OR-ed with the LVDS lines on EEM1. @@ -280,15 +278,15 @@ class CPLD: self.cfg_write(c) @kernel - def cfg_switches(self, state: TInt32): + def cfg_switches(self, state: int32): """Configure all four RF switches through the configuration register. :param state: RF switch state as a 4-bit integer. """ self.cfg_write((self.cfg_reg & ~0xf) | state) - @portable(flags={"fast-math"}) - def mu_to_att(self, att_mu: TInt32) -> TFloat: + @portable + def mu_to_att(self, att_mu: int32) -> float: """Convert a digital attenuation setting to dB. :param att_mu: Digital attenuation setting. @@ -296,20 +294,20 @@ class CPLD: """ return (255 - (att_mu & 0xff)) / 8 - @portable(flags={"fast-math"}) - def att_to_mu(self, att: TFloat) -> TInt32: + @portable + def att_to_mu(self, att: float) -> int32: """Convert an attenuation setting in dB to machine units. :param att: Attenuation setting in dB. :return: Digital attenuation setting. """ - code = int32(255) - int32(round(att * 8)) + code = 255 - round(att * 8.) if code < 0 or code > 255: raise ValueError("Invalid urukul.CPLD attenuation!") return code @kernel - def set_att_mu(self, channel: TInt32, att: TInt32): + def set_att_mu(self, channel: int32, att: int32): """Set digital step attenuator in machine units. This method will also write the attenuator settings of the three @@ -325,19 +323,19 @@ class CPLD: self.set_all_att_mu(a) @kernel - def set_all_att_mu(self, att_reg: TInt32): + def set_all_att_mu(self, att_reg: int32): """Set all four digital step attenuators (in machine units). See also :meth:`set_att_mu`. :param att_reg: Attenuator setting string (32-bit) """ - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32, + self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32, SPIT_ATT_WR, CS_ATT) self.bus.write(att_reg) self.att_reg = att_reg @kernel - def set_att(self, channel: TInt32, att: TFloat): + def set_att(self, channel: int32, att: float): """Set digital step attenuator in SI units. This method will write the attenuator settings of all four channels. @@ -351,7 +349,7 @@ class CPLD: self.set_att_mu(channel, self.att_to_mu(att)) @kernel - def get_att_mu(self) -> TInt32: + def get_att_mu(self) -> int32: """Return the digital step attenuator settings in machine units. The result is stored and will be used in future calls of @@ -361,18 +359,18 @@ class CPLD: :return: 32-bit attenuator settings """ - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 32, + self.bus.set_config_mu(SPI_CONFIG | SPI_INPUT, 32, SPIT_ATT_RD, CS_ATT) self.bus.write(0) # shift in zeros, shift out current value - self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32, + self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32, SPIT_ATT_WR, CS_ATT) - delay(10 * us) + self.core.delay(10. * us) self.att_reg = self.bus.read() self.bus.write(self.att_reg) # shift in current value again and latch return self.att_reg @kernel - def get_channel_att_mu(self, channel: TInt32) -> TInt32: + def get_channel_att_mu(self, channel: int32) -> int32: """Get digital step attenuator value for a channel in machine units. The result is stored and will be used in future calls of @@ -387,7 +385,7 @@ class CPLD: return int32((self.get_att_mu() >> (channel * 8)) & 0xff) @kernel - def get_channel_att(self, channel: TInt32) -> TFloat: + def get_channel_att(self, channel: int32) -> float: """Get digital step attenuator value for a channel in SI units. See also :meth:`get_channel_att_mu`. @@ -400,7 +398,7 @@ class CPLD: return self.mu_to_att(self.get_channel_att_mu(channel)) @kernel - def set_sync_div(self, div: TInt32): + def set_sync_div(self, div: int32): """Set the ``SYNC_IN`` AD9910 pulse generator frequency and align it to the current RTIO timestamp. @@ -414,10 +412,11 @@ class CPLD: ftw_max = 1 << 4 ftw = ftw_max // div assert ftw * div == ftw_max - self.sync.set_mu(ftw) + if self.sync.is_some(): + self.sync.unwrap().set_mu(ftw) @kernel - def set_profile(self, profile: TInt32): + def set_profile(self, profile: int32): """Set the PROFILE pins. The PROFILE pins are common to all four DDS channels. @@ -427,3 +426,24 @@ class CPLD: cfg = self.cfg_reg & ~(7 << CFG_PROFILE) cfg |= (profile & 7) << CFG_PROFILE self.cfg_write(cfg) + + +@nac3 +class _RegIOUpdate: + core: KernelInvariant[Core] + cpld: KernelInvariant[CPLD] + + def __init__(self, core, cpld): + self.core = core + self.cpld = cpld + + @kernel + def pulse_mu(self, t: int64): + cfg = self.cpld.cfg_reg + self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE)) + delay_mu(t) + self.cpld.cfg_write(cfg) + + @kernel + def pulse(self, t: float): + self.pulse_mu(self.core.seconds_to_mu(t)) diff --git a/artiq/coredevice/zotino.py b/artiq/coredevice/zotino.py index 0c2ac1595..41050077e 100644 --- a/artiq/coredevice/zotino.py +++ b/artiq/coredevice/zotino.py @@ -4,19 +4,23 @@ Output event replacement is not supported and issuing commands at the same time results in a collision error. """ -from artiq.language.core import kernel -from artiq.coredevice import spi2 as spi +from numpy import int32 + +from artiq.language.core import nac3, kernel +from artiq.coredevice.spi2 import * from artiq.coredevice.ad53xx import SPI_AD53XX_CONFIG, AD53xx -_SPI_SR_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END | - 0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | - 0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) + +_SPI_SR_CONFIG = (0*SPI_OFFLINE | 1*SPI_END | + 0*SPI_INPUT | 0*SPI_CS_POLARITY | + 0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE | + 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX) _SPI_CS_DAC = 1 _SPI_CS_SR = 2 +@nac3 class Zotino(AD53xx): """ Zotino 32-channel, 16-bit 1MSPS DAC. @@ -42,8 +46,8 @@ class Zotino(AD53xx): div_read=div_read, core=core) @kernel - def set_leds(self, leds): - """ Sets the states of the 8 user LEDs. + def set_leds(self, leds: int32): + """Sets the states of the 8 user LEDs. :param leds: 8-bit word with LED state """ diff --git a/artiq/examples/kasli/device_db.py b/artiq/examples/kasli/device_db.py deleted file mode 100644 index 274fed61e..000000000 --- a/artiq/examples/kasli/device_db.py +++ /dev/null @@ -1,249 +0,0 @@ -# Tester device database - -core_addr = "192.168.1.70" - -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": { - "host": core_addr, - "ref_period": 1e-9, - "analyzer_proxy": "core_analyzer" - } - }, - "core_log": { - "type": "controller", - "host": "::1", - "port": 1068, - "command": "aqctl_corelog -p {port} --bind {bind} " + core_addr - }, - "core_moninj": { - "type": "controller", - "host": "::1", - "port_proxy": 1383, - "port": 1384, - "command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr - }, - "core_analyzer": { - "type": "controller", - "host": "::1", - "port_proxy": 1385, - "port": 1386, - "command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr - }, - "core_cache": { - "type": "local", - "module": "artiq.coredevice.cache", - "class": "CoreCache" - }, - "core_dma": { - "type": "local", - "module": "artiq.coredevice.dma", - "class": "CoreDMA" - }, - - "i2c_switch0": { - "type": "local", - "module": "artiq.coredevice.i2c", - "class": "I2CSwitch", - "arguments": {"address": 0xe0} - }, - "i2c_switch1": { - "type": "local", - "module": "artiq.coredevice.i2c", - "class": "I2CSwitch", - "arguments": {"address": 0xe2} - }, -} - - -# DIO (EEM5) starting at RTIO channel 0 -for i in range(8): - device_db["ttl" + str(i)] = { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLInOut" if i < 4 else "TTLOut", - "arguments": {"channel": i}, - } - device_db["ttl{}_counter".format(i)] = { - "type": "local", - "module": "artiq.coredevice.edge_counter", - "class": "EdgeCounter", - "arguments": {"channel": 8 + i}, - } - - -# Urukul (EEM1) starting at RTIO channel 12 -device_db.update( - eeprom_urukul0={ - "type": "local", - "module": "artiq.coredevice.kasli_i2c", - "class": "KasliEEPROM", - "arguments": {"port": "EEM1"} - }, - spi_urukul0={ - "type": "local", - "module": "artiq.coredevice.spi2", - "class": "SPIMaster", - "arguments": {"channel": 12} - }, - ttl_urukul0_sync={ - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLClockGen", - "arguments": {"channel": 13, "acc_width": 4} - }, - ttl_urukul0_io_update={ - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 14} - }, - ttl_urukul0_sw0={ - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 15} - }, - ttl_urukul0_sw1={ - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 16} - }, - ttl_urukul0_sw2={ - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 17} - }, - ttl_urukul0_sw3={ - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 18} - }, - urukul0_cpld={ - "type": "local", - "module": "artiq.coredevice.urukul", - "class": "CPLD", - "arguments": { - "spi_device": "spi_urukul0", - "io_update_device": "ttl_urukul0_io_update", - "sync_device": "ttl_urukul0_sync", - "refclk": 125e6, - "clk_sel": 2 - } - } -) - -for i in range(4): - device_db["urukul0_ch" + str(i)] = { - "type": "local", - "module": "artiq.coredevice.ad9910", - "class": "AD9910", - "arguments": { - "pll_n": 32, - "chip_select": 4 + i, - "cpld_device": "urukul0_cpld", - "sw_device": "ttl_urukul0_sw" + str(i), - "sync_delay_seed": "eeprom_urukul0:" + str(64 + 4*i), - "io_update_delay": "eeprom_urukul0:" + str(64 + 4*i), - } - } - - -# Sampler (EEM3) starting at RTIO channel 19 -device_db["spi_sampler0_adc"] = { - "type": "local", - "module": "artiq.coredevice.spi2", - "class": "SPIMaster", - "arguments": {"channel": 19} -} -device_db["spi_sampler0_pgia"] = { - "type": "local", - "module": "artiq.coredevice.spi2", - "class": "SPIMaster", - "arguments": {"channel": 20} -} -device_db["spi_sampler0_cnv"] = { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 21}, -} -device_db["sampler0"] = { - "type": "local", - "module": "artiq.coredevice.sampler", - "class": "Sampler", - "arguments": { - "spi_adc_device": "spi_sampler0_adc", - "spi_pgia_device": "spi_sampler0_pgia", - "cnv_device": "spi_sampler0_cnv" - } -} - - -# Zotino (EEM4) starting at RTIO channel 22 -device_db["spi_zotino0"] = { - "type": "local", - "module": "artiq.coredevice.spi2", - "class": "SPIMaster", - "arguments": {"channel": 22} -} -device_db["ttl_zotino0_ldac"] = { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 23} -} -device_db["ttl_zotino0_clr"] = { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 24} -} -device_db["zotino0"] = { - "type": "local", - "module": "artiq.coredevice.zotino", - "class": "Zotino", - "arguments": { - "spi_device": "spi_zotino0", - "ldac_device": "ttl_zotino0_ldac", - "clr_device": "ttl_zotino0_clr" - } -} - - -device_db.update( - led0={ - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 25} - }, - led1={ - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 26} - }, -) - -device_db.update( - i2c_switch="i2c_switch0", - - ttl_out="ttl4", - ttl_out_serdes="ttl4", - - loop_out="ttl4", - loop_in="ttl0", - loop_in_counter="ttl0_counter", - - # Urukul CPLD with sync and io_update, IFC MODE 0b1000 - urukul_cpld="urukul0_cpld", - # Urukul AD9910 with switch TTL, internal 125 MHz MMCX connection - urukul_ad9910="urukul0_ch0", -) diff --git a/artiq/examples/kasli/idle_kernel.py b/artiq/examples/kasli/idle_kernel.py deleted file mode 100644 index 05184f731..000000000 --- a/artiq/examples/kasli/idle_kernel.py +++ /dev/null @@ -1,21 +0,0 @@ -from artiq.experiment import * - - -class IdleKernel(EnvExperiment): - def build(self): - self.setattr_device("core") - self.setattr_device("led0") - - @kernel - def run(self): - start_time = now_mu() + self.core.seconds_to_mu(500*ms) - while self.core.get_rtio_counter_mu() < start_time: - pass - self.core.reset() - while True: - self.led0.pulse(250*ms) - delay(125*ms) - self.led0.pulse(125*ms) - delay(125*ms) - self.led0.pulse(125*ms) - delay(250*ms) diff --git a/artiq/examples/kasli_drtioswitching/device_db.py b/artiq/examples/kasli_drtioswitching/device_db.py deleted file mode 100644 index 43dc22a0f..000000000 --- a/artiq/examples/kasli_drtioswitching/device_db.py +++ /dev/null @@ -1,52 +0,0 @@ -core_addr = "192.168.1.70" - -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": { - "host": core_addr, - "ref_period": 1e-9, - "analyzer_proxy": "core_analyzer" - } - }, - "core_log": { - "type": "controller", - "host": "::1", - "port": 1068, - "command": "aqctl_corelog -p {port} --bind {bind} " + core_addr - }, - "core_moninj": { - "type": "controller", - "host": "::1", - "port_proxy": 1383, - "port": 1384, - "command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr - }, - "core_analyzer": { - "type": "controller", - "host": "::1", - "port_proxy": 1385, - "port": 1386, - "command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr - }, - "core_cache": { - "type": "local", - "module": "artiq.coredevice.cache", - "class": "CoreCache" - }, - "core_dma": { - "type": "local", - "module": "artiq.coredevice.dma", - "class": "CoreDMA" - }, -} - -for i in range(3): - device_db["led" + str(i)] = { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": i << 16}, - } diff --git a/artiq/examples/kasli_drtioswitching/repository/blink.py b/artiq/examples/kasli_drtioswitching/repository/blink.py deleted file mode 100644 index 2af2d1233..000000000 --- a/artiq/examples/kasli_drtioswitching/repository/blink.py +++ /dev/null @@ -1,16 +0,0 @@ -from artiq.experiment import * - - -class Blink(EnvExperiment): - def build(self): - self.setattr_device("core") - self.leds = [self.get_device("led0"), self.get_device("led2")] - - @kernel - def run(self): - self.core.reset() - - while True: - for led in self.leds: - led.pulse(200*ms) - delay(200*ms) diff --git a/artiq/examples/kasli_shuttler/repository/shuttler.py b/artiq/examples/kasli_shuttler/repository/shuttler.py index be1c034a7..a2c647847 100644 --- a/artiq/examples/kasli_shuttler/repository/shuttler.py +++ b/artiq/examples/kasli_shuttler/repository/shuttler.py @@ -1,61 +1,82 @@ -from artiq.experiment import * -from artiq.coredevice.shuttler import shuttler_volt_to_mu +from numpy import int32, int64 -DAC_Fs_MHZ = 125 +from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.ttl import TTLOut +from artiq.coredevice.shuttler import ( + shuttler_volt_to_mu, + Config as ShuttlerConfig, + Trigger as ShuttlerTrigger, + DCBias as ShuttlerDCBias, + DDS as ShuttlerDDS, + Relay as ShuttlerRelay, + ADC as ShuttlerADC) + + +DAC_Fs_MHZ = 125. CORDIC_GAIN = 1.64676 @portable -def shuttler_phase_offset(offset_degree): - return round(offset_degree / 360 * (2 ** 16)) +def shuttler_phase_offset(offset_degree: float) -> int32: + return round(offset_degree / 360. * float(2 ** 16)) @portable -def shuttler_freq_mu(freq_mhz): +def shuttler_freq_mu(freq_mhz: float) -> int32: return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz) @portable -def shuttler_chirp_rate_mu(freq_mhz_per_us): +def shuttler_chirp_rate_mu(freq_mhz_per_us: float) -> int32: return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2)) @portable -def shuttler_freq_sweep(start_f_MHz, end_f_MHz, time_us): - return shuttler_chirp_rate_mu((end_f_MHz - start_f_MHz)/(time_us)) +def shuttler_freq_sweep(start_f_MHz: float, end_f_MHz: float, time_us: float) -> int32: + return shuttler_chirp_rate_mu((end_f_MHz - start_f_MHz)/time_us) @portable -def shuttler_volt_amp_mu(volt): +def shuttler_volt_amp_mu(volt: float) -> int32: return shuttler_volt_to_mu(volt) @portable -def shuttler_volt_damp_mu(volt_per_us): - return round(float(2) ** 32 * (volt_per_us / 20) / DAC_Fs_MHZ) +def shuttler_volt_damp_mu(volt_per_us: float) -> int32: + return round(float(2) ** 32 * (volt_per_us / 20.) / DAC_Fs_MHZ) @portable -def shuttler_volt_ddamp_mu(volt_per_us_square): - return round(float(2) ** 48 * (volt_per_us_square / 20) * 2 / (DAC_Fs_MHZ ** 2)) +def shuttler_volt_ddamp_mu(volt_per_us_square: float) -> int64: + return round64(float(2) ** 48 * (volt_per_us_square / 20.) * 2. / (DAC_Fs_MHZ ** 2)) @portable -def shuttler_volt_dddamp_mu(volt_per_us_cube): - return round(float(2) ** 48 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3)) +def shuttler_volt_dddamp_mu(volt_per_us_cube: float) -> int64: + return round64(float(2) ** 48 * (volt_per_us_cube / 20.) * 6. / (DAC_Fs_MHZ ** 3)) @portable -def shuttler_dds_amp_mu(volt): +def shuttler_dds_amp_mu(volt: float) -> int32: return shuttler_volt_amp_mu(volt / CORDIC_GAIN) @portable -def shuttler_dds_damp_mu(volt_per_us): +def shuttler_dds_damp_mu(volt_per_us: float) -> int32: return shuttler_volt_damp_mu(volt_per_us / CORDIC_GAIN) @portable -def shuttler_dds_ddamp_mu(volt_per_us_square): +def shuttler_dds_ddamp_mu(volt_per_us_square: float) -> int64: return shuttler_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN) @portable -def shuttler_dds_dddamp_mu(volt_per_us_cube): +def shuttler_dds_dddamp_mu(volt_per_us_cube: float) -> int64: return shuttler_volt_dddamp_mu(volt_per_us_cube / CORDIC_GAIN) +@nac3 class Shuttler(EnvExperiment): + core: KernelInvariant[Core] + shuttler0_leds: KernelInvariant[list[TTLOut]] + shuttler0_config: KernelInvariant[ShuttlerConfig] + shuttler0_trigger: KernelInvariant[ShuttlerTrigger] + shuttler0_dcbias: KernelInvariant[list[ShuttlerDCBias]] + shuttler0_dds: KernelInvariant[list[ShuttlerDDS]] + shuttler0_relay: KernelInvariant[ShuttlerRelay] + shuttler0_adc: KernelInvariant[ShuttlerADC] + def build(self): self.setattr_device("core") - self.setattr_device("core_dma") self.setattr_device("scheduler") self.shuttler0_leds = [ self.get_device("shuttler0_led{}".format(i)) for i in range(2) ] self.setattr_device("shuttler0_config") @@ -64,12 +85,6 @@ class Shuttler(EnvExperiment): self.shuttler0_dds = [ self.get_device("shuttler0_dds{}".format(i)) for i in range(16) ] self.setattr_device("shuttler0_relay") self.setattr_device("shuttler0_adc") - - - @kernel - def record(self): - with self.core_dma.record("example_waveform"): - self.example_waveform() @kernel def init(self): @@ -84,35 +99,32 @@ class Shuttler(EnvExperiment): self.core.break_realtime() self.init() - self.record() - example_waveform_handle = self.core_dma.get_handle("example_waveform") - - print("Example Waveforms are on OUT0 and OUT1") + print_rpc("Example Waveforms are on OUT0 and OUT1") self.core.break_realtime() while not(self.scheduler.check_termination()): - delay(1*s) - self.core_dma.playback_handle(example_waveform_handle) + self.core.delay(1.*s) + self.example_waveform() @kernel def shuttler_reset(self): for i in range(16): self.shuttler_channel_reset(i) # To avoid RTIO Underflow - delay(50*us) + self.core.delay(50.*us) @kernel - def shuttler_channel_reset(self, ch): + def shuttler_channel_reset(self, ch: int32): self.shuttler0_dcbias[ch].set_waveform( a0=0, a1=0, - a2=0, - a3=0, + a2=int64(0), + a3=int64(0), ) self.shuttler0_dds[ch].set_waveform( b0=0, b1=0, - b2=0, - b3=0, + b2=int64(0), + b3=int64(0), c0=0, c1=0, c2=0, @@ -163,13 +175,13 @@ class Shuttler(EnvExperiment): ## Step 2 ## start_f_MHz = 0.01 end_f_MHz = 0.05 - duration_us = 500 + duration_us = 500. # OUT0 and OUT1 have their frequency and phase aligned at 500us self.shuttler0_dds[0].set_waveform( b0=shuttler_dds_amp_mu(1.0), b1=0, - b2=0, - b3=0, + b2=int64(0), + b3=int64(0), c0=0, c1=shuttler_freq_mu(start_f_MHz), c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us), @@ -177,22 +189,22 @@ class Shuttler(EnvExperiment): self.shuttler0_dds[1].set_waveform( b0=shuttler_dds_amp_mu(1.0), b1=0, - b2=0, - b3=0, + b2=int64(0), + b3=int64(0), c0=0, c1=shuttler_freq_mu(end_f_MHz), c2=0, ) self.shuttler0_trigger.trigger(0b11) - delay(500*us) + self.core.delay(500.*us) ## Step 3 ## # OUT0 and OUT1 has 180 degree phase difference self.shuttler0_dds[0].set_waveform( b0=shuttler_dds_amp_mu(1.0), b1=0, - b2=0, - b3=0, + b2=int64(0), + b3=int64(0), c0=shuttler_phase_offset(180.0), c1=shuttler_freq_mu(end_f_MHz), c2=0, @@ -200,7 +212,7 @@ class Shuttler(EnvExperiment): # Phase and Output Setting of OUT1 is retained # if the channel is not triggered or config is not cleared self.shuttler0_trigger.trigger(0b1) - delay(500*us) + self.core.delay(500.*us) ## Step 4 ## # b(0) = 0, b(250) = 8.545, b(500) = 0 @@ -208,7 +220,7 @@ class Shuttler(EnvExperiment): b0=0, b1=shuttler_dds_damp_mu(0.06835937), b2=shuttler_dds_ddamp_mu(-0.0001367187), - b3=0, + b3=int64(0), c0=0, c1=shuttler_freq_mu(end_f_MHz), c2=0, @@ -217,26 +229,26 @@ class Shuttler(EnvExperiment): b0=0, b1=shuttler_dds_damp_mu(0.06835937), b2=shuttler_dds_ddamp_mu(-0.0001367187), - b3=0, + b3=int64(0), c0=0, c1=0, c2=0, ) self.shuttler0_trigger.trigger(0b11) - delay(500*us) + self.core.delay(500.*us) ## Step 5 ## self.shuttler0_dcbias[0].set_waveform( a0=shuttler_volt_amp_mu(-5.0), a1=int32(shuttler_volt_damp_mu(0.01)), - a2=0, - a3=0, + a2=int64(0), + a3=int64(0), ) self.shuttler0_dds[0].set_waveform( b0=shuttler_dds_amp_mu(1.0), b1=0, - b2=0, - b3=0, + b2=int64(0), + b3=int64(0), c0=0, c1=shuttler_freq_mu(end_f_MHz), c2=0, @@ -244,59 +256,59 @@ class Shuttler(EnvExperiment): self.shuttler0_dcbias[1].set_waveform( a0=shuttler_volt_amp_mu(-5.0), a1=int32(shuttler_volt_damp_mu(0.01)), - a2=0, - a3=0, + a2=int64(0), + a3=int64(0), ) self.shuttler0_dds[1].set_waveform( b0=0, b1=0, - b2=0, - b3=0, + b2=int64(0), + b3=int64(0), c0=0, c1=0, c2=0, ) self.shuttler0_trigger.trigger(0b11) - delay(1000*us) + self.core.delay(1000.*us) ## Step 6 ## self.shuttler0_dcbias[0].set_waveform( a0=shuttler_volt_amp_mu(-2.5), a1=int32(shuttler_volt_damp_mu(0.01)), - a2=0, - a3=0, + a2=int64(0), + a3=int64(0), ) self.shuttler0_dds[0].set_waveform( b0=0, b1=shuttler_dds_damp_mu(0.06835937), b2=shuttler_dds_ddamp_mu(-0.0001367187), - b3=0, + b3=int64(0), c0=0, c1=shuttler_freq_mu(start_f_MHz), c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us), ) self.shuttler0_trigger.trigger(0b1) self.shuttler_channel_reset(1) - delay(500*us) + self.core.delay(500.*us) ## Step 7 ## self.shuttler0_dcbias[0].set_waveform( a0=shuttler_volt_amp_mu(2.5), a1=int32(shuttler_volt_damp_mu(-0.01)), - a2=0, - a3=0, + a2=int64(0), + a3=int64(0), ) self.shuttler0_dds[0].set_waveform( b0=0, b1=shuttler_dds_damp_mu(-0.06835937), b2=shuttler_dds_ddamp_mu(0.0001367187), - b3=0, + b3=int64(0), c0=shuttler_phase_offset(180.0), c1=shuttler_freq_mu(end_f_MHz), c2=shuttler_freq_sweep(end_f_MHz, start_f_MHz, duration_us), ) self.shuttler0_trigger.trigger(0b1) - delay(500*us) + self.core.delay(500.*us) ## Step 8 ## self.shuttler0_relay.enable(0) @@ -308,7 +320,7 @@ class Shuttler(EnvExperiment): for i in range(2): for j in range(3): self.shuttler0_leds[i].pulse(.1*s) - delay(.1*s) + self.core.delay(.1*s) @kernel def relay_init(self): diff --git a/artiq/examples/kasli_suservo/repository/suservo.py b/artiq/examples/kasli_suservo/repository/suservo.py index 291858521..c79ab6046 100644 --- a/artiq/examples/kasli_suservo/repository/suservo.py +++ b/artiq/examples/kasli_suservo/repository/suservo.py @@ -1,7 +1,24 @@ +from numpy import int32 + from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.ttl import TTLOut +from artiq.coredevice.suservo import SUServo, Channel as SUServoChannel +@nac3 +class SUServoDemo(EnvExperiment): + core: KernelInvariant[Core] + led0: KernelInvariant[TTLOut] + suservo0: KernelInvariant[SUServo] + suservo0_ch0: KernelInvariant[SUServoChannel] + suservo0_ch1: KernelInvariant[SUServoChannel] + suservo0_ch2: KernelInvariant[SUServoChannel] + suservo0_ch3: KernelInvariant[SUServoChannel] + suservo0_ch4: KernelInvariant[SUServoChannel] + suservo0_ch5: KernelInvariant[SUServoChannel] + suservo0_ch6: KernelInvariant[SUServoChannel] + suservo0_ch7: KernelInvariant[SUServoChannel] -class SUServo(EnvExperiment): def build(self): self.setattr_device("core") self.setattr_device("led0") @@ -9,40 +26,37 @@ class SUServo(EnvExperiment): for i in range(8): self.setattr_device("suservo0_ch{}".format(i)) - def run(self): - self.init() - - def p(self, d): + @rpc + def p(self, d: list[int32]): mask = 1 << 18 - 1 for name, val in zip("ftw1 b1 pow cfg offset a1 ftw0 b0".split(), d): val = -(val & mask) + (val & ~mask) print("{}: {:#x} = {}".format(name, val, val)) - @rpc(flags={"async"}) - def p1(self, adc, asf, st): + @rpc # NAC3TODO (flags={"async"}) + def p1(self, adc: float, asf: float, st: int32): print("ADC: {:10s}, ASF: {:10s}, clipped: {}".format( "#"*int(adc), "#"*int(asf*10), (st >> 8) & 1), end="\r") @kernel - def init(self): - self.core.break_realtime() + def run(self): self.core.reset() self.led() self.suservo0.init() - delay(1*us) + self.core.delay(1.*us) # ADC PGIA gain for i in range(8): self.suservo0.set_pgia_mu(i, 0) - delay(10*us) + self.core.delay(10.*us) # DDS attenuator self.suservo0.cplds[0].set_att(0, 10.) - delay(1*us) + self.core.delay(1.*us) # Servo is done and disabled assert self.suservo0.get_status() & 0xff == 2 # set up profile 0 on channel 0: - delay(120*us) + self.core.delay(120.*us) self.suservo0_ch0.set_y( profile=0, y=0. # clear integrator @@ -61,39 +75,39 @@ class SUServo(EnvExperiment): self.suservo0_ch0.set_dds( profile=0, offset=-.5, # 5 V with above PGIA settings - frequency=71*MHz, + frequency=71.*MHz, phase=0.) # enable RF, IIR updates and profile 0 - self.suservo0_ch0.set(en_out=1, en_iir=1, profile=0) + self.suservo0_ch0.set(en_out=True, en_iir=True, profile=0) # enable global servo iterations - self.suservo0.set_config(enable=1) + self.suservo0.set_config(enable=True) # check servo enabled assert self.suservo0.get_status() & 0x01 == 1 - delay(10*us) + self.core.delay(10.*us) # read back profile data - data = [0] * 8 + data = [0 for _ in range(8)] self.suservo0_ch0.get_profile_mu(0, data) self.p(data) - delay(10*ms) + self.core.delay(10.*ms) while True: - self.suservo0.set_config(0) - delay(10*us) + self.suservo0.set_config(False) + self.core.delay(10.*us) v = self.suservo0.get_adc(7) - delay(30*us) + self.core.delay(30.*us) w = self.suservo0_ch0.get_y(0) - delay(20*us) + self.core.delay(20.*us) x = self.suservo0.get_status() - delay(10*us) - self.suservo0.set_config(1) + self.core.delay(10.*us) + self.suservo0.set_config(True) self.p1(v, w, x) - delay(20*ms) + self.core.delay(20.*ms) @kernel def led(self): self.core.break_realtime() for i in range(3): self.led0.pulse(.1*s) - delay(.1*s) + self.core.delay(.1*s) diff --git a/artiq/examples/kc705_nist_clock/idle_kernel.py b/artiq/examples/kc705_nist_clock/idle_kernel.py index d4d746fd3..964a99674 100644 --- a/artiq/examples/kc705_nist_clock/idle_kernel.py +++ b/artiq/examples/kc705_nist_clock/idle_kernel.py @@ -1,21 +1,26 @@ from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.ttl import TTLOut - +@nac3 class IdleKernel(EnvExperiment): + core: KernelInvariant[Core] + led: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("led") @kernel def run(self): - start_time = now_mu() + self.core.seconds_to_mu(500*ms) + start_time = now_mu() + self.core.seconds_to_mu(500.*ms) while self.core.get_rtio_counter_mu() < start_time: pass self.core.reset() while True: - self.led.pulse(250*ms) - delay(125*ms) - self.led.pulse(125*ms) - delay(125*ms) - self.led.pulse(125*ms) - delay(250*ms) + self.led.pulse(250.*ms) + self.core.delay(125.*ms) + self.led.pulse(125.*ms) + self.core.delay(125.*ms) + self.led.pulse(125.*ms) + self.core.delay(250.*ms) diff --git a/artiq/examples/kc705_nist_clock/repository/blink_forever.py b/artiq/examples/kc705_nist_clock/repository/blink_forever.py index 83c589ebe..68d5e031e 100644 --- a/artiq/examples/kc705_nist_clock/repository/blink_forever.py +++ b/artiq/examples/kc705_nist_clock/repository/blink_forever.py @@ -1,7 +1,12 @@ from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.ttl import TTLOut - +@nac3 class BlinkForever(EnvExperiment): + core: KernelInvariant[Core] + led: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("led") @@ -10,5 +15,5 @@ class BlinkForever(EnvExperiment): def run(self): self.core.reset() while True: - self.led.pulse(100*ms) - delay(100*ms) + self.led.pulse(100.*ms) + self.core.delay(100.*ms) diff --git a/artiq/examples/kc705_nist_clock/repository/core_pause.py b/artiq/examples/kc705_nist_clock/repository/core_pause.py index f483f5f6b..10bb900ab 100644 --- a/artiq/examples/kc705_nist_clock/repository/core_pause.py +++ b/artiq/examples/kc705_nist_clock/repository/core_pause.py @@ -1,20 +1,30 @@ from time import sleep from artiq.experiment import * +from artiq.coredevice.core import Core + +# NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/282 + +@rpc +def sleep_rpc(): + sleep(1) +@nac3 class CorePause(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") self.setattr_device("scheduler") @kernel def k(self): - print("kernel starting") + print_rpc("kernel starting") while not self.scheduler.check_pause(): - print("main kernel loop running...") - sleep(1) - print("kernel exiting") + print_rpc("main kernel loop running...") + sleep_rpc() + print_rpc("kernel exiting") def run(self): while True: diff --git a/artiq/examples/kc705_nist_clock/repository/dds_setter.py b/artiq/examples/kc705_nist_clock/repository/dds_setter.py index 29e391c9e..45e1efdd4 100644 --- a/artiq/examples/kc705_nist_clock/repository/dds_setter.py +++ b/artiq/examples/kc705_nist_clock/repository/dds_setter.py @@ -1,10 +1,16 @@ from operator import itemgetter from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.ad9914 import AD9914 +@nac3 class DDSSetter(EnvExperiment): """DDS Setter""" + + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") @@ -24,10 +30,10 @@ class DDSSetter(EnvExperiment): } @kernel - def set_dds(self, dds, frequency): + def set_dds(self, dds: AD9914, frequency: float): self.core.break_realtime() dds.set(frequency) - delay(200*ms) + self.core.delay(200.*ms) def run(self): for k, v in self.dds.items(): diff --git a/artiq/examples/kc705_nist_clock/repository/dds_test.py b/artiq/examples/kc705_nist_clock/repository/dds_test.py index dceb10438..9200ba3bf 100644 --- a/artiq/examples/kc705_nist_clock/repository/dds_test.py +++ b/artiq/examples/kc705_nist_clock/repository/dds_test.py @@ -1,9 +1,22 @@ from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.ad9914 import AD9914 +from artiq.coredevice.ttl import TTLOut +@nac3 class DDSTest(EnvExperiment): """DDS test""" + core: KernelInvariant[Core] + dds0: KernelInvariant[AD9914] + dds1: KernelInvariant[AD9914] + dds2: KernelInvariant[AD9914] + ttl0: KernelInvariant[TTLOut] + ttl1: KernelInvariant[TTLOut] + ttl2: KernelInvariant[TTLOut] + led: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.dds0 = self.get_device("ad9914dds0") @@ -17,21 +30,21 @@ class DDSTest(EnvExperiment): @kernel def run(self): self.core.reset() - delay(200*us) - self.dds1.set(120*MHz) - delay(10*us) - self.dds2.set(200*MHz) - delay(1*us) + self.core.delay(200.*us) + self.dds1.set(120.*MHz) + self.core.delay(10.*us) + self.dds2.set(200.*MHz) + self.core.delay(1.*us) for i in range(10000): - if i & 0x200: + if bool(i & 0x200): self.led.on() else: self.led.off() with parallel: with sequential: - self.dds0.set(100*MHz + 4*i*kHz) - self.ttl0.pulse(500*us) - self.ttl1.pulse(500*us) - self.ttl2.pulse(100*us) + self.dds0.set(100.*MHz + 4.*float(i)*kHz) + self.ttl0.pulse(500.*us) + self.ttl1.pulse(500.*us) + self.ttl2.pulse(100.*us) self.led.off() diff --git a/artiq/examples/kc705_nist_clock/repository/dma_blink.py b/artiq/examples/kc705_nist_clock/repository/dma_blink.py index 92c96327c..46cf8cccf 100644 --- a/artiq/examples/kc705_nist_clock/repository/dma_blink.py +++ b/artiq/examples/kc705_nist_clock/repository/dma_blink.py @@ -1,6 +1,8 @@ from artiq.experiment import * +# NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/75 + class DMABlink(EnvExperiment): def build(self): self.setattr_device("core") diff --git a/artiq/examples/kc705_nist_clock/repository/handover.py b/artiq/examples/kc705_nist_clock/repository/handover.py index c63ef8a74..eb1e90391 100644 --- a/artiq/examples/kc705_nist_clock/repository/handover.py +++ b/artiq/examples/kc705_nist_clock/repository/handover.py @@ -1,15 +1,21 @@ from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.ttl import TTLOut +@nac3 class Handover(EnvExperiment): + core: KernelInvariant[Core] + led: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("led") @kernel def blink_once(self): - delay(250*ms) - self.led.pulse(250*ms) + self.core.delay(250.*ms) + self.led.pulse(250.*ms) def run(self): self.core.reset() diff --git a/artiq/examples/kc705_nist_clock/repository/mandelbrot.py b/artiq/examples/kc705_nist_clock/repository/mandelbrot.py index 81e477548..bfe968453 100644 --- a/artiq/examples/kc705_nist_clock/repository/mandelbrot.py +++ b/artiq/examples/kc705_nist_clock/repository/mandelbrot.py @@ -1,17 +1,25 @@ import sys +from numpy import int32 + from artiq.experiment import * +from artiq.coredevice.core import Core +@nac3 class Mandelbrot(EnvExperiment): """Mandelbrot set demo""" + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") - def col(self, i): + @rpc + def col(self, i: int32): sys.stdout.write(" .,-:;i+hHM$*#@ "[i]) + @rpc def row(self): print("") @@ -22,22 +30,22 @@ class Mandelbrot(EnvExperiment): maxX = 1.0 width = 78 height = 36 - aspectRatio = 2 + aspectRatio = 2.0 - yScale = (maxX-minX)*(height/width)*aspectRatio + yScale = (maxX-minX)*(float(height)/float(width))*aspectRatio for y in range(height): for x in range(width): - c_r = minX+x*(maxX-minX)/width - c_i = y*yScale/height-yScale/2 + c_r = minX+float(x)*(maxX-minX)/float(width) + c_i = float(y)*yScale/float(height)-yScale/2.0 z_r = c_r z_i = c_i i = 0 for i in range(16): - if z_r*z_r + z_i*z_i > 4: + if z_r*z_r + z_i*z_i > 4.0: break new_z_r = (z_r*z_r)-(z_i*z_i) + c_r - z_i = 2*z_r*z_i + c_i + z_i = 2.0*z_r*z_i + c_i z_r = new_z_r self.col(i) self.row() diff --git a/artiq/examples/kc705_nist_clock/repository/photon_histogram.py b/artiq/examples/kc705_nist_clock/repository/photon_histogram.py index 91c39d5fb..331430c76 100644 --- a/artiq/examples/kc705_nist_clock/repository/photon_histogram.py +++ b/artiq/examples/kc705_nist_clock/repository/photon_histogram.py @@ -1,9 +1,28 @@ +from numpy import int32 + from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.ad9914 import AD9914 +from artiq.coredevice.ttl import TTLOut, TTLInOut +@nac3 class PhotonHistogram(EnvExperiment): """Photon histogram""" + core: KernelInvariant[Core] + bd_dds: KernelInvariant[AD9914] + bd_sw: KernelInvariant[TTLOut] + bdd_dds: KernelInvariant[AD9914] + bdd_sw: KernelInvariant[TTLOut] + pmt: KernelInvariant[TTLInOut] + + nbins: KernelInvariant[int32] + repeats: KernelInvariant[int32] + cool_f: KernelInvariant[float] + detect_f: KernelInvariant[float] + detect_t: KernelInvariant[float] + def build(self): self.setattr_device("core") self.setattr_device("bd_dds") @@ -22,20 +41,21 @@ class PhotonHistogram(EnvExperiment): @kernel def program_cooling(self): delay_mu(-self.bd_dds.set_duration_mu) - self.bd_dds.set(200*MHz) + self.bd_dds.set(200.*MHz) delay_mu(self.bd_dds.set_duration_mu) - self.bdd_dds.set(300*MHz) + self.bdd_dds.set(300.*MHz) @kernel - def cool_detect(self): + def cool_detect(self) -> int32: with parallel: - self.bd_sw.pulse(1*ms) - self.bdd_sw.pulse(1*ms) + self.bd_sw.pulse(1.*ms) + self.bdd_sw.pulse(1.*ms) self.bd_dds.set(self.cool_f) - self.bd_sw.pulse(100*us) + self.bd_sw.pulse(100.*us) self.bd_dds.set(self.detect_f) + gate_end_mu = int64(0) with parallel: self.bd_sw.pulse(self.detect_t) gate_end_mu = self.pmt.gate_rising(self.detect_t) @@ -45,6 +65,11 @@ class PhotonHistogram(EnvExperiment): self.bdd_sw.on() return self.pmt.count(gate_end_mu) + + @rpc + def report(self, hist: list[int32], ion_present: bool): + self.set_dataset("cooling_photon_histogram", hist) + self.set_dataset("ion_present", ion_present, broadcast=True) @kernel def run(self): @@ -55,18 +80,11 @@ class PhotonHistogram(EnvExperiment): total = 0 for i in range(self.repeats): - delay(0.5*ms) + self.core.delay(0.5*ms) n = self.cool_detect() if n >= self.nbins: n = self.nbins - 1 hist[n] += 1 total += n - self.set_dataset("cooling_photon_histogram", hist) - self.set_dataset("ion_present", total > 5*self.repeats, - broadcast=True) - - -if __name__ == "__main__": - from artiq.frontend.artiq_run import run - run() + self.report(hist, total > 5*self.repeats) diff --git a/artiq/examples/kc705_nist_clock/repository/precompile.py b/artiq/examples/kc705_nist_clock/repository/precompile.py index 6bfaaf057..4b264c672 100644 --- a/artiq/examples/kc705_nist_clock/repository/precompile.py +++ b/artiq/examples/kc705_nist_clock/repository/precompile.py @@ -1,18 +1,21 @@ from artiq.experiment import * +@nac3 class Precompile(EnvExperiment): + hello_str: Kernel[str] + def build(self): self.setattr_device("core") self.hello_str = "hello ARTIQ" def prepare(self): - self.precompiled = self.core.precompile(self.hello, "world") + self.precompiled = self.core.precompile(self.hello, "world") @kernel - def hello(self, arg): - print(self.hello_str, arg) - self.hello_str = "nowriteback" + def hello(self, arg: str): + print_rpc((self.hello_str, arg)) + self.hello_str = "nowriteback" def run(self): self.precompiled() diff --git a/artiq/examples/kc705_nist_clock/repository/speed_benchmark.py b/artiq/examples/kc705_nist_clock/repository/speed_benchmark.py index 35ae16b4b..be61828a8 100644 --- a/artiq/examples/kc705_nist_clock/repository/speed_benchmark.py +++ b/artiq/examples/kc705_nist_clock/repository/speed_benchmark.py @@ -1,6 +1,9 @@ import time +from numpy import int32 + from artiq.experiment import * +from artiq.coredevice.core import Core class _PayloadNOP(EnvExperiment): @@ -11,7 +14,10 @@ class _PayloadNOP(EnvExperiment): pass +@nac3 class _PayloadCoreNOP(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") @@ -20,11 +26,15 @@ class _PayloadCoreNOP(EnvExperiment): pass +@nac3 class _PayloadCoreSend100Ints(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") - def devnull(self, d): + @rpc + def devnull(self, d: int32): pass @kernel @@ -33,11 +43,15 @@ class _PayloadCoreSend100Ints(EnvExperiment): self.devnull(42) +@nac3 class _PayloadCoreSend1MB(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") - def devnull(self, d): + @rpc + def devnull(self, d: list[int32]): pass @kernel @@ -46,11 +60,15 @@ class _PayloadCoreSend1MB(EnvExperiment): self.devnull(data) +@nac3 class _PayloadCorePrimes(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") - def devnull(self, d): + @rpc + def devnull(self, d: int32): pass @kernel @@ -104,9 +122,14 @@ class SpeedBenchmark(EnvExperiment): def run_without_scheduler(self, pause): payload = globals()["_Payload" + self.payload](self) + start_time = time.monotonic() for i in range(int(self.nruns)): - payload.run() + try: + payload.run() + except: + import traceback + print(traceback.format_exc()) if pause: self.core.comm.close() self.scheduler.pause() diff --git a/artiq/examples/kc705_nist_clock/repository/tdr.py b/artiq/examples/kc705_nist_clock/repository/tdr.py index 21dc9338a..0a7380e14 100644 --- a/artiq/examples/kc705_nist_clock/repository/tdr.py +++ b/artiq/examples/kc705_nist_clock/repository/tdr.py @@ -1,24 +1,30 @@ # Copyright (C) 2014, 2015 Robert Jordens +from numpy import int32, int64 + from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.ttl import TTLOut, TTLInOut +@nac3 class PulseNotReceivedError(Exception): pass +@nac3 class TDR(EnvExperiment): """Time domain reflectometer. - From ttl2 an impedance matched pulse is send onto a coax - cable with an open end. pmt0 (very short stub, high impedance) also - listens on the transmission line near ttl2. + From ttl0 an impedance matched pulse is send onto a coax + cable with an open end. ttl3 (very short stub, high impedance) also + listens on the transmission line near ttl0. - When the forward propagating pulse passes pmt0, the voltage is half of the + When the forward propagating pulse passes ttl3, the voltage is half of the logic voltage and does not register as a rising edge. Once the - rising edge is reflected at an open end (same sign) and passes by pmt0 on - its way back to ttl2, it is detected. Analogously, hysteresis leads to - detection of the falling edge once the reflection reaches pmt0 after + rising edge is reflected at an open end (same sign) and passes by ttl3 on + its way back to ttl0, it is detected. Analogously, hysteresis leads to + detection of the falling edge once the reflection reaches ttl3 after one round trip time. This works marginally and is just a proof of principle: it relies on @@ -30,10 +36,17 @@ class TDR(EnvExperiment): This is also equivalent to a loopback tester or a delay measurement. """ + + core: KernelInvariant[Core] + ttl3: KernelInvariant[TTLInOut] + ttl0: KernelInvariant[TTLOut] + + t: Kernel[list[int32]] + def build(self): self.setattr_device("core") - self.setattr_device("pmt0") - self.setattr_device("ttl2") + self.setattr_device("ttl3") + self.setattr_device("ttl0") def run(self): self.core.reset() @@ -54,20 +67,20 @@ class TDR(EnvExperiment): t_rise/1e-9, t_fall/1e-9)) @kernel - def many(self, n, p): + def many(self, n: int32, p: int64): self.core.break_realtime() for i in range(n): self.one(p) @kernel - def one(self, p): + def one(self, p: int64): t0 = now_mu() with parallel: - self.pmt0.gate_both_mu(2*p) - self.ttl2.pulse_mu(p) + self.ttl3.gate_both_mu(int64(2)*p) + self.ttl0.pulse_mu(p) for i in range(len(self.t)): - ti = self.pmt0.timestamp_mu(now_mu()) - if ti <= 0: - raise PulseNotReceivedError() - self.t[i] = int(self.t[i] + ti - t0) - self.pmt0.count(now_mu()) # flush + ti = self.ttl3.timestamp_mu(now_mu()) + if ti <= int64(0): + raise PulseNotReceivedError + self.t[i] = int32(int64(self.t[i]) + ti - t0) + self.ttl3.count(now_mu()) # flush diff --git a/artiq/examples/nac3devices/nac3devices.json b/artiq/examples/nac3devices/nac3devices.json new file mode 100644 index 000000000..d9b148ddb --- /dev/null +++ b/artiq/examples/nac3devices/nac3devices.json @@ -0,0 +1,60 @@ +{ + "target": "kasli", + "min_artiq_version": "9.0", + "variant": "nac3devices", + "hw_rev": "v2.0", + "drtio_role": "master", + "core_addr": "192.168.1.70", + "peripherals": [ + { + "type": "zotino", + "ports": [0] + }, + { + "type": "mirny", + "almazny": true, + "ports": [1], + "clk_sel": "mmcx", + "refclk": 125e6 + }, + { + "type": "urukul", + "dds": "ad9912", + "ports": [2, 3], + "clk_sel": 2 + }, + { + "type": "urukul", + "dds": "ad9910", + "ports": [4, 5], + "clk_sel": 2 + }, + { + "type": "sampler", + "ports": [6] + }, + { + "type": "dio", + "ports": [7], + "bank_direction_low": "input", + "bank_direction_high": "output", + "edge_counter": true + }, + { + "type": "grabber", + "ports": [8] + }, + { + "type": "fastino", + "ports": [9] + }, + { + "type": "phaser", + "ports": [10] + }, + { + "type": "shuttler", + "ports": [11] + } + ] +} diff --git a/artiq/examples/nac3devices/nac3devices.py b/artiq/examples/nac3devices/nac3devices.py new file mode 100644 index 000000000..97447b9c0 --- /dev/null +++ b/artiq/examples/nac3devices/nac3devices.py @@ -0,0 +1,87 @@ +from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.cache import CoreCache +from artiq.coredevice.kasli_i2c import KasliEEPROM +from artiq.coredevice.zotino import Zotino +from artiq.coredevice.mirny import Mirny as MirnyCPLD +from artiq.coredevice.almazny import AlmaznyChannel +from artiq.coredevice.adf5356 import ADF5356 +from artiq.coredevice.urukul import CPLD as UrukulCPLD +from artiq.coredevice.ad9912 import AD9912 +from artiq.coredevice.ad9910 import AD9910 +from artiq.coredevice.sampler import Sampler +from artiq.coredevice.edge_counter import EdgeCounter +from artiq.coredevice.grabber import Grabber +from artiq.coredevice.fastino import Fastino +from artiq.coredevice.phaser import Phaser +from artiq.coredevice.shuttler import DCBias as ShuttlerDCBias, DDS as ShuttlerDDS + + +@nac3 +class NAC3Devices(EnvExperiment): + core: KernelInvariant[Core] + core_cache: KernelInvariant[CoreCache] + zotino0: KernelInvariant[Zotino] + mirny0_cpld: KernelInvariant[MirnyCPLD] + mirny0_ch0: KernelInvariant[ADF5356] + mirny0_almazny0: KernelInvariant[AlmaznyChannel] + urukul0_cpld: KernelInvariant[UrukulCPLD] + eeprom_urukul0: KernelInvariant[KasliEEPROM] + urukul0_ch0: KernelInvariant[AD9912] + urukul1_cpld: KernelInvariant[UrukulCPLD] + urukul1_ch0: KernelInvariant[AD9910] + sampler0: KernelInvariant[Sampler] + ttl0_counter: KernelInvariant[EdgeCounter] + grabber0: KernelInvariant[Grabber] + fastino0: KernelInvariant[Fastino] + phaser0: KernelInvariant[Phaser] + shuttler0_dcbias0: KernelInvariant[ShuttlerDCBias] + shuttler0_dds0: KernelInvariant[ShuttlerDDS] + + def build(self): + self.setattr_device("core") + self.setattr_device("core_cache") + self.setattr_device("zotino0") + self.setattr_device("mirny0_cpld") + self.setattr_device("mirny0_ch0") + self.setattr_device("mirny0_almazny0") + self.setattr_device("urukul0_cpld") + self.setattr_device("eeprom_urukul0") + self.setattr_device("urukul0_ch0") + self.setattr_device("urukul1_cpld") + self.setattr_device("urukul1_ch0") + self.setattr_device("sampler0") + self.setattr_device("ttl0_counter") + self.setattr_device("grabber0") + self.setattr_device("fastino0") + self.setattr_device("phaser0") + self.setattr_device("shuttler0_dcbias0") + self.setattr_device("shuttler0_dds0") + + @kernel + def run(self): + self.core.reset() + self.zotino0.init() + self.zotino0.set_leds(0x15) + self.zotino0.set_dac([1.2, -5.3, 3.4, 4.5]) + + self.core.break_realtime() + self.mirny0_cpld.init() + self.mirny0_ch0.init() + self.mirny0_ch0.set_att_mu(160) + self.mirny0_ch0.sw.on() + self.mirny0_ch0.set_frequency(2500.*MHz) + + self.core.break_realtime() + self.urukul0_cpld.init() + self.urukul0_ch0.init() + self.urukul0_ch0.sw.unwrap().on() + for i in range(10): + self.urukul0_ch0.set((10. + float(i))*MHz) + self.urukul0_ch0.set_att(6.) + self.core.delay(500.*ms) + + self.core.break_realtime() + self.sampler0.init() + samples = [0. for _ in range(8)] + self.sampler0.sample(samples) diff --git a/artiq/examples/no_hardware/repository/al_spectroscopy.py b/artiq/examples/no_hardware/repository/al_spectroscopy.py index d48c2ead7..8566dc11a 100644 --- a/artiq/examples/no_hardware/repository/al_spectroscopy.py +++ b/artiq/examples/no_hardware/repository/al_spectroscopy.py @@ -22,17 +22,17 @@ class AluminumSpectroscopy(EnvExperiment): for count in range(100): self.mains_sync.gate_rising(1*s/60) at_mu(self.mains_sync.timestamp_mu(now_mu()) + 100*us) - delay(10*us) + self.core.delay(10.*us) self.laser_cooling.pulse(100*MHz, 100*us) - delay(5*us) + self.core.delay(5.*us) with parallel: self.spectroscopy.pulse(self.spectroscopy_freq, 100*us) with sequential: - delay(50*us) + self.core.delay(50.*us) self.spectroscopy_b.set(200) - delay(5*us) + self.core.delay(5.*us) while True: - delay(5*us) + self.core.delay(5.*us) with parallel: self.state_detection.pulse(100*MHz, 10*us) photon_count = self.pmt.count(self.pmt.gate_rising(10*us)) diff --git a/artiq/examples/no_hardware/repository/simple_simulation.py b/artiq/examples/no_hardware/repository/simple_simulation.py index 1b9205825..e07c6f770 100644 --- a/artiq/examples/no_hardware/repository/simple_simulation.py +++ b/artiq/examples/no_hardware/repository/simple_simulation.py @@ -27,7 +27,7 @@ def main(): dmgr["core"] = devices.Core(dmgr) for wo in "abcd": dmgr[wo] = devices.WaveOutput(dmgr, wo) - exp = SimpleSimulation(dmgr) + exp = SimpleSimulation((dmgr, None, None, {})) exp.run() if __name__ == "__main__": diff --git a/artiq/firmware/ksupport/api.rs b/artiq/firmware/ksupport/api.rs index 435eaef73..a4744dab3 100644 --- a/artiq/firmware/ksupport/api.rs +++ b/artiq/firmware/ksupport/api.rs @@ -177,7 +177,7 @@ static mut API: &'static [(&'static str, *const ())] = &[ /* * syscall for unit tests * Used in `artiq.tests.coredevice.test_exceptions.ExceptionTest.test_raise_exceptions_kernel` - * This syscall checks that the exception IDs used in the Python `EmbeddingMap` (in `artiq.compiler.embedding`) + * This syscall checks that the exception IDs used in the Python `EmbeddingMap` (in `artiq.language.embedding_map`) * match the `EXCEPTION_ID_LOOKUP` defined in the firmware (`artiq::firmware::ksupport::eh_artiq`) */ api!(test_exception_id_sync = ::eh_artiq::test_exception_id_sync) diff --git a/artiq/firmware/ksupport/eh_artiq.rs b/artiq/firmware/ksupport/eh_artiq.rs index 92806d7b2..84aff00ca 100644 --- a/artiq/firmware/ksupport/eh_artiq.rs +++ b/artiq/firmware/ksupport/eh_artiq.rs @@ -328,7 +328,7 @@ extern fn stop_fn(_version: c_int, } } -// Must be kept in sync with `artiq.compiler.embedding` +// Must be kept in sync with `nac3artiq::Nac3::new` static EXCEPTION_ID_LOOKUP: [(&str, u32); 22] = [ ("RTIOUnderflow", 0), ("RTIOOverflow", 1), @@ -388,4 +388,3 @@ pub extern "C-unwind" fn test_exception_id_sync(exn_id: u32) { }; unsafe { raise(&exn) }; } - diff --git a/artiq/frontend/artiq_compile.py b/artiq/frontend/artiq_compile.py index d492cb35e..1f202f861 100755 --- a/artiq/frontend/artiq_compile.py +++ b/artiq/frontend/artiq_compile.py @@ -8,7 +8,7 @@ from artiq import __version__ as artiq_version from artiq.master.databases import DeviceDB, DatasetDB from artiq.master.worker_db import DeviceManager, DatasetManager from artiq.language.environment import ProcessArgumentManager -from artiq.coredevice.core import CompileError +from artiq.language.embedding_map import EmbeddingMap from artiq.tools import * @@ -45,11 +45,17 @@ def main(): common_args.init_logger_from_args(args) device_mgr = DeviceManager(DeviceDB(args.device_db)) - dataset_db = DatasetDB(args.dataset_db) try: - dataset_mgr = DatasetManager(dataset_db) - + dataset_db = DatasetDB(args.dataset_db) try: + dataset_mgr = DatasetManager(dataset_db) + embedding_map = EmbeddingMap() + + output = args.output + if output is None: + basename, ext = os.path.splitext(args.file) + output = "{}.elf".format(basename) + module = file_import(args.file, prefix="artiq_run_") exp = get_experiment(module, args.class_name) arguments = parse_arguments(args.arguments) @@ -57,71 +63,13 @@ def main(): exp_inst = exp((device_mgr, dataset_mgr, argument_mgr, {})) argument_mgr.check_unprocessed_arguments() - - if not hasattr(exp.run, "artiq_embedded"): + if not getattr(exp.run, "__artiq_kernel__", False): raise ValueError("Experiment entry point must be a kernel") - core_name = exp.run.artiq_embedded.core_name - core = getattr(exp_inst, core_name) - - object_map, main_kernel_library, _, _, subkernel_arg_types = \ - core.compile(exp.run, [exp_inst], {}, - attribute_writeback=False, print_as_rpc=False) - - subkernels = object_map.subkernels() - compiled_subkernels = {} - while True: - new_subkernels = {} - for sid, subkernel_fn in subkernels.items(): - if sid in compiled_subkernels.keys(): - continue - destination, subkernel_library, embedding_map = core.compile_subkernel( - sid, subkernel_fn, object_map, - [exp_inst], subkernel_arg_types, subkernels) - compiled_subkernels[sid] = (destination, subkernel_library) - new_subkernels.update(embedding_map.subkernels()) - if new_subkernels == subkernels: - break - subkernels.update(new_subkernels) - except CompileError as error: - return + exp_inst.core.compile(exp_inst.run, [], {}, embedding_map, file_output=output) finally: - device_mgr.close_devices() + dataset_db.close_db() finally: - dataset_db.close_db() - - if object_map.has_rpc(): - raise ValueError("Experiment must not use RPC") - - output = args.output - - if not subkernels: - # just write the ELF file - if output is None: - basename, ext = os.path.splitext(args.file) - output = "{}.elf".format(basename) - - with open(output, "wb") as f: - f.write(main_kernel_library) - else: - # combine them in a tar archive - if output is None: - basename, ext = os.path.splitext(args.file) - output = "{}.tar".format(basename) - - with tarfile.open(output, "w:") as tar: - # write the main lib as "main.elf" - main_kernel_fileobj = io.BytesIO(main_kernel_library) - main_kernel_info = tarfile.TarInfo(name="main.elf") - main_kernel_info.size = len(main_kernel_library) - tar.addfile(main_kernel_info, fileobj=main_kernel_fileobj) - - # subkernels as " .elf" - for sid, (destination, subkernel_library) in compiled_subkernels.items(): - subkernel_fileobj = io.BytesIO(subkernel_library) - subkernel_info = tarfile.TarInfo(name="{} {}.elf".format(sid, destination)) - subkernel_info.size = len(subkernel_library) - tar.addfile(subkernel_info, fileobj=subkernel_fileobj) - + device_mgr.close_devices() if __name__ == "__main__": main() diff --git a/artiq/frontend/artiq_ddb_template.py b/artiq/frontend/artiq_ddb_template.py index f19f9cc18..4fe6bb58a 100755 --- a/artiq/frontend/artiq_ddb_template.py +++ b/artiq/frontend/artiq_ddb_template.py @@ -416,34 +416,6 @@ class PeripheralManager: return next(channel) - def process_novogorny(self, rtio_offset, peripheral): - self.gen(""" - device_db["spi_{name}_adc"] = {{ - "type": "local", - "module": "artiq.coredevice.spi2", - "class": "SPIMaster", - "arguments": {{"channel": 0x{adc_channel:06x}}} - }} - device_db["ttl_{name}_cnv"] = {{ - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {{"channel": 0x{cnv_channel:06x}}}, - }} - device_db["{name}"] = {{ - "type": "local", - "module": "artiq.coredevice.novogorny", - "class": "Novogorny", - "arguments": {{ - "spi_adc_device": "spi_{name}_adc", - "cnv_device": "ttl_{name}_cnv" - }} - }}""", - name=self.get_name("novogorny"), - adc_channel=rtio_offset, - cnv_channel=rtio_offset + 1) - return 2 - def process_sampler(self, rtio_offset, peripheral): self.gen(""" device_db["spi_{name}_adc"] = {{ diff --git a/artiq/frontend/artiq_run.py b/artiq/frontend/artiq_run.py index cf6d5e45c..b51ed1fb1 100755 --- a/artiq/frontend/artiq_run.py +++ b/artiq/frontend/artiq_run.py @@ -11,58 +11,30 @@ from collections import defaultdict import h5py -from llvmlite import binding as llvm - from sipyco import common_args, pyon from artiq import __version__ as artiq_version from artiq.language.environment import EnvExperiment, ProcessArgumentManager -from artiq.language.types import TBool 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 import import_cache +from artiq.language import import_cache from artiq.tools import * logger = logging.getLogger(__name__) -class StubObject: - def __setattr__(self, name, value): - pass - - -class StubEmbeddingMap: - def __init__(self): - stub_object = StubObject() - self.object_forward_map = defaultdict(lambda: stub_object) - self.object_forward_map[1] = lambda _: None # return RPC - self.object_current_id = -1 - - def retrieve_object(self, object_id): - return self.object_forward_map[object_id] - - def store_object(self, value): - self.object_forward_map[self.object_current_id] = value - self.object_current_id -= 1 - - class FileRunner(EnvExperiment): def build(self, file): self.setattr_device("core") self.file = file - self.target = self.core.target_cls() def run(self): kernel_library = self.compile() self.core.comm.load(kernel_library) self.core.comm.run() - self.core.comm.serve(StubEmbeddingMap(), - lambda addresses: self.target.symbolize(kernel_library, addresses), \ - lambda symbols: self.target.demangle(symbols)) + self.core.comm.serve(None, None) class ELFRunner(FileRunner): @@ -71,22 +43,6 @@ class ELFRunner(FileRunner): return f.read() -class LLVMIRRunner(FileRunner): - def compile(self): - with open(self.file, "r") as f: - llmodule = llvm.parse_assembly(f.read()) - llmodule.verify() - return self.target.link([self.target.assemble(llmodule)]) - - -class LLVMBitcodeRunner(FileRunner): - def compile(self): - with open(self.file, "rb") as f: - llmodule = llvm.parse_bitcode(f.read()) - llmodule.verify() - return self.target.link([self.target.assemble(llmodule)]) - - class TARRunner(FileRunner): def compile(self): with tarfile.open(self.file, "r:") as tar: @@ -125,10 +81,9 @@ class DummyScheduler: def get_status(self): return dict() - def check_pause(self, rid=None) -> TBool: + def check_pause(self, rid=None) -> bool: return False - @host_only def pause(self): pass @@ -194,9 +149,7 @@ def _build_experiment(device_mgr, dataset_mgr, args): if hasattr(args, "file"): is_tar = tarfile.is_tarfile(args.file) is_elf = args.file.endswith(".elf") - is_ll = args.file.endswith(".ll") - is_bc = args.file.endswith(".bc") - if is_elf or is_ll or is_bc: + if is_elf: if args.arguments: raise ValueError("arguments not supported for precompiled kernels") if args.class_name: @@ -206,10 +159,6 @@ def _build_experiment(device_mgr, dataset_mgr, args): return TARRunner(managers, file=args.file) elif is_elf: return ELFRunner(managers, file=args.file) - elif is_ll: - return LLVMIRRunner(managers, file=args.file) - elif is_bc: - return LLVMBitcodeRunner(managers, file=args.file) else: import_cache.install_hook() module = file_import(args.file, prefix="artiq_run_") @@ -232,21 +181,18 @@ def run(with_file=False): args = get_argparser(with_file).parse_args() common_args.init_logger_from_args(args) - device_mgr = DeviceManager(DeviceDB(args.device_db), - virtual_devices={"scheduler": DummyScheduler(), - "ccb": DummyCCB()}) dataset_db = DatasetDB(args.dataset_db) try: dataset_mgr = DatasetManager(dataset_db) - + device_mgr = DeviceManager(DeviceDB(args.device_db), + virtual_devices={"scheduler": DummyScheduler(), + "ccb": DummyCCB()}) try: exp_inst = _build_experiment(device_mgr, dataset_mgr, args) exp_inst.prepare() exp_inst.run() device_mgr.notify_run_end() exp_inst.analyze() - except CompileError as error: - return except Exception as exn: if hasattr(exn, "artiq_core_exception"): print(exn.artiq_core_exception, file=sys.stderr) @@ -260,8 +206,8 @@ def run(with_file=False): else: for k, v in sorted(dataset_mgr.local.items(), key=itemgetter(0)): print("{}: {}".format(k, v)) - dataset_db.save() finally: + dataset_db.save() dataset_db.close_db() diff --git a/artiq/frontend/artiq_sinara_tester.py b/artiq/frontend/artiq_sinara_tester.py index f273a3d4b..f0c5d27a7 100755 --- a/artiq/frontend/artiq_sinara_tester.py +++ b/artiq/frontend/artiq_sinara_tester.py @@ -6,10 +6,30 @@ import os import select import sys +from numpy import int32 + from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.ttl import TTLOut, TTLInOut +from artiq.coredevice.urukul import CPLD as UrukulCPLD from artiq.coredevice.ad9910 import AD9910, SyncDataEeprom -from artiq.coredevice.phaser import PHASER_GW_BASE, PHASER_GW_MIQRO -from artiq.coredevice.shuttler import shuttler_volt_to_mu +from artiq.coredevice.mirny import Mirny +from artiq.coredevice.almazny import AlmaznyLegacy, AlmaznyChannel +from artiq.coredevice.adf5356 import ADF5356 +from artiq.coredevice.sampler import Sampler +from artiq.coredevice.zotino import Zotino +from artiq.coredevice.fastino import Fastino +from artiq.coredevice.phaser import Phaser, PHASER_GW_BASE, PHASER_GW_MIQRO +from artiq.coredevice.grabber import Grabber +from artiq.coredevice.suservo import SUServo, Channel as SUServoChannel +from artiq.coredevice.shuttler import ( + Config as ShuttlerConfig, + Trigger as ShuttlerTrigger, + DCBias as ShuttlerDCBias, + DDS as ShuttlerDDS, + Relay as ShuttlerRelay, + ADC as ShuttlerADC, + shuttler_volt_to_mu) from artiq.master.databases import DeviceDB from artiq.master.worker_db import DeviceManager @@ -29,7 +49,8 @@ def chunker(seq, size): yield res -def is_enter_pressed() -> TBool: +@rpc +def is_enter_pressed() -> bool: if os.name == "nt": if msvcrt.kbhit() and msvcrt.getch() == b"\r": return True @@ -43,7 +64,10 @@ def is_enter_pressed() -> TBool: return False +@nac3 class SinaraTester(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") @@ -167,7 +191,7 @@ class SinaraTester(EnvExperiment): self.shuttler = sorted(self.shuttler.items(), key=lambda x: x[1]["leds"][0].channel) @kernel - def test_led(self, led): + def test_led(self, led: TTLOut): while not is_enter_pressed(): self.core.break_realtime() # do not fill the FIFOs too much to avoid long response times @@ -175,8 +199,8 @@ class SinaraTester(EnvExperiment): while self.core.get_rtio_counter_mu() < t: pass for i in range(3): - led.pulse(100*ms) - delay(100*ms) + led.pulse(100.*ms) + self.core.delay(100.*ms) def test_leds(self): print("*** Testing LEDs.") @@ -187,7 +211,7 @@ class SinaraTester(EnvExperiment): self.test_led(led_dev) @kernel - def test_ttl_out_chunk(self, ttl_chunk): + def test_ttl_out_chunk(self, ttl_chunk: list[TTLOut]): while not is_enter_pressed(): self.core.break_realtime() for _ in range(50000): @@ -195,9 +219,9 @@ class SinaraTester(EnvExperiment): for ttl in ttl_chunk: i += 1 for _ in range(i): - ttl.pulse(1*us) - delay(1*us) - delay(10*us) + ttl.pulse(1.*us) + self.core.delay(1.*us) + self.core.delay(10.*us) def test_ttl_outs(self): print("*** Testing TTL outputs.") @@ -210,17 +234,18 @@ class SinaraTester(EnvExperiment): print("Testing TTL outputs: {}.".format(", ".join(name for name, dev in ttl_chunk))) self.test_ttl_out_chunk([dev for name, dev in ttl_chunk]) + # NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/101 @kernel - def test_ttl_in(self, ttl_out, ttl_in): + def test_ttl_in(self, ttl_out: TTLOut, ttl_in: TTLInOut) -> bool: n = 42 self.core.break_realtime() with parallel: - ttl_in.gate_rising(1*ms) + ttl_in.gate_rising(1.*ms) with sequential: - delay(50*us) + self.core.delay(50.*us) for _ in range(n): - ttl_out.pulse(2*us) - delay(2*us) + ttl_out.pulse(2.*us) + self.core.delay(2.*us) return ttl_in.count(now_mu()) == n def test_ttl_ins(self): @@ -245,23 +270,25 @@ class SinaraTester(EnvExperiment): print("FAILED") @kernel - def init_urukul(self, cpld): + def init_urukul(self, cpld: UrukulCPLD): self.core.break_realtime() cpld.init() @kernel - def test_urukul_att(self, cpld): + def test_urukul_att(self, cpld: UrukulCPLD): 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 + print_rpc((readback_word, test_word)) + # NAC3TODO raise ValueError + pass + # NAC3TODO: AD9912 support @kernel - def calibrate_urukul(self, channel): + def calibrate_urukul(self, channel: AD9910) -> tuple[int32, int32]: self.core.break_realtime() channel.init() self.core.break_realtime() @@ -271,7 +298,7 @@ class SinaraTester(EnvExperiment): return sync_delay_seed, io_update_delay @kernel - def setup_urukul(self, channel, frequency): + def setup_urukul(self, channel: AD9910, frequency: float): self.core.break_realtime() channel.init() channel.set(frequency*MHz) @@ -279,12 +306,12 @@ class SinaraTester(EnvExperiment): channel.set_att(6.) @kernel - def cfg_sw_off_urukul(self, channel): + def cfg_sw_off_urukul(self, channel: AD9910): self.core.break_realtime() channel.cfg_sw(False) @kernel - def rf_switch_wave(self, channels): + def rf_switch_wave(self, channels: list[TTLOut]): while not is_enter_pressed(): self.core.break_realtime() # do not fill the FIFOs too much to avoid long response times @@ -292,8 +319,8 @@ class SinaraTester(EnvExperiment): while self.core.get_rtio_counter_mu() < t: pass for channel in channels: - channel.pulse(100*ms) - delay(100*ms) + channel.pulse(100.*ms) + self.core.delay(100.*ms) # We assume that RTIO channels for switches are grouped by card. def test_urukuls(self): @@ -316,7 +343,7 @@ class SinaraTester(EnvExperiment): offset = channel_dev.sync_data.eeprom_offset sync_delay_seed, io_update_delay = self.calibrate_urukul(channel_dev) print("{}\t{} {}".format(channel_name, sync_delay_seed, io_update_delay)) - eeprom_word = (sync_delay_seed << 24) | (io_update_delay << 16) + eeprom_word = int32((sync_delay_seed << 24) | (io_update_delay << 16)) eeprom.write_i32(offset, eeprom_word) print("...done") @@ -327,25 +354,25 @@ class SinaraTester(EnvExperiment): for channel_n, (channel_name, channel_dev) in enumerate(channels): frequency = 10*(card_n + 1) + channel_n print("{}\t{}MHz".format(channel_name, frequency)) - self.setup_urukul(channel_dev, frequency) + self.setup_urukul(channel_dev, float(frequency)) print("Press ENTER when done.") input() - sw = [channel_dev for channel_name, channel_dev in self.urukuls if hasattr(channel_dev, "sw")] + sw = [channel_dev for channel_name, channel_dev in self.urukuls if channel_dev.sw.is_some()] if sw: print("Testing RF switch control. Check LEDs at urukul RF ports.") print("Press ENTER when done.") for swi in sw: self.cfg_sw_off_urukul(swi) - self.rf_switch_wave([swi.sw for swi in sw]) + self.rf_switch_wave([swi.sw.unwrap() for swi in sw]) @kernel - def init_mirny(self, cpld): + def init_mirny(self, cpld: Mirny): self.core.break_realtime() cpld.init() @kernel - def setup_mirny(self, channel, frequency): + def setup_mirny(self, channel: ADF5356, frequency: float): self.core.break_realtime() channel.init() @@ -354,15 +381,15 @@ class SinaraTester(EnvExperiment): self.core.break_realtime() channel.set_frequency(frequency*MHz) - delay(5*ms) + self.core.delay(5.*ms) @kernel - def sw_off_mirny(self, channel): + def sw_off_mirny(self, channel: ADF5356): self.core.break_realtime() channel.sw.off() @kernel - def mirny_rf_switch_wave(self, channels): + def mirny_rf_switch_wave(self, channels: list[TTLOut]): while not is_enter_pressed(): self.core.break_realtime() # do not fill the FIFOs too much to avoid long response times @@ -370,21 +397,21 @@ class SinaraTester(EnvExperiment): while self.core.get_rtio_counter_mu() < t: pass for channel in channels: - channel.pulse(100*ms) - delay(100*ms) + channel.pulse(100.*ms) + self.core.delay(100.*ms) @kernel - def init_legacy_almazny(self, almazny): + def init_legacy_almazny(self, almazny: AlmaznyLegacy): self.core.break_realtime() almazny.init() almazny.output_toggle(True) @kernel - def legacy_almazny_set_attenuators_mu(self, almazny, ch, atts): + def legacy_almazny_set_attenuators_mu(self, almazny: AlmaznyLegacy, ch: int32, atts: int32): self.core.break_realtime() almazny.set_att_mu(ch, atts) @kernel - def legacy_almazny_att_test(self, almazny): + def legacy_almazny_att_test(self, almazny: AlmaznyLegacy): # change attenuation bit by bit over time for all channels att_mu = 0 while not is_enter_pressed(): @@ -394,14 +421,14 @@ class SinaraTester(EnvExperiment): pass for ch in range(4): almazny.set_att_mu(ch, att_mu) - delay(250*ms) + self.core.delay(250.*ms) if att_mu == 0: att_mu = 1 else: att_mu = (att_mu << 1) & 0x3F @kernel - def legacy_almazny_toggle_output(self, almazny, rf_on): + def legacy_almazny_toggle_output(self, almazny: AlmaznyLegacy, rf_on: bool): self.core.break_realtime() almazny.output_toggle(rf_on) @@ -417,7 +444,7 @@ class SinaraTester(EnvExperiment): print("Testing attenuators. Frequencies:") for card_n, channels in enumerate(chunker(self.mirnies, 4)): for channel_n, (channel_name, channel_dev) in enumerate(channels): - frequency = 2000 + card_n * 250 + channel_n * 50 + frequency = 2000. + card_n * 250 + channel_n * 50 print("{}\t{}MHz".format(channel_name, frequency*2)) self.setup_mirny(channel_dev, frequency) self.init_legacy_almazny(almazny) @@ -430,7 +457,7 @@ class SinaraTester(EnvExperiment): self.legacy_almazny_toggle_output(almazny, False) @kernel - def almazny_led_wave(self, almaznys): + def almazny_led_wave(self, almaznys: list[AlmaznyChannel]): while not is_enter_pressed(): self.core.break_realtime() # do not fill the FIFOs too much to avoid long response times @@ -439,11 +466,11 @@ class SinaraTester(EnvExperiment): pass for ch in almaznys: ch.set(31.5, False, True) - delay(100*ms) + self.core.delay(100.*ms) ch.set(31.5, False, False) @kernel - def almazny_att_test(self, almaznys): + def almazny_att_test(self, almaznys: list[AlmaznyChannel]): rf_en = 1 led = 1 att_mu = 0 @@ -455,7 +482,7 @@ class SinaraTester(EnvExperiment): setting = led << 7 | rf_en << 6 | (att_mu & 0x3F) for ch in almaznys: ch.set_mu(setting) - delay(250*ms) + self.core.delay(250.*ms) if att_mu == 0: att_mu = 1 else: @@ -492,7 +519,7 @@ class SinaraTester(EnvExperiment): print("Frequencies:") for card_n, channels in enumerate(chunker(self.mirnies, 4)): for channel_n, (channel_name, channel_dev) in enumerate(channels): - frequency = 1000 + 100 * (card_n + 1) + channel_n * 10 + frequency = float(1000 + 100 * (card_n + 1) + channel_n * 10) print("{}\t{}MHz".format(channel_name, frequency)) self.setup_mirny(channel_dev, frequency) print("{} info: {}".format(channel_name, channel_dev.info())) @@ -507,17 +534,21 @@ class SinaraTester(EnvExperiment): self.sw_off_mirny(swi) self.mirny_rf_switch_wave([swi.sw for swi in sw]) + @rpc + def update_sampler_voltages(self, voltages: list[float]): + self.sampler_voltages = voltages + @kernel - def get_sampler_voltages(self, sampler, cb): + def get_sampler_voltages(self, sampler: Sampler): self.core.break_realtime() sampler.init() - delay(5*ms) + self.core.delay(5.*ms) for i in range(8): sampler.set_gain_mu(i, 0) - delay(100*us) - smp = [0.0]*8 + self.core.delay(100.*us) + smp = [0. for _ in range(8)] sampler.sample(smp) - cb(smp) + self.update_sampler_voltages(smp) def test_samplers(self): print("*** Testing Sampler ADCs.") @@ -528,14 +559,10 @@ class SinaraTester(EnvExperiment): print("Apply 1.5V to channel {}. Press ENTER when done.".format(channel)) input() - voltages = [] - def setv(x): - nonlocal voltages - voltages = x - self.get_sampler_voltages(card_dev, setv) + self.get_sampler_voltages(card_dev) passed = True - for n, voltage in enumerate(voltages): + for n, voltage in enumerate(self.sampler_voltages): if n == channel: if abs(voltage - 1.5) > 0.2: passed = False @@ -546,22 +573,22 @@ class SinaraTester(EnvExperiment): print("PASSED") else: print("FAILED") - print(" ".join(["{:.1f}".format(x) for x in voltages])) + print(" ".join(["{:.1f}".format(x) for x in self.sampler_voltages])) @kernel - def set_zotino_voltages(self, zotino, voltages): + def set_zotino_voltages(self, zotino: Zotino, voltages: list[float]): self.core.break_realtime() zotino.init() - delay(200*us) + self.core.delay(200.*us) i = 0 for voltage in voltages: zotino.write_dac(i, voltage) - delay(100*us) + self.core.delay(100.*us) i += 1 zotino.load() @kernel - def zotinos_led_wave(self, zotinos): + def zotinos_led_wave(self, zotinos: list[Zotino]): while not is_enter_pressed(): self.core.break_realtime() # do not fill the FIFOs too much to avoid long response times @@ -571,9 +598,9 @@ class SinaraTester(EnvExperiment): for zotino in zotinos: for i in range(8): zotino.set_leds(1 << i) - delay(100*ms) + self.core.delay(100.*ms) zotino.set_leds(0) - delay(100*ms) + self.core.delay(100.*ms) def test_zotinos(self): print("*** Testing Zotino DACs and USER LEDs.") @@ -589,18 +616,18 @@ class SinaraTester(EnvExperiment): ) @kernel - def set_fastino_voltages(self, fastino, voltages): + def set_fastino_voltages(self, fastino: Fastino, voltages: list[float]): self.core.break_realtime() fastino.init() - delay(200*us) + self.core.delay(200.*us) i = 0 for voltage in voltages: fastino.set_dac(i, voltage) - delay(100*us) + self.core.delay(100.*us) i += 1 @kernel - def fastinos_led_wave(self, fastinos): + def fastinos_led_wave(self, fastinos: list[Fastino]): while not is_enter_pressed(): self.core.break_realtime() # do not fill the FIFOs too much to avoid long response times @@ -610,9 +637,9 @@ class SinaraTester(EnvExperiment): for fastino in fastinos: for i in range(8): fastino.set_leds(1 << i) - delay(100*ms) + self.core.delay(100.*ms) fastino.set_leds(0) - delay(100*ms) + self.core.delay(100.*ms) def test_fastinos(self): print("*** Testing Fastino DACs and USER LEDs.") @@ -628,44 +655,44 @@ class SinaraTester(EnvExperiment): ) @kernel - def set_phaser_frequencies(self, phaser, duc, osc): + def set_phaser_frequencies(self, phaser: Phaser, duc: float, osc: list[float]): self.core.break_realtime() phaser.init() - delay(1*ms) + self.core.delay(1.*ms) if phaser.gw_rev == PHASER_GW_BASE: phaser.channel[0].set_duc_frequency(duc) phaser.channel[0].set_duc_cfg() - phaser.channel[0].set_att(6*dB) + phaser.channel[0].set_att(6.*dB) phaser.channel[1].set_duc_frequency(-duc) phaser.channel[1].set_duc_cfg() - phaser.channel[1].set_att(6*dB) + phaser.channel[1].set_att(6.*dB) phaser.duc_stb() - delay(1*ms) + self.core.delay(1.*ms) for i in range(len(osc)): phaser.channel[0].oscillator[i].set_frequency(osc[i]) phaser.channel[0].oscillator[i].set_amplitude_phase(.2) phaser.channel[1].oscillator[i].set_frequency(-osc[i]) phaser.channel[1].oscillator[i].set_amplitude_phase(.2) - delay(1*ms) + self.core.delay(1.*ms) elif phaser.gw_rev == PHASER_GW_MIQRO: for ch in range(2): - phaser.channel[ch].set_att(6*dB) + phaser.channel[ch].set_att(6.*dB) phaser.channel[ch].set_duc_cfg() - sign = 1. - 2.*ch + sign = 1. - 2.*float(ch) for i in range(len(osc)): phaser.channel[ch].miqro.set_profile(i, profile=1, - frequency=sign*(duc + osc[i]), amplitude=1./len(osc)) - delay(100*us) + frequency=sign*(duc + osc[i]), amplitude=1./float(len(osc))) + self.core.delay(100.*us) phaser.channel[ch].miqro.set_window( - start=0x000, iq=[[1., 0.]], order=0, tail=0) + start=0x000, iq=[(1., 0.)], order=0, tail=False) phaser.channel[ch].miqro.pulse( window=0x000, profiles=[1 for _ in range(len(osc))]) - delay(1*ms) + self.core.delay(1.*ms) else: raise ValueError @kernel - def phaser_led_wave(self, phasers): + def phaser_led_wave(self, phasers: list[Phaser]): while not is_enter_pressed(): self.core.break_realtime() # do not fill the FIFOs too much to avoid long response times @@ -675,9 +702,9 @@ class SinaraTester(EnvExperiment): for phaser in phasers: for i in range(6): phaser.set_leds(1 << i) - delay(100*ms) + self.core.delay(100.*ms) phaser.set_leds(0) - delay(100*ms) + self.core.delay(100.*ms) def test_phasers(self): print("*** Testing Phaser DACs and 6 USER LEDs.") @@ -696,9 +723,9 @@ class SinaraTester(EnvExperiment): ) @kernel - def grabber_capture(self, card_dev, rois): + def grabber_capture(self, card_dev: Grabber, rois: list[list[int32]]): self.core.break_realtime() - delay(100*us) + self.core.delay(100.*us) mask = 0 for i in range(len(rois)): i = rois[i][0] @@ -709,11 +736,11 @@ class SinaraTester(EnvExperiment): mask |= 1 << i card_dev.setup_roi(i, x0, y0, x1, y1) card_dev.gate_roi(mask) - n = [0]*len(rois) + n = [0 for _ in range(len(rois))] card_dev.input_mu(n) self.core.break_realtime() card_dev.gate_roi(0) - print("ROI sums:", n) + print_rpc(("ROI sums:", n)) def test_grabbers(self): print("*** Testing Grabber Frame Grabbers.") @@ -730,25 +757,25 @@ class SinaraTester(EnvExperiment): self.grabber_capture(card_dev, rois) @kernel - def setup_suservo(self, channel): + def setup_suservo(self, channel: SUServo): self.core.break_realtime() channel.init() - delay(1*us) + self.core.delay(1.*us) # ADC PGIA gain 0 for i in range(8): channel.set_pgia_mu(i, 0) - delay(10*us) + self.core.delay(10.*us) # DDS attenuator 10dB for i in range(4): for cpld in channel.cplds: cpld.set_att(i, 10.) - delay(1*us) + self.core.delay(1.*us) # Servo is done and disabled - assert channel.get_status() & 0xff == 2 - delay(10*us) + # NAC3TODO assert channel.get_status() & 0xff == 2 + self.core.delay(10.*us) @kernel - def setup_suservo_loop(self, channel, loop_nr): + def setup_suservo_loop(self, channel: SUServoChannel, loop_nr: int32): self.core.break_realtime() channel.set_y( profile=loop_nr, @@ -763,24 +790,24 @@ class SinaraTester(EnvExperiment): delay=0. # no IIR update delay after enabling ) # setpoint 0.5 (5 V with above PGIA gain setting) - delay(100*us) + self.core.delay(100.*us) channel.set_dds( profile=loop_nr, offset=-.3, # 3 V with above PGIA settings - frequency=10*MHz, + 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) + self.core.delay(10.*us) + channel.set(en_out=True, en_iir=True, profile=loop_nr) @kernel - def setup_start_suservo(self, channel): + def setup_start_suservo(self, channel: SUServo): self.core.break_realtime() - channel.set_config(enable=1) - delay(10*us) + channel.set_config(enable=True) + self.core.delay(10.*us) # check servo enabled - assert channel.get_status() & 0x01 == 1 - delay(10*us) + # NAC3TODO assert channel.get_status() & 0x01 == 1 + self.core.delay(10.*us) def test_suservos(self): print("*** Testing SUServos.") @@ -809,7 +836,8 @@ class SinaraTester(EnvExperiment): input() @kernel - def setup_shuttler_init(self, relay, adc, dcbias, dds, trigger, config): + def setup_shuttler_init(self, relay: ShuttlerRelay, adc: ShuttlerADC, dcbias: list[ShuttlerDCBias], + dds: list[ShuttlerDDS], trigger: ShuttlerTrigger, config: ShuttlerConfig): self.core.break_realtime() # Reset Shuttler Output Relay relay.init() @@ -824,7 +852,7 @@ class SinaraTester(EnvExperiment): delay_mu(int64(self.core.ref_multiplier)) if adc.read_id() >> 4 != 0x038d: - print("Remote AFE Board's ADC is not found. Check Remote AFE Board's Cables Connections") + print_rpc("Remote AFE Board's ADC is not found. Check Remote AFE Board's Cables Connections") assert adc.read_id() >> 4 == 0x038d delay_mu(int64(self.core.ref_multiplier)) @@ -835,31 +863,33 @@ class SinaraTester(EnvExperiment): self.setup_shuttler_set_output(dcbias, dds, trigger, ch, 0.0) @kernel - def set_shuttler_relay(self, relay, val): + def set_shuttler_relay(self, relay: ShuttlerRelay, val: int32): self.core.break_realtime() relay.enable(val) + # NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/101 @kernel - def get_shuttler_output_voltage(self, adc, ch, cb): + def get_shuttler_output_voltage(self, adc: ShuttlerADC, ch: int32) -> float: self.core.break_realtime() - cb(adc.read_ch(ch)) + return adc.read_ch(ch) @kernel - def setup_shuttler_set_output(self, dcbias, dds, trigger, ch, volt): + def setup_shuttler_set_output(self, dcbias: list[ShuttlerDCBias], dds: list[ShuttlerDDS], + trigger: ShuttlerTrigger, ch: int32, volt: float): self.core.break_realtime() dcbias[ch].set_waveform( a0=shuttler_volt_to_mu(volt), a1=0, - a2=0, - a3=0, + a2=int64(0), + a3=int64(0), ) delay_mu(int64(self.core.ref_multiplier)) dds[ch].set_waveform( b0=0, b1=0, - b2=0, - b3=0, + b2=int64(0), + b3=int64(0), c0=0, c1=0, c2=0, @@ -870,7 +900,7 @@ class SinaraTester(EnvExperiment): delay_mu(int64(self.core.ref_multiplier)) @kernel - def shuttler_relay_led_wave(self, relay): + def shuttler_relay_led_wave(self, relay: ShuttlerRelay): while not is_enter_pressed(): self.core.break_realtime() # do not fill the FIFOs too much to avoid long response times @@ -879,9 +909,9 @@ class SinaraTester(EnvExperiment): pass for ch in range(16): relay.enable(1 << ch) - delay(100*ms) + self.core.delay(100.*ms) relay.enable(0x0000) - delay(100*ms) + self.core.delay(100.*ms) def test_shuttler(self): print("*** Testing Shuttler.") @@ -890,9 +920,6 @@ class SinaraTester(EnvExperiment): print("Testing: ", card_name) output_voltage = 0.0 - def setv(x): - nonlocal output_voltage - output_voltage = x self.setup_shuttler_init(card_dev["relay"], card_dev["adc"], card_dev["dcbias"], card_dev["dds"], card_dev["trigger"], card_dev["config"]) @@ -911,7 +938,7 @@ class SinaraTester(EnvExperiment): for ch, volt in enumerate(volt_set): self.setup_shuttler_set_output(card_dev["dcbias"], card_dev["dds"], card_dev["trigger"], ch, volt) - self.get_shuttler_output_voltage(card_dev["adc"], ch, setv) + output_voltage = self.get_shuttler_output_voltage(card_dev["adc"], ch) if (abs(volt) - abs(output_voltage)) > 0.1: passed = False adc_readings.append(output_voltage) diff --git a/artiq/gateware/eem.py b/artiq/gateware/eem.py index 71f5bb0fe..dc7261c69 100644 --- a/artiq/gateware/eem.py +++ b/artiq/gateware/eem.py @@ -342,53 +342,6 @@ class Sampler(_EEM): target.specials += DifferentialOutput(1, sdr.p, sdr.n) -class Novogorny(_EEM): - @staticmethod - def io(eem, iostandard): - return [ - ("novogorny{}_spi_p".format(eem), 0, - Subsignal("clk", Pins(_eem_pin(eem, 0, "p"))), - Subsignal("mosi", Pins(_eem_pin(eem, 1, "p"))), - Subsignal("miso", Pins(_eem_pin(eem, 2, "p"))), - Subsignal("cs_n", Pins( - _eem_pin(eem, 3, "p"), _eem_pin(eem, 4, "p"))), - iostandard(eem), - ), - ("novogorny{}_spi_n".format(eem), 0, - Subsignal("clk", Pins(_eem_pin(eem, 0, "n"))), - Subsignal("mosi", Pins(_eem_pin(eem, 1, "n"))), - Subsignal("miso", Pins(_eem_pin(eem, 2, "n"))), - Subsignal("cs_n", Pins( - _eem_pin(eem, 3, "n"), _eem_pin(eem, 4, "n"))), - iostandard(eem), - ), - ] + [ - ("novogorny{}_{}".format(eem, sig), 0, - Subsignal("p", Pins(_eem_pin(j, i, "p"))), - Subsignal("n", Pins(_eem_pin(j, i, "n"))), - iostandard(j) - ) for i, j, sig in [ - (5, eem, "cnv"), - (6, eem, "busy"), - (7, eem, "scko"), - ] - ] - - @classmethod - def add_std(cls, target, eem, ttl_out_cls, iostandard=default_iostandard): - cls.add_extension(target, eem, iostandard=iostandard) - - phy = spi2.SPIMaster(target.platform.request("novogorny{}_spi_p".format(eem)), - target.platform.request("novogorny{}_spi_n".format(eem))) - target.submodules += phy - target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=16)) - - pads = target.platform.request("novogorny{}_cnv".format(eem)) - phy = ttl_out_cls(pads.p, pads.n) - target.submodules += phy - target.rtio_channels.append(rtio.Channel.from_phy(phy)) - - class Zotino(_EEM): @staticmethod def io(eem, iostandard): diff --git a/artiq/gateware/eem_7series.py b/artiq/gateware/eem_7series.py index 2ca444d37..683646c02 100644 --- a/artiq/gateware/eem_7series.py +++ b/artiq/gateware/eem_7series.py @@ -50,13 +50,6 @@ def peripheral_urukul(module, peripheral, **kwargs): peripheral["dds"], sync_gen_cls, **kwargs) -def peripheral_novogorny(module, peripheral, **kwargs): - if len(peripheral["ports"]) != 1: - raise ValueError("wrong number of ports") - eem.Novogorny.add_std(module, peripheral["ports"][0], - ttl_serdes_7series.Output_8X, **kwargs) - - def peripheral_sampler(module, peripheral, **kwargs): if len(peripheral["ports"]) == 1: port, port_aux = peripheral["ports"][0], None @@ -147,7 +140,6 @@ peripheral_processors = { "dio": peripheral_dio, "dio_spi": peripheral_dio_spi, "urukul": peripheral_urukul, - "novogorny": peripheral_novogorny, "sampler": peripheral_sampler, "suservo": peripheral_suservo, "zotino": peripheral_zotino, diff --git a/artiq/language/__init__.py b/artiq/language/__init__.py index e5433d3b0..99c019a75 100644 --- a/artiq/language/__init__.py +++ b/artiq/language/__init__.py @@ -1,13 +1,12 @@ -from artiq.language import core, types, environment, units, scan +from artiq.language import core, environment, units, scan from artiq.language.core import * -from artiq.language.types import * from artiq.language.environment import * from artiq.language.units import * from artiq.language.scan import * +from . import import_cache -__all__ = [] +__all__ = ["import_cache"] __all__.extend(core.__all__) -__all__.extend(types.__all__) __all__.extend(environment.__all__) __all__.extend(units.__all__) __all__.extend(scan.__all__) diff --git a/artiq/language/core.py b/artiq/language/core.py index 050fb81ab..b6a6491a5 100644 --- a/artiq/language/core.py +++ b/artiq/language/core.py @@ -2,239 +2,168 @@ Core ARTIQ extensions to the Python language. """ -from collections import namedtuple +from typing import Generic, TypeVar from functools import wraps -import numpy +from inspect import getfullargspec, getmodule +from types import SimpleNamespace +from math import floor, ceil + +from numpy import int32, int64 + +from artiq.language import import_cache -__all__ = ["kernel", "portable", "rpc", "subkernel", "syscall", "host_only", - "kernel_from_string", "set_time_manager", "set_watchdog_factory", - "TerminationRequested"] - -# global namespace for kernels -kernel_globals = ( - "sequential", "parallel", "interleave", - "delay_mu", "now_mu", "at_mu", "delay", - "watchdog" -) -__all__.extend(kernel_globals) +__all__ = [ + "Kernel", "KernelInvariant", "virtual", "ConstGeneric", + "round64", "floor64", "ceil64", + "extern", "kernel", "portable", "nac3", "rpc", + "print_rpc", + "Option", "Some", "none", "UnwrapNoneError", + "set_time_manager", + "parallel", "legacy_parallel", "sequential", + "delay_mu", "now_mu", "at_mu", + "set_watchdog_factory", "watchdog", "TerminationRequested", +] -_ARTIQEmbeddedInfo = namedtuple("_ARTIQEmbeddedInfo", - "core_name portable function syscall forbidden destination flags") +T = TypeVar('T') -def kernel(arg=None, flags={}): - """ - This decorator marks an object's method for execution on the core - device. +class Kernel(Generic[T]): + pass - When a decorated method is called from the Python interpreter, the ``core`` - attribute of the object is retrieved and used as core device driver. The - core device driver will typically compile, transfer and run the method - (kernel) on the device. +class KernelInvariant(Generic[T]): + pass - When kernels call another method: +class virtual(Generic[T]): + pass - - if the method is a kernel for the same core device, it is compiled - and sent in the same binary. Calls between kernels happen entirely on - the device. - - if the method is a regular Python method (not a kernel), it generates - a remote procedure call (RPC) for execution on the host. +class _ConstGenericMarker: + pass - The decorator takes an optional parameter that defaults to ``core`` and - specifies the name of the attribute to use as core device driver. +def ConstGeneric(name, constraint): + return TypeVar(name, _ConstGenericMarker, constraint) - This decorator must be present in the global namespace of all modules using - it for the import cache to work properly. - """ - if isinstance(arg, str): - def inner_decorator(function): - @wraps(function) - def run_on_core(self, *k_args, **k_kwargs): - return getattr(self, arg).run(run_on_core, ((self,) + k_args), k_kwargs) - run_on_core.artiq_embedded = _ARTIQEmbeddedInfo( - core_name=arg, portable=False, function=function, syscall=None, - forbidden=False, destination=None, flags=set(flags)) - return run_on_core - return inner_decorator - elif arg is None: - def inner_decorator(function): - return kernel(function, flags) - return inner_decorator +def round64(x): + return int64(round(x)) + +def floor64(x): + return int64(floor(x)) + +def ceil64(x): + return int64(ceil(x)) + + +# Delay NAC3 analysis until all referenced variables are supposed to exist on the CPython side. +_registered_functions = set() +_registered_classes = set() +_registered_modules = set() + +def _register_function(fun): + import_cache.add_module_to_cache(getmodule(fun)) + _registered_functions.add(fun) + +def _register_class(cls): + import_cache.add_module_to_cache(getmodule(cls)) + _registered_classes.add(cls) + +def register_content_module(module): + # for kernels sent by content, they have no modules + # thus their source must be analyzed instead + _registered_modules.add(module) + + +def extern(function): + """Decorates a function declaration defined by the core device runtime.""" + _register_function(function) + return function + + +def kernel(function_or_method): + """Decorates a function or method to be executed on the core device.""" + _register_function(function_or_method) + argspec = getfullargspec(function_or_method) + if argspec.args and argspec.args[0] == "self": + @wraps(function_or_method) + def run_on_core(self, *args, **kwargs): + fake_method = SimpleNamespace(__self__=self, __name__=function_or_method.__name__) + return self.core.run(fake_method, args, kwargs) else: - return kernel("core", flags)(arg) + @wraps(function_or_method) + def run_on_core(*args, **kwargs): + raise RuntimeError("Kernel functions need explicit core.run()") + run_on_core.__artiq_kernel__ = True + return run_on_core -def subkernel(arg=None, destination=0, flags={}): + +def portable(function): + """Decorates a function or method to be executed on the same device (host/core device) as the caller.""" + _register_function(function) + return function + + +def nac3(cls): """ - This decorator marks an object's method or function for execution on a satellite device. - Destination must be given, and it must be between 1 and 255 (inclusive). - - Subkernels behave similarly to kernels, with few key differences: - - - they are started from main kernels, - - they do not support RPCs, - - but they can call other kernels or subkernels. - - Subkernels can accept arguments and return values. However, they must be fully - annotated with ARTIQ types. - - To call a subkernel, call it like a normal function. - - To await its finishing execution, call ``subkernel_await(subkernel, [timeout])``. - The timeout parameter is optional, and by default is equal to 10000 (miliseconds). - This time can be adjusted for subkernels that take a long time to execute. - - The compiled subkernel is copied to satellites, but not yet to the kernel core - until it's called. For bigger subkernels it may take some time before they - actually start running. To help with that, subkernels can be preloaded, with - ``subkernel_preload(subkernel)`` function. A call to a preloaded subkernel - will take less time, but only one subkernel can be preloaded at a time. + Decorates a class to be analyzed by NAC3. + All classes containing kernels or portable methods must use this decorator. """ - if isinstance(arg, str): - def inner_decorator(function): - @wraps(function) - def run_subkernel(self, *k_args, **k_kwargs): - sid = getattr(self, arg).prepare_subkernel(destination, run_subkernel, ((self,) + k_args), k_kwargs) - getattr(self, arg).run_subkernel(sid) - run_subkernel.artiq_embedded = _ARTIQEmbeddedInfo( - core_name=arg, portable=False, function=function, syscall=None, - forbidden=False, destination=destination, flags=set(flags)) - return run_subkernel - return inner_decorator - elif arg is None: - def inner_decorator(function): - return subkernel(function, destination, flags) - return inner_decorator - else: - return subkernel("core", destination, flags)(arg) + _register_class(cls) + return cls -def portable(arg=None, flags={}): - """ - This decorator marks a function for execution on the same device as its - caller. - - In other words, a decorated function called from the interpreter on the - host will be executed on the host (no compilation and execution on the - core device). A decorated function called from a kernel will be executed - on the core device (no RPC). - - This decorator must be present in the global namespace of all modules using - it for the import cache to work properly. - """ - if arg is None: - def inner_decorator(function): - return portable(function, flags) - return inner_decorator - else: - arg.artiq_embedded = \ - _ARTIQEmbeddedInfo(core_name=None, portable=True, function=arg, syscall=None, - forbidden=False, destination=None, flags=set(flags)) - return arg def rpc(arg=None, flags={}): """ This decorator marks a function for execution on the host interpreter. - This is also the default behavior of ARTIQ; however, this decorator allows for - specifying additional flags. """ if arg is None: def inner_decorator(function): return rpc(function, flags) return inner_decorator - else: - arg.artiq_embedded = \ - _ARTIQEmbeddedInfo(core_name=None, portable=False, function=arg, syscall=None, - forbidden=False, destination=None, flags=set(flags)) - return arg - -def syscall(arg=None, flags={}): - """ - This decorator marks a function as a system call. When executed on a core - device, a C function with the provided name (or the same name as - the Python function, if not provided) will be called. When executed on - host, the Python function will be called as usual. - - Every argument and the return value must be annotated with ARTIQ types. - - Only drivers should normally define syscalls. - """ - if isinstance(arg, str): - def inner_decorator(function): - function.artiq_embedded = \ - _ARTIQEmbeddedInfo(core_name=None, portable=False, function=None, - syscall=arg, forbidden=False, destination=None, - flags=set(flags)) - return function - return inner_decorator - elif arg is None: - def inner_decorator(function): - return syscall(function.__name__, flags)(function) - return inner_decorator - else: - return syscall(arg.__name__)(arg) - -def host_only(function): - """ - This decorator marks a function so that it can only be executed - in the host Python interpreter. - """ - function.artiq_embedded = \ - _ARTIQEmbeddedInfo(core_name=None, portable=False, function=None, syscall=None, - forbidden=True, destination=None, flags={}) - return function + return arg -def kernel_from_string(parameters, body_code, decorator=kernel): - """Build a kernel function from the supplied source code in string form, - similar to ``exec()``/``eval()``. +@rpc +def print_rpc(a: T): + print(a) - Operating on pieces of source code as strings is a very brittle form of - metaprogramming; kernels generated like this are hard to debug, and - inconvenient to write. Nevertheless, this can sometimes be useful to work - around restrictions in ARTIQ Python. In that instance, care should be taken - to keep string-generated code to a minimum and cleanly separate it from - surrounding code. - The resulting function declaration is also evaluated using ``exec()`` for - use from host Python code. To encourage a modicum of code hygiene, no - global symbols are available by default; any objects accessed by the - function body must be passed in explicitly as parameters. +@nac3 +class UnwrapNoneError(Exception): + """Raised when unwrapping a none Option.""" + artiq_builtin = True - :param parameters: A list of parameter names the generated functions - accepts. Each entry can either be a string or a tuple of two strings; - if the latter, the second element specifies the type annotation. - :param body_code: The code for the function body, in string form. - ``return`` statements can be used to return values, as usual. - :param decorator: One of ``kernel`` or ``portable`` (optionally with - parameters) to specify how the function will be executed. +class Option(Generic[T]): + _nac3_option: T - :return: The function generated from the arguments. - """ + def __init__(self, v: T): + self._nac3_option = v - # Build complete function declaration. - decl = "def kernel_from_string_fn(" - for p in parameters: - type_annotation = "" - if isinstance(p, tuple): - name, typ = p - type_annotation = ": " + typ + def is_none(self): + return self._nac3_option is None + + def is_some(self): + return self._nac3_option is not None + + def unwrap(self): + if self.is_none(): + raise UnwrapNoneError + return self._nac3_option + + def __repr__(self) -> str: + if self.is_none(): + return "none" else: - name = p - decl += name + type_annotation + "," - decl += "):\n" - decl += "\n".join(" " + line for line in body_code.split("\n")) + return "Some({})".format(repr(self._nac3_option)) - # Evaluate to get host-side function declaration. - context = {} - try: - exec(decl, context) - except SyntaxError: - raise SyntaxError("Error parsing kernel function: '{}'".format(decl)) - fn = decorator(context["kernel_from_string_fn"]) + def __str__(self) -> str: + if self.is_none(): + return "none" + else: + return "Some({})".format(str(self._nac3_option)) - # Save source code for the compiler to pick up later. - fn.artiq_embedded = fn.artiq_embedded._replace(function=decl) - return fn +def Some(v: T) -> Option[T]: + return Option(v) + +none = Option(None) class _DummyTimeManager: @@ -252,7 +181,6 @@ class _DummyTimeManager: _time_manager = _DummyTimeManager() - def set_time_manager(time_manager): """Set the time manager used for simulating kernels by running them directly inside the Python interpreter. The time manager responds to the @@ -263,32 +191,48 @@ def set_time_manager(time_manager): _time_manager = time_manager -class _Sequential: - """In a sequential block, statements are executed one after another, with - the time increasing as one moves down the statement list.""" - def __enter__(self): - _time_manager.enter_sequential() - - def __exit__(self, type, value, traceback): - _time_manager.exit() -sequential = _Sequential() - - -class _Parallel: - """In a parallel block, all top-level statements start their execution at +@nac3 +class _ParallelContextManager: + """In a parallel block, all statements start their execution at the same time. - The execution time of a parallel block is the execution time of its longest statement. A parallel block may contain sequential blocks, which themselves may contain parallel blocks, etc. """ + # Those methods are not actual RPCs, but they need to be registered with + # NAC3 for the context manager to typecheck. + # In the codegen phase, NAC3ARTIQ detects those special context managers + # and takes over, without generating the RPC calling code. + @rpc def __enter__(self): _time_manager.enter_parallel() - def __exit__(self, type, value, traceback): + @rpc + def __exit__(self, *exc_info): _time_manager.exit() -parallel = _Parallel() -interleave = _Parallel() # no difference in semantics on host + +@nac3 +class _SequentialContextManager: + """In a sequential block, statements are executed one after another, with + the time increasing as one moves down the statement list.""" + @rpc + def __enter__(self): + _time_manager.enter_sequential() + + @rpc + def __exit__(self, *exc_info): + _time_manager.exit() + +parallel = _ParallelContextManager() +legacy_parallel = _ParallelContextManager() +sequential = _SequentialContextManager() + +_special_ids = { + "parallel": id(parallel), + "legacy_parallel": id(legacy_parallel), + "sequential": id(sequential), +} + def delay_mu(duration): """Increases the RTIO time by the given amount (in machine units).""" @@ -297,7 +241,6 @@ def delay_mu(duration): def now_mu(): """Retrieve the current RTIO timeline cursor, in machine units. - Note the conceptual difference between this and the current value of the hardware RTIO counter; see e.g. :meth:`artiq.coredevice.core.Core.get_rtio_counter_mu` for the latter. @@ -310,11 +253,6 @@ def at_mu(time): _time_manager.set_time_mu(time) -def delay(duration): - """Increases the RTIO time by the given amount (in seconds).""" - _time_manager.take_time(duration) - - class _DummyWatchdog: def __init__(self, timeout): pass diff --git a/artiq/language/embedding_map.py b/artiq/language/embedding_map.py new file mode 100644 index 000000000..13da0a8ca --- /dev/null +++ b/artiq/language/embedding_map.py @@ -0,0 +1,38 @@ +class EmbeddingMap: + def __init__(self): + self.object_inverse_map = {} + self.object_map = {} + self.string_map = {} + self.string_reverse_map = {} + self.function_map = {} + self.attributes_writeback = [] + + def store_function(self, key, fun): + self.function_map[key] = fun + return key + + def store_object(self, obj): + obj_id = id(obj) + if obj_id in self.object_inverse_map: + return self.object_inverse_map[obj_id] + key = len(self.object_map) + 1 + self.object_map[key] = obj + self.object_inverse_map[obj_id] = key + return key + + def store_str(self, s): + if s in self.string_reverse_map: + return self.string_reverse_map[s] + key = len(self.string_map) + self.string_map[key] = s + self.string_reverse_map[s] = key + return key + + def retrieve_function(self, key): + return self.function_map[key] + + def retrieve_object(self, key): + return self.object_map[key] + + def retrieve_str(self, key): + return self.string_map[key] diff --git a/artiq/language/import_cache.py b/artiq/language/import_cache.py new file mode 100644 index 000000000..8ccc8d6ba --- /dev/null +++ b/artiq/language/import_cache.py @@ -0,0 +1,53 @@ +""" +Caches source files on import so that inspect.getsource returns the source code that +was imported (or at least with a small race window), not what is currently on the +filesystem at the time inspect.getsource is called. +This is a hack and it still has races, and it would be better if Python supported +this correctly, but it does not. +""" + +import linecache +import tokenize +import logging + + +__all__ = ["install_hook", "add_module_to_cache"] + + +logger = logging.getLogger(__name__) + + +cache = dict() +linecache_getlines = None + + +def add_module_to_cache(module): + if hasattr(module, "__file__"): + fn = module.__file__ + if fn not in cache: + try: + with tokenize.open(fn) as fp: + lines = fp.readlines() + if lines and not lines[-1].endswith("\n"): + lines[-1] += "\n" + cache[fn] = lines + except: + logger.warning("failed to add '%s' to cache", fn, exc_info=True) + else: + logger.debug("added '%s' to cache", fn) + + +def hook_getlines(filename, module_globals=None): + if filename in cache: + return cache[filename] + else: + return linecache_getlines(filename, module_globals) + + +def install_hook(): + global linecache_getlines + + linecache_getlines = linecache.getlines + linecache.getlines = hook_getlines + + logger.debug("hook installed") diff --git a/artiq/language/types.py b/artiq/language/types.py deleted file mode 100644 index 4a186788a..000000000 --- a/artiq/language/types.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Values representing ARTIQ types, to be used in function type -annotations. -""" - -from artiq.compiler import types, builtins - -__all__ = ["TNone", "TTuple", - "TBool", "TInt32", "TInt64", "TFloat", - "TStr", "TBytes", "TByteArray", - "TList", "TArray", "TRange32", "TRange64", - "TVar"] - -TNone = builtins.TNone() -TBool = builtins.TBool() -TInt32 = builtins.TInt(types.TValue(32)) -TInt64 = builtins.TInt(types.TValue(64)) -TFloat = builtins.TFloat() -TStr = builtins.TStr() -TBytes = builtins.TBytes() -TByteArray = builtins.TByteArray() -TTuple = types.TTuple -TList = builtins.TList -TArray = builtins.TArray -TRange32 = builtins.TRange(builtins.TInt(types.TValue(32))) -TRange64 = builtins.TRange(builtins.TInt(types.TValue(64))) -TVar = types.TVar diff --git a/artiq/master/worker_impl.py b/artiq/master/worker_impl.py index d185202af..916c43728 100644 --- a/artiq/master/worker_impl.py +++ b/artiq/master/worker_impl.py @@ -22,16 +22,18 @@ from sipyco import pipe_ipc, pyon from sipyco.packed_exceptions import raise_packed_exc from sipyco.logging_tools import multiline_log_config +from nac3artiq import CompileError + import artiq from artiq import tools from artiq.master.worker_db import DeviceManager, DatasetManager, DummyDevice from artiq.language.environment import ( is_public_experiment, TraceArgumentManager, ProcessArgumentManager ) -from artiq.language.core import host_only, set_watchdog_factory, TerminationRequested -from artiq.language.types import TBool -from artiq.compiler import import_cache -from artiq.coredevice.core import CompileError, _render_diagnostic +from artiq.language.core import ( + register_content_module, set_watchdog_factory, TerminationRequested +) +from artiq.language import import_cache from artiq import __version__ as artiq_version @@ -101,19 +103,19 @@ class Scheduler: self.priority = priority pause_noexc = staticmethod(make_parent_action("pause")) - @host_only + def pause(self): if self.pause_noexc(): raise TerminationRequested _check_pause = staticmethod(make_parent_action("scheduler_check_pause")) - def check_pause(self, rid=None) -> TBool: + def check_pause(self, rid=None) -> bool: if rid is None: rid = self.rid return self._check_pause(rid) _check_termination = staticmethod(make_parent_action("scheduler_check_termination")) - def check_termination(self, rid=None) -> TBool: + def check_termination(self, rid=None) -> bool: if rid is None: rid = self.rid return self._check_termination(rid) @@ -166,6 +168,7 @@ def get_experiment_from_content(content, class_name): StringLoader(fake_filename, content) ) module = importlib.util.module_from_spec(spec) + register_content_module(module) spec.loader.exec_module(module) linecache.lazycache(fake_filename, module.__dict__) return tools.get_experiment(module, class_name) @@ -233,32 +236,6 @@ class ArgumentManager(ProcessArgumentManager): return arguments -def setup_diagnostics(experiment_file, repository_path): - def render_diagnostic(self, diagnostic): - message = "While compiling {}\n".format(experiment_file) + \ - _render_diagnostic(diagnostic, colored=False) - if repository_path is not None: - message = message.replace(repository_path, "") - - if diagnostic.level == "warning": - logging.warning(message) - else: - logging.error(message) - - # This is kind of gross, but 1) we do not have any explicit connection - # between the worker and a coredevice.core.Core instance at all, - # and 2) the diagnostic engine really ought to be per-Core, since - # that's what uses it and the repository path is per-Core. - # So I don't know how to implement this properly for now. - # - # This hack is as good or bad as any other solution that involves - # putting inherently local objects (the diagnostic engine) into - # global slots, and there isn't any point in making it prettier by - # wrapping it in layers of indirection. - artiq.coredevice.core._DiagnosticEngine.render_diagnostic = \ - render_diagnostic - - def put_completed(): put_object({"action": "completed"}) @@ -332,10 +309,8 @@ def main(): else: experiment_file = expid["file"] repository_path = None - setup_diagnostics(experiment_file, repository_path) exp = get_experiment_from_file(experiment_file, expid["class_name"]) else: - setup_diagnostics("", None) exp = get_experiment_from_content(expid["content"], expid["class_name"]) device_mgr.virtual_devices["scheduler"].set_run_info( rid, obj["pipeline_name"], expid, obj["priority"]) diff --git a/artiq/sim/devices.py b/artiq/sim/devices.py index 41b7b59c5..7704bb1a6 100644 --- a/artiq/sim/devices.py +++ b/artiq/sim/devices.py @@ -1,18 +1,18 @@ from random import Random -import numpy -from artiq.language.core import delay, at_mu, kernel +from artiq.language.core import kernel, sequential, at_mu, delay_mu from artiq.sim import time class Core: def __init__(self, dmgr): - self.ref_period = 1 + self.ref_period = 1e-6 self._level = 0 def run(self, k_function, k_args, k_kwargs): self._level += 1 - r = k_function.artiq_embedded.function(*k_args, **k_kwargs) + with sequential: + r = getattr(k_function.__self__, k_function.__name__).__wrapped__(k_function.__self__, *k_args, **k_kwargs) self._level -= 1 if self._level == 0: print(time.manager.format_timeline()) @@ -20,33 +20,35 @@ class Core: return r def seconds_to_mu(self, seconds): - return numpy.int64(seconds//self.ref_period) + return round(seconds/self.ref_period) def mu_to_seconds(self, mu): return mu*self.ref_period + def delay(self, seconds): + delay_mu(self.seconds_to_mu(seconds)) + class Input: def __init__(self, dmgr, name): self.core = dmgr.get("core") self.name = name - self.prng = Random() @kernel def gate_rising(self, duration): time.manager.event(("gate_rising", self.name, duration)) - delay(duration) + self.core.delay(duration) @kernel def gate_falling(self, duration): time.manager.event(("gate_falling", self.name, duration)) - delay(duration) + self.core.delay(duration) @kernel def gate_both(self, duration): time.manager.event(("gate_both", self.name, duration)) - delay(duration) + self.core.delay(duration) @kernel def count(self, up_to_timestamp_mu): @@ -75,7 +77,7 @@ class Output: @kernel def pulse(self, duration): time.manager.event(("pulse", self.name, duration)) - delay(duration) + self.core.delay(duration) @kernel def on(self): @@ -93,8 +95,8 @@ class WaveOutput: @kernel def pulse(self, frequency, duration): - time.manager.event(("pulse", self.name, frequency, duration)) - delay(duration) + time.manager.event(("pulse", self.name, frequency, self.core.seconds_to_mu(duration))) + self.core.delay(duration) class VoltageOutput: diff --git a/artiq/sim/time.py b/artiq/sim/time.py index 72cd3313f..d090962eb 100644 --- a/artiq/sim/time.py +++ b/artiq/sim/time.py @@ -62,7 +62,7 @@ class Manager: r = "" prev_time = 0*s for time, description in sorted(self.timeline, key=itemgetter(0)): - r += "@{:.9f} (+{:.9f}) ".format(time, time-prev_time) + r += "{:35}".format("@{:.9f} (+{:.9f}) ".format(time, time-prev_time)) for item in description: r += "{:16}".format(str(item)) r += "\n" diff --git a/artiq/test/compiler/__init__.py b/artiq/test/compiler/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/artiq/test/compiler/test_domination.py b/artiq/test/compiler/test_domination.py deleted file mode 100644 index 0c4334617..000000000 --- a/artiq/test/compiler/test_domination.py +++ /dev/null @@ -1,166 +0,0 @@ -import unittest -from artiq.compiler.analyses.domination import DominatorTree, PostDominatorTree - -class MockBasicBlock: - def __init__(self, name): - self.name = name - self._successors = [] - self._predecessors = [] - - def successors(self): - return self._successors - - def predecessors(self): - return self._predecessors - - def set_successors(self, successors): - self._successors = list(successors) - for block in self._successors: - block._predecessors.append(self) - -class MockFunction: - def __init__(self, entry, basic_blocks): - self._entry = entry - self.basic_blocks = basic_blocks - - def entry(self): - return self._entry - -def makefn(entry_name, graph): - blocks = {} - for block_name in graph: - blocks[block_name] = MockBasicBlock(block_name) - for block_name in graph: - successors = list(map(lambda name: blocks[name], graph[block_name])) - blocks[block_name].set_successors(successors) - return MockFunction(blocks[entry_name], blocks.values()) - -def dom(function, domtree): - dom = {} - for block in function.basic_blocks: - dom[block.name] = [dom_block.name for dom_block in domtree.dominators(block)] - return dom - -def idom(function, domtree): - idom = {} - for block in function.basic_blocks: - idom_block = domtree.immediate_dominator(block) - idom[block.name] = idom_block.name if idom_block else None - return idom - -class TestDominatorTree(unittest.TestCase): - def test_linear(self): - func = makefn('A', { - 'A': ['B'], - 'B': ['C'], - 'C': [] - }) - domtree = DominatorTree(func) - self.assertEqual({ - 'C': 'B', 'B': 'A', 'A': 'A' - }, idom(func, domtree)) - self.assertEqual({ - 'C': ['C', 'B', 'A'], 'B': ['B', 'A'], 'A': ['A'] - }, dom(func, domtree)) - - def test_diamond(self): - func = makefn('A', { - 'A': ['C', 'B'], - 'B': ['D'], - 'C': ['D'], - 'D': [] - }) - domtree = DominatorTree(func) - self.assertEqual({ - 'D': 'A', 'C': 'A', 'B': 'A', 'A': 'A' - }, idom(func, domtree)) - - def test_combined(self): - func = makefn('A', { - 'A': ['B', 'D'], - 'B': ['C'], - 'C': ['E'], - 'D': ['E'], - 'E': [] - }) - domtree = DominatorTree(func) - self.assertEqual({ - 'A': 'A', 'B': 'A', 'C': 'B', 'D': 'A', 'E': 'A' - }, idom(func, domtree)) - - def test_figure_2(self): - func = makefn(5, { - 5: [3, 4], - 4: [1], - 3: [2], - 2: [1], - 1: [2] - }) - domtree = DominatorTree(func) - self.assertEqual({ - 1: 5, 2: 5, 3: 5, 4: 5, 5: 5 - }, idom(func, domtree)) - - def test_figure_4(self): - func = makefn(6, { - 6: [4, 5], - 5: [1], - 4: [3, 2], - 3: [2], - 2: [1, 3], - 1: [2] - }) - domtree = DominatorTree(func) - self.assertEqual({ - 1: 6, 2: 6, 3: 6, 4: 6, 5: 6, 6: 6 - }, idom(func, domtree)) - -class TestPostDominatorTree(unittest.TestCase): - def test_linear(self): - func = makefn('A', { - 'A': ['B'], - 'B': ['C'], - 'C': [] - }) - domtree = PostDominatorTree(func) - self.assertEqual({ - 'A': 'B', 'B': 'C', 'C': None - }, idom(func, domtree)) - - def test_diamond(self): - func = makefn('A', { - 'A': ['B', 'D'], - 'B': ['C'], - 'C': ['E'], - 'D': ['E'], - 'E': [] - }) - domtree = PostDominatorTree(func) - self.assertEqual({ - 'E': None, 'D': 'E', 'C': 'E', 'B': 'C', 'A': 'E' - }, idom(func, domtree)) - - def test_multi_exit(self): - func = makefn('A', { - 'A': ['B', 'C'], - 'B': [], - 'C': [] - }) - domtree = PostDominatorTree(func) - self.assertEqual({ - 'A': None, 'B': None, 'C': None - }, idom(func, domtree)) - - def test_multi_exit_diamond(self): - func = makefn('A', { - 'A': ['B', 'C'], - 'B': ['D'], - 'C': ['D'], - 'D': ['E', 'F'], - 'E': [], - 'F': [] - }) - domtree = PostDominatorTree(func) - self.assertEqual({ - 'A': 'D', 'B': 'D', 'C': 'D', 'D': None, 'E': None, 'F': None - }, idom(func, domtree)) diff --git a/artiq/test/coredevice/test_analyzer.py b/artiq/test/coredevice/test_analyzer.py index cf8a64540..31e08bce8 100644 --- a/artiq/test/coredevice/test_analyzer.py +++ b/artiq/test/coredevice/test_analyzer.py @@ -1,11 +1,22 @@ +import unittest + +from numpy import int64 + from artiq.experiment import * from artiq.coredevice.comm_analyzer import (decode_dump, StoppedMessage, OutputMessage, InputMessage, _extract_log_chars, get_analyzer_dump) +from artiq.coredevice.core import Core +from artiq.coredevice.ttl import TTLOut, TTLInOut from artiq.test.hardware_testbench import ExperimentCase +@nac3 class CreateTTLPulse(EnvExperiment): + core: KernelInvariant[Core] + loop_in: KernelInvariant[TTLInOut] + loop_out: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("loop_in") @@ -16,7 +27,7 @@ class CreateTTLPulse(EnvExperiment): self.core.reset() self.loop_in.input() self.loop_out.output() - delay(1*us) + self.core.delay(1.*us) self.loop_out.off() @kernel @@ -24,12 +35,15 @@ class CreateTTLPulse(EnvExperiment): self.core.break_realtime() with parallel: with sequential: - delay_mu(100) - self.loop_out.pulse_mu(1000) - self.loop_in.count(self.loop_in.gate_both_mu(1200)) + delay_mu(int64(100)) + self.loop_out.pulse_mu(int64(1000)) + self.loop_in.count(self.loop_in.gate_both_mu(int64(1200))) +@nac3 class WriteLog(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") diff --git a/artiq/test/coredevice/test_cache.py b/artiq/test/coredevice/test_cache.py index 07452cf07..3574711d0 100644 --- a/artiq/test/coredevice/test_cache.py +++ b/artiq/test/coredevice/test_cache.py @@ -1,28 +1,38 @@ +import unittest + +from numpy import int32 + from artiq.experiment import * +from artiq.coredevice.core import Core +from artiq.coredevice.cache import CoreCache from artiq.coredevice.exceptions import CacheError -from artiq.compiler.targets import CortexA9Target from artiq.test.hardware_testbench import ExperimentCase +# NAC3TODO @nac3 class _Cache(EnvExperiment): + core: KernelInvariant[Core] + core_cache: KernelInvariant[CoreCache] + def build(self): self.setattr_device("core") self.setattr_device("core_cache") @kernel - def get(self, key): + def get(self, key: str) -> list[int32]: return self.core_cache.get(key) @kernel - def put(self, key, value): + def put(self, key: str, value: list[int32]): self.core_cache.put(key, value) @kernel - def get_put(self, key, value): + def get_put(self, key: str, value: list[int32]): self.get(key) self.put(key, value) +@unittest.skip("NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/299") class CacheTest(ExperimentCase): def test_get_empty(self): exp = self.create(_Cache) @@ -41,7 +51,7 @@ class CacheTest(ExperimentCase): def test_borrow(self): exp = self.create(_Cache) - if exp.core.target_cls == CortexA9Target: + if exp.core.target == "cortexa9": self.skipTest("Zynq port memory management does not need CacheError") exp.put("x4", [1, 2, 3]) with self.assertRaises(CacheError): diff --git a/artiq/test/coredevice/test_compile.py b/artiq/test/coredevice/test_compile.py index 9b97c7968..a1a02bef5 100644 --- a/artiq/test/coredevice/test_compile.py +++ b/artiq/test/coredevice/test_compile.py @@ -3,6 +3,8 @@ import sys import subprocess import unittest import tempfile + +from artiq.coredevice.core import Core from artiq.coredevice.comm_mgmt import CommMgmt from artiq.test.hardware_testbench import ExperimentCase from artiq.experiment import * @@ -11,7 +13,16 @@ from artiq.experiment import * artiq_root = os.getenv("ARTIQ_ROOT") +# NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/297 +@extern +def core_log(s: str): + ... + + +@nac3 class CheckLog(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") @@ -20,28 +31,7 @@ class CheckLog(EnvExperiment): core_log("test_artiq_compile") - -class _Precompile(EnvExperiment): - def build(self): - self.setattr_device("core") - self.x = 1 - self.y = 2 - self.z = 3 - - def set_attr(self, value): - self.x = value - - @kernel - def the_kernel(self, arg): - self.set_attr(arg + self.y) - self.z = 23 - - def run(self): - precompiled = self.core.precompile(self.the_kernel, 40) - self.y = 0 - precompiled() - - +@unittest.skip("NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/300") class TestCompile(ExperimentCase): def test_compile(self): core_addr = self.device_mgr.get_desc("core")["arguments"]["host"] @@ -56,9 +46,3 @@ class TestCompile(ExperimentCase): log = mgmt.get_log() self.assertIn("test_artiq_compile", log) mgmt.close() - - def test_precompile(self): - exp = self.create(_Precompile) - exp.run() - self.assertEqual(exp.x, 42) - self.assertEqual(exp.z, 3) diff --git a/artiq/test/coredevice/test_edge_counter.py b/artiq/test/coredevice/test_edge_counter.py index 1199af6a5..8152a3922 100644 --- a/artiq/test/coredevice/test_edge_counter.py +++ b/artiq/test/coredevice/test_edge_counter.py @@ -1,43 +1,54 @@ +from numpy import int32, int64 + from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase +from artiq.coredevice.core import Core +from artiq.coredevice.ttl import TTLOut +from artiq.coredevice.edge_counter import EdgeCounter +@nac3 class EdgeCounterExp(EnvExperiment): + core: KernelInvariant[Core] + loop_in_counter: KernelInvariant[EdgeCounter] + loop_out: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("loop_in_counter") self.setattr_device("loop_out") - @kernel - def count_pulse_edges(self, gate_fn): + # NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/461 + # NAC3TODO @kernel + def count_pulse_edges(self, gate_fn) -> tuple[int32, int32]: self.core.break_realtime() with parallel: with sequential: - delay(5 * us) - self.loop_out.pulse(10 * us) + self.core.delay(5. * us) + self.loop_out.pulse(10. * us) with sequential: - gate_fn(10 * us) - delay(1 * us) + gate_fn(10. * us) + self.core.delay(1. * us) gate_fn(10 * us) return (self.loop_in_counter.fetch_count(), self.loop_in_counter.fetch_count()) @kernel - def timeout_timestamp(self): + def timeout_timestamp(self) -> int64: self.core.break_realtime() timestamp_mu, _ = self.loop_in_counter.fetch_timestamped_count( now_mu()) return timestamp_mu @kernel - def gate_relative_timestamp(self): + def gate_relative_timestamp(self) -> int64: self.core.break_realtime() - gate_end_mu = self.loop_in_counter.gate_rising(1 * us) + gate_end_mu = self.loop_in_counter.gate_rising(1. * us) timestamp_mu, _ = self.loop_in_counter.fetch_timestamped_count() return timestamp_mu - gate_end_mu @kernel - def many_pulses_split(self, num_pulses): + def many_pulses_split(self, num_pulses: int32) -> tuple[int32, int32]: self.core.break_realtime() self.loop_in_counter.set_config( @@ -47,8 +58,8 @@ class EdgeCounterExp(EnvExperiment): reset_to_zero=True) for _ in range(num_pulses): - self.loop_out.pulse(5 * us) - delay(5 * us) + self.loop_out.pulse(5. * us) + self.core.delay(5. * us) self.loop_in_counter.set_config( count_rising=True, @@ -57,8 +68,8 @@ class EdgeCounterExp(EnvExperiment): reset_to_zero=False) for _ in range(num_pulses): - self.loop_out.pulse(5 * us) - delay(5 * us) + self.loop_out.pulse(5. * us) + self.core.delay(5. * us) self.loop_in_counter.set_config( count_rising=False, diff --git a/artiq/test/coredevice/test_embedding.py b/artiq/test/coredevice/test_embedding.py index 149f3caef..a503e8c08 100644 --- a/artiq/test/coredevice/test_embedding.py +++ b/artiq/test/coredevice/test_embedding.py @@ -1,10 +1,14 @@ -import numpy import unittest from time import sleep +from typing import Literal + +import numpy +from numpy import int32, int64, ndarray from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase from artiq.coredevice.comm_kernel import RPCReturnValueError +from artiq.coredevice.core import Core class _Roundtrip(EnvExperiment): @@ -15,7 +19,7 @@ class _Roundtrip(EnvExperiment): def roundtrip(self, obj, fn): fn(obj) - +@unittest.skip("NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/461") class RoundtripTest(ExperimentCase): def assertRoundtrip(self, obj): exp = self.create(_Roundtrip) @@ -43,8 +47,8 @@ class RoundtripTest(ExperimentCase): self.assertRoundtrip(numpy.False_) def test_int(self): - self.assertRoundtrip(numpy.int32(42)) - self.assertRoundtrip(numpy.int64(42)) + self.assertRoundtrip(int32(42)) + self.assertRoundtrip(int64(42)) def test_float(self): self.assertRoundtrip(42.0) @@ -65,7 +69,7 @@ class RoundtripTest(ExperimentCase): self.assertRoundtrip([True, False]) def test_int64_list(self): - self.assertRoundtrip([numpy.int64(0), numpy.int64(1)]) + self.assertRoundtrip([int64(0), int64(1)]) def test_object(self): obj = object() @@ -88,12 +92,12 @@ class RoundtripTest(ExperimentCase): def test_array_1d(self): self.assertArrayRoundtrip(numpy.array([True, False])) - self.assertArrayRoundtrip(numpy.array([1, 2, 3], dtype=numpy.int32)) + self.assertArrayRoundtrip(numpy.array([1, 2, 3], dtype=int32)) self.assertArrayRoundtrip(numpy.array([1.0, 2.0, 3.0])) self.assertArrayRoundtrip(numpy.array(["a", "b", "c"])) def test_array_2d(self): - self.assertArrayRoundtrip(numpy.array([[1, 2], [3, 4]], dtype=numpy.int32)) + self.assertArrayRoundtrip(numpy.array([[1, 2], [3, 4]], dtype=int32)) self.assertArrayRoundtrip(numpy.array([[1.0, 2.0], [3.0, 4.0]])) self.assertArrayRoundtrip(numpy.array([["a", "b"], ["c", "d"]])) @@ -105,64 +109,74 @@ class RoundtripTest(ExperimentCase): self.assertArrayRoundtrip(numpy.array([[1, 2], [3]], dtype=object)) +@nac3 class _DefaultArg(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") - def test(self, foo=42) -> TInt32: + @rpc + def test(self, foo: int32 = 42) -> int32: return foo + # NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/101 @kernel - def run(self): + def run(self) -> int32: return self.test() class DefaultArgTest(ExperimentCase): + @unittest.skip("NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/528") def test_default_arg(self): exp = self.create(_DefaultArg) self.assertEqual(exp.run(), 42) +@nac3 class _RPCTypes(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") - def return_bool(self) -> TBool: + @rpc + def return_bool(self) -> bool: return True - def return_int32(self) -> TInt32: + @rpc + def return_int32(self) -> int32: return 1 - def return_int64(self) -> TInt64: + @rpc + def return_int64(self) -> int64: return 0x100000000 - def return_float(self) -> TFloat: + @rpc + def return_float(self) -> float: return 1.0 - def return_str(self) -> TStr: + @rpc + def return_str(self) -> str: return "foo" - def return_bytes(self) -> TBytes: - return b"foo" - - def return_bytearray(self) -> TByteArray: - return bytearray(b"foo") - - def return_tuple(self) -> TTuple([TInt32, TInt32]): + @rpc + def return_tuple(self) -> tuple[int32, int32]: return (1, 2) - def return_list(self) -> TList(TInt32): + @rpc + def return_list(self) -> list[int32]: return [2, 3] - def return_range(self) -> TRange32: - return range(10) - - def return_array(self) -> TArray(TInt32): + @rpc + def return_array(self) -> ndarray[int32, Literal[1]]: return numpy.array([1, 2]) - def return_matrix(self) -> TArray(TInt32, 2): + @rpc + def return_matrix(self) -> ndarray[int32, Literal[2]]: return numpy.array([[1, 2], [3, 4]]) + @rpc def return_mismatch(self): return b"foo" @@ -173,28 +187,21 @@ class _RPCTypes(EnvExperiment): core_log(self.return_int64()) core_log(self.return_float()) core_log(self.return_str()) - core_log(self.return_bytes()) - core_log(self.return_bytearray()) core_log(self.return_tuple()) core_log(self.return_list()) - core_log(self.return_range()) core_log(self.return_array()) core_log(self.return_matrix()) def accept(self, value): pass - @kernel + # NAC3TODO @kernel def run_send(self): self.accept(True) self.accept(1) self.accept(0x100000000) self.accept(1.0) self.accept("foo") - self.accept(b"foo") - self.accept(bytearray(b"foo")) - self.accept(bytes([1, 2])) - self.accept(bytearray([1, 2])) self.accept((2, 3)) self.accept([1, 2]) self.accept(range(10)) @@ -222,15 +229,18 @@ class RPCTypesTest(ExperimentCase): exp.run_mismatch() +# NAC3TODO class _RPCCalls(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") - self._list_int64 = [numpy.int64(1)] + self._list_int64 = [int64(1)] - def args(self, *args) -> TInt32: + def args(self, *args) -> int32: return len(args) - def kwargs(self, x="", **kwargs) -> TInt32: + def kwargs(self, x="", **kwargs) -> int32: return len(kwargs) @kernel @@ -267,7 +277,7 @@ class _RPCCalls(EnvExperiment): @kernel def numpy_things(self): - return (numpy.int32(10), numpy.int64(20)) + return (int32(10), int64(20)) @kernel def builtin(self): @@ -286,6 +296,7 @@ class _RPCCalls(EnvExperiment): class RPCCallsTest(ExperimentCase): + @unittest.skip("NAC3TODO") def test_args(self): exp = self.create(_RPCCalls) self.assertEqual(exp.args0(), 0) @@ -296,40 +307,47 @@ class RPCCallsTest(ExperimentCase): self.assertEqual(exp.kwargs2(), 2) self.assertEqual(exp.args1kwargs2(), 2) self.assertEqual(exp.numpy_things(), - (numpy.int32(10), numpy.int64(20))) + (int32(10), 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.assertEqual(list_int64, [int64(1)]) + self.assertTrue(isinstance(list_int64[0], int64)) exp.builtin() exp.async_in_try() +@nac3 class _Annotation(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") @kernel - def overflow(self, x: TInt64) -> TBool: - return (x << 32) != 0 + def overflow(self, x: int64) -> bool: + return (x << 32) != int64(0) @kernel - def monomorphize(self, x: TList(TInt32)): + def monomorphize(self, x: list[int32]): pass class AnnotationTest(ExperimentCase): def test_annotation(self): exp = self.create(_Annotation) - self.assertEqual(exp.overflow(1), True) + self.assertEqual(exp.overflow(int64(1)), True) exp.monomorphize([]) + +@nac3 class _Async(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") @rpc(flags={"async"}) - def recv_async(self, data): + def recv_async(self, data: list[int32]): pass @kernel @@ -346,11 +364,15 @@ class AsyncTest(ExperimentCase): exp.run() +@nac3 class _Payload1MB(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") - def devnull(self, d): + @rpc + def devnull(self, d: list[int32]): pass @kernel @@ -365,7 +387,10 @@ class LargePayloadTest(ExperimentCase): exp.run() +@nac3 class _ListTuple(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") @@ -383,27 +408,34 @@ class _ListTuple(EnvExperiment): self.verify(d) @kernel - def verify(self, data): + def verify(self, data: list[int32]): for i in range(len(data)): if data[i] != data[0] + i: raise ValueError - def get_num_iters(self) -> TInt32: + @rpc + def get_num_iters(self) -> int32: return 2 - def get_values(self, base_a, base_b, n) -> TTuple([TList(TInt32), TList(TInt32)]): - return [numpy.int32(base_a + i) for i in range(n)], \ - [numpy.int32(base_b + i) for i in range(n)] + @rpc + def get_values(self, base_a: int32, base_b: int32, n: int32) -> tuple[list[int32], list[int32]]: + return [int32(base_a + i) for i in range(n)], \ + [int32(base_b + i) for i in range(n)] +@nac3 class _NestedTupleList(EnvExperiment): + core: KernelInvariant[Core] + data: KernelInvariant[list[tuple[int32, list[tuple[str, list[float], list[int32]]]]]] + def build(self): self.setattr_device("core") self.data = [(0x12345678, [("foo", [0.0, 1.0], [2, 3])]), (0x76543210, [("bar", [4.0, 5.0], [6, 7])])] - def get_data(self) -> TList(TTuple( - [TInt32, TList(TTuple([TStr, TList(TFloat), TList(TInt32)]))])): + @rpc + def get_data(self) -> list[tuple + [int32, list[tuple[str, list[float], list[int32]]]]]: return self.data @kernel @@ -413,11 +445,15 @@ class _NestedTupleList(EnvExperiment): raise ValueError +@nac3 class _EmptyList(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") - def get_empty(self) -> TList(TInt32): + @rpc + def get_empty(self) -> list[int32]: return [] @kernel @@ -428,6 +464,7 @@ class _EmptyList(EnvExperiment): class ListTupleTest(ExperimentCase): + @unittest.skip("NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/529") def test_list_tuple(self): self.create(_ListTuple).run() @@ -438,11 +475,18 @@ class ListTupleTest(ExperimentCase): self.create(_EmptyList).run() +@nac3 class _ArrayQuoting(EnvExperiment): + core: KernelInvariant[Core] + vec_i32: KernelInvariant[ndarray[int32, Literal[1]]] + mat_i64: KernelInvariant[ndarray[int64, Literal[2]]] + arr_f64: KernelInvariant[ndarray[float, Literal[3]]] + strs: KernelInvariant[ndarray[str, Literal[1]]] + def build(self): self.setattr_device("core") - self.vec_i32 = numpy.array([0, 1], dtype=numpy.int32) - self.mat_i64 = numpy.array([[0, 1], [2, 3]], dtype=numpy.int64) + self.vec_i32 = numpy.array([0, 1], dtype=int32) + self.mat_i64 = numpy.array([[0, 1], [2, 3]], dtype=int64) self.arr_f64 = numpy.array([[[0.0, 1.0], [2.0, 3.0]], [[4.0, 5.0], [6.0, 7.0]]]) self.strs = numpy.array(["foo", "bar"]) @@ -452,10 +496,10 @@ class _ArrayQuoting(EnvExperiment): assert self.vec_i32[0] == 0 assert self.vec_i32[1] == 1 - assert self.mat_i64[0, 0] == 0 - assert self.mat_i64[0, 1] == 1 - assert self.mat_i64[1, 0] == 2 - assert self.mat_i64[1, 1] == 3 + assert self.mat_i64[0, 0] == int64(0) + assert self.mat_i64[0, 1] == int64(1) + assert self.mat_i64[1, 0] == int64(2) + assert self.mat_i64[1, 1] == int64(3) assert self.arr_f64[0, 0, 0] == 0.0 assert self.arr_f64[0, 0, 1] == 1.0 @@ -466,8 +510,9 @@ class _ArrayQuoting(EnvExperiment): assert self.arr_f64[1, 1, 0] == 6.0 assert self.arr_f64[1, 1, 1] == 7.0 - assert self.strs[0] == "foo" - assert self.strs[1] == "bar" + # NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/421 + #assert self.strs[0] == "foo" + #assert self.strs[1] == "bar" class ArrayQuotingTest(ExperimentCase): @@ -475,20 +520,24 @@ class ArrayQuotingTest(ExperimentCase): self.create(_ArrayQuoting).run() +@nac3 class _Assert(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") @kernel - def check(self, value): + def check(self, value: bool): assert value @kernel - def check_msg(self, value): + def check_msg(self, value: bool): assert value, "foo" class AssertTest(ExperimentCase): + @unittest.skip("NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/530") def test_assert(self): exp = self.create(_Assert) @@ -503,7 +552,12 @@ class AssertTest(ExperimentCase): check_fail(lambda: exp.check_msg(False), "foo") +@nac3 class _NumpyBool(EnvExperiment): + core: KernelInvariant[Core] + np_true: KernelInvariant[bool] + np_false: KernelInvariant[bool] + def build(self): self.setattr_device("core") self.np_true = numpy.True_ @@ -523,7 +577,16 @@ class NumpyBoolTest(ExperimentCase): self.create(_NumpyBool).run() +@nac3 class _Alignment(EnvExperiment): + core: KernelInvariant[Core] + a: KernelInvariant[bool] + b: KernelInvariant[float] + c: KernelInvariant[bool] + d: KernelInvariant[bool] + e: KernelInvariant[float] + f: KernelInvariant[bool] + def build(self): self.setattr_device("core") self.a = False @@ -534,7 +597,7 @@ class _Alignment(EnvExperiment): self.f = False @rpc - def get_tuples(self) -> TList(TTuple([TBool, TFloat, TBool])): + def get_tuples(self) -> list[tuple[bool, float, bool]]: return [(self.a, self.b, self.c), (self.d, self.e, self.f)] @kernel @@ -559,14 +622,17 @@ class AlignmentTest(ExperimentCase): self.create(_Alignment).run() +@nac3 class _NumpyQuoting(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") @kernel def run(self): - a = numpy.array([10, 20]) - b = numpy.sqrt(4.0) + a = np_array([10, 20]) + b = np_sqrt(4.0) class NumpyQuotingTest(ExperimentCase): @@ -575,20 +641,27 @@ class NumpyQuotingTest(ExperimentCase): self.create(_NumpyQuoting).run() +@nac3 class _IntBoundary(EnvExperiment): + core: KernelInvariant[Core] + int32_min: KernelInvariant[int32] + int32_max: KernelInvariant[int32] + int64_min: KernelInvariant[int64] + int64_max: KernelInvariant[int64] + def build(self): self.setattr_device("core") - self.int32_min = numpy.iinfo(numpy.int32).min - self.int32_max = numpy.iinfo(numpy.int32).max - self.int64_min = numpy.iinfo(numpy.int64).min - self.int64_max = numpy.iinfo(numpy.int64).max + self.int32_min = numpy.iinfo(int32).min + self.int32_max = numpy.iinfo(int32).max + self.int64_min = int64(numpy.iinfo(int64).min) + self.int64_max = int64(numpy.iinfo(int64).max) @kernel - def test_int32_bounds(self, min_val: TInt32, max_val: TInt32): + def test_int32_bounds(self, min_val: int32, max_val: int32) -> bool: return min_val == self.int32_min and max_val == self.int32_max @kernel - def test_int64_bounds(self, min_val: TInt64, max_val: TInt64): + def test_int64_bounds(self, min_val: int64, max_val: int64) -> bool: return min_val == self.int64_min and max_val == self.int64_max @kernel diff --git a/artiq/test/coredevice/test_exceptions.py b/artiq/test/coredevice/test_exceptions.py index ac5292976..b3ea5db91 100644 --- a/artiq/test/coredevice/test_exceptions.py +++ b/artiq/test/coredevice/test_exceptions.py @@ -3,8 +3,9 @@ import re from artiq.experiment import * from artiq.master.worker_db import DeviceError from artiq.test.hardware_testbench import ExperimentCase -from artiq.compiler.embedding import EmbeddingMap +from artiq.language.embedding_map import EmbeddingMap from artiq.coredevice.core import test_exception_id_sync +<<<<<<< HEAD import artiq.coredevice.exceptions as exceptions @@ -96,6 +97,9 @@ class ExceptionFormatTest(ExperimentCase): with self.assertRaisesRegex(RTIOUnderflow, RTIO_UNDERFLOW_PATTERN): self.execute(KernelRTIOUnderflow) +======= +from numpy import int32 +>>>>>>> upstream/nac3 """ Test sync in exceptions raised between host and kernel @@ -107,33 +111,39 @@ Considers the following two cases: Ensures same exception is raised on both kernel and host in either case """ -exception_names = EmbeddingMap().str_reverse_map +exception_names = EmbeddingMap().string_map +@nac3 class _TestExceptionSync(EnvExperiment): def build(self): self.setattr_device("core") @rpc - def _raise_exception_host(self, id): + def _raise_exception_host(self, id: int32): exn = exception_names[id].split('.')[-1].split(':')[-1] exn = getattr(exceptions, exn) raise exn @kernel - def raise_exception_host(self, id): + def raise_exception_host(self, id: int32): self._raise_exception_host(id) @kernel - def raise_exception_kernel(self, id): + def raise_exception_kernel(self, id: int32): test_exception_id_sync(id) +<<<<<<< HEAD class ExceptionSyncTest(ExperimentCase): +======= + +class ExceptionTest(ExperimentCase): +>>>>>>> upstream/nac3 def test_raise_exceptions_kernel(self): exp = self.create(_TestExceptionSync) - for id, name in list(exception_names.items())[::-1]: + for id, name in exception_names.items(): name = name.split('.')[-1].split(':')[-1] with self.assertRaises(getattr(exceptions, name)) as ctx: exp.raise_exception_kernel(id) diff --git a/artiq/test/coredevice/test_i2c.py b/artiq/test/coredevice/test_i2c.py index e8293db9d..3282485f0 100644 --- a/artiq/test/coredevice/test_i2c.py +++ b/artiq/test/coredevice/test_i2c.py @@ -3,14 +3,23 @@ import os, unittest from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase from artiq.coredevice.exceptions import I2CError +from artiq.coredevice.core import Core from artiq.coredevice.i2c import I2CSwitch, i2c_read_byte +@nac3 class I2CSwitchTest(EnvExperiment): + core: KernelInvariant[Core] + i2c_switch: KernelInvariant[I2CSwitch] + def build(self): self.setattr_device("core") self.setattr_device("i2c_switch") + @rpc + def set_passed(self, passed: bool): + self.set_dataset("passed", passed) + @kernel def run(self): passed = True @@ -20,10 +29,14 @@ class I2CSwitchTest(EnvExperiment): # otherwise we cannot guarantee exact readback values if i2c_read_byte(self.i2c_switch.busno, self.i2c_switch.address) != 1 << i: passed = False - self.set_dataset("passed", passed) + self.set_passed(passed) +@nac3 class NonexistentI2CBus(EnvExperiment): + core: KernelInvariant[Core] + broken_switch: KernelInvariant[I2CSwitch] + def build(self): self.setattr_device("core") self.setattr_device("i2c_switch") # HACK: only run this test on boards with I2C diff --git a/artiq/test/coredevice/test_numpy.py b/artiq/test/coredevice/test_numpy.py deleted file mode 100644 index 7fe56f265..000000000 --- a/artiq/test/coredevice/test_numpy.py +++ /dev/null @@ -1,157 +0,0 @@ -from artiq.experiment import * -import numpy -import scipy.special -from artiq.test.hardware_testbench import ExperimentCase -from artiq.compiler.targets import CortexA9Target -from artiq.compiler import math_fns - - -class _RunOnDevice(EnvExperiment): - def build(self): - self.setattr_device("core") - - @kernel - def run_on_kernel_unary(self, a, callback, numpy, scipy): - self.run(a, callback, numpy, scipy) - - @kernel - def run_on_kernel_binary(self, a, b, callback, numpy, scipy): - self.run(a, b, callback, numpy, scipy) - - -# Binary operations supported for scalars and arrays of any dimension, including -# broadcasting. -ELEM_WISE_BINOPS = ["+", "*", "//", "%", "**", "-", "/"] - - -class CompareHostDeviceTest(ExperimentCase): - def _test_binop(self, op, a, b): - exp = self.create(_RunOnDevice) - exp.run = kernel_from_string(["a", "b", "callback", "numpy", "scipy"], - "callback(" + op + ")", - decorator=portable) - checked = False - - def with_host_result(host): - def with_both_results(device): - nonlocal checked - checked = True - self.assertTrue( - numpy.allclose(host, device, equal_nan=True), - "Discrepancy in binop test for '{}': Expexcted ({}, {}) -> {}, got {}" - .format(op, a, b, host, device)) - - exp.run_on_kernel_binary(a, b, with_both_results, numpy, scipy) - - exp.run(a, b, with_host_result, numpy, scipy) - self.assertTrue(checked, "Test did not run") - - def _test_unaryop(self, op, a): - exp = self.create(_RunOnDevice) - exp.run = kernel_from_string(["a", "callback", "numpy", "scipy"], - "callback(" + op + ")", - decorator=portable) - checked = False - - def with_host_result(host): - def with_both_results(device): - nonlocal checked - checked = True - self.assertTrue( - numpy.allclose(host, device, equal_nan=True), - "Discrepancy in unaryop test for '{}': Expexcted {} -> {}, got {}" - .format(op, a, host, device)) - - exp.run_on_kernel_unary(a, with_both_results, numpy, scipy) - - exp.run(a, with_host_result, numpy, scipy) - self.assertTrue(checked, "Test did not run") - - def test_scalar_scalar_binops(self): - # Some arbitrarily chosen arguments of different types. Could be turned into - # randomised tests instead. - # TODO: Provoke overflows, division by zero, etc., and compare results. - args = [(typ(a), typ(b)) for a, b in [(0, 1), (3, 2), (11, 6)] - for typ in [numpy.int32, numpy.int64, numpy.float64]] - for op in ELEM_WISE_BINOPS: - for arg in args: - self._test_binop("a" + op + "b", *arg) - - def test_scalar_matrix_binops(self): - for typ in [numpy.int32, numpy.int64, numpy.float64]: - scalar = typ(3) - matrix = numpy.array([[4, 5, 6], [7, 8, 9]], dtype=typ) - for op in ELEM_WISE_BINOPS: - code = "a" + op + "b" - self._test_binop(code, scalar, matrix) - self._test_binop(code, matrix, scalar) - self._test_binop(code, matrix, matrix) - - def test_matrix_mult(self): - for typ in [numpy.int32, numpy.int64, numpy.float64]: - mat_a = numpy.array([[1, 2, 3], [4, 5, 6]], dtype=typ) - mat_b = numpy.array([[7, 8], [9, 10], [11, 12]], dtype=typ) - self._test_binop("a @ b", mat_a, mat_b) - - def test_unary_math_fns(self): - names = [ - a for a, _ in math_fns.unary_fp_intrinsics + math_fns.unary_fp_runtime_calls - ] - exp = self.create(_RunOnDevice) - if exp.core.target_cls != CortexA9Target: - names.remove("exp2") - names.remove("log2") - names.remove("trunc") - for name in names: - op = "numpy.{}(a)".format(name) - # Avoid 0.5, as numpy.rint's rounding mode currently doesn't match. - self._test_unaryop(op, 0.51) - self._test_unaryop(op, numpy.array([[0.3, 0.4], [0.51, 0.6]])) - - def test_unary_scipy_fns(self): - names = [name for name, _ in math_fns.scipy_special_unary_runtime_calls] - if self.create(_RunOnDevice).core.target_cls != CortexA9Target: - names.remove("gamma") - for name in names: - op = "scipy.special.{}(a)".format(name) - self._test_unaryop(op, 0.5) - self._test_unaryop(op, numpy.array([[0.3, 0.4], [0.5, 0.6]])) - - def test_binary_math_fns(self): - names = [name for name, _ in math_fns.binary_fp_runtime_calls] - exp = self.create(_RunOnDevice) - if exp.core.target_cls != CortexA9Target: - names.remove("fmax") - names.remove("fmin") - for name in names: - code = "numpy.{}(a, b)".format(name) - # Avoid 0.5, as numpy.rint's rounding mode currently doesn't match. - self._test_binop(code, 1.0, 2.0) - self._test_binop(code, 1.0, numpy.array([2.0, 3.0])) - self._test_binop(code, numpy.array([1.0, 2.0]), 3.0) - self._test_binop(code, numpy.array([1.0, 2.0]), numpy.array([3.0, 4.0])) - - -class _MatrixMult(EnvExperiment): - """Regression test for GitHub #1578 (ICE when mixing different matrix multiplication - types in one kernel). - """ - def build(self): - self.setattr_device("core") - self.imat = numpy.arange(4, dtype=numpy.int64).reshape((2, 2)) - self.fmat = numpy.arange(4, dtype=numpy.float64).reshape((2, 2)) - - @kernel - def run(self): - self.verify(self.imat, self.imat, self.imat @ self.imat) - self.verify(self.imat, self.fmat, self.imat @ self.fmat) - self.verify(self.fmat, self.imat, self.fmat @ self.imat) - self.verify(self.fmat, self.fmat, self.fmat @ self.fmat) - - def verify(self, a, b, ab): - if not numpy.allclose(a @ b, ab): - raise ValueError("Mismatch for {} @ {}", a, b) - -class TestMatrixMult(ExperimentCase): - def test_multiple_matrix_mult_types(self): - self.create(_MatrixMult).run() diff --git a/artiq/test/coredevice/test_performance.py b/artiq/test/coredevice/test_performance.py index 3ca1f86ba..2f3bc17e4 100644 --- a/artiq/test/coredevice/test_performance.py +++ b/artiq/test/coredevice/test_performance.py @@ -1,30 +1,39 @@ import os import time import unittest +from typing import Literal + import numpy +from numpy import int32, float64, ndarray from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase +from artiq.coredevice.core import Core + + +bool_list_large = [True] * (1 << 20) +bool_list_small = [True] * (1 << 10) # large: 1MB payload # small: 1KB payload -bytes_large = b"\x00" * (1 << 20) -bytes_small = b"\x00" * (1 << 10) - list_large = [123] * (1 << 18) list_small = [123] * (1 << 8) -array_large = numpy.array(list_large, numpy.int32) -array_small = numpy.array(list_small, numpy.int32) - -byte_list_large = [True] * (1 << 20) -byte_list_small = [True] * (1 << 10) +array_large = numpy.array(list_large, int32) +array_small = numpy.array(list_small, int32) received_bytes = 0 time_start = 0 time_end = 0 + +@nac3 class _Transfer(EnvExperiment): + core: KernelInvariant[Core] + count: KernelInvariant[int32] + h2d: Kernel[list[float]] + d2h: Kernel[list[float]] + def build(self): self.setattr_device("core") self.count = 10 @@ -32,120 +41,93 @@ class _Transfer(EnvExperiment): self.d2h = [0.0] * self.count @rpc - def get_bytes(self, large: TBool) -> TBytes: - if large: - return bytes_large - else: - return bytes_small - - @rpc - def get_list(self, large: TBool) -> TList(TInt32): + def get_list(self, large: bool) -> list[int32]: if large: return list_large else: return list_small @rpc - def get_byte_list(self, large: TBool) -> TList(TBool): + def get_bool_list(self, large: bool) -> list[bool]: if large: - return byte_list_large + return bool_list_large else: - return byte_list_small + return bool_list_small @rpc - def get_array(self, large: TBool) -> TArray(TInt32): + def get_array(self, large: bool) -> ndarray[int32, Literal[1]]: if large: return array_large else: return array_small @rpc - def get_string_list(self) -> TList(TStr): + def get_string_list(self) -> list[str]: return string_list @rpc - def sink(self, data): + def sink_bool_list(self, data: list[bool]): + pass + + @rpc + def sink_list(self, data: list[int32]): + pass + + @rpc + def sink_array(self, data: ndarray[int32, Literal[1]]): pass @rpc(flags={"async"}) - def sink_async(self, data): + def sink_async(self, data: list[int32]): global received_bytes, time_start, time_end if received_bytes == 0: time_start = time.time() - received_bytes += len(data) + received_bytes += 4*len(data) if received_bytes == (1024 ** 2)*128: time_end = time.time() - @rpc - def get_async_throughput(self) -> TFloat: + def get_async_throughput(self) -> float: return 128.0 / (time_end - time_start) @kernel - def test_bytes(self, large): - def inner(): + def test_bool_list(self, large: bool): + for i in range(self.count): t0 = self.core.get_rtio_counter_mu() - data = self.get_bytes(large) + data = self.get_bool_list(large) t1 = self.core.get_rtio_counter_mu() - self.sink(data) + self.sink_bool_list(data) t2 = self.core.get_rtio_counter_mu() self.h2d[i] = self.core.mu_to_seconds(t1 - t0) self.d2h[i] = self.core.mu_to_seconds(t2 - t1) - for i in range(self.count): - inner() - return (self.h2d, self.d2h) - @kernel - def test_byte_list(self, large): - def inner(): - t0 = self.core.get_rtio_counter_mu() - data = self.get_byte_list(large) - t1 = self.core.get_rtio_counter_mu() - self.sink(data) - t2 = self.core.get_rtio_counter_mu() - self.h2d[i] = self.core.mu_to_seconds(t1 - t0) - self.d2h[i] = self.core.mu_to_seconds(t2 - t1) - + def test_list(self, large: bool): for i in range(self.count): - inner() - return (self.h2d, self.d2h) - - @kernel - def test_list(self, large): - def inner(): t0 = self.core.get_rtio_counter_mu() data = self.get_list(large) t1 = self.core.get_rtio_counter_mu() - self.sink(data) + self.sink_list(data) t2 = self.core.get_rtio_counter_mu() self.h2d[i] = self.core.mu_to_seconds(t1 - t0) self.d2h[i] = self.core.mu_to_seconds(t2 - t1) - for i in range(self.count): - inner() - return (self.h2d, self.d2h) - @kernel - def test_array(self, large): - def inner(): + def test_array(self, large: bool): + for i in range(self.count): t0 = self.core.get_rtio_counter_mu() data = self.get_array(large) t1 = self.core.get_rtio_counter_mu() - self.sink(data) + self.sink_array(data) t2 = self.core.get_rtio_counter_mu() self.h2d[i] = self.core.mu_to_seconds(t1 - t0) self.d2h[i] = self.core.mu_to_seconds(t2 - t1) - for i in range(self.count): - inner() - return (self.h2d, self.d2h) - @kernel def test_async(self): - data = self.get_bytes(True) + data = self.get_list(True) for _ in range(128): self.sink_async(data) - return self.get_async_throughput() + class TransferTest(ExperimentCase): @classmethod @@ -168,59 +150,35 @@ class TransferTest(ExperimentCase): print("| {} | {:>12.2f} | {:>12.2f} |".format( pad(v[0]), v[1], v[2])) - def test_bytes_large(self): + def test_bool_list_large(self): exp = self.create(_Transfer) - results = exp.test_bytes(True) - host_to_device = (1 << 20) / numpy.array(results[0], numpy.float64) - device_to_host = (1 << 20) / numpy.array(results[1], numpy.float64) + exp.test_bool_list(True) + host_to_device = (1 << 20) / numpy.array(exp.h2d, float64) + device_to_host = (1 << 20) / numpy.array(exp.d2h, float64) host_to_device /= 1024*1024 device_to_host /= 1024*1024 - self.results.append(["Bytes (1MB) H2D", host_to_device.mean(), + self.results.append(["Bool List (1MB) H2D", host_to_device.mean(), host_to_device.std()]) - self.results.append(["Bytes (1MB) D2H", device_to_host.mean(), + self.results.append(["Bool List (1MB) D2H", device_to_host.mean(), device_to_host.std()]) - def test_bytes_small(self): + def test_bool_list_small(self): exp = self.create(_Transfer) - results = exp.test_bytes(False) - host_to_device = (1 << 10) / numpy.array(results[0], numpy.float64) - device_to_host = (1 << 10) / numpy.array(results[1], numpy.float64) + exp.test_bool_list(False) + host_to_device = (1 << 10) / numpy.array(exp.h2d, float64) + device_to_host = (1 << 10) / numpy.array(exp.d2h, float64) host_to_device /= 1024*1024 device_to_host /= 1024*1024 - self.results.append(["Bytes (1KB) H2D", host_to_device.mean(), + self.results.append(["Bool List (1KB) H2D", host_to_device.mean(), host_to_device.std()]) - self.results.append(["Bytes (1KB) D2H", device_to_host.mean(), - device_to_host.std()]) - - def test_byte_list_large(self): - exp = self.create(_Transfer) - results = exp.test_byte_list(True) - host_to_device = (1 << 20) / numpy.array(results[0], numpy.float64) - device_to_host = (1 << 20) / numpy.array(results[1], numpy.float64) - host_to_device /= 1024*1024 - device_to_host /= 1024*1024 - self.results.append(["Bytes List (1MB) H2D", host_to_device.mean(), - host_to_device.std()]) - self.results.append(["Bytes List (1MB) D2H", device_to_host.mean(), - device_to_host.std()]) - - def test_byte_list_small(self): - exp = self.create(_Transfer) - results = exp.test_byte_list(False) - host_to_device = (1 << 10) / numpy.array(results[0], numpy.float64) - device_to_host = (1 << 10) / numpy.array(results[1], numpy.float64) - host_to_device /= 1024*1024 - device_to_host /= 1024*1024 - self.results.append(["Bytes List (1KB) H2D", host_to_device.mean(), - host_to_device.std()]) - self.results.append(["Bytes List (1KB) D2H", device_to_host.mean(), + self.results.append(["Bool List (1KB) D2H", device_to_host.mean(), device_to_host.std()]) def test_list_large(self): exp = self.create(_Transfer) - results = exp.test_list(True) - host_to_device = (1 << 20) / numpy.array(results[0], numpy.float64) - device_to_host = (1 << 20) / numpy.array(results[1], numpy.float64) + exp.test_list(True) + host_to_device = (1 << 20) / numpy.array(exp.h2d, float64) + device_to_host = (1 << 20) / numpy.array(exp.d2h, float64) host_to_device /= 1024*1024 device_to_host /= 1024*1024 self.results.append(["I32 List (1MB) H2D", host_to_device.mean(), @@ -230,9 +188,9 @@ class TransferTest(ExperimentCase): def test_list_small(self): exp = self.create(_Transfer) - results = exp.test_list(False) - host_to_device = (1 << 10) / numpy.array(results[0], numpy.float64) - device_to_host = (1 << 10) / numpy.array(results[1], numpy.float64) + exp.test_list(False) + host_to_device = (1 << 10) / numpy.array(exp.h2d, float64) + device_to_host = (1 << 10) / numpy.array(exp.d2h, float64) host_to_device /= 1024*1024 device_to_host /= 1024*1024 self.results.append(["I32 List (1KB) H2D", host_to_device.mean(), @@ -242,9 +200,9 @@ class TransferTest(ExperimentCase): def test_array_large(self): exp = self.create(_Transfer) - results = exp.test_array(True) - host_to_device = (1 << 20) / numpy.array(results[0], numpy.float64) - device_to_host = (1 << 20) / numpy.array(results[1], numpy.float64) + exp.test_array(True) + host_to_device = (1 << 20) / numpy.array(exp.h2d, float64) + device_to_host = (1 << 20) / numpy.array(exp.d2h, float64) host_to_device /= 1024*1024 device_to_host /= 1024*1024 self.results.append(["I32 Array (1MB) H2D", host_to_device.mean(), @@ -254,9 +212,9 @@ class TransferTest(ExperimentCase): def test_array_small(self): exp = self.create(_Transfer) - results = exp.test_array(False) - host_to_device = (1 << 10) / numpy.array(results[0], numpy.float64) - device_to_host = (1 << 10) / numpy.array(results[1], numpy.float64) + exp.test_array(False) + host_to_device = (1 << 10) / numpy.array(exp.h2d, float64) + device_to_host = (1 << 10) / numpy.array(exp.d2h, float64) host_to_device /= 1024*1024 device_to_host /= 1024*1024 self.results.append(["I32 Array (1KB) H2D", host_to_device.mean(), @@ -266,10 +224,15 @@ class TransferTest(ExperimentCase): def test_async_throughput(self): exp = self.create(_Transfer) - results = exp.test_async() + exp.test_async() + results = exp.get_async_throughput() print("Async throughput: {:>6.2f}MiB/s".format(results)) + +@nac3 class _KernelOverhead(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") diff --git a/artiq/test/coredevice/test_phaser.py b/artiq/test/coredevice/test_phaser.py index 18aac6a57..4f23f67db 100644 --- a/artiq/test/coredevice/test_phaser.py +++ b/artiq/test/coredevice/test_phaser.py @@ -1,7 +1,7 @@ import unittest from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase -from artiq.language.core import kernel, delay +from artiq.language.core import kernel from artiq.language.units import us diff --git a/artiq/test/coredevice/test_portability.py b/artiq/test/coredevice/test_portability.py index 21f73000a..8c7245559 100644 --- a/artiq/test/coredevice/test_portability.py +++ b/artiq/test/coredevice/test_portability.py @@ -1,6 +1,9 @@ +import unittest from operator import itemgetter -from fractions import Fraction +from numpy import int32, int64 + +from artiq.coredevice.core import Core from artiq.experiment import * from artiq.sim import devices as sim_devices from artiq.test.hardware_testbench import ExperimentCase @@ -16,13 +19,18 @@ def _run_on_host(k_class, *args, **kwargs): return k_inst +@nac3 class _Primes(EnvExperiment): + core: KernelInvariant[Core] + maximum: KernelInvariant[int32] + def build(self, output_list, maximum): self.setattr_device("core") self.output_list = output_list self.maximum = maximum - def _add_output(self, x): + @rpc + def _add_output(self, x: int32): self.output_list.append(x) @kernel @@ -39,8 +47,11 @@ class _Primes(EnvExperiment): self._add_output(x) +@nac3 class _Math(EnvExperiment): - """Test kernel math""" + core: KernelInvariant[Core] + x: KernelInvariant[float] + x_sqrt: Kernel[float] def build(self): self.setattr_device("core") @@ -52,7 +63,18 @@ class _Math(EnvExperiment): self.x_sqrt = self.x**0.5 +@nac3 class _Misc(EnvExperiment): + core: KernelInvariant[Core] + + input: KernelInvariant[int32] + al: KernelInvariant[list[int32]] + list_copy_in: KernelInvariant[list[float]] + + half_input: Kernel[int32] + acc: Kernel[int32] + list_copy_out: Kernel[list[float]] + def build(self): self.setattr_device("core") @@ -73,7 +95,10 @@ class _Misc(EnvExperiment): self.list_copy_out = self.list_copy_in +@nac3 class _PulseLogger(EnvExperiment): + core: KernelInvariant[Core] + def build(self, parent_test, name): self.setattr_device("core") self.parent_test = parent_test @@ -86,20 +111,29 @@ class _PulseLogger(EnvExperiment): t_usec = round(self.core.mu_to_seconds(t-origin)*1000000) self.parent_test.output_list.append((self.name, t_usec, l, f)) - def on(self, t, f): + @rpc + def on(self, t: int64, f: int32): self._append(t, True, f) - def off(self, t): + @rpc + def off(self, t: int64): self._append(t, False, 0) @kernel - def pulse(self, f, duration): + def pulse(self, f: int32, duration: float): self.on(now_mu(), f) - delay(duration) + self.core.delay(duration) self.off(now_mu()) +@nac3 class _Pulses(EnvExperiment): + core: KernelInvariant[Core] + a: KernelInvariant[_PulseLogger] + b: KernelInvariant[_PulseLogger] + c: KernelInvariant[_PulseLogger] + d: KernelInvariant[_PulseLogger] + def build(self, output_list): self.setattr_device("core") self.output_list = output_list @@ -115,22 +149,28 @@ class _Pulses(EnvExperiment): for i in range(3): with parallel: with sequential: - self.a.pulse(100+i, 20*us) - self.b.pulse(200+i, 20*us) + self.a.pulse(100+i, 20.*us) + self.b.pulse(200+i, 20.*us) with sequential: - self.c.pulse(300+i, 10*us) - self.d.pulse(400+i, 20*us) + self.c.pulse(300+i, 10.*us) + self.d.pulse(400+i, 20.*us) +@nac3 class _MyException(Exception): pass + +@nac3 class _NestedFinally(EnvExperiment): + core: KernelInvariant[Core] + def build(self, trace): self.setattr_device("core") self.trace = trace - def _trace(self, i): + @rpc + def _trace(self, i: int32): self.trace.append(i) @kernel @@ -140,7 +180,7 @@ class _NestedFinally(EnvExperiment): raise ValueError finally: try: - raise IndexError() + raise IndexError except ValueError: self._trace(0) except: @@ -148,12 +188,17 @@ class _NestedFinally(EnvExperiment): finally: self._trace(2) + +@nac3 class _NestedExceptions(EnvExperiment): + core: KernelInvariant[Core] + def build(self, trace): self.setattr_device("core") self.trace = trace - def _trace(self, i): + @rpc + def _trace(self, i: int32): self.trace.append(i) @kernel @@ -166,7 +211,7 @@ class _NestedExceptions(EnvExperiment): raise finally: try: - raise IndexError() + raise IndexError except ValueError: self._trace(1) raise @@ -177,12 +222,17 @@ class _NestedExceptions(EnvExperiment): finally: self._trace(4) + +@nac3 class _Exceptions(EnvExperiment): + core: KernelInvariant[Core] + def build(self, trace): self.setattr_device("core") self.trace = trace - def _trace(self, i): + @rpc + def _trace(self, i: int32): self.trace.append(i) @kernel @@ -212,7 +262,7 @@ class _Exceptions(EnvExperiment): if i == 1: raise _MyException() elif i == 2: - raise IndexError() + raise IndexError except IndexError: self._trace(101) raise @@ -224,13 +274,19 @@ class _Exceptions(EnvExperiment): self._trace(104) +@nac3 class _RPCExceptions(EnvExperiment): + core: KernelInvariant[Core] + catch: KernelInvariant[bool] + success: Kernel[bool] + def build(self, catch): self.setattr_device("core") self.catch = catch self.success = False + @rpc def exception_raiser(self): raise _MyException @@ -253,13 +309,18 @@ class _RPCExceptions(EnvExperiment): self.success = True +@nac3 class _Keywords(EnvExperiment): + core: KernelInvariant[Core] + value: KernelInvariant[int32] + def build(self, value, output): self.setattr_device("core") - self.value = value + self.value = value self.output = output - def rpc(self, kw): + @rpc + def rpc(self, kw: int32): self.output.append(kw) @kernel @@ -323,6 +384,7 @@ class HostVsDeviceCase(ExperimentCase): uut = self.execute(_RPCExceptions, catch=True) self.assertTrue(uut.success) + @unittest.skip("NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/533") def test_keywords(self): for f in self.execute, _run_on_host: output = [] @@ -331,9 +393,3 @@ class HostVsDeviceCase(ExperimentCase): output = [] f(_Keywords, value=1, output=output) self.assertEqual(output, [1]) - output = [] - f(_Keywords, value=False, output=output) - self.assertEqual(output, [False]) - output = [] - f(_Keywords, value=True, output=output) - self.assertEqual(output, [True]) diff --git a/artiq/test/coredevice/test_rtio.py b/artiq/test/coredevice/test_rtio.py index 3313b5c14..1c46f90e1 100644 --- a/artiq/test/coredevice/test_rtio.py +++ b/artiq/test/coredevice/test_rtio.py @@ -2,159 +2,217 @@ # Copyright (C) 2014, 2015 Robert Jordens import os, unittest -import numpy as np +from numpy import int32, int64 from math import sqrt from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase from artiq.coredevice import exceptions +from artiq.coredevice.core import Core +from artiq.coredevice.ttl import TTLOut, TTLInOut, TTLClockGen +from artiq.coredevice.ad9914 import AD9914 from artiq.coredevice.comm_mgmt import CommMgmt from artiq.coredevice.comm_analyzer import (StoppedMessage, OutputMessage, InputMessage, decode_dump, get_analyzer_dump) -from artiq.compiler.targets import CortexA9Target artiq_low_latency = os.getenv("ARTIQ_LOW_LATENCY") -artiq_in_devel = os.getenv("ARTIQ_IN_DEVEL") +@nac3 class RTIOCounter(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") + @rpc + def report(self, dt: float): + self.set_dataset("dt", dt) + @kernel def run(self): t0 = self.core.get_rtio_counter_mu() t1 = self.core.get_rtio_counter_mu() - self.set_dataset("dt", self.core.mu_to_seconds(t1 - t0)) + self.report(self.core.mu_to_seconds(t1 - t0)) +@nac3 class InvalidCounter(Exception): pass +@nac3 class WaitForRTIOCounter(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") @kernel def run(self): self.core.break_realtime() - target_mu = now_mu() + 10000 + target_mu = now_mu() + int64(10000) self.core.wait_until_mu(target_mu) if self.core.get_rtio_counter_mu() < target_mu: raise InvalidCounter +@nac3 class PulseNotReceived(Exception): pass +@nac3 class RTT(EnvExperiment): + core: KernelInvariant[Core] + ttl_inout: KernelInvariant[TTLInOut] + def build(self): self.setattr_device("core") self.setattr_device("ttl_inout") + @rpc + def report(self, rtt: float): + self.set_dataset("rtt", rtt) + @kernel def run(self): self.core.reset() self.ttl_inout.output() - delay(1*us) - with interleave: + self.core.delay(1.*us) + t0 = int64(0) + with parallel: # make sure not to send two commands into the same RTIO # channel with the same timestamp - self.ttl_inout.gate_rising(5*us) + self.ttl_inout.gate_rising(5.*us) with sequential: - delay(1*us) + self.core.delay(1.*us) t0 = now_mu() - self.ttl_inout.pulse(1*us) + self.ttl_inout.pulse(1.*us) t1 = self.ttl_inout.timestamp_mu(now_mu()) - if t1 < 0: - raise PulseNotReceived() - self.set_dataset("rtt", self.core.mu_to_seconds(t1 - t0)) + if t1 < int64(0): + raise PulseNotReceived + self.report(self.core.mu_to_seconds(t1 - t0)) +@nac3 class Loopback(EnvExperiment): + core: KernelInvariant[Core] + loop_in: KernelInvariant[TTLInOut] + loop_out: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("loop_in") self.setattr_device("loop_out") + @rpc + def report(self, rtt: float): + self.set_dataset("rtt", rtt) + @kernel def run(self): self.core.reset() self.loop_in.input() self.loop_out.off() - delay(1*us) + self.core.delay(1.*us) + t0 = int64(0) with parallel: - self.loop_in.gate_rising(2*us) + self.loop_in.gate_rising(2.*us) with sequential: - delay(1*us) + self.core.delay(1.*us) t0 = now_mu() - self.loop_out.pulse(1*us) + self.loop_out.pulse(1.*us) t1 = self.loop_in.timestamp_mu(now_mu()) - if t1 < 0: - raise PulseNotReceived() - self.set_dataset("rtt", self.core.mu_to_seconds(t1 - t0)) + if t1 < int64(0): + raise PulseNotReceived + self.report(self.core.mu_to_seconds(t1 - t0)) +@nac3 class ClockGeneratorLoopback(EnvExperiment): + core: KernelInvariant[Core] + loop_clock_in: KernelInvariant[TTLInOut] + loop_clock_out: KernelInvariant[TTLClockGen] + def build(self): self.setattr_device("core") self.setattr_device("loop_clock_in") self.setattr_device("loop_clock_out") + @rpc + def report(self, count: int32): + self.set_dataset("count", count) + @kernel def run(self): self.core.reset() self.loop_clock_in.input() self.loop_clock_out.stop() - delay(200*us) + self.core.delay(200.*us) with parallel: - self.loop_clock_in.gate_rising(10*us) + self.loop_clock_in.gate_rising(10.*us) with sequential: - delay(200*ns) - self.loop_clock_out.set(1*MHz) - self.set_dataset("count", self.loop_clock_in.count(now_mu())) + self.core.delay(200.*ns) + self.loop_clock_out.set(1.*MHz) + self.report(self.loop_clock_in.count(now_mu())) +@nac3 class PulseRate(EnvExperiment): + core: KernelInvariant[Core] + ttl_out: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("ttl_out") + @rpc + def report(self, pulse_rate: float): + self.set_dataset("pulse_rate", pulse_rate) + @kernel def run(self): self.core.reset() - dt = self.core.seconds_to_mu(300*ns) - while True: - for i in range(10000): - try: - self.ttl_out.pulse_mu(dt) - delay_mu(dt) - except RTIOUnderflow: - dt += 1 - self.core.break_realtime() - break + dt = self.core.seconds_to_mu(300.*ns) + i = 10000 + while i > 0: + try: + self.ttl_out.pulse_mu(dt) + delay_mu(dt) + except RTIOUnderflow: + dt += int64(1) + i = 10000 + self.core.break_realtime() else: - self.set_dataset("pulse_rate", self.core.mu_to_seconds(dt)) - return + i -= 1 + self.report(self.core.mu_to_seconds(dt)) +@nac3 class PulseRateAD9914DDS(EnvExperiment): + core: KernelInvariant[Core] + ad9914dds0: KernelInvariant[AD9914] + ad9914dds1: KernelInvariant[AD9914] + def build(self): self.setattr_device("core") self.setattr_device("ad9914dds0") self.setattr_device("ad9914dds1") + @rpc + def report(self, pulse_rate: float): + self.set_dataset("pulse_rate", pulse_rate) + @kernel def run(self): self.core.reset() - dt = self.core.seconds_to_mu(5*us) - freq = self.ad9914dds0.frequency_to_ftw(100*MHz) + dt = self.core.seconds_to_mu(5.*us) + freq = self.ad9914dds0.frequency_to_ftw(100.*MHz) while True: - delay(10*ms) + self.core.delay(10.*ms) for i in range(1250): try: delay_mu(-self.ad9914dds0.set_duration_mu) @@ -163,22 +221,29 @@ class PulseRateAD9914DDS(EnvExperiment): self.ad9914dds1.set_mu(freq) delay_mu(dt) except RTIOUnderflow: - dt += 100 + dt += int64(100) self.core.break_realtime() break else: - self.set_dataset("pulse_rate", self.core.mu_to_seconds(dt//2)) + self.report(self.core.mu_to_seconds(dt//int64(2))) return +@nac3 class LoopbackCount(EnvExperiment): + core: KernelInvariant[Core] + loop_in: KernelInvariant[TTLInOut] + loop_out: KernelInvariant[TTLOut] + npulses: KernelInvariant[int32] + def build(self, npulses): self.setattr_device("core") self.setattr_device("loop_in") self.setattr_device("loop_out") self.npulses = npulses - def set_count(self, count): + @rpc + def report(self, count: int32): self.set_dataset("count", count) @kernel @@ -186,21 +251,27 @@ class LoopbackCount(EnvExperiment): self.core.reset() self.loop_in.input() self.loop_out.output() - delay(5*us) + self.core.delay(5.*us) with parallel: - self.loop_in.gate_rising(10*us) + self.loop_in.gate_rising(10.*us) with sequential: for i in range(self.npulses): - delay(25*ns) - self.loop_out.pulse(25*ns) - self.set_dataset("count", self.loop_in.count(now_mu())) + self.core.delay(25.*ns) + self.loop_out.pulse(25.*ns) + self.report(self.loop_in.count(now_mu())) +@nac3 class IncorrectPulseTiming(Exception): pass +@nac3 class LoopbackGateTiming(EnvExperiment): + core: KernelInvariant[Core] + loop_in: KernelInvariant[TTLInOut] + loop_out: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("loop_in") @@ -212,19 +283,20 @@ class LoopbackGateTiming(EnvExperiment): self.core.reset() self.loop_in.input() self.loop_out.output() - delay_mu(500) + delay_mu(int64(500)) self.loop_out.off() - delay_mu(5000) + delay_mu(int64(5000)) # Determine loop delay. + out_mu = int64(0) with parallel: - self.loop_in.gate_rising_mu(10000) + self.loop_in.gate_rising_mu(int64(10000)) with sequential: - delay_mu(5000) + delay_mu(int64(5000)) out_mu = now_mu() - self.loop_out.pulse_mu(1000) + self.loop_out.pulse_mu(int64(1000)) in_mu = self.loop_in.timestamp_mu(now_mu()) - if in_mu < 0: + if in_mu < int64(0): raise PulseNotReceived("Cannot determine loop delay") loop_delay_mu = in_mu - out_mu @@ -232,37 +304,46 @@ class LoopbackGateTiming(EnvExperiment): # In the most common configuration, 24 mu == 24 ns == 3 coarse periods, # which should be plenty of slack. # FIXME: ZC706 with NIST_QC2 needs 48ns - hw problem? - delay_mu(10000) + delay_mu(int64(10000)) gate_start_mu = now_mu() - self.loop_in.gate_both_mu(48) # XXX + self.loop_in.gate_both_mu(int64(48)) # XXX gate_end_mu = now_mu() # gateware latency offset between gate and input - lat_offset = 11*8 + lat_offset = int64(11*8) out_mu = gate_start_mu - loop_delay_mu + lat_offset at_mu(out_mu) - self.loop_out.pulse_mu(48) # XXX + self.loop_out.pulse_mu(int64(48)) # XXX in_mu = self.loop_in.timestamp_mu(gate_end_mu) - print("timings: ", gate_start_mu, in_mu - lat_offset, gate_end_mu) - if in_mu < 0: - raise PulseNotReceived() + print_rpc("timings:") + print_rpc(gate_start_mu) + print_rpc(in_mu - lat_offset) + print_rpc(gate_end_mu) + if in_mu < int64(0): + raise PulseNotReceived if not (gate_start_mu <= (in_mu - lat_offset) <= gate_end_mu): raise IncorrectPulseTiming("Input event should occur during gate") - if not (-2 < (in_mu - out_mu - loop_delay_mu) < 2): + if not (int64(-2) < (in_mu - out_mu - loop_delay_mu) < int64(2)): raise IncorrectPulseTiming("Loop delay should not change") in_mu = self.loop_in.timestamp_mu(gate_end_mu) - if in_mu > 0: + if in_mu > int64(0): raise IncorrectPulseTiming("Only one pulse should be received") +@nac3 class IncorrectLevel(Exception): pass +@nac3 class Level(EnvExperiment): + core: KernelInvariant[Core] + loop_in: KernelInvariant[TTLInOut] + loop_out: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("loop_in") @@ -273,20 +354,25 @@ class Level(EnvExperiment): self.core.reset() self.loop_in.input() self.loop_out.output() - delay(5*us) + self.core.delay(5.*us) self.loop_out.off() - delay(5*us) - if self.loop_in.sample_get_nonrt(): + self.core.delay(5.*us) + if self.loop_in.sample_get_nonrt() != 0: raise IncorrectLevel self.loop_out.on() - delay(5*us) - if not self.loop_in.sample_get_nonrt(): + self.core.delay(5.*us) + if self.loop_in.sample_get_nonrt() == 0: raise IncorrectLevel +@nac3 class Watch(EnvExperiment): + core: KernelInvariant[Core] + loop_in: KernelInvariant[TTLInOut] + loop_out: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("loop_in") @@ -297,27 +383,31 @@ class Watch(EnvExperiment): self.core.reset() self.loop_in.input() self.loop_out.output() - delay(5*us) + self.core.delay(5.*us) self.loop_out.off() - delay(5*us) + self.core.delay(5.*us) if not self.loop_in.watch_stay_off(): raise IncorrectLevel - delay(10*us) + self.core.delay(10.*us) if not self.loop_in.watch_done(): raise IncorrectLevel - delay(10*us) + self.core.delay(10.*us) if not self.loop_in.watch_stay_off(): raise IncorrectLevel - delay(3*us) + self.core.delay(3.*us) self.loop_out.on() - delay(10*us) + self.core.delay(10.*us) if self.loop_in.watch_done(): raise IncorrectLevel +@nac3 class Underflow(EnvExperiment): + core: KernelInvariant[Core] + ttl_out: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("ttl_out") @@ -326,11 +416,15 @@ class Underflow(EnvExperiment): def run(self): self.core.reset() while True: - delay(25*ns) - self.ttl_out.pulse(25*ns) + self.core.delay(25.*ns) + self.ttl_out.pulse(25.*ns) +@nac3 class SequenceError(EnvExperiment): + core: KernelInvariant[Core] + ttl_out: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("ttl_out") @@ -338,13 +432,17 @@ class SequenceError(EnvExperiment): @kernel def run(self): self.core.reset() - delay(55*256*us) + self.core.delay(55.*256.*us) for _ in range(256): - self.ttl_out.pulse(25*us) - delay(-75*us) + self.ttl_out.pulse(25.*us) + self.core.delay(-75.*us) +@nac3 class Collision(EnvExperiment): + core: KernelInvariant[Core] + ttl_out_serdes: KernelInvariant[TTLOut] + def build(self): self.setattr_device("core") self.setattr_device("ttl_out_serdes") @@ -352,15 +450,19 @@ class Collision(EnvExperiment): @kernel def run(self): self.core.reset() - delay(5*ms) # make sure we won't get underflow + self.core.delay(5.*ms) # make sure we won't get underflow for i in range(16): - self.ttl_out_serdes.pulse_mu(1) - delay_mu(1) + self.ttl_out_serdes.pulse_mu(int64(1)) + delay_mu(int64(1)) while self.core.get_rtio_counter_mu() < now_mu(): pass +@nac3 class AddressCollision(EnvExperiment): + core: KernelInvariant[Core] + loop_in: KernelInvariant[TTLInOut] + def build(self): self.setattr_device("core") self.setattr_device("loop_in") @@ -369,61 +471,90 @@ class AddressCollision(EnvExperiment): def run(self): self.core.reset() self.loop_in.input() - self.loop_in.pulse(10*us) + self.loop_in.pulse(10.*us) while self.core.get_rtio_counter_mu() < now_mu(): pass +@nac3 class TimeKeepsRunning(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") + @rpc + def report(self, time_at_start: int64): + self.set_dataset("time_at_start", time_at_start) + @kernel def run(self): self.core.reset() - self.set_dataset("time_at_start", now_mu()) + self.report(now_mu()) +@nac3 class Handover(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") + @rpc + def report(self, var: str, t: int64): + self.set_dataset(var, t) + @kernel - def k(self, var): - self.set_dataset(var, now_mu()) - delay_mu(1234) + def k(self, var: str): + self.report(var, now_mu()) + delay_mu(int64(1234)) def run(self): self.k("t1") self.k("t2") +@nac3 class Rounding(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") + @rpc + def report(self, delta: int64): + self.set_dataset("delta", delta) + @kernel def run(self): self.core.reset() t1 = now_mu() - delay(8*us) + self.core.delay(8.*us) t2 = now_mu() - self.set_dataset("delta", t2 - t1) + self.report(t2 - t1) +@nac3 class DummyException(Exception): pass +@nac3 class HandoverException(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") + @rpc + def report(self, var: str, t: int64): + self.set_dataset(var, t) + @kernel - def k(self, var): - self.set_dataset(var, now_mu()) - delay_mu(1234) - raise DummyException() + def k(self, var: str): + self.report(var, now_mu()) + delay_mu(int64(1234)) + raise DummyException def run(self): try: @@ -460,13 +591,14 @@ class CoredeviceTest(ExperimentCase): count = self.dataset_mgr.get("count") self.assertEqual(count, 10) + @unittest.skip("NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/532") def test_pulse_rate(self): """Minimum interval for sustained TTL output switching""" exp = self.execute(PulseRate) rate = self.dataset_mgr.get("pulse_rate") print(rate) self.assertGreater(rate, 100*ns) - if exp.core.target_cls == CortexA9Target: + if exp.core.target == "cortexa9": # Crappy AXI PS/PL interface from Xilinx is slow. self.assertLess(rate, 810*ns) else: @@ -544,11 +676,17 @@ class CoredeviceTest(ExperimentCase): self.assertEqual(dt, 8000) +@nac3 class RPCTiming(EnvExperiment): + core: KernelInvariant[Core] + repeats: KernelInvariant[int32] + ts: Kernel[list[float]] + def build(self, repeats=100): self.setattr_device("core") self.repeats = repeats + @rpc def nop(self): pass @@ -587,7 +725,7 @@ class _DMA(EnvExperiment): self.setattr_device("core_dma") self.setattr_device("ttl_out") self.trace_name = trace_name - self.delta = np.int64(0) + self.delta = int64(0) @kernel def record(self, for_handle=True): @@ -659,6 +797,7 @@ class _DMA(EnvExperiment): self.core_dma.playback_handle(handle) +@unittest.skip("NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/338") class DMATest(ExperimentCase): def test_dma_storage(self): exp = self.create(_DMA) @@ -728,7 +867,7 @@ class DMATest(ExperimentCase): raise unittest.SkipTest("skipped on Kasli for now") exp = self.create(_DMA) - is_zynq = exp.core.target_cls == CortexA9Target + is_zynq = exp.core.target == "cortexa9" count = 20000 exp.record_many(40) exp.playback_many(count, is_zynq) diff --git a/artiq/test/coredevice/test_spi.py b/artiq/test/coredevice/test_spi.py index 0faf4677f..e6b38ebcd 100644 --- a/artiq/test/coredevice/test_spi.py +++ b/artiq/test/coredevice/test_spi.py @@ -1,18 +1,23 @@ import unittest from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase -from artiq.language.core import (kernel, delay_mu, delay) +from artiq.language.core import kernel from artiq.language.units import us -from artiq.coredevice import spi2 as spi +from artiq.coredevice.core import Core +from artiq.coredevice.spi2 import * -_SDCARD_SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END | - 0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY | - 0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE | - 0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX) +_SDCARD_SPI_CONFIG = (0*SPI_OFFLINE | 0*SPI_END | + 0*SPI_INPUT | 0*SPI_CS_POLARITY | + 0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE | + 0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX) +@nac3 class CardTest(EnvExperiment): + core: KernelInvariant[Core] + spi_mmc: KernelInvariant[SPIMaster] + def build(self): self.setattr_device("core") self.setattr_device("spi_mmc") @@ -20,18 +25,18 @@ class CardTest(EnvExperiment): @kernel def run(self): self.core.reset() - delay(1*ms) + self.core.delay(1.*ms) - freq = 1*MHz + freq = 1.*MHz cs = 1 # run a couple of clock cycles with miso high to wake up the card self.spi_mmc.set_config(_SDCARD_SPI_CONFIG, 32, freq, 0) for i in range(10): - self.spi_mmc.write(0xffffffff) - self.spi_mmc.set_config(_SDCARD_SPI_CONFIG | spi.SPI_END, 32, freq, 0) - self.spi_mmc.write(0xffffffff) - delay(200*us) + self.spi_mmc.write(-1) + self.spi_mmc.set_config(_SDCARD_SPI_CONFIG | SPI_END, 32, freq, 0) + self.spi_mmc.write(-1) + self.core.delay(200.*us) self.spi_mmc.set_config(_SDCARD_SPI_CONFIG, 8, freq, cs) self.spi_mmc.write(0x40 << 24) # CMD @@ -39,20 +44,20 @@ class CardTest(EnvExperiment): self.spi_mmc.write(0x00000000) # ARG self.spi_mmc.set_config(_SDCARD_SPI_CONFIG, 8, freq, cs) self.spi_mmc.write(0x95 << 24) # CRC - self.spi_mmc.set_config(_SDCARD_SPI_CONFIG | spi.SPI_INPUT, 8, freq, cs) + self.spi_mmc.set_config(_SDCARD_SPI_CONFIG | SPI_INPUT, 8, freq, cs) idle = False response = 0 for i in range(8): self.spi_mmc.write(0xff << 24) # NCR response = self.spi_mmc.read() - delay(100*us) + self.core.delay(100.*us) if response == 0x01: idle = True break - self.spi_mmc.set_config(_SDCARD_SPI_CONFIG | spi.SPI_END, 8, freq, cs) + self.spi_mmc.set_config(_SDCARD_SPI_CONFIG | SPI_END, 8, freq, cs) self.spi_mmc.write(0xff << 24) if not idle: - print(response) + print_rpc(response) raise ValueError("SD Card did not reply with IDLE") diff --git a/artiq/test/coredevice/test_stress.py b/artiq/test/coredevice/test_stress.py index 514a2d8ac..4e3f6d36c 100644 --- a/artiq/test/coredevice/test_stress.py +++ b/artiq/test/coredevice/test_stress.py @@ -1,23 +1,26 @@ -import os -import time import unittest +from numpy import int32 + from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase +from artiq.coredevice.core import Core - +@nac3 class _Stress(EnvExperiment): + core: KernelInvariant[Core] + def build(self): self.setattr_device("core") @rpc(flags={"async"}) - def sink(self, data): + def sink(self, data: int32): pass @kernel - def async_rpc(self, n): + def async_rpc(self, n: int32): for _ in range(n): - self.sink(b"") + self.sink(0) class StressTest(ExperimentCase): diff --git a/artiq/test/hardware_testbench.py b/artiq/test/hardware_testbench.py index 55a4d33cb..2e82f0916 100644 --- a/artiq/test/hardware_testbench.py +++ b/artiq/test/hardware_testbench.py @@ -7,9 +7,10 @@ import unittest import logging from tempfile import TemporaryDirectory +from nac3artiq import CompileError + from artiq.master.databases import DeviceDB, DatasetDB from artiq.master.worker_db import DeviceManager, DatasetManager, DeviceError -from artiq.coredevice.core import CompileError from artiq.frontend.artiq_run import DummyScheduler diff --git a/artiq/test/libartiq_support/lib.rs b/artiq/test/libartiq_support/lib.rs deleted file mode 100644 index 4a940fd70..000000000 --- a/artiq/test/libartiq_support/lib.rs +++ /dev/null @@ -1,90 +0,0 @@ -#![feature(libc, panic_unwind, rustc_private, c_unwind)] -#![crate_name = "artiq_support"] -#![crate_type = "cdylib"] - -extern crate std as core; -extern crate libc; -extern crate unwind; - -// Note: this does *not* match the cslice crate! -// ARTIQ Python has the slice length field fixed at 32 bits, even on 64-bit platforms. -mod cslice { - use core::marker::PhantomData; - use core::convert::AsRef; - use core::slice; - - #[repr(C)] - #[derive(Clone, Copy)] - pub struct CSlice<'a, T> { - base: *const T, - len: u32, - phantom: PhantomData<&'a ()> - } - - impl<'a, T> CSlice<'a, T> { - pub fn len(&self) -> usize { - self.len as usize - } - - pub fn as_ptr(&self) -> *const T { - self.base - } - } - - impl<'a, T> AsRef<[T]> for CSlice<'a, T> { - fn as_ref(&self) -> &[T] { - unsafe { - slice::from_raw_parts(self.base, self.len as usize) - } - } - } - - pub trait AsCSlice<'a, T> { - fn as_c_slice(&'a self) -> CSlice<'a, T>; - } - - impl<'a> AsCSlice<'a, u8> for str { - fn as_c_slice(&'a self) -> CSlice<'a, u8> { - CSlice { - base: self.as_ptr(), - len: self.len() as u32, - phantom: PhantomData - } - } - } -} - -#[path = "."] -pub mod eh { - #[path = "../../firmware/libeh/dwarf.rs"] - pub mod dwarf; - #[path = "../../firmware/libeh/eh_artiq.rs"] - pub mod eh_artiq; -} -#[path = "../../firmware/ksupport/eh_artiq.rs"] -pub mod eh_artiq; - -use std::{str, process}; - -fn terminate(exceptions: &'static [Option>], - _stack_pointers: &'static [eh_artiq::StackPointerBacktrace], - _backtrace: &'static mut [(usize, usize)]) -> ! { - println!("{}", exceptions.len()); - for exception in exceptions.iter() { - let exception = exception.as_ref().unwrap(); - println!("Uncaught {}: {} ({}, {}, {})", - exception.id, - str::from_utf8(exception.message.as_ref()).unwrap(), - exception.param[0], - exception.param[1], - exception.param[2]); - println!("at {}:{}:{}", - str::from_utf8(exception.file.as_ref()).unwrap(), - exception.line, - exception.column); - } - process::exit(1); -} - -#[export_name = "now"] -pub static mut NOW: i64 = 0; diff --git a/artiq/test/lit/codegen/assign_none.py b/artiq/test/lit/codegen/assign_none.py deleted file mode 100644 index 000e87ac7..000000000 --- a/artiq/test/lit/codegen/assign_none.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.llvmgen %s - -def f(): - pass -def g(): - a = f() diff --git a/artiq/test/lit/codegen/custom_comparison.py b/artiq/test/lit/codegen/custom_comparison.py deleted file mode 100644 index 8a7a1d32b..000000000 --- a/artiq/test/lit/codegen/custom_comparison.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class Foo: - def __init__(self): - pass - -a = Foo() -b = Foo() - -# CHECK-L: ${LINE:+1}: error: Custom object comparison is not supported -a > b - diff --git a/artiq/test/lit/codegen/custom_inclusion.py b/artiq/test/lit/codegen/custom_inclusion.py deleted file mode 100644 index 92cd1a772..000000000 --- a/artiq/test/lit/codegen/custom_inclusion.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class Foo: - def __init__(self): - pass - -a = Foo() -b = Foo() - -# CHECK-L: ${LINE:+1}: error: Custom object inclusion test is not supported -a in b - diff --git a/artiq/test/lit/codegen/error_illegal_return.py b/artiq/test/lit/codegen/error_illegal_return.py deleted file mode 100644 index 5e15cade7..000000000 --- a/artiq/test/lit/codegen/error_illegal_return.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: this function must return a value of type numpy.int32 explicitly -def foo(x): - if x: - return 1 - -foo(True) diff --git a/artiq/test/lit/codegen/none_retval.py b/artiq/test/lit/codegen/none_retval.py deleted file mode 100644 index ed2c9eb25..000000000 --- a/artiq/test/lit/codegen/none_retval.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.llvmgen %s - -def make_none(): - return None - -def take_arg(arg): - pass - -def run(): - retval = make_none() - take_arg(retval) diff --git a/artiq/test/lit/codegen/noop_coercion.py b/artiq/test/lit/codegen/noop_coercion.py deleted file mode 100644 index df4ce830b..000000000 --- a/artiq/test/lit/codegen/noop_coercion.py +++ /dev/null @@ -1,4 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.llvmgen %s - -def f(): - return float(1.0) diff --git a/artiq/test/lit/codegen/unreachable_implicit_return.py b/artiq/test/lit/codegen/unreachable_implicit_return.py deleted file mode 100644 index a434f8a28..000000000 --- a/artiq/test/lit/codegen/unreachable_implicit_return.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s - -def foo(x): - if x: - return 1 - else: - return 2 - -foo(True) diff --git a/artiq/test/lit/codegen/warning_useless_bool.py b/artiq/test/lit/codegen/warning_useless_bool.py deleted file mode 100644 index d81fa2941..000000000 --- a/artiq/test/lit/codegen/warning_useless_bool.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: warning: this expression, which is always truthful, is coerced to bool -bool(IndexError()) diff --git a/artiq/test/lit/constant_hoisting/device_db.py b/artiq/test/lit/constant_hoisting/device_db.py deleted file mode 100644 index e39c83c09..000000000 --- a/artiq/test/lit/constant_hoisting/device_db.py +++ /dev/null @@ -1,8 +0,0 @@ -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {"host": None, "ref_period": 1e-9} - } -} diff --git a/artiq/test/lit/constant_hoisting/invariant_load.py b/artiq/test/lit/constant_hoisting/invariant_load.py deleted file mode 100644 index 62fac4202..000000000 --- a/artiq/test/lit/constant_hoisting/invariant_load.py +++ /dev/null @@ -1,25 +0,0 @@ -# RUN: env ARTIQ_DUMP_IR=%t ARTIQ_IR_NO_LOC=1 %python -m artiq.compiler.testbench.embedding +compile %s -# RUN: OutputCheck %s --file-to-check=%t.txt - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: %LOC.self.FLD.foo = numpy.int32 getattr('foo') %ARG.self -# CHECK-L: for.head: - -class c: - kernel_invariants = {"foo"} - - def __init__(self): - self.foo = 1 - - @kernel - def run(self): - for _ in range(10): - core_log(1.0 * self.foo) - -i = c() - -@kernel -def entrypoint(): - i.run() diff --git a/artiq/test/lit/devirtualization/device_db.py b/artiq/test/lit/devirtualization/device_db.py deleted file mode 100644 index e39c83c09..000000000 --- a/artiq/test/lit/devirtualization/device_db.py +++ /dev/null @@ -1,8 +0,0 @@ -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {"host": None, "ref_period": 1e-9} - } -} diff --git a/artiq/test/lit/devirtualization/function.py b/artiq/test/lit/devirtualization/function.py deleted file mode 100644 index c8fe65691..000000000 --- a/artiq/test/lit/devirtualization/function.py +++ /dev/null @@ -1,23 +0,0 @@ -# RUN: env ARTIQ_DUMP_IR=%t %python -m artiq.compiler.testbench.embedding +compile %s -# RUN: OutputCheck %s --file-to-check=%t.txt -# XFAIL: * - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: call ()->NoneType %local.testbench.entrypoint ; calls testbench.entrypoint - -@kernel -def baz(): - pass - -class foo: - @kernel - def bar(self): - # CHECK-L: call ()->NoneType %local.testbench.baz ; calls testbench.baz - baz() -x = foo() - -@kernel -def entrypoint(): - x.bar() diff --git a/artiq/test/lit/devirtualization/method.py b/artiq/test/lit/devirtualization/method.py deleted file mode 100644 index 8c258a41b..000000000 --- a/artiq/test/lit/devirtualization/method.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: env ARTIQ_DUMP_IR=%t %python -m artiq.compiler.testbench.embedding +compile %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t.txt -# XFAIL: * - -from artiq.language.core import * -from artiq.language.types import * - -class foo: - @kernel - def bar(self): - pass -x = foo() - -@kernel -def entrypoint(): - # CHECK-L: ; calls testbench.foo.bar - x.bar() diff --git a/artiq/test/lit/embedding/annotation.py b/artiq/test/lit/embedding/annotation.py deleted file mode 100644 index 21ae25332..000000000 --- a/artiq/test/lit/embedding/annotation.py +++ /dev/null @@ -1,22 +0,0 @@ -# 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 * - -# CHECK: i64 @_Z13testbench.foozz\(i64 %ARG.x, \{ i1, i64 \} %ARG.y\) - -@kernel -def foo(x: TInt64, y: TInt64 = 1) -> TInt64: - print(x+y) - return x+y - -@kernel -def bar(x: TInt64) -> None: - print(x) - -@kernel -def entrypoint(): - print(foo(0, 2)) - print(foo(1, 3)) - bar(3) diff --git a/artiq/test/lit/embedding/annotation_py.py b/artiq/test/lit/embedding/annotation_py.py deleted file mode 100644 index c790b6914..000000000 --- a/artiq/test/lit/embedding/annotation_py.py +++ /dev/null @@ -1,34 +0,0 @@ -# RUN: env ARTIQ_DUMP_LLVM=%t %python -m artiq.compiler.testbench.embedding +compile %s -# RUN: OutputCheck %s --file-to-check=%t.ll - -from typing import List, Tuple - -import numpy as np - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: i64 @_Z13testbench.foozz(i64 %ARG.x, { i1, i32 } %ARG.y) - -@kernel -def foo(x: np.int64, y: np.int32 = 1) -> np.int64: - print(x + y) - return x + y - -# CHECK-L: void @_Z13testbench.barzz() -@kernel -def bar(x: np.int32) -> None: - print(x) - -# CHECK-L: @_Z21testbench.unpack_listzz({ i1, i64 }* nocapture writeonly sret({ i1, i64 }) %.1, { i64*, i32 }* %ARG.xs) -@kernel -def unpack_list(xs: List[np.int64]) -> Tuple[bool, np.int64]: - print(xs) - return (len(xs) == 1, xs[0]) - -@kernel -def entrypoint(): - print(foo(0, 2)) - print(foo(1, 3)) - bar(3) - print(unpack_list([1, 2, 3])) diff --git a/artiq/test/lit/embedding/array_math_fns.py b/artiq/test/lit/embedding/array_math_fns.py deleted file mode 100644 index d23540b48..000000000 --- a/artiq/test/lit/embedding/array_math_fns.py +++ /dev/null @@ -1,26 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -import numpy as np - -@kernel -def entrypoint(): - # Just make sure everything compiles. - - # LLVM intrinsic: - a = np.array([1.0, 2.0, 3.0]) - b = np.sin(a) - assert b.shape == a.shape - - # libm: - c = np.array([1.0, 2.0, 3.0]) - d = np.arctan(c) - assert d.shape == c.shape - - # libm, binary: - e = np.array([1.0, 2.0, 3.0]) - f = np.array([4.0, 5.0, 6.0]) - g = np.arctan2(e, f) - # g = np.arctan2(e, 0.0) - # g = np.arctan2(0.0, f) - assert g.shape == e.shape diff --git a/artiq/test/lit/embedding/array_transpose.py b/artiq/test/lit/embedding/array_transpose.py deleted file mode 100644 index 2ab44bd7d..000000000 --- a/artiq/test/lit/embedding/array_transpose.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -import numpy as np - -@kernel -def entrypoint(): - # FIXME: This needs to be a runtime test (but numpy.* integration is - # currently embedding-only). - a = np.array([1, 2, 3]) - b = np.transpose(a) - assert a.shape == b.shape - for i in range(len(a)): - assert a[i] == b[i] - - c = np.array([[1, 2, 3], [4, 5, 6]]) - d = np.transpose(c) - assert c.shape == d.shape - for i in range(2): - for j in range(3): - assert c[i][j] == d[j][i] diff --git a/artiq/test/lit/embedding/arrays.py b/artiq/test/lit/embedding/arrays.py deleted file mode 100644 index 63d846585..000000000 --- a/artiq/test/lit/embedding/arrays.py +++ /dev/null @@ -1,36 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -from numpy import array - -int_vec = array([1, 2, 3]) -float_vec = array([1.0, 2.0, 3.0]) -int_mat = array([[1, 2], [3, 4]]) -float_mat = array([[1.0, 2.0], [3.0, 4.0]]) - - -@kernel -def entrypoint(): - # TODO: These need to be runtime tests! - assert int_vec.shape == (3, ) - assert int_vec[0] == 1 - assert int_vec[1] == 2 - assert int_vec[2] == 3 - - assert float_vec.shape == (3, ) - assert float_vec[0] == 1.0 - assert float_vec[1] == 2.0 - assert float_vec[2] == 3.0 - - assert int_mat.shape == (2, 2) - assert int_mat[0][0] == 1 - assert int_mat[0][1] == 2 - assert int_mat[1][0] == 3 - assert int_mat[1][1] == 4 - - assert float_mat.shape == (2, 2) - assert float_mat[0][0] == 1.0 - assert float_mat[0][1] == 2.0 - assert float_mat[1][0] == 3.0 - assert float_mat[1][1] == 4.0 diff --git a/artiq/test/lit/embedding/async_rpc.py b/artiq/test/lit/embedding/async_rpc.py deleted file mode 100644 index 47c3041a5..000000000 --- a/artiq/test/lit/embedding/async_rpc.py +++ /dev/null @@ -1,15 +0,0 @@ -# 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 * - -# CHECK: call void @rpc_send_async - -@rpc(flags={"async"}) -def foo(): - pass - -@kernel -def entrypoint(): - foo() diff --git a/artiq/test/lit/embedding/bug_477.py b/artiq/test/lit/embedding/bug_477.py deleted file mode 100644 index 19d496ca6..000000000 --- a/artiq/test/lit/embedding/bug_477.py +++ /dev/null @@ -1,20 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.experiment import * - -class MyClass: - def __init__(self, **kwargs): - for k, v in kwargs.items(): - setattr(self, k, v) - - -sl = [MyClass(x=1), MyClass(x=2)] - -@kernel -def bug(l): - for c in l: - print(c.x) - -@kernel -def entrypoint(): - bug(sl) diff --git a/artiq/test/lit/embedding/class_fn_direct_call.py b/artiq/test/lit/embedding/class_fn_direct_call.py deleted file mode 100644 index 91bdac519..000000000 --- a/artiq/test/lit/embedding/class_fn_direct_call.py +++ /dev/null @@ -1,20 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -class C: - @kernel - def f(self): - pass - -class D(C): - @kernel - def f(self): - # super().f() # super() not bound - C.f(self) # KeyError in compile - -di = D() -@kernel -def entrypoint(): - di.f() diff --git a/artiq/test/lit/embedding/class_same_name.py b/artiq/test/lit/embedding/class_same_name.py deleted file mode 100644 index 46cf7c16d..000000000 --- a/artiq/test/lit/embedding/class_same_name.py +++ /dev/null @@ -1,51 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * - - -class InnerA: - def __init__(self, val): - self.val = val - - @kernel - def run_once(self): - return self.val - - -class InnerB: - def __init__(self, val): - self.val = val - - @kernel - def run_once(self): - return self.val - - -def make_runner(InnerCls, val): - class Runner: - def __init__(self): - self.inner = InnerCls(val) - - @kernel - def run_once(self): - return self.inner.run_once() - - return Runner() - - -class Parent: - def __init__(self): - self.a = make_runner(InnerA, 1) - self.b = make_runner(InnerB, 42.0) - - @kernel - def run_once(self): - return self.a.run_once() + self.b.run_once() - - -parent = Parent() - - -@kernel -def entrypoint(): - parent.run_once() diff --git a/artiq/test/lit/embedding/device_db.py b/artiq/test/lit/embedding/device_db.py deleted file mode 100644 index e39c83c09..000000000 --- a/artiq/test/lit/embedding/device_db.py +++ /dev/null @@ -1,8 +0,0 @@ -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {"host": None, "ref_period": 1e-9} - } -} diff --git a/artiq/test/lit/embedding/error_attr_absent.py b/artiq/test/lit/embedding/error_attr_absent.py deleted file mode 100644 index 986c5d06a..000000000 --- a/artiq/test/lit/embedding/error_attr_absent.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - pass - -@kernel -def entrypoint(): - # CHECK-L: :1: error: host object does not have an attribute 'x' - # CHECK-L: ${LINE:+1}: note: expanded from here - a = c - # CHECK-L: ${LINE:+1}: note: attribute accessed here - a.x diff --git a/artiq/test/lit/embedding/error_attr_absent_suggest.py b/artiq/test/lit/embedding/error_attr_absent_suggest.py deleted file mode 100644 index 474a5791a..000000000 --- a/artiq/test/lit/embedding/error_attr_absent_suggest.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - xx = 1 - -@kernel -def entrypoint(): - # CHECK-L: :1: error: host object does not have an attribute 'x'; did you mean 'xx'? - # CHECK-L: ${LINE:+1}: note: expanded from here - a = c - # CHECK-L: ${LINE:+1}: note: attribute accessed here - a.x diff --git a/artiq/test/lit/embedding/error_attr_conflict.py b/artiq/test/lit/embedding/error_attr_conflict.py deleted file mode 100644 index a8556dd2c..000000000 --- a/artiq/test/lit/embedding/error_attr_conflict.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - pass - -i1 = c() -i1.x = 1 - -i2 = c() -i2.x = 1.0 - -@kernel -def entrypoint(): - # CHECK-L: :1: error: host object has an attribute 'x' of type float, which is different from previously inferred type numpy.int32 for the same attribute - i1.x - # CHECK-L: ${LINE:+1}: note: expanded from here - i2.x diff --git a/artiq/test/lit/embedding/error_attr_constant.py b/artiq/test/lit/embedding/error_attr_constant.py deleted file mode 100644 index d78d4a65c..000000000 --- a/artiq/test/lit/embedding/error_attr_constant.py +++ /dev/null @@ -1,18 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - kernel_invariants = {"a"} - - def __init__(self): - self.a = 1 - -i = c() - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: error: cannot assign to constant attribute 'a' of class 'testbench.c' - i.a = 1 diff --git a/artiq/test/lit/embedding/error_attr_unify.py b/artiq/test/lit/embedding/error_attr_unify.py deleted file mode 100644 index c8f6321a8..000000000 --- a/artiq/test/lit/embedding/error_attr_unify.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - x = [1, "x"] - -@kernel -def entrypoint(): - # CHECK-L: :1: error: cannot unify numpy.int? with str - # CHECK-NEXT-L: [1, 'x'] - # CHECK-L: ${LINE:+1}: note: expanded from here - a = c - # CHECK-L: ${LINE:+1}: note: while inferring a type for an attribute 'x' of a host object - a.x diff --git a/artiq/test/lit/embedding/error_host_only.py b/artiq/test/lit/embedding/error_host_only.py deleted file mode 100644 index 2e2eba6db..000000000 --- a/artiq/test/lit/embedding/error_host_only.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class foo: - # CHECK-L: ${LINE:+2}: fatal: this function cannot be called as an RPC - @host_only - def pause(self): - pass - -x = foo() - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+2}: note: in function called remotely here - # CHECK-L: ${LINE:+1}: note: while inferring a type for an attribute 'pause' of a host object - x.pause() diff --git a/artiq/test/lit/embedding/error_kernel_method_no_self.py b/artiq/test/lit/embedding/error_kernel_method_no_self.py deleted file mode 100644 index ed129fc39..000000000 --- a/artiq/test/lit/embedding/error_kernel_method_no_self.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - pass -@kernel -def f(): - pass -c.f = f -x = c().f - -@kernel -def entrypoint(): - # CHECK-L: :1: error: function 'f()->NoneType delay('a)' of class 'testbench.c' cannot accept a self argument - # CHECK-L: ${LINE:+1}: note: expanded from here - x diff --git a/artiq/test/lit/embedding/error_kernel_method_self_unify.py b/artiq/test/lit/embedding/error_kernel_method_self_unify.py deleted file mode 100644 index 925ee1d20..000000000 --- a/artiq/test/lit/embedding/error_kernel_method_self_unify.py +++ /dev/null @@ -1,24 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -@kernel -def f(self): - core_log(self.x) -class c: - a = f - x = 1 -class d: - b = f - x = 2 -xa = c().a -xb = d().b - -@kernel -def entrypoint(): - xa() - # CHECK-L: :1: error: cannot unify with %t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -def foo(): - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: fatal: name 'fo0' is not bound to anything; did you mean 'foo'? - fo0() diff --git a/artiq/test/lit/embedding/error_rpc_annot_return.py b/artiq/test/lit/embedding/error_rpc_annot_return.py deleted file mode 100644 index b60709f7c..000000000 --- a/artiq/test/lit/embedding/error_rpc_annot_return.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+1}: error: type annotation for return type, '1', is not an ARTIQ type -def foo() -> 1: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: in function called remotely here - foo() diff --git a/artiq/test/lit/embedding/error_rpc_async_return.py b/artiq/test/lit/embedding/error_rpc_async_return.py deleted file mode 100644 index 72f3c79c3..000000000 --- a/artiq/test/lit/embedding/error_rpc_async_return.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: fatal: functions that return a value cannot be defined as async RPCs -@rpc(flags={"async"}) -def foo() -> TInt32: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: function called here - foo() diff --git a/artiq/test/lit/embedding/error_specialized_annot.py b/artiq/test/lit/embedding/error_specialized_annot.py deleted file mode 100644 index 5d901d0d4..000000000 --- a/artiq/test/lit/embedding/error_specialized_annot.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.experiment import * - -class c(): -# CHECK-L: ${LINE:+2}: error: type annotation for argument 'x', '', is not an ARTIQ type - @kernel - def hello(self, x: list): - pass - - @kernel - def run(self): - self.hello([]) - -i = c() -@kernel -def entrypoint(): - i.run() diff --git a/artiq/test/lit/embedding/error_subkernel_annot.py b/artiq/test/lit/embedding/error_subkernel_annot.py deleted file mode 100644 index 3f4bc1c5c..000000000 --- a/artiq/test/lit/embedding/error_subkernel_annot.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: type annotation for argument 'x', '1', is not an ARTIQ type -@subkernel(destination=1) -def foo(x: 1) -> TNone: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: in subkernel call here - foo() diff --git a/artiq/test/lit/embedding/error_subkernel_annot_return.py b/artiq/test/lit/embedding/error_subkernel_annot_return.py deleted file mode 100644 index 51977ae00..000000000 --- a/artiq/test/lit/embedding/error_subkernel_annot_return.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: type annotation for return type, '1', is not an ARTIQ type -@subkernel(destination=1) -def foo() -> 1: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: in subkernel call here - foo() diff --git a/artiq/test/lit/embedding/error_syscall_annot.py b/artiq/test/lit/embedding/error_syscall_annot.py deleted file mode 100644 index 4849910b3..000000000 --- a/artiq/test/lit/embedding/error_syscall_annot.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: type annotation for argument 'x', '1', is not an ARTIQ type -@syscall -def foo(x: 1) -> TNone: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: in system call here - foo() diff --git a/artiq/test/lit/embedding/error_syscall_annot_return.py b/artiq/test/lit/embedding/error_syscall_annot_return.py deleted file mode 100644 index dfc24db9f..000000000 --- a/artiq/test/lit/embedding/error_syscall_annot_return.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: type annotation for return type, '1', is not an ARTIQ type -@syscall -def foo() -> 1: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: in system call here - foo() diff --git a/artiq/test/lit/embedding/error_syscall_arg.py b/artiq/test/lit/embedding/error_syscall_arg.py deleted file mode 100644 index a077e7d57..000000000 --- a/artiq/test/lit/embedding/error_syscall_arg.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: system call argument 'x' must have a type annotation -@syscall -def foo(x) -> TNone: - pass - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: note: in system call here - foo() diff --git a/artiq/test/lit/embedding/error_syscall_default_arg.py b/artiq/test/lit/embedding/error_syscall_default_arg.py deleted file mode 100644 index 0bfdce88a..000000000 --- a/artiq/test/lit/embedding/error_syscall_default_arg.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: system call argument 'x' must not have a default value -@syscall -def foo(x=1) -> TNone: - pass - -@kernel -def entrypoint(): - foo() diff --git a/artiq/test/lit/embedding/error_syscall_return.py b/artiq/test/lit/embedding/error_syscall_return.py deleted file mode 100644 index bad7b20d1..000000000 --- a/artiq/test/lit/embedding/error_syscall_return.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: ${LINE:+2}: error: system call must have a return type annotation -@syscall -def foo(): - pass - -@kernel -def entrypoint(): - foo() diff --git a/artiq/test/lit/embedding/error_tuple_index_assign.py b/artiq/test/lit/embedding/error_tuple_index_assign.py deleted file mode 100644 index ffa68a6b6..000000000 --- a/artiq/test/lit/embedding/error_tuple_index_assign.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -@kernel -def modify(x): - # CHECK-L: ${LINE:+1}: error: cannot assign to a tuple element - x[0] = 2 - -@kernel -def entrypoint(): - modify((1, "foo", True)) - modify((2, "bar", False)) diff --git a/artiq/test/lit/embedding/eval.py b/artiq/test/lit/embedding/eval.py deleted file mode 100644 index d2f91cbf9..000000000 --- a/artiq/test/lit/embedding/eval.py +++ /dev/null @@ -1,18 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * - - -def make_incrementer(increment): - return kernel_from_string(["a"], "return a + {}".format(increment), - portable) - - -foo = make_incrementer(1) -bar = make_incrementer(2) - - -@kernel -def entrypoint(): - assert foo(4) == 5 - assert bar(4) == 6 diff --git a/artiq/test/lit/embedding/exception.py b/artiq/test/lit/embedding/exception.py deleted file mode 100644 index 0a0830de4..000000000 --- a/artiq/test/lit/embedding/exception.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -from artiq.coredevice.exceptions import RTIOUnderflow - -@kernel -def entrypoint(): - try: - pass - except RTIOUnderflow: - pass diff --git a/artiq/test/lit/embedding/fast_math_flags.py b/artiq/test/lit/embedding/fast_math_flags.py deleted file mode 100644 index 3ed40f70d..000000000 --- a/artiq/test/lit/embedding/fast_math_flags.py +++ /dev/null @@ -1,20 +0,0 @@ -# RUN: env ARTIQ_DUMP_UNOPT_LLVM=%t %python -m artiq.compiler.testbench.embedding +compile %s -# RUN: OutputCheck %s --file-to-check=%t_unopt.ll - -from artiq.language.core import * -from artiq.language.types import * - -# CHECK-L: fmul fast double 1.000000e+00, 0.000000e+00 -@kernel(flags=["fast-math"]) -def foo(): - core_log(1.0 * 0.0) - -# CHECK-L: fmul fast double 2.000000e+00, 0.000000e+00 -@portable(flags=["fast-math"]) -def bar(): - core_log(2.0 * 0.0) - -@kernel -def entrypoint(): - foo() - bar() diff --git a/artiq/test/lit/embedding/fn_ptr_list.py b/artiq/test/lit/embedding/fn_ptr_list.py deleted file mode 100644 index 73e6ad3be..000000000 --- a/artiq/test/lit/embedding/fn_ptr_list.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -@kernel -def a(): - pass - -fns = [a, a] - -@kernel -def entrypoint(): - fns[0]() - fns[1]() diff --git a/artiq/test/lit/embedding/function_polymorphism.py b/artiq/test/lit/embedding/function_polymorphism.py deleted file mode 100644 index 52df5ee4f..000000000 --- a/artiq/test/lit/embedding/function_polymorphism.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -def f(x): - print(x) - -@kernel -def entrypoint(): - f("foo") - f(42) diff --git a/artiq/test/lit/embedding/index_tuple.py b/artiq/test/lit/embedding/index_tuple.py deleted file mode 100644 index fced90201..000000000 --- a/artiq/test/lit/embedding/index_tuple.py +++ /dev/null @@ -1,16 +0,0 @@ -# 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 * - -# CHECK-L: void @_Z16testbench.unpackzz({ i32, { i8*, i32 }, i1 } %ARG.x) - -@kernel -def unpack(x): - print(x[0]) - -@kernel -def entrypoint(): - unpack((1, "foo", True)) - unpack((2, "bar", False)) diff --git a/artiq/test/lit/embedding/inheritance.py b/artiq/test/lit/embedding/inheritance.py deleted file mode 100644 index 0863d9163..000000000 --- a/artiq/test/lit/embedding/inheritance.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -class a: - @kernel - def f(self): - print(self.x) - return None - -class b(a): - x = 1 -class c(a): - x = 2 - -bi = b() -ci = c() -@kernel -def entrypoint(): - bi.f() - ci.f() diff --git a/artiq/test/lit/embedding/invariant_nested.py b/artiq/test/lit/embedding/invariant_nested.py deleted file mode 100644 index 5957dcb3d..000000000 --- a/artiq/test/lit/embedding/invariant_nested.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - - -class ClassA: - def __init__(self): - self.foo = False - - -class ClassB: - kernel_invariants = {"bar"} - - def __init__(self): - self.bar = ClassA() - -obj = ClassB() - -@kernel -def entrypoint(): - obj.bar.foo = True diff --git a/artiq/test/lit/embedding/invariant_propagation.py b/artiq/test/lit/embedding/invariant_propagation.py deleted file mode 100644 index e1af5c9c1..000000000 --- a/artiq/test/lit/embedding/invariant_propagation.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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 * - -class Class: - kernel_invariants = {"foo"} - - def __init__(self): - self.foo = True - - @kernel - def run(self): - if self.foo: - print("bar") - else: - # Make sure all the code for this branch will be completely elided: - # CHECK-NOT: baz - print("baz") - -obj = Class() - -@kernel -def entrypoint(): - obj.run() diff --git a/artiq/test/lit/embedding/math_fns.py b/artiq/test/lit/embedding/math_fns.py deleted file mode 100644 index 6f9416c8d..000000000 --- a/artiq/test/lit/embedding/math_fns.py +++ /dev/null @@ -1,31 +0,0 @@ -# RUN: env ARTIQ_DUMP_LLVM=%t %python -m artiq.compiler.testbench.embedding %s -# RUN: OutputCheck %s --file-to-check=%t.ll - -from artiq.language.core import * -from artiq.language.types import * -import numpy - -@kernel -def entrypoint(): - # LLVM's constant folding for transcendental functions is good enough that - # we can do a basic smoke test by just making sure the module compiles and - # all assertions are statically eliminated. - - # CHECK-NOT: assert - assert numpy.sin(0.0) == 0.0 - assert numpy.cos(0.0) == 1.0 - assert numpy.exp(0.0) == 1.0 - assert numpy.exp2(1.0) == 2.0 - assert numpy.log(numpy.exp(1.0)) == 1.0 - assert numpy.log10(10.0) == 1.0 - assert numpy.log2(2.0) == 1.0 - assert numpy.fabs(-1.0) == 1.0 - assert numpy.floor(42.5) == 42.0 - assert numpy.ceil(42.5) == 43.0 - assert numpy.trunc(41.5) == 41.0 - assert numpy.rint(41.5) == 42.0 - assert numpy.tan(0.0) == 0.0 - assert numpy.arcsin(0.0) == 0.0 - assert numpy.arccos(1.0) == 0.0 - assert numpy.arctan(0.0) == 0.0 - assert numpy.arctan2(0.0, 1.0) == 0.0 diff --git a/artiq/test/lit/embedding/method_on_instance.py b/artiq/test/lit/embedding/method_on_instance.py deleted file mode 100644 index 018545f6b..000000000 --- a/artiq/test/lit/embedding/method_on_instance.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -class a: - def foo(self, x): - print(x) - -class b: - def __init__(self): - self.obj = a() - self.meth = self.obj.foo - - @kernel - def run(self): - self.meth(1) - -bi = b() -@kernel -def entrypoint(): - bi.run() diff --git a/artiq/test/lit/embedding/mixed_tuple.py b/artiq/test/lit/embedding/mixed_tuple.py deleted file mode 100644 index aaf947149..000000000 --- a/artiq/test/lit/embedding/mixed_tuple.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -@kernel -def consume_tuple(x: TTuple([TInt32, TBool])): - print(x) - -@kernel -def return_tuple() -> TTuple([TInt32, TBool]): - return (123, False) - -@kernel -def entrypoint(): - consume_tuple(return_tuple()) diff --git a/artiq/test/lit/embedding/module.py b/artiq/test/lit/embedding/module.py deleted file mode 100644 index d25475b6d..000000000 --- a/artiq/test/lit/embedding/module.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -import time, os - -@kernel -def entrypoint(): - time.sleep(10) - os.mkdir("foo") diff --git a/artiq/test/lit/embedding/rpc_method_polymorphism.py b/artiq/test/lit/embedding/rpc_method_polymorphism.py deleted file mode 100644 index b7ca9f525..000000000 --- a/artiq/test/lit/embedding/rpc_method_polymorphism.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * - -class c: - def p(self, foo): - print(foo) -i = c() - -@kernel -def entrypoint(): - i.p("foo") - i.p(42) diff --git a/artiq/test/lit/embedding/subkernel_message_recv.py b/artiq/test/lit/embedding/subkernel_message_recv.py deleted file mode 100644 index 35e094aa6..000000000 --- a/artiq/test/lit/embedding/subkernel_message_recv.py +++ /dev/null @@ -1,22 +0,0 @@ -# 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 * - -@kernel -def entrypoint(): - # CHECK: call void @subkernel_load_run\(i32 1, i8 1, i1 true\), !dbg !. - message_pass() - # CHECK-NOT: call void @subkernel_send_message\(i32 ., i1 false, i8 1, i8 1, .*\), !dbg !. - # CHECK: call i8 @subkernel_await_message\(i32 2, i64 -1, { i8\*, i32 }\* nonnull .*, i8 1, i8 1\), !dbg !. - subkernel_recv("message", TInt32) - - -# CHECK-L: declare void @subkernel_load_run(i32, i8, i1) local_unnamed_addr -# CHECK-NOT-L: declare void @subkernel_send_message(i32, i1, i8, i8, { i8*, i32 }*, i8**) local_unnamed_addr -# CHECK-L: declare i8 @subkernel_await_message(i32, i64, { i8*, i32 }*, i8, i8) local_unnamed_addr -# CHECK-NOT-L: declare void @subkernel_await_finish(i32, i64) local_unnamed_addr -@subkernel(destination=1) -def message_pass() -> TNone: - subkernel_send(0, "message", 15) diff --git a/artiq/test/lit/embedding/subkernel_message_send.py b/artiq/test/lit/embedding/subkernel_message_send.py deleted file mode 100644 index 3f0f77e35..000000000 --- a/artiq/test/lit/embedding/subkernel_message_send.py +++ /dev/null @@ -1,20 +0,0 @@ -# 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 * - -@kernel -def entrypoint(): - # CHECK: call void @subkernel_load_run\(i32 1, i8 1, i1 true\), !dbg !. - message_pass() - # CHECK: call void @subkernel_send_message\(i32 2, i1 false, i8 1, i8 1, .*\), !dbg !. - # CHECK-NOT: call i8 @subkernel_await_message\(i32 1, i64 10000, { i8\*, i32 }\* nonnull .*, i8 1, i8 1\), !dbg !. - subkernel_send(1, "message", 15) - - -# CHECK-L: declare void @subkernel_load_run(i32, i8, i1) local_unnamed_addr -# CHECK-L: declare void @subkernel_send_message(i32, i1, i8, i8, { i8*, i32 }*, i8**) local_unnamed_addr -@subkernel(destination=1) -def message_pass() -> TNone: - subkernel_recv("message", TInt32) diff --git a/artiq/test/lit/embedding/subkernel_no_arg.py b/artiq/test/lit/embedding/subkernel_no_arg.py deleted file mode 100644 index 605fbbfaa..000000000 --- a/artiq/test/lit/embedding/subkernel_no_arg.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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 * - -@kernel -def entrypoint(): - # CHECK: call void @subkernel_load_run\(i32 1, i8 1, i1 true\), !dbg !. - # CHECK-NOT: call void @subkernel_send_message\(.*\), !dbg !. - no_arg() - - -# CHECK-L: declare void @subkernel_load_run(i32, i8, i1) local_unnamed_addr -# CHECK-NOT-L: declare void @subkernel_send_message(i32, i1, i8, { i8*, i32 }*, i8**) local_unnamed_addr -@subkernel(destination=1) -def no_arg() -> TStr: - pass diff --git a/artiq/test/lit/embedding/subkernel_return.py b/artiq/test/lit/embedding/subkernel_return.py deleted file mode 100644 index 3c9d1169a..000000000 --- a/artiq/test/lit/embedding/subkernel_return.py +++ /dev/null @@ -1,22 +0,0 @@ -# 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 * - -@kernel -def entrypoint(): - # CHECK: call void @subkernel_load_run\(i32 1, i8 1, i1 true\), !dbg !. - # CHECK-NOT: call void @subkernel_send_message\(.*\), !dbg !. - returning() - # CHECK: call i8 @subkernel_await_message\(i32 1, i64 -1, { i8\*, i32 }\* nonnull .*, i8 1, i8 1\), !dbg !. - # CHECK: call void @subkernel_await_finish\(i32 1, i64 -1\), !dbg !. - subkernel_await(returning) - -# CHECK-L: declare void @subkernel_load_run(i32, i8, i1) local_unnamed_addr -# CHECK-NOT-L: declare void @subkernel_send_message(i32, i1, i8, i8, { i8*, i32 }*, i8**) local_unnamed_addr -# CHECK-L: declare i8 @subkernel_await_message(i32, i64, { i8*, i32 }*, i8, i8) local_unnamed_addr -# CHECK-L: declare void @subkernel_await_finish(i32, i64) local_unnamed_addr -@subkernel(destination=1) -def returning() -> TInt32: - return 1 diff --git a/artiq/test/lit/embedding/subkernel_return_none.py b/artiq/test/lit/embedding/subkernel_return_none.py deleted file mode 100644 index a7795f785..000000000 --- a/artiq/test/lit/embedding/subkernel_return_none.py +++ /dev/null @@ -1,22 +0,0 @@ -# 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 * - -@kernel -def entrypoint(): - # CHECK: call void @subkernel_load_run\(i32 1, i8 1, i1 true\), !dbg !. - # CHECK-NOT: call void @subkernel_send_message\(.*\), !dbg !. - returning_none() - # CHECK: call void @subkernel_await_finish\(i32 1, i64 -1\), !dbg !. - # CHECK-NOT: call i8 @subkernel_await_message\(i32 1, i64 -1\, .*\), !dbg !. - subkernel_await(returning_none) - -# CHECK-L: declare void @subkernel_load_run(i32, i8, i1) local_unnamed_addr -# CHECK-NOT-L: declare void @subkernel_send_message(i32, i1, i8, { i8*, i32 }*, i8**) local_unnamed_addr -# CHECK-L: declare void @subkernel_await_finish(i32, i64) local_unnamed_addr -# CHECK-NOT-L: declare i8 @subkernel_await_message(i32, i64, { i8*, i32 }*, i8, i8) local_unnamed_addr -@subkernel(destination=1) -def returning_none() -> TNone: - pass diff --git a/artiq/test/lit/embedding/subkernel_self.py b/artiq/test/lit/embedding/subkernel_self.py deleted file mode 100644 index 7bf9cbafd..000000000 --- a/artiq/test/lit/embedding/subkernel_self.py +++ /dev/null @@ -1,25 +0,0 @@ -# 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 * - -class A: - @subkernel(destination=1) - def sk(self): - pass - - @kernel - def kernel_entrypoint(self): - # CHECK: call void @subkernel_load_run\(i32 1, i8 1, i1 true\), !dbg !. - # CHECK-NOT: call void @subkernel_send_message\(.*\), !dbg !. - self.sk() - -a = A() - -@kernel -def entrypoint(): - a.kernel_entrypoint() - -# CHECK-L: declare void @subkernel_load_run(i32, i8, i1) local_unnamed_addr -# CHECK-NOT-L: declare void @subkernel_send_message(i32, i1, i8, i8, { i8*, i32 }*, i8**) local_unnamed_addr diff --git a/artiq/test/lit/embedding/subkernel_self_args.py b/artiq/test/lit/embedding/subkernel_self_args.py deleted file mode 100644 index 5aebed2e9..000000000 --- a/artiq/test/lit/embedding/subkernel_self_args.py +++ /dev/null @@ -1,25 +0,0 @@ -# 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 * - -class A: - @subkernel(destination=1) - def sk(self, a): - pass - - @kernel - def kernel_entrypoint(self): - # CHECK: call void @subkernel_load_run\(i32 1, i8 1, i1 true\), !dbg !. - # CHECK: call void @subkernel_send_message\(i32 1, i1 false, i8 1, i8 1, .*\), !dbg !. - self.sk(1) - -a = A() - -@kernel -def entrypoint(): - a.kernel_entrypoint() - -# CHECK-L: declare void @subkernel_load_run(i32, i8, i1) local_unnamed_addr -# CHECK-L: declare void @subkernel_send_message(i32, i1, i8, i8, { i8*, i32 }*, i8**) local_unnamed_addr diff --git a/artiq/test/lit/embedding/subkernel_with_arg.py b/artiq/test/lit/embedding/subkernel_with_arg.py deleted file mode 100644 index 114516586..000000000 --- a/artiq/test/lit/embedding/subkernel_with_arg.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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 * - -@kernel -def entrypoint(): - # CHECK: call void @subkernel_load_run\(i32 1, i8 1, i1 true\), !dbg !. - # CHECK: call void @subkernel_send_message\(i32 ., i1 false, i8 1, i8 1, .*\), !dbg !. - accept_arg(1) - - -# CHECK-L: declare void @subkernel_load_run(i32, i8, i1) local_unnamed_addr -# CHECK-L: declare void @subkernel_send_message(i32, i1, i8, i8, { i8*, i32 }*, i8**) local_unnamed_addr -@subkernel(destination=1) -def accept_arg(arg: TInt32) -> TNone: - pass diff --git a/artiq/test/lit/embedding/subkernel_with_opt_arg.py b/artiq/test/lit/embedding/subkernel_with_opt_arg.py deleted file mode 100644 index fb5cc3df1..000000000 --- a/artiq/test/lit/embedding/subkernel_with_opt_arg.py +++ /dev/null @@ -1,21 +0,0 @@ -# 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 * - -@kernel -def entrypoint(): - # CHECK: call void @subkernel_load_run\(i32 1, i8 1, i1 true\), !dbg !. - # CHECK: call void @subkernel_send_message\(i32 ., i1 false, i8 1, i8 1, .*\), !dbg !. - accept_arg(1) - # CHECK: call void @subkernel_load_run\(i32 1, i8 1, i1 true\), !dbg !. - # CHECK: call void @subkernel_send_message\(i32 ., i1 false, i8 1, i8 2, .*\), !dbg !. - accept_arg(1, 2) - - -# CHECK-L: declare void @subkernel_load_run(i32, i8, i1) local_unnamed_addr -# CHECK-L: declare void @subkernel_send_message(i32, i1, i8, i8, { i8*, i32 }*, i8**) local_unnamed_addr -@subkernel(destination=1) -def accept_arg(arg_a, arg_b=5) -> TNone: - pass diff --git a/artiq/test/lit/embedding/syscall_arg_attrs.py b/artiq/test/lit/embedding/syscall_arg_attrs.py deleted file mode 100644 index a20d90bed..000000000 --- a/artiq/test/lit/embedding/syscall_arg_attrs.py +++ /dev/null @@ -1,30 +0,0 @@ -# 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\({ i8\*, i32 }\)\) -@syscall -def accept_str(name: TStr) -> TNone: - pass - - -# CHECK: declare void @return_str\({ i8\*, i32 }\* sret\({ i8\*, i32 }\)\) -@syscall -def return_str() -> TStr: - pass diff --git a/artiq/test/lit/embedding/syscall_flags.py b/artiq/test/lit/embedding/syscall_flags.py deleted file mode 100644 index b2e27d6a0..000000000 --- a/artiq/test/lit/embedding/syscall_flags.py +++ /dev/null @@ -1,26 +0,0 @@ -# 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 * - -# CHECK: call void @foo\(\)(, !dbg !\d+)? - -# CHECK-L: ; Function Attrs: inaccessiblememonly nounwind -# CHECK-NEXT-L: declare void @foo() - -@syscall(flags={"nounwind", "nowrite"}) -def foo() -> TNone: - pass - -# sret nowrite functions shouldn't be marked inaccessiblememonly. -# CHECK-L: ; Function Attrs: nounwind -# CHECK-NEXT-L: declare void @bar({ i32, i64 }* sret({ i32, i64 })) -@syscall(flags={"nounwind", "nowrite"}) -def bar() -> TTuple([TInt32, TInt64]): - pass - -@kernel -def entrypoint(): - foo() - bar() diff --git a/artiq/test/lit/embedding/tuple.py b/artiq/test/lit/embedding/tuple.py deleted file mode 100644 index 6f9a14a32..000000000 --- a/artiq/test/lit/embedding/tuple.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * - -values = (1, 2) - -@kernel -def entrypoint(): - assert values == (1, 2) diff --git a/artiq/test/lit/embedding/warning_invariant_1.py b/artiq/test/lit/embedding/warning_invariant_1.py deleted file mode 100644 index 5a0b8bdd1..000000000 --- a/artiq/test/lit/embedding/warning_invariant_1.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - a = b = 0 - def __init__(self, invariants): - self.kernel_invariants = invariants - - def __repr__(self): - return "" - -i1 = c({'a'}) -i2 = c({'a', 'b'}) - -@kernel -def entrypoint(): - # CHECK-L: :1: warning: object of type declares attribute(s) 'b' as kernel invariant, but other objects of the same type do not; the invariant annotation on this object will be ignored - # CHECK-L: ${LINE:+1}: note: expanded from here - [i1, i2] diff --git a/artiq/test/lit/embedding/warning_invariant_2.py b/artiq/test/lit/embedding/warning_invariant_2.py deleted file mode 100644 index b630b21d8..000000000 --- a/artiq/test/lit/embedding/warning_invariant_2.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - a = b = 0 - def __init__(self, invariants): - self.kernel_invariants = invariants - - def __repr__(self): - return "" - -i1 = c({"a", "b"}) -i2 = c({"a"}) - -@kernel -def entrypoint(): - # CHECK-L: :1: warning: object of type does not declare attribute(s) 'b' as kernel invariant, but other objects of the same type do; the invariant annotation on other objects will be ignored - # CHECK-L: ${LINE:+1}: note: expanded from here - [i1, i2] diff --git a/artiq/test/lit/embedding/warning_invariant_missing.py b/artiq/test/lit/embedding/warning_invariant_missing.py deleted file mode 100644 index 6fc91028f..000000000 --- a/artiq/test/lit/embedding/warning_invariant_missing.py +++ /dev/null @@ -1,20 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.language.core import * -from artiq.language.types import * - -class c: - kernel_invariants = {"a", "b"} - a = 0 - - def __repr__(self): - return "" - -i = c() - -@kernel -def entrypoint(): - # CHECK-L: :1: warning: object of type declares attribute 'b' as kernel invariant, but the instance referenced here does not have this attribute - # CHECK-L: ${LINE:+1}: note: expanded from here - i diff --git a/artiq/test/lit/escape/const_string.py b/artiq/test/lit/escape/const_string.py deleted file mode 100644 index f5c844c05..000000000 --- a/artiq/test/lit/escape/const_string.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.experiment import * - -@kernel -def foo(): - return "x" - -@kernel -def entrypoint(): - foo() - - # Test reassigning strings. - a = "a" - b = a diff --git a/artiq/test/lit/escape/device_db.py b/artiq/test/lit/escape/device_db.py deleted file mode 100644 index e39c83c09..000000000 --- a/artiq/test/lit/escape/device_db.py +++ /dev/null @@ -1,8 +0,0 @@ -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {"host": None, "ref_period": 1e-9} - } -} diff --git a/artiq/test/lit/escape/error_list_aug_asgn.py b/artiq/test/lit/escape/error_list_aug_asgn.py deleted file mode 100644 index 1cdbd8838..000000000 --- a/artiq/test/lit/escape/error_list_aug_asgn.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.experiment import * - -@kernel -def entrypoint(): - a = [1,2] - # CHECK-L: ${LINE:+2}: error: lists cannot be mutated in-place - # CHECK-L: ${LINE:+1}: note: try using `a = a + [3,4]` - a += [3,4] - diff --git a/artiq/test/lit/escape/error_mutable_attr.py b/artiq/test/lit/escape/error_mutable_attr.py deleted file mode 100644 index 2d5bc885a..000000000 --- a/artiq/test/lit/escape/error_mutable_attr.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.experiment import * - -class c: - x = [] - -cc = c() - -@kernel -def entrypoint(): - # CHECK-L: ${LINE:+1}: error: the assigned value does not outlive the assignment target - cc.x = [1] diff --git a/artiq/test/lit/escape/error_numpy_array.py b/artiq/test/lit/escape/error_numpy_array.py deleted file mode 100644 index 1ebdeda81..000000000 --- a/artiq/test/lit/escape/error_numpy_array.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.experiment import * -import numpy as np - -@kernel -def a(): - # CHECK-L: ${LINE:+2}: error: cannot return an allocated value that does not live forever - # CHECK-L: ${LINE:+1}: note: ... to this point - return np.array([0, 1]) - -@kernel -def entrypoint(): - a() diff --git a/artiq/test/lit/escape/error_numpy_full.py b/artiq/test/lit/escape/error_numpy_full.py deleted file mode 100644 index 66158d8ca..000000000 --- a/artiq/test/lit/escape/error_numpy_full.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.experiment import * -import numpy as np - -@kernel -def a(): - # CHECK-L: ${LINE:+2}: error: cannot return an allocated value that does not live forever - # CHECK-L: ${LINE:+1}: note: ... to this point - return np.full(10, 42.0) - - -@kernel -def entrypoint(): - a() diff --git a/artiq/test/lit/escape/error_numpy_transpose.py b/artiq/test/lit/escape/error_numpy_transpose.py deleted file mode 100644 index e1dc32d51..000000000 --- a/artiq/test/lit/escape/error_numpy_transpose.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.experiment import * -import numpy as np - -data = np.array([[0, 1], [2, 3]]) - -@kernel -def a(): - # CHECK-L: ${LINE:+2}: error: cannot return an allocated value that does not live forever - # CHECK-L: ${LINE:+1}: note: ... to this point - return np.transpose(data) - -@kernel -def entrypoint(): - a() diff --git a/artiq/test/lit/escape/error_string.py b/artiq/test/lit/escape/error_string.py deleted file mode 100644 index 33fbb71a6..000000000 --- a/artiq/test/lit/escape/error_string.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding +diag %s 2>%t -# RUN: OutputCheck %s --file-to-check=%t - -from artiq.experiment import * - -@kernel -def foo(): - # CHECK-NOT-L: ${LINE:+1}: error: - return "x" - -@kernel -def bar(): - # CHECK-L: ${LINE:+1}: error: cannot return an allocated value that does not live forever - return "x" + "y" - -@kernel -def entrypoint(): - foo() - bar() diff --git a/artiq/test/lit/exceptions/catch.py b/artiq/test/lit/exceptions/catch.py deleted file mode 100644 index d6c2866c1..000000000 --- a/artiq/test/lit/exceptions/catch.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -try: - 1/0 -except ZeroDivisionError: - # CHECK-L: OK - print("OK") diff --git a/artiq/test/lit/exceptions/catch_all.py b/artiq/test/lit/exceptions/catch_all.py deleted file mode 100644 index f3f497f12..000000000 --- a/artiq/test/lit/exceptions/catch_all.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def catch(f): - try: - f() - except Exception as e: - print(e) - -# CHECK-L: 19(0, 0, 0) -catch(lambda: 1/0) -# CHECK-L: 10(10, 1, 0) -catch(lambda: [1.0][10]) diff --git a/artiq/test/lit/exceptions/catch_multi.py b/artiq/test/lit/exceptions/catch_multi.py deleted file mode 100644 index 72712785d..000000000 --- a/artiq/test/lit/exceptions/catch_multi.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def catch(f): - try: - f() - except ZeroDivisionError as zde: - print(zde) - except IndexError as ie: - print(ie) - -# CHECK-L: 19(0, 0, 0) -catch(lambda: 1/0) -# CHECK-L: 10(10, 1, 0) -catch(lambda: [1.0][10]) diff --git a/artiq/test/lit/exceptions/catch_outer.py b/artiq/test/lit/exceptions/catch_outer.py deleted file mode 100644 index de7253eaf..000000000 --- a/artiq/test/lit/exceptions/catch_outer.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - except ValueError: - # CHECK-NOT-L: FAIL - print("FAIL") - -try: - f() -except ZeroDivisionError: - # CHECK-L: OK - print("OK") diff --git a/artiq/test/lit/exceptions/finally.py b/artiq/test/lit/exceptions/finally.py deleted file mode 100644 index 17304fc15..000000000 --- a/artiq/test/lit/exceptions/finally.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - finally: - print("f-fin") - print("f-out") - -def g(): - try: - f() - except: - print("g-except") - -# CHECK-L: f-fin -# CHECK-NOT-L: f-out -# CHECK-L: g-except -g() diff --git a/artiq/test/lit/exceptions/finally_catch.py b/artiq/test/lit/exceptions/finally_catch.py deleted file mode 100644 index 23bc39730..000000000 --- a/artiq/test/lit/exceptions/finally_catch.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - except: - print("f-except") - finally: - print("f-fin") - print("f-out") - -# CHECK-L: f-except -# CHECK-L: f-fin -# CHECK-L: f-out -f() diff --git a/artiq/test/lit/exceptions/finally_catch_try.py b/artiq/test/lit/exceptions/finally_catch_try.py deleted file mode 100644 index c1b500a78..000000000 --- a/artiq/test/lit/exceptions/finally_catch_try.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def doit(): - try: - try: - raise RuntimeError("Error") - except ValueError: - print("ValueError") - except RuntimeError: - print("Caught") - finally: - print("Cleanup") - -doit() - -# CHECK-L: Caught -# CHECK-NEXT-L: Cleanup diff --git a/artiq/test/lit/exceptions/finally_raise.py b/artiq/test/lit/exceptions/finally_raise.py deleted file mode 100644 index 02c41ea7e..000000000 --- a/artiq/test/lit/exceptions/finally_raise.py +++ /dev/null @@ -1,23 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - finally: - print("f-fin") - raise ValueError() - -def g(): - try: - f() - except ZeroDivisionError: - print("g-except-zde") - except ValueError: - print("g-except-ve") - -# CHECK-L: f-fin -# CHECK-L: g-except-ve -# CHECK-NOT-L: g-except-zde -g() diff --git a/artiq/test/lit/exceptions/finally_reraise.py b/artiq/test/lit/exceptions/finally_reraise.py deleted file mode 100644 index ef4da2af9..000000000 --- a/artiq/test/lit/exceptions/finally_reraise.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -x = 1 - -def doit(): - try: - if x > 0: - raise ZeroDivisionError - r = 0 - finally: - print('final') - return r - -try: - doit() -except ZeroDivisionError: - print('caught') - -# CHECK-L: final -# CHECK-L: caught diff --git a/artiq/test/lit/exceptions/finally_squash.py b/artiq/test/lit/exceptions/finally_squash.py deleted file mode 100644 index 8c7b58fc3..000000000 --- a/artiq/test/lit/exceptions/finally_squash.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - finally: - print("f-fin") - return - -def g(): - try: - f() - except: - print("g-except") - -# CHECK-L: f-fin -# CHECK-NOT-L: f-out -# CHECK-NOT-L: g-except -g() diff --git a/artiq/test/lit/exceptions/finally_try.py b/artiq/test/lit/exceptions/finally_try.py deleted file mode 100644 index 377ca1c06..000000000 --- a/artiq/test/lit/exceptions/finally_try.py +++ /dev/null @@ -1,20 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def doit(): - try: - try: - raise RuntimeError("Error") - except ValueError: - print("ValueError") - finally: - print("Cleanup") - -try: - doit() -except RuntimeError: - print("Caught") - -# CHECK-L: Cleanup -# CHECK-NEXT-L: Caught diff --git a/artiq/test/lit/exceptions/finally_uncaught.py b/artiq/test/lit/exceptions/finally_uncaught.py deleted file mode 100644 index 1eb211663..000000000 --- a/artiq/test/lit/exceptions/finally_uncaught.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - try: - 1/0 - finally: - print("f-fin") - -# CHECK-L: f-fin -f() diff --git a/artiq/test/lit/exceptions/reraise.py b/artiq/test/lit/exceptions/reraise.py deleted file mode 100644 index ef3f02dd1..000000000 --- a/artiq/test/lit/exceptions/reraise.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - # CHECK-L: Uncaught 19 - # CHECK-L: at input.py:${LINE:+1}: - 1/0 - -def g(): - try: - f() - except: - raise - -g() diff --git a/artiq/test/lit/exceptions/reraise_update.py b/artiq/test/lit/exceptions/reraise_update.py deleted file mode 100644 index 32e1d11dc..000000000 --- a/artiq/test/lit/exceptions/reraise_update.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def f(): - 1/0 - -def g(): - try: - f() - except Exception as e: - # CHECK-L: Uncaught 19 - # CHECK-L: at input.py:${LINE:+1}: - raise e - -g() diff --git a/artiq/test/lit/exceptions/uncaught.py b/artiq/test/lit/exceptions/uncaught.py deleted file mode 100644 index a86f93b88..000000000 --- a/artiq/test/lit/exceptions/uncaught.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %not %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -# CHECK-L: Uncaught 19: cannot divide by zero (0, 0, 0) -# CHECK-L: at input.py:${LINE:+1}: -1/0 diff --git a/artiq/test/lit/inferencer/array_creation.py b/artiq/test/lit/inferencer/array_creation.py deleted file mode 100644 index 824150c22..000000000 --- a/artiq/test/lit/inferencer/array_creation.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: numpy.array(elt='a, num_dims=1) -array([]) -# CHECK-L: numpy.array(elt='b, num_dims=2) -array([[], []]) - -# CHECK-L: numpy.array(elt=numpy.int?, num_dims=1) -array([1, 2, 3]) -# CHECK-L: numpy.array(elt=numpy.int?, num_dims=2) -array([[1, 2, 3], [4, 5, 6]]) - -# Jagged arrays produce runtime failure: -# CHECK-L: numpy.array(elt=numpy.int?, num_dims=2) -array([[1, 2, 3], [4, 5]]) diff --git a/artiq/test/lit/inferencer/builtin_calls.py b/artiq/test/lit/inferencer/builtin_calls.py deleted file mode 100644 index a4b2f81fe..000000000 --- a/artiq/test/lit/inferencer/builtin_calls.py +++ /dev/null @@ -1,38 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: bool:():bool -bool() - -# CHECK-L: bool:([]:list(elt='a)):bool -bool([]) - -# CHECK-L: int:():numpy.int? -int() - -# CHECK-L: int:(1.0:float):numpy.int? -int(1.0) - -# CHECK-L: int64:(1.0:float):numpy.int64 -int64(1.0) - -# CHECK-L: float:():float -float() - -# CHECK-L: float:(1:numpy.int?):float -float(1) - -# CHECK-L: list:():list(elt='b) -list() - -# CHECK-L: len:([]:list(elt='c)):numpy.int32 -len([]) - -# CHECK-L: round:(1.0:float):numpy.int? -round(1.0) - -# CHECK-L: abs:(1:numpy.int?):numpy.int? -abs(1) - -# CHECK-L: abs:(1.0:float):float -abs(1.0) diff --git a/artiq/test/lit/inferencer/cast.py b/artiq/test/lit/inferencer/cast.py deleted file mode 100644 index be2ddbf4a..000000000 --- a/artiq/test/lit/inferencer/cast.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +mono %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: 2:numpy.int64 -int64(2)**32 - -# CHECK-L: round:(1.0:float):numpy.int64 -int64(round(1.0)) diff --git a/artiq/test/lit/inferencer/class.py b/artiq/test/lit/inferencer/class.py deleted file mode 100644 index 4e26b7c58..000000000 --- a/artiq/test/lit/inferencer/class.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class c: - a = 1 - def f(): - pass - def m(self): - pass - -# CHECK-L: c:NoneType delay('a), m: (self:)->NoneType delay('b)}> -c -# CHECK-L: .a:numpy.int? -c.a -# CHECK-L: .f:()->NoneType delay('a) -c.f - -# CHECK-L: .m:method(fn=(self:)->NoneType delay('b), self=) -c().m() diff --git a/artiq/test/lit/inferencer/coerce.py b/artiq/test/lit/inferencer/coerce.py deleted file mode 100644 index 714bf6ad0..000000000 --- a/artiq/test/lit/inferencer/coerce.py +++ /dev/null @@ -1,41 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -1 | 2 -# CHECK-L: 1:numpy.int?:numpy.int? | 2:numpy.int?:numpy.int?:numpy.int? - -1 + 2 -# CHECK-L: 1:numpy.int?:numpy.int? + 2:numpy.int?:numpy.int?:numpy.int? - -(1,) + (2.0,) -# CHECK-L: (1:numpy.int?,):(numpy.int?,) + (2.0:float,):(float,):(numpy.int?, float) - -[1] + [2] -# CHECK-L: [1:numpy.int?]:list(elt=numpy.int?) + [2:numpy.int?]:list(elt=numpy.int?):list(elt=numpy.int?) - -1 * 2 -# CHECK-L: 1:numpy.int?:numpy.int? * 2:numpy.int?:numpy.int?:numpy.int? - -[1] * 2 -# CHECK-L: [1:numpy.int?]:list(elt=numpy.int?) * 2:numpy.int?:list(elt=numpy.int?) - -1 // 2 -# CHECK-L: 1:numpy.int?:numpy.int? // 2:numpy.int?:numpy.int?:numpy.int? - -1 + 1.0 -# CHECK-L: 1:numpy.int?:float + 1.0:float:float - -a = []; a += [1] -# CHECK-L: a:list(elt=numpy.int?) = []:list(elt=numpy.int?); a:list(elt=numpy.int?) += [1:numpy.int?]:list(elt=numpy.int?) - -[] is [1] -# CHECK-L: []:list(elt=numpy.int?) is [1:numpy.int?]:list(elt=numpy.int?):bool - -1 in [1] -# CHECK-L: 1:numpy.int? in [1:numpy.int?]:list(elt=numpy.int?):bool - -[] < [1] -# CHECK-L: []:list(elt=numpy.int?) < [1:numpy.int?]:list(elt=numpy.int?):bool - -1.0 < 1 -# CHECK-L: 1.0:float < 1:numpy.int?:float:bool diff --git a/artiq/test/lit/inferencer/coerce_explicit.py b/artiq/test/lit/inferencer/coerce_explicit.py deleted file mode 100644 index 4455c596c..000000000 --- a/artiq/test/lit/inferencer/coerce_explicit.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +mono %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: n:numpy.int32 = -n = 0 -# CHECK-L: a:numpy.int32 = -a = n // 1 -# CHECK-L: b:numpy.int32 = -b = n // 10 -# CHECK-L: q:numpy.int64 = -q = (a << 0) + (b << 8) -core_log(int64(q)) diff --git a/artiq/test/lit/inferencer/error_array.py b/artiq/test/lit/inferencer/error_array.py deleted file mode 100644 index 8099ef9d8..000000000 --- a/artiq/test/lit/inferencer/error_array.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: array cannot be invoked with the arguments () -a = array() - -b = array([1, 2, 3]) - -# CHECK-L: ${LINE:+1}: error: too many indices for array of dimension 1 -b[1, 2] - -# CHECK-L: ${LINE:+1}: error: strided slicing not yet supported for NumPy arrays -b[::-1] - -# CHECK-L: ${LINE:+1}: error: array attributes cannot be assigned to -b.shape = (5, ) diff --git a/artiq/test/lit/inferencer/error_array_augassign.py b/artiq/test/lit/inferencer/error_array_augassign.py deleted file mode 100644 index c868e50c6..000000000 --- a/artiq/test/lit/inferencer/error_array_augassign.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -a = array([[1, 2], [3, 4]]) -b = 0.0 - -# CHECK-L: ${LINE:+1}: error: the result of this operation has type numpy.array(elt=float, num_dims=2), which cannot be assigned to a left-hand side of type float -b /= a - -# CHECK-L: ${LINE:+1}: error: the result of this operation has type numpy.array(elt=float, num_dims=2), which cannot be assigned to a left-hand side of type numpy.array(elt=numpy.int?, num_dims=2) -a /= a diff --git a/artiq/test/lit/inferencer/error_array_ops.py b/artiq/test/lit/inferencer/error_array_ops.py deleted file mode 100644 index 4f85290c1..000000000 --- a/artiq/test/lit/inferencer/error_array_ops.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -a = array([[1, 2], [3, 4]]) -b = array([7, 8]) - -# NumPy supports implicit broadcasting over axes, which we don't (yet). -# Make sure there is a nice error message. -# CHECK-L: ${LINE:+3}: error: dimensions of '+' array operands must match -# CHECK-L: ${LINE:+2}: note: operand of dimension 2 -# CHECK-L: ${LINE:+1}: note: operand of dimension 1 -a + b diff --git a/artiq/test/lit/inferencer/error_assert.py b/artiq/test/lit/inferencer/error_assert.py deleted file mode 100644 index 1e7c10284..000000000 --- a/artiq/test/lit/inferencer/error_assert.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = "A" -# CHECK-L: ${LINE:+1}: error: assertion message must be a string literal -assert True, x diff --git a/artiq/test/lit/inferencer/error_builtin_calls.py b/artiq/test/lit/inferencer/error_builtin_calls.py deleted file mode 100644 index 643011f2d..000000000 --- a/artiq/test/lit/inferencer/error_builtin_calls.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: the argument of len() must be of an iterable type -len(1) - -# CHECK-L: ${LINE:+1}: error: the argument of list() must be of an iterable type -list(1) - -# CHECK-L: ${LINE:+1}: error: the arguments of min() must be of a numeric type -min([1], [1]) - -# CHECK-L: ${LINE:+1}: error: the arguments of abs() must be of a numeric type -abs([1.0]) - -# CHECK-L: ${LINE:+1}: error: strings currently cannot be constructed -str(1) diff --git a/artiq/test/lit/inferencer/error_bytes_subscript_mut.py b/artiq/test/lit/inferencer/error_bytes_subscript_mut.py deleted file mode 100644 index 96abe780d..000000000 --- a/artiq/test/lit/inferencer/error_bytes_subscript_mut.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: type bytes is not mutable -(b"a")[0] = 1 diff --git a/artiq/test/lit/inferencer/error_call.py b/artiq/test/lit/inferencer/error_call.py deleted file mode 100644 index 1497c7821..000000000 --- a/artiq/test/lit/inferencer/error_call.py +++ /dev/null @@ -1,23 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: cannot call this expression of type numpy.int? -(1)() - -def f(x, y, z=1): - pass - -# CHECK-L: ${LINE:+1}: error: variadic arguments are not supported -f(*[]) - -# CHECK-L: ${LINE:+1}: error: variadic arguments are not supported -f(**[]) - -# CHECK-L: ${LINE:+1}: error: the argument 'x' has been passed earlier as positional -f(1, x=1) - -# CHECK-L: ${LINE:+1}: error: mandatory argument 'x' is not passed -f() - -# CHECK: ${LINE:+1}: error: this function of type .* does not accept argument 'q' -f(1, q=1) diff --git a/artiq/test/lit/inferencer/error_class.py b/artiq/test/lit/inferencer/error_class.py deleted file mode 100644 index 0cc075b05..000000000 --- a/artiq/test/lit/inferencer/error_class.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: inheritance is not supported -class a(1): - pass - -class b: - # CHECK-L: ${LINE:+1}: fatal: class body must contain only assignments and function definitions - x += 1 diff --git a/artiq/test/lit/inferencer/error_class_redefine.py b/artiq/test/lit/inferencer/error_class_redefine.py deleted file mode 100644 index d5556fd98..000000000 --- a/artiq/test/lit/inferencer/error_class_redefine.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class c: - pass -# CHECK-L: ${LINE:+1}: fatal: variable 'c' is already defined -class c: - pass diff --git a/artiq/test/lit/inferencer/error_coerce.py b/artiq/test/lit/inferencer/error_coerce.py deleted file mode 100644 index c4f732e7e..000000000 --- a/artiq/test/lit/inferencer/error_coerce.py +++ /dev/null @@ -1,37 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: expected '<<' operand to be of integer type, not float -1 << 2.0 - -# CHECK-L: ${LINE:+3}: error: expected every '+' operand to be a list in this context -# CHECK-L: ${LINE:+2}: note: list of type list(elt=numpy.int?) -# CHECK-L: ${LINE:+1}: note: numpy.int?, which cannot be added to a list -[1] + 2 - -# CHECK-L: ${LINE:+1}: error: cannot unify list(elt=numpy.int?) with list(elt=float): numpy.int? is incompatible with float -[1] + [2.0] - -# CHECK-L: ${LINE:+3}: error: expected every '+' operand to be a tuple in this context -# CHECK-L: ${LINE:+2}: note: tuple of type (numpy.int?,) -# CHECK-L: ${LINE:+1}: note: numpy.int?, which cannot be added to a tuple -(1,) + 2 - -# CHECK-L: ${LINE:+1}: error: passing tuples to '*' is not supported -(1,) * 2 - -# CHECK-L: ${LINE:+3}: error: expected '*' operands to be a list and an integer in this context -# CHECK-L: ${LINE:+2}: note: list operand of type list(elt=numpy.int?) -# CHECK-L: ${LINE:+1}: note: operand of type list(elt='a), which is not a valid repetition amount -[1] * [] - -# CHECK-L: ${LINE:+1}: error: cannot coerce list(elt='a) to a numeric type -[] - 1.0 - -# CHECK-L: ${LINE:+2}: error: the result of this operation has type float, which cannot be assigned to a left-hand side of type numpy.int? -# CHECK-L: ${LINE:+1}: note: expression of type float -a = 1; a += 1.0 - -# CHECK-L: ${LINE:+2}: error: the result of this operation has type (numpy.int?, float), which cannot be assigned to a left-hand side of type (numpy.int?,) -# CHECK-L: ${LINE:+1}: note: expression of type (float,) -b = (1,); b += (1.0,) diff --git a/artiq/test/lit/inferencer/error_comprehension.py b/artiq/test/lit/inferencer/error_comprehension.py deleted file mode 100644 index d586dd657..000000000 --- a/artiq/test/lit/inferencer/error_comprehension.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: if clauses in comprehensions are not supported -[x for x in [] if x] - -# CHECK-L: ${LINE:+1}: error: multiple for clauses in comprehensions are not supported -[(x, y) for x in [] for y in []] diff --git a/artiq/test/lit/inferencer/error_control_flow.py b/artiq/test/lit/inferencer/error_control_flow.py deleted file mode 100644 index 65e300511..000000000 --- a/artiq/test/lit/inferencer/error_control_flow.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: return statement outside of a function -return - -# CHECK-L: ${LINE:+1}: error: break statement outside of a loop -break - -# CHECK-L: ${LINE:+1}: error: continue statement outside of a loop -continue - -while True: - def f(): - # CHECK-L: ${LINE:+1}: error: break statement outside of a loop - break - - # CHECK-L: ${LINE:+1}: error: continue statement outside of a loop - continue diff --git a/artiq/test/lit/inferencer/error_exception.py b/artiq/test/lit/inferencer/error_exception.py deleted file mode 100644 index ee049bb2a..000000000 --- a/artiq/test/lit/inferencer/error_exception.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -try: - pass -# CHECK-L: ${LINE:+1}: error: this expression must refer to an exception constructor -except 1: - pass - -try: - pass -# CHECK-L: ${LINE:+1}: error: cannot unify numpy.int? with Exception -except Exception as e: - e = 1 diff --git a/artiq/test/lit/inferencer/error_iterable.py b/artiq/test/lit/inferencer/error_iterable.py deleted file mode 100644 index 73afa2fcc..000000000 --- a/artiq/test/lit/inferencer/error_iterable.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: type numpy.int? is not iterable -for x in 1: pass diff --git a/artiq/test/lit/inferencer/error_local_unbound.py b/artiq/test/lit/inferencer/error_local_unbound.py deleted file mode 100644 index 7327a8548..000000000 --- a/artiq/test/lit/inferencer/error_local_unbound.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: fatal: undefined variable 'x' -x diff --git a/artiq/test/lit/inferencer/error_locals.py b/artiq/test/lit/inferencer/error_locals.py deleted file mode 100644 index 3a5185ca8..000000000 --- a/artiq/test/lit/inferencer/error_locals.py +++ /dev/null @@ -1,35 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = 1 -def a(): - # CHECK-L: ${LINE:+1}: error: cannot declare name 'x' as nonlocal: it is not bound in any outer scope - nonlocal x - -def f(): - y = 1 - def b(): - nonlocal y - # CHECK-L: ${LINE:+1}: error: name 'y' cannot be nonlocal and global simultaneously - global y - - def c(): - global y - # CHECK-L: ${LINE:+1}: error: name 'y' cannot be global and nonlocal simultaneously - nonlocal y - - def d(y): - # CHECK-L: ${LINE:+1}: error: name 'y' cannot be a parameter and global simultaneously - global y - - def e(y): - # CHECK-L: ${LINE:+1}: error: name 'y' cannot be a parameter and nonlocal simultaneously - nonlocal y - -# CHECK-L: ${LINE:+1}: error: duplicate parameter 'x' -def f(x, x): - pass - -# CHECK-L: ${LINE:+1}: error: variadic arguments are not supported -def g(*x): - pass diff --git a/artiq/test/lit/inferencer/error_matmult.py b/artiq/test/lit/inferencer/error_matmult.py deleted file mode 100644 index 2586aec31..000000000 --- a/artiq/test/lit/inferencer/error_matmult.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: expected matrix multiplication operand to be of array type -1 @ 2 - -# CHECK-L: ${LINE:+1}: error: expected matrix multiplication operand to be of array type -[1] @ [2] - -# CHECK-L: ${LINE:+1}: error: expected matrix multiplication operand to be 1- or 2-dimensional -array([[[0]]]) @ array([[[1]]]) diff --git a/artiq/test/lit/inferencer/error_method.py b/artiq/test/lit/inferencer/error_method.py deleted file mode 100644 index 2f4d1d3fe..000000000 --- a/artiq/test/lit/inferencer/error_method.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class c: - def f(): - pass - - def g(self): - pass - -# CHECK-L: ${LINE:+1}: error: function 'f()->NoneType delay('a)' of class 'c' cannot accept a self argument -c().f() - -c.g(1) -# CHECK-L: ${LINE:+1}: error: cannot unify with numpy.int? while inferring the type for self argument -c().g() diff --git a/artiq/test/lit/inferencer/error_return.py b/artiq/test/lit/inferencer/error_return.py deleted file mode 100644 index 40e6fd889..000000000 --- a/artiq/test/lit/inferencer/error_return.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+2}: error: cannot unify numpy.int? with NoneType -# CHECK-L: ${LINE:+1}: note: function with return type numpy.int? -def a(): - return 1 - # CHECK-L: ${LINE:+1}: note: a statement returning NoneType - return - -# CHECK-L: ${LINE:+2}: error: cannot unify numpy.int? with list(elt='a) -# CHECK-L: ${LINE:+1}: note: function with return type numpy.int? -def b(): - return 1 - # CHECK-L: ${LINE:+1}: note: a statement returning list(elt='a) - return [] diff --git a/artiq/test/lit/inferencer/error_str_iter.py b/artiq/test/lit/inferencer/error_str_iter.py deleted file mode 100644 index a79c52f83..000000000 --- a/artiq/test/lit/inferencer/error_str_iter.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: type str is not iterable -("a")[0] diff --git a/artiq/test/lit/inferencer/error_subscript.py b/artiq/test/lit/inferencer/error_subscript.py deleted file mode 100644 index d8332ab09..000000000 --- a/artiq/test/lit/inferencer/error_subscript.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = [] - -# CHECK-L: ${LINE:+1}: error: multi-dimensional indexing only supported for arrays -x[1,2] - -# CHECK-L: ${LINE:+1}: error: multi-dimensional slices are not supported -x[1:2,3:4] diff --git a/artiq/test/lit/inferencer/error_tuple_index.py b/artiq/test/lit/inferencer/error_tuple_index.py deleted file mode 100644 index dd6f2dca9..000000000 --- a/artiq/test/lit/inferencer/error_tuple_index.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -i = 0 -x = (1, "foo", True) - -# CHECK-L: ${LINE:+1}: error: tuples can only be indexed by a constant -x[i] - -# CHECK-L: ${LINE:+1}: error: tuples can only be indexed by a constant -x[0:2] - -# CHECK-L: ${LINE:+1}: error: index 3 is out of range for tuple of size 3 -x[3] diff --git a/artiq/test/lit/inferencer/error_unify.py b/artiq/test/lit/inferencer/error_unify.py deleted file mode 100644 index 636d4f85a..000000000 --- a/artiq/test/lit/inferencer/error_unify.py +++ /dev/null @@ -1,27 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -a = 1 -b = [] - -# CHECK-L: ${LINE:+1}: error: cannot unify numpy.int? with list(elt='a) -a = b - -# CHECK-L: ${LINE:+1}: error: cannot unify numpy.int? with list(elt='a) -[1, []] -# CHECK-L: note: a list element of type numpy.int? -# CHECK-L: note: a list element of type list(elt='a) - -# CHECK-L: ${LINE:+1}: error: cannot unify numpy.int? with bool -1 and False -# CHECK-L: note: an operand of type numpy.int? -# CHECK-L: note: an operand of type bool - -# CHECK-L: ${LINE:+1}: error: expected unary '+' operand to be of numeric type, not list(elt='a) -+[] - -# CHECK-L: ${LINE:+1}: error: expected '~' operand to be of integer type, not float -~1.0 - -# CHECK-L: ${LINE:+1}: error: type numpy.int? does not have an attribute 'x' -(1).x diff --git a/artiq/test/lit/inferencer/error_with_arity.py b/artiq/test/lit/inferencer/error_with_arity.py deleted file mode 100644 index c91a3996b..000000000 --- a/artiq/test/lit/inferencer/error_with_arity.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class contextmgr: - def __enter__(self, n1): - pass - - def __exit__(self, n1, n2): - pass - -def foo(): - # CHECK-L: ${LINE:+2}: error: function '__enter__(self:, n1:'a)->NoneType delay('b)' must accept 1 positional argument and no optional arguments - # CHECK-L: ${LINE:+1}: error: function '__exit__(self:, n1:'c, n2:'d)->NoneType delay('e)' must accept 4 positional arguments and no optional arguments - with contextmgr(): - pass diff --git a/artiq/test/lit/inferencer/error_with_exn.py b/artiq/test/lit/inferencer/error_with_exn.py deleted file mode 100644 index 283dadb5a..000000000 --- a/artiq/test/lit/inferencer/error_with_exn.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class contextmgr: - def __enter__(self): - pass - - def __exit__(self, n1, n2, n3): - n3 = 1 - pass - -def foo(): - # CHECK-L: ${LINE:+2}: error: cannot unify numpy.int? with NoneType - # CHECK-L: ${LINE:+1}: note: exception handling via context managers is not supported; the argument 'n3' of function '__exit__(self:, n1:NoneType, n2:NoneType, n3:numpy.int?)->NoneType delay('a)' will always be None - with contextmgr(): - pass diff --git a/artiq/test/lit/inferencer/error_with_many.py b/artiq/test/lit/inferencer/error_with_many.py deleted file mode 100644 index 81dc52017..000000000 --- a/artiq/test/lit/inferencer/error_with_many.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: ${LINE:+1}: error: the 'interleave' context manager must be the only one in a 'with' statement -with interleave, sequential: - pass diff --git a/artiq/test/lit/inferencer/error_with_self.py b/artiq/test/lit/inferencer/error_with_self.py deleted file mode 100644 index d22c5c085..000000000 --- a/artiq/test/lit/inferencer/error_with_self.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class contextmgr: - def __enter__(self): - pass - - def __exit__(self, n1, n2, n3): - pass - -def foo(): - contextmgr.__enter__(1) - # CHECK-L: ${LINE:+3}: error: cannot unify with numpy.int? while inferring the type for self argument - # CHECK-L: ${LINE:+2}: note: expression of type - # CHECK-L: ${LINE:+1}: note: reference to an instance with a method '__enter__(self:numpy.int?)->NoneType delay('a)' - with contextmgr(): - pass diff --git a/artiq/test/lit/inferencer/error_with_var.py b/artiq/test/lit/inferencer/error_with_var.py deleted file mode 100644 index 97b1b345a..000000000 --- a/artiq/test/lit/inferencer/error_with_var.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -class contextmgr: - def __enter__(self): - return 1 - - def __exit__(self, n1, n2, n3): - pass - -def foo(): - x = "x" - # CHECK-L: ${LINE:+3}: error: cannot unify str with NoneType - # CHECK-L: ${LINE:+2}: note: expression of type str - # CHECK-L: ${LINE:+1}: note: context manager with an '__enter__' method returning NoneType - with contextmgr() as x: - pass diff --git a/artiq/test/lit/inferencer/exception.py b/artiq/test/lit/inferencer/exception.py deleted file mode 100644 index e0e0f9645..000000000 --- a/artiq/test/lit/inferencer/exception.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: Exception: -Exception - -try: - pass -except Exception: - pass -except Exception as e: - # CHECK-L: e:Exception - e diff --git a/artiq/test/lit/inferencer/gcd.py b/artiq/test/lit/inferencer/gcd.py deleted file mode 100644 index 3137d7cc0..000000000 --- a/artiq/test/lit/inferencer/gcd.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t - -def _gcd(a, b): - if a < 0: - a = -a - while a: - c = a - a = b % a - b = c - return b - -# CHECK-L: _gcd:(a:numpy.int?, b:numpy.int?)->numpy.int?(10:numpy.int?, 25:numpy.int?):numpy.int? -_gcd(10, 25) diff --git a/artiq/test/lit/inferencer/if_expr.py b/artiq/test/lit/inferencer/if_expr.py deleted file mode 100644 index d955bc736..000000000 --- a/artiq/test/lit/inferencer/if_expr.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: def foo(val:bool)->numpy.int?: -def foo(val): - return 1 if val else 0 diff --git a/artiq/test/lit/inferencer/matmult.py b/artiq/test/lit/inferencer/matmult.py deleted file mode 100644 index e8e982c57..000000000 --- a/artiq/test/lit/inferencer/matmult.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -vec = array([0, 1]) -mat = array([[0, 1], [2, 3]]) - -# CHECK-L: ):numpy.int? -vec @ vec - -# CHECK-L: ):numpy.array(elt=numpy.int?, num_dims=1) -vec @ mat - -# CHECK-L: ):numpy.array(elt=numpy.int?, num_dims=1) -mat @ vec - -# CHECK-L: ):numpy.array(elt=numpy.int?, num_dims=2) -mat @ mat diff --git a/artiq/test/lit/inferencer/prelude.py b/artiq/test/lit/inferencer/prelude.py deleted file mode 100644 index 288cc78b3..000000000 --- a/artiq/test/lit/inferencer/prelude.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: x: -x = len - -def f(): - global len - # CHECK-L: len:numpy.int? = - len = 1 diff --git a/artiq/test/lit/inferencer/scoping.py b/artiq/test/lit/inferencer/scoping.py deleted file mode 100644 index 9847ef0ea..000000000 --- a/artiq/test/lit/inferencer/scoping.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: []:list(elt=numpy.int?) -x = [] - -def f(): - global x - x[0] = 1 diff --git a/artiq/test/lit/inferencer/slice.py b/artiq/test/lit/inferencer/slice.py deleted file mode 100644 index 610fa9dc3..000000000 --- a/artiq/test/lit/inferencer/slice.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = [0] -# CHECK-L: [::numpy.int32] -x[:] = [1] diff --git a/artiq/test/lit/inferencer/unify.py b/artiq/test/lit/inferencer/unify.py deleted file mode 100644 index dcff73487..000000000 --- a/artiq/test/lit/inferencer/unify.py +++ /dev/null @@ -1,82 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -a = 1 -# CHECK-L: a:numpy.int? - -b = a -# CHECK-L: b:numpy.int? - -c = True -# CHECK-L: c:bool - -d = False -# CHECK-L: d:bool - -e = None -# CHECK-L: e:NoneType - -f = 1.0 -# CHECK-L: f:float - -g = [] -# CHECK-L: g:list(elt='a) - -h = [1] -# CHECK-L: h:list(elt=numpy.int?) - -i = [] -i[0] = 1 -# CHECK-L: i:list(elt=numpy.int?) - -j = [] -j += [1.0] -# CHECK-L: j:list(elt=float) - -1 if c else 2 -# CHECK-L: 1:numpy.int? if c:bool else 2:numpy.int?:numpy.int? - -True and False -# CHECK-L: True:bool and False:bool:bool - -1 and 0 -# CHECK-L: 1:numpy.int? and 0:numpy.int?:numpy.int? - -~1 -# CHECK-L: 1:numpy.int?:numpy.int? - -not 1 -# CHECK-L: 1:numpy.int?:bool - -[x for x in [1]] -# CHECK-L: [x:numpy.int? for x:numpy.int? in [1:numpy.int?]:list(elt=numpy.int?)]:list(elt=numpy.int?) - -lambda x, y=1: x -# CHECK-L: lambda x:'b, y:numpy.int?=1:numpy.int?: x:'b:(x:'b, ?y:numpy.int?)->'b - -k = "x" -# CHECK-L: k:str - -ka = b"x" -# CHECK-L: ka:bytes - -kb = bytearray(b"x") -# CHECK-L: kb:bytearray - -l = array([1]) -# CHECK-L: l:numpy.array(elt=numpy.int?, num_dims=1) - -IndexError() -# CHECK-L: IndexError:():IndexError - -IndexError("x") -# CHECK-L: IndexError:("x":str):IndexError - -IndexError("x", 1) -# CHECK-L: IndexError:("x":str, 1:numpy.int64):IndexError - -IndexError("x", 1, 1) -# CHECK-L: IndexError:("x":str, 1:numpy.int64, 1:numpy.int64):IndexError - -IndexError("x", 1, 1, 1) -# CHECK-L: IndexError:("x":str, 1:numpy.int64, 1:numpy.int64, 1:numpy.int64):IndexError diff --git a/artiq/test/lit/inferencer/with.py b/artiq/test/lit/inferencer/with.py deleted file mode 100644 index c3128eeef..000000000 --- a/artiq/test/lit/inferencer/with.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: as x:NoneType -with interleave as x: pass diff --git a/artiq/test/lit/integration/abs.py b/artiq/test/lit/integration/abs.py deleted file mode 100644 index ba279ab4b..000000000 --- a/artiq/test/lit/integration/abs.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert abs(1234) == 1234 -assert abs(-1234) == 1234 -assert abs(1234.5) == 1234.5 -assert abs(-1234.5) == 1234.5 diff --git a/artiq/test/lit/integration/arithmetics.py b/artiq/test/lit/integration/arithmetics.py deleted file mode 100644 index 4fee4d27d..000000000 --- a/artiq/test/lit/integration/arithmetics.py +++ /dev/null @@ -1,64 +0,0 @@ -# RUN: %python %s -# RUN: %python -m artiq.compiler.testbench.jit %s -# REQUIRES: exceptions - -assert -(-1) == 1 -assert -(-1.0) == 1.0 -assert +1 == 1 -assert +1.0 == 1.0 -assert 1 + 1 == 2 -assert 1.0 + 1.0 == 2.0 -assert 1 - 1 == 0 -assert 1.0 - 1.0 == 0.0 -assert 2 * 2 == 4 -assert 2.0 * 2.0 == 4.0 -assert 3 / 2 == 1.5 -assert 3.0 / 2.0 == 1.5 -assert 3 // 2 == 1 -assert 3.0 // 2.0 == 1.0 -assert 3 % 2 == 1 -assert -3 % 2 == 1 -assert 3 % -2 == -1 -assert -3 % -2 == -1 -assert -1 % 8 == 7 -#ARTIQ#assert int64(3) % 2 == 1 -#ARTIQ#assert int64(-3) % 2 == 1 -#ARTIQ#assert int64(3) % -2 == -1 -#ARTIQ#assert int64(-3) % -2 == -1 -assert -1 % 8 == 7 -assert 3.0 % 2.0 == 1.0 -assert -3.0 % 2.0 == 1.0 -assert 3.0 % -2.0 == -1.0 -assert -3.0 % -2.0 == -1.0 -assert 3 ** 2 == 9 -assert 3.0 ** 2.0 == 9.0 -assert 9.0 ** 0.5 == 3.0 -assert 1 << 1 == 2 -assert 2 >> 1 == 1 -assert -2 >> 1 == -1 -#ARTIQ#assert 1 << 32 == 0 -assert -1 >> 32 == -1 -assert 0x18 & 0x0f == 0x08 -assert 0x18 | 0x0f == 0x1f -assert 0x18 ^ 0x0f == 0x17 -#ARTIQ#assert ~0x18 == -25 -#ARTIQ#assert ~int64(0x18) == -25 - -try: - 1 / 0 -except ZeroDivisionError: - pass -else: - assert False -try: - 1 // 0 -except ZeroDivisionError: - pass -else: - assert False -try: - 1 % 0 -except ZeroDivisionError: - pass -else: - assert False diff --git a/artiq/test/lit/integration/array_binops.py b/artiq/test/lit/integration/array_binops.py deleted file mode 100644 index cfca32d92..000000000 --- a/artiq/test/lit/integration/array_binops.py +++ /dev/null @@ -1,98 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -a = array([1, 2, 3]) -b = array([4, 5, 6]) - -c = a + b -assert c[0] == 5 -assert c[1] == 7 -assert c[2] == 9 - -c += a -assert c[0] == 6 -assert c[1] == 9 -assert c[2] == 12 - -c = b - a -assert c[0] == 3 -assert c[1] == 3 -assert c[2] == 3 - -c -= a -assert c[0] == 2 -assert c[1] == 1 -assert c[2] == 0 - -c = a * b -assert c[0] == 4 -assert c[1] == 10 -assert c[2] == 18 - -c *= a -assert c[0] == 4 -assert c[1] == 20 -assert c[2] == 54 - -c = b // a -assert c[0] == 4 -assert c[1] == 2 -assert c[2] == 2 - -c //= a -assert c[0] == 4 -assert c[1] == 1 -assert c[2] == 0 - -c = a ** b -assert c[0] == 1 -assert c[1] == 32 -assert c[2] == 729 - -c **= a -assert c[0] == 1 -assert c[1] == 1024 -assert c[2] == 387420489 - -c = b % a -assert c[0] == 0 -assert c[1] == 1 -assert c[2] == 0 - -c %= a -assert c[0] == 0 -assert c[1] == 1 -assert c[2] == 0 - -cf = b / a -assert cf[0] == 4.0 -assert cf[1] == 2.5 -assert cf[2] == 2.0 - -cf2 = cf + a -assert cf2[0] == 5.0 -assert cf2[1] == 4.5 -assert cf2[2] == 5.0 - -cf2 += a -assert cf2[0] == 6.0 -assert cf2[1] == 6.5 -assert cf2[2] == 8.0 - -cf /= a -assert cf[0] == 4.0 -assert cf[1] == 1.25 -assert cf[2] == 2.0 / 3.0 - -d = array([[1, 2], [3, 4]]) -e = array([[5, 6], [7, 8]]) -f = d + e -assert f[0][0] == 6 -assert f[0][1] == 8 -assert f[1][0] == 10 -assert f[1][1] == 12 - -f += d -assert f[0][0] == 7 -assert f[0][1] == 10 -assert f[1][0] == 13 -assert f[1][1] == 16 diff --git a/artiq/test/lit/integration/array_broadcast.py b/artiq/test/lit/integration/array_broadcast.py deleted file mode 100644 index d7cbc5998..000000000 --- a/artiq/test/lit/integration/array_broadcast.py +++ /dev/null @@ -1,55 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -a = array([1, 2, 3]) - -c = a + 1 -assert c[0] == 2 -assert c[1] == 3 -assert c[2] == 4 - -c = 1 - a -assert c[0] == 0 -assert c[1] == -1 -assert c[2] == -2 - -c = a * 1 -assert c[0] == 1 -assert c[1] == 2 -assert c[2] == 3 - -c = a // 2 -assert c[0] == 0 -assert c[1] == 1 -assert c[2] == 1 - -c = a ** 2 -assert c[0] == 1 -assert c[1] == 4 -assert c[2] == 9 - -c = 2 ** a -assert c[0] == 2 -assert c[1] == 4 -assert c[2] == 8 - -c = a % 2 -assert c[0] == 1 -assert c[1] == 0 -assert c[2] == 1 - -cf = a / 2 -assert cf[0] == 0.5 -assert cf[1] == 1.0 -assert cf[2] == 1.5 - -cf2 = 2 / array([1, 2, 4]) -assert cf2[0] == 2.0 -assert cf2[1] == 1.0 -assert cf2[2] == 0.5 - -d = array([[1, 2], [3, 4]]) -e = d + 1 -assert e[0][0] == 2 -assert e[0][1] == 3 -assert e[1][0] == 4 -assert e[1][1] == 5 diff --git a/artiq/test/lit/integration/array_creation.py b/artiq/test/lit/integration/array_creation.py deleted file mode 100644 index 512382cda..000000000 --- a/artiq/test/lit/integration/array_creation.py +++ /dev/null @@ -1,50 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# REQUIRES: exceptions - -ary = array([1, 2, 3]) -assert len(ary) == 3 -assert ary.shape == (3,) -assert [x * x for x in ary] == [1, 4, 9] - -# Reassign to an existing value to disambiguate type of empty array. -empty_array = array([1]) -empty_array = array([]) -assert len(empty_array) == 0 -assert empty_array.shape == (0,) -assert [x * x for x in empty_array] == [] - -# Creating arrays from generic iterables, rectangularity is assumed (and ensured -# with runtime checks). -list_of_lists = [[1, 2], [3, 4]] -array_of_lists = array(list_of_lists) -assert array_of_lists.shape == (2, 2) -assert [[y for y in x] for x in array_of_lists] == list_of_lists - -matrix = array([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]) -assert len(matrix) == 2 -assert matrix.shape == (2, 3) -# FIXME: Need to decide on a solution for array comparisons — -# NumPy returns an array of bools! -# assert [x for x in matrix] == [array([1.0, 2.0, 3.0]), array([4.0, 5.0, 6.0])] -assert matrix[0, 0] == 1.0 -assert matrix[0, 1] == 2.0 -assert matrix[0, 2] == 3.0 -assert matrix[1, 0] == 4.0 -assert matrix[1, 1] == 5.0 -assert matrix[1, 2] == 6.0 - -matrix[0, 0] = 7.0 -matrix[1, 1] = 8.0 -assert matrix[0, 0] == 7.0 -assert matrix[0, 1] == 2.0 -assert matrix[0, 2] == 3.0 -assert matrix[1, 0] == 4.0 -assert matrix[1, 1] == 8.0 -assert matrix[1, 2] == 6.0 - -array_of_matrices = array([matrix, matrix]) -assert array_of_matrices.shape == (2, 2, 3) - -three_tensor = array([[[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]]) -assert len(three_tensor) == 1 -assert three_tensor.shape == (1, 2, 3) diff --git a/artiq/test/lit/integration/array_matmult.py b/artiq/test/lit/integration/array_matmult.py deleted file mode 100644 index 7519c10ff..000000000 --- a/artiq/test/lit/integration/array_matmult.py +++ /dev/null @@ -1,25 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -mat23 = array([[1, 2, 3], [4, 5, 6]]) -mat32 = array([[1, 2], [3, 4], [5, 6]]) -vec2 = array([1, 2]) -vec3 = array([1, 2, 3]) - -assert vec3 @ vec3 == 14 - -a = mat23 @ mat32 -assert a.shape == (2, 2) -assert a[0][0] == 22 -assert a[0][1] == 28 -assert a[1][0] == 49 -assert a[1][1] == 64 - -b = mat23 @ vec3 -assert b.shape == (2,) -assert b[0] == 14 -assert b[1] == 32 - -b = vec3 @ mat32 -assert b.shape == (2,) -assert b[0] == 22 -assert b[1] == 28 diff --git a/artiq/test/lit/integration/array_slice.py b/artiq/test/lit/integration/array_slice.py deleted file mode 100644 index 6c37f8366..000000000 --- a/artiq/test/lit/integration/array_slice.py +++ /dev/null @@ -1,24 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -a = array([0, 1, 2, 3]) - -b = a[2:3] -assert b.shape == (1,) -assert b[0] == 2 -b[0] = 5 -assert a[2] == 5 - -b = a[3:2] -assert b.shape == (0,) - -c = array([[0, 1], [2, 3]]) - -d = c[:1] -assert d.shape == (1, 2) -assert d[0, 0] == 0 -assert d[0, 1] == 1 -d[0, 0] = 5 -assert c[0, 0] == 5 - -d = c[1:0] -assert d.shape == (0, 2) diff --git a/artiq/test/lit/integration/array_unaryops.py b/artiq/test/lit/integration/array_unaryops.py deleted file mode 100644 index e55b6f733..000000000 --- a/artiq/test/lit/integration/array_unaryops.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -a = array([1, 2]) - -b = +a -assert b[0] == 1 -assert b[1] == 2 - -b = -a -assert b[0] == -1 -assert b[1] == -2 diff --git a/artiq/test/lit/integration/attribute.py b/artiq/test/lit/integration/attribute.py deleted file mode 100644 index 301243b54..000000000 --- a/artiq/test/lit/integration/attribute.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -r = range(10) -assert r.start == 0 -assert r.stop == 10 -assert r.step == 1 diff --git a/artiq/test/lit/integration/bool.py b/artiq/test/lit/integration/bool.py deleted file mode 100644 index 1a68ebd1c..000000000 --- a/artiq/test/lit/integration/bool.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert (not 0) == True -assert (not 1) == False - -assert (0 and 0) is 0 -assert (1 and 0) is 0 -assert (0 and 1) is 0 -assert (1 and 2) is 2 - -assert (0 or 0) is 0 -assert (1 or 0) is 1 -assert (0 or 1) is 1 -assert (1 or 2) is 1 - -assert bool(False) is False and bool(False) is False -assert bool(0) is False and bool(1) is True -assert bool(0.0) is False and bool(1.0) is True -x = []; assert bool(x) is False; x = [1]; assert bool(x) is True -assert bool(range(0)) is False and bool(range(1)) is True diff --git a/artiq/test/lit/integration/builtin.py b/artiq/test/lit/integration/builtin.py deleted file mode 100644 index 1562edf57..000000000 --- a/artiq/test/lit/integration/builtin.py +++ /dev/null @@ -1,26 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s -# REQUIRES: exceptions - -assert bool() is False -# bool(x) is tested in bool.py - -assert int() is 0 -assert int(1.0) is 1 -#ARTIQ#assert int64(1) << 40 is 1099511627776 - -#ARTIQ#assert float() is 0.0 -#ARTIQ#assert float(1) is 1.0 - -x = list() -if False: x = [1] -assert x == [] - -#ARTIQ#assert range(10) is range(0, 10, 1) -#ARTIQ#assert range(1, 10) is range(1, 10, 1) - -assert len([1, 2, 3]) is 3 -assert len(range(10)) is 10 -assert len(range(0, 10, 2)) is 5 - -#ARTIQ#assert round(1.4) is 1 and round(1.6) is 2 diff --git a/artiq/test/lit/integration/bytes.py b/artiq/test/lit/integration/bytes.py deleted file mode 100644 index da68591e2..000000000 --- a/artiq/test/lit/integration/bytes.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert b"xy" == b"xy" -assert (b"x" + b"y") == b"xy" diff --git a/artiq/test/lit/integration/class.py b/artiq/test/lit/integration/class.py deleted file mode 100644 index 205210ea0..000000000 --- a/artiq/test/lit/integration/class.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -class c: - a = 1 - def f(): - return 2 - def g(self): - return self.a + 5 - def h(self, x): - return self.a + x - -assert c.a == 1 -assert c.f() == 2 -assert c().g() == 6 -assert c().h(9) == 10 diff --git a/artiq/test/lit/integration/compare.py b/artiq/test/lit/integration/compare.py deleted file mode 100644 index 48a33cc09..000000000 --- a/artiq/test/lit/integration/compare.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert 1 < 2 and not (2 < 1) -assert 2 > 1 and not (1 > 2) -assert 1 == 1 and not (1 == 2) -assert 1 != 2 and not (1 != 1) -assert 1 <= 1 and 1 <= 2 and not (2 <= 1) -assert 1 >= 1 and 2 >= 1 and not (1 >= 2) -assert 1 is 1 and not (1 is 2) -assert 1 is not 2 and not (1 is not 1) - -x, y = [1], [1] -assert x is x and x is not y -#ARTIQ#assert range(10) is range(10) and range(10) is not range(11) - -lst = [1, 2, 3] -assert 1 in lst and 0 not in lst -assert 1 in range(10) and 11 not in range(10) and -1 not in range(10) diff --git a/artiq/test/lit/integration/finally.py b/artiq/test/lit/integration/finally.py deleted file mode 100644 index e629602ad..000000000 --- a/artiq/test/lit/integration/finally.py +++ /dev/null @@ -1,78 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t.1 -# RUN: OutputCheck %s --file-to-check=%t.1 -# RUN: %python %s >%t.2 -# RUN: OutputCheck %s --file-to-check=%t.2 -# REQUIRES: exceptions - -def f(): - while True: - try: - print("f-try") - break - finally: - print("f-finally") - print("f-out") - -def g(): - x = True - while x: - try: - print("g-try") - x = False - continue - finally: - print("g-finally") - print("g-out") - -def h(): - try: - print("h-try") - return 10 - finally: - print("h-finally") - print("h-out") - return 20 - -def i(): - try: - print("i-try") - return 10 - finally: - print("i-finally") - return 30 - print("i-out") - return 20 - -def j(): - try: - print("j-try") - finally: - print("j-finally") - print("j-out") - -# CHECK-L: f-try -# CHECK-L: f-finally -# CHECK-L: f-out -f() - -# CHECK-L: g-try -# CHECK-L: g-finally -# CHECK-L: g-out -g() - -# CHECK-L: h-try -# CHECK-L: h-finally -# CHECK-NOT-L: h-out -# CHECK-L: h 10 -print("h", h()) - -# CHECK-L: i-try -# CHECK-L: i-finally -# CHECK-NOT-L: i-out -# CHECK-L: i 30 -print("i", i()) - -# CHECK-L: j-try -# CHECK-L: j-finally -# CHECK-L: j-out -print("j", j()) diff --git a/artiq/test/lit/integration/for.py b/artiq/test/lit/integration/for.py deleted file mode 100644 index 7156bee5d..000000000 --- a/artiq/test/lit/integration/for.py +++ /dev/null @@ -1,53 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -count = 0 -for x in range(10): - count += 1 -assert count == 10 - -for x in range(10): - assert True -else: - assert True - -for x in range(0): - assert False -else: - assert True - -for x in range(10): - continue - assert False -else: - assert True - -for x in range(10): - break - assert False -else: - assert False - -# Verify continue target is reset in else block. -cond = False -while True: - if cond: - break - cond = True - for _ in range(1): - pass - else: - continue - assert False -else: - assert False - -# Verify for target is reset in else block. -while True: - for _ in range(1): - pass - else: - break - assert False -else: - assert False diff --git a/artiq/test/lit/integration/function.py b/artiq/test/lit/integration/function.py deleted file mode 100644 index bbaca2083..000000000 --- a/artiq/test/lit/integration/function.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -def fib(x): - if x == 1: - return x - else: - return x * fib(x - 1) -assert fib(5) == 120 - -# argument combinations handled in lambda.py diff --git a/artiq/test/lit/integration/if.py b/artiq/test/lit/integration/if.py deleted file mode 100644 index 8d8eec4f2..000000000 --- a/artiq/test/lit/integration/if.py +++ /dev/null @@ -1,27 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -if True: - assert True - -if False: - assert False - -if True: - assert True -else: - assert False - -if False: - assert False -else: - assert True - -assert (0 if True else 1) == 0 -assert (0 if False else 1) == 1 - -if 0: - assert True - -if 1: - assert True diff --git a/artiq/test/lit/integration/instance.py b/artiq/test/lit/integration/instance.py deleted file mode 100644 index 5acea8721..000000000 --- a/artiq/test/lit/integration/instance.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -class c: - a = 1 - -i = c() - -def f(): - c = None - assert i.a == 1 - -assert i.a == 1 -f() diff --git a/artiq/test/lit/integration/lambda.py b/artiq/test/lit/integration/lambda.py deleted file mode 100644 index a1f08763a..000000000 --- a/artiq/test/lit/integration/lambda.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert (lambda: 1)() == 1 -assert (lambda x: x)(1) == 1 -assert (lambda x, y: x + y)(1, 2) == 3 -assert (lambda x, y=1: x + y)(1) == 2 -assert (lambda x, y=1: x + y)(1, 2) == 3 -assert (lambda x, y=1: x + y)(x=3) == 4 -assert (lambda x, y=1: x + y)(y=2, x=3) == 5 diff --git a/artiq/test/lit/integration/list.py b/artiq/test/lit/integration/list.py deleted file mode 100644 index 97fad6a6c..000000000 --- a/artiq/test/lit/integration/list.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s -# REQUIRES: exceptions - -[x, y] = [1, 2] -assert (x, y) == (1, 2) - -lst = [1, 2, 3] -assert [x*x for x in lst] == [1, 4, 9] - -assert [0] == [0] -assert [0] != [1] -assert [[0]] == [[0]] -assert [[0]] != [[1]] -assert [[[0]]] == [[[0]]] -assert [[[0]]] != [[[1]]] - -assert [1] + [2] == [1, 2] -assert [1] * 3 == [1, 1, 1] diff --git a/artiq/test/lit/integration/locals.py b/artiq/test/lit/integration/locals.py deleted file mode 100644 index 6ad9b0763..000000000 --- a/artiq/test/lit/integration/locals.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -x = 1 -assert x == 1 -x += 1 -assert x == 2 diff --git a/artiq/test/lit/integration/minmax.py b/artiq/test/lit/integration/minmax.py deleted file mode 100644 index 5f325be7b..000000000 --- a/artiq/test/lit/integration/minmax.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert min(1, 2) == 1 -assert max(1, 2) == 2 -assert min(1.0, 2.0) == 1.0 -assert max(1.0, 2.0) == 2.0 diff --git a/artiq/test/lit/integration/print.py b/artiq/test/lit/integration/print.py deleted file mode 100644 index 11887bf77..000000000 --- a/artiq/test/lit/integration/print.py +++ /dev/null @@ -1,41 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: None -print(None) - -# CHECK-L: True False -print(True, False) - -# CHECK-L: 1 -1 -print(1, -1) - -# CHECK-L: 10000000000 -print(10000000000) - -# CHECK-L: 1.5 -print(1.5) - -# CHECK-L: (True, 1) -print((True, 1)) - -# CHECK-L: (True,) -print((True,)) - -# CHECK-L: [1, 2, 3] -print([1, 2, 3]) - -# CHECK-L: [[1, 2], [3]] -print([[1, 2], [3]]) - -# CHECK-L: range(0, 10, 1) -print(range(10)) - -# CHECK-L: array([1, 2]) -print(array([1, 2])) - -# CHECK-L: bytes([97, 98]) -print(b"ab") - -# CHECK-L: bytearray([97, 98]) -print(bytearray(b"ab")) diff --git a/artiq/test/lit/integration/raise.py b/artiq/test/lit/integration/raise.py deleted file mode 100644 index 7b9d4fc70..000000000 --- a/artiq/test/lit/integration/raise.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s -# REQUIRES: exceptions - -try: - raise ValueError -except ValueError: - pass diff --git a/artiq/test/lit/integration/str.py b/artiq/test/lit/integration/str.py deleted file mode 100644 index 9d75399e9..000000000 --- a/artiq/test/lit/integration/str.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -assert "xy" == "xy" -assert not ("xy" == "xz") - -assert "xy" != "xz" -assert not ("xy" != "xy") - -assert ("x" + "y") == "xy" diff --git a/artiq/test/lit/integration/subscript.py b/artiq/test/lit/integration/subscript.py deleted file mode 100644 index db50809e6..000000000 --- a/artiq/test/lit/integration/subscript.py +++ /dev/null @@ -1,27 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s -# REQUIRES: exceptions - -lst = list(range(10)) -assert lst[0] == 0 -assert lst[1] == 1 -assert lst[-1] == 9 -assert lst[0:1] == [0] -assert lst[0:2] == [0, 1] -assert lst[0:10] == lst -assert lst[1:-1] == lst[1:9] -assert lst[0:1:2] == [0] -assert lst[0:2:2] == [0] -assert lst[0:3:2] == [0, 2] - -lst = [0, 0, 0, 0, 0] -lst[0:5:2] = [1, 2, 3] -assert lst == [1, 0, 2, 0, 3] - -byt = b"abc" -assert byt[0] == 97 -assert byt[1] == 98 - -barr = bytearray(b"abc") -assert barr[0] == 97 -assert barr[1] == 98 diff --git a/artiq/test/lit/integration/tuple.py b/artiq/test/lit/integration/tuple.py deleted file mode 100644 index 44564c151..000000000 --- a/artiq/test/lit/integration/tuple.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -x, y = 2, 1 -x, y = y, x -assert x == 1 and y == 2 -assert (1, 2) + (3.0,) == (1, 2, 3.0) - -assert (0,) == (0,) -assert (0,) != (1,) - -assert ([0],) == ([0],) -assert ([0],) != ([1],) diff --git a/artiq/test/lit/integration/tuple_index.py b/artiq/test/lit/integration/tuple_index.py deleted file mode 100644 index 46ebb1428..000000000 --- a/artiq/test/lit/integration/tuple_index.py +++ /dev/null @@ -1,22 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -# Basic indexing -a = (1, "xyz", True) - -assert a[0] == 1 -assert a[1] == "xyz" -assert a[2] == True - -# Nested indexing -b = (a, 2, (3, "abc", a)) - -assert b[0][0] == 1 -assert b[1] == 2 -assert b[2][2][0] == 1 - -# Usage on the LHS of an assignment -c = (1, 2, [1, 2, 3]) - -c[2][0] = 456 -assert c[2][0] == 456 diff --git a/artiq/test/lit/integration/while.py b/artiq/test/lit/integration/while.py deleted file mode 100644 index 397055c9a..000000000 --- a/artiq/test/lit/integration/while.py +++ /dev/null @@ -1,62 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -cond, count = True, 0 -while cond: - count += 1 - cond = False -assert count == 1 - -while False: - pass -else: - assert True - -cond = True -while cond: - cond = False -else: - assert True - -while True: - break - assert False -else: - assert False - -cond = True -while cond: - cond = False - continue - assert False - -# Verify continue target is reset in else block. -cond = False -while True: - if cond: - break - cond = True - while False: - assert False - else: - continue - assert False -else: - assert False - -# Verify break target is reset in else block. -while True: - while False: - assert False - else: - break - assert False -else: - assert False - -while 0: - assert False - -while 1: - assert True - break diff --git a/artiq/test/lit/integration/with.py b/artiq/test/lit/integration/with.py deleted file mode 100644 index 8f2b9b9a8..000000000 --- a/artiq/test/lit/integration/with.py +++ /dev/null @@ -1,33 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# RUN: %python %s - -class contextmgr: - def __enter__(self): - print(2) - - def __exit__(self, n1, n2, n3): - print(4) - -# CHECK-L: a 1 -# CHECK-L: 2 -# CHECK-L: a 3 -# CHECK-L: 4 -# CHECK-L: a 5 -print("a", 1) -with contextmgr(): - print("a", 3) -print("a", 5) - -# CHECK-L: b 1 -# CHECK-L: 2 -# CHECK-L: 4 -# CHECK-L: b 6 -try: - print("b", 1) - with contextmgr(): - [0][1] - print("b", 3) - print("b", 5) -except: - pass -print("b", 6) diff --git a/artiq/test/lit/interleaving/control_flow.py b/artiq/test/lit/interleaving/control_flow.py deleted file mode 100644 index c4f7356a3..000000000 --- a/artiq/test/lit/interleaving/control_flow.py +++ /dev/null @@ -1,18 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - with interleave: - if True: - print(1) - else: - print(2) - while False: - print(3) - break - delay_mu(1) - print(4) - -# CHECK-L: 1 -# CHECK-L: 4 -f() diff --git a/artiq/test/lit/interleaving/error_inlining.py b/artiq/test/lit/interleaving/error_inlining.py deleted file mode 100644 index dbf237538..000000000 --- a/artiq/test/lit/interleaving/error_inlining.py +++ /dev/null @@ -1,16 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - delay_mu(2) - -def g(): - delay_mu(2) - -x = f if True else g - -def h(): - with interleave: - f() - # CHECK-L: ${LINE:+1}: fatal: it is not possible to interleave this function call within a 'with interleave:' statement because the compiler could not prove that the same function would always be called - x() diff --git a/artiq/test/lit/interleaving/error_toplevel_control_flow.py b/artiq/test/lit/interleaving/error_toplevel_control_flow.py deleted file mode 100644 index acbb7815e..000000000 --- a/artiq/test/lit/interleaving/error_toplevel_control_flow.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - # CHECK-L: ${LINE:+1}: error: cannot interleave this 'with interleave:' statement - with interleave: - # CHECK-L: ${LINE:+1}: note: this 'return' statement transfers control out of the 'with interleave:' statement - return - delay(1.0) - -def g(): - while True: - # CHECK-L: ${LINE:+1}: error: cannot interleave this 'with interleave:' statement - with interleave: - # CHECK-L: ${LINE:+1}: note: this 'break' statement transfers control out of the 'with interleave:' statement - break - delay(1.0) diff --git a/artiq/test/lit/interleaving/indirect.py b/artiq/test/lit/interleaving/indirect.py deleted file mode 100644 index 0f03f55ac..000000000 --- a/artiq/test/lit/interleaving/indirect.py +++ /dev/null @@ -1,28 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - delay_mu(2) - -def g(): - with interleave: - with sequential: - print("A", now_mu()) - f() - # - print("B", now_mu()) - with sequential: - print("C", now_mu()) - f() - # - print("D", now_mu()) - f() - # - print("E", now_mu()) - -# CHECK-L: A 0 -# CHECK-L: C 0 -# CHECK-L: B 2 -# CHECK-L: D 2 -# CHECK-L: E 4 -g() diff --git a/artiq/test/lit/interleaving/indirect_arg.py b/artiq/test/lit/interleaving/indirect_arg.py deleted file mode 100644 index c8df81ba0..000000000 --- a/artiq/test/lit/interleaving/indirect_arg.py +++ /dev/null @@ -1,28 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(n): - delay_mu(n) - -def g(): - with interleave: - with sequential: - print("A", now_mu()) - f(2) - # - print("B", now_mu()) - with sequential: - print("C", now_mu()) - f(2) - # - print("D", now_mu()) - f(2) - # - print("E", now_mu()) - -# CHECK-L: A 0 -# CHECK-L: C 0 -# CHECK-L: B 2 -# CHECK-L: D 2 -# CHECK-L: E 4 -g() diff --git a/artiq/test/lit/interleaving/nonoverlapping.py b/artiq/test/lit/interleaving/nonoverlapping.py deleted file mode 100644 index ffc67326e..000000000 --- a/artiq/test/lit/interleaving/nonoverlapping.py +++ /dev/null @@ -1,25 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def g(): - with interleave: - with sequential: - print("A", now_mu()) - delay_mu(2) - # - print("B", now_mu()) - with sequential: - print("C", now_mu()) - delay_mu(2) - # - print("D", now_mu()) - delay_mu(2) - # - print("E", now_mu()) - -# CHECK-L: A 0 -# CHECK-L: C 0 -# CHECK-L: B 2 -# CHECK-L: D 2 -# CHECK-L: E 4 -g() diff --git a/artiq/test/lit/interleaving/overlapping.py b/artiq/test/lit/interleaving/overlapping.py deleted file mode 100644 index 32914ed1c..000000000 --- a/artiq/test/lit/interleaving/overlapping.py +++ /dev/null @@ -1,25 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def g(): - with interleave: - with sequential: - print("A", now_mu()) - delay_mu(3) - # - print("B", now_mu()) - with sequential: - print("C", now_mu()) - delay_mu(2) - # - print("D", now_mu()) - delay_mu(2) - # - print("E", now_mu()) - -# CHECK-L: A 0 -# CHECK-L: C 0 -# CHECK-L: D 2 -# CHECK-L: B 3 -# CHECK-L: E 4 -g() diff --git a/artiq/test/lit/interleaving/pure_impure_tie.py b/artiq/test/lit/interleaving/pure_impure_tie.py deleted file mode 100644 index b6aca7b81..000000000 --- a/artiq/test/lit/interleaving/pure_impure_tie.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - delay_mu(2) - -def g(): - with interleave: - f() - delay_mu(2) - print(now_mu()) - -# CHECK-L: 2 -g() diff --git a/artiq/test/lit/interleaving/unrolling.py b/artiq/test/lit/interleaving/unrolling.py deleted file mode 100644 index 824032f07..000000000 --- a/artiq/test/lit/interleaving/unrolling.py +++ /dev/null @@ -1,23 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -with interleave: - for x in range(10): - delay_mu(1) - print("a", x) - with sequential: - delay_mu(5) - print("c") - with sequential: - delay_mu(3) - print("b") - -# CHECK-L: a 0 -# CHECK-L: a 1 -# CHECK-L: a 2 -# CHECK-L: b -# CHECK-L: a 3 -# CHECK-L: a 4 -# CHECK-L: c -# CHECK-L: a 5 -# CHECK-L: a 6 diff --git a/artiq/test/lit/iodelay/argument.py b/artiq/test/lit/iodelay/argument.py deleted file mode 100644 index 94da1c764..000000000 --- a/artiq/test/lit/iodelay/argument.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: (a:float, b:numpy.int64)->NoneType delay(s->mu(a) + b mu) -def f(a, b): - delay(a) - delay_mu(b) diff --git a/artiq/test/lit/iodelay/arith.py b/artiq/test/lit/iodelay/arith.py deleted file mode 100644 index 05d42e666..000000000 --- a/artiq/test/lit/iodelay/arith.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: (a:numpy.int32, b:numpy.int32, c:numpy.int32, d:numpy.int32, e:numpy.int32)->NoneType delay(s->mu(a * b // c + d - 10 / e) mu) -def f(a, b, c, d, e): - delay(a * b // c + d - 10 / e) - -f(1,2,3,4,5) diff --git a/artiq/test/lit/iodelay/call.py b/artiq/test/lit/iodelay/call.py deleted file mode 100644 index e08446da1..000000000 --- a/artiq/test/lit/iodelay/call.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - delay_mu(1) - -# CHECK-L: g: ()->NoneType delay(2 mu) -def g(): - f() - f() diff --git a/artiq/test/lit/iodelay/call_subst.py b/artiq/test/lit/iodelay/call_subst.py deleted file mode 100644 index fddc007c6..000000000 --- a/artiq/test/lit/iodelay/call_subst.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def pulse(len): - # "on" - delay_mu(len) - # "off" - delay_mu(len) - -# CHECK-L: f: ()->NoneType delay(600 mu) -def f(): - pulse(100) - pulse(200) diff --git a/artiq/test/lit/iodelay/class.py b/artiq/test/lit/iodelay/class.py deleted file mode 100644 index 63fab1862..000000000 --- a/artiq/test/lit/iodelay/class.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: g: (i:)->NoneType delay(1000000 mu) -def g(i): - i.f(1.0) - -class c: - def f(self, x): - delay(x) - -g(c()) diff --git a/artiq/test/lit/iodelay/error_argument.py b/artiq/test/lit/iodelay/error_argument.py deleted file mode 100644 index f92dd559d..000000000 --- a/artiq/test/lit/iodelay/error_argument.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(x): - delay_mu(x) - -x = 1 - -def g(): - # CHECK-L: ${LINE:+2}: error: call cannot be interleaved because an argument cannot be statically evaluated - # CHECK-L: ${LINE:+1}: note: this expression is not supported in the expression for argument 'x' that affects I/O delay - f(x if True else x) diff --git a/artiq/test/lit/iodelay/error_arith.py b/artiq/test/lit/iodelay/error_arith.py deleted file mode 100644 index 6a4c9f0c1..000000000 --- a/artiq/test/lit/iodelay/error_arith.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(a): - b = 1.0 - # CHECK-L: ${LINE:+3}: error: call cannot be interleaved - # CHECK-L: ${LINE:+2}: note: this variable is not an argument of the innermost function - # CHECK-L: ${LINE:-4}: note: only these arguments are in scope of analysis - delay(b) - -def g(): - # CHECK-L: ${LINE:+2}: error: call cannot be interleaved - # CHECK-L: ${LINE:+1}: note: this operator is not supported as an argument for delay() - delay(2.0**2) - -def h(): - # CHECK-L: ${LINE:+2}: error: call cannot be interleaved - # CHECK-L: ${LINE:+1}: note: this expression is not supported as an argument for delay_mu() - delay_mu(1 if False else 2) - -f(1) diff --git a/artiq/test/lit/iodelay/error_bad_interleave.py b/artiq/test/lit/iodelay/error_bad_interleave.py deleted file mode 100644 index b368c28f3..000000000 --- a/artiq/test/lit/iodelay/error_bad_interleave.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - with interleave: - # CHECK-L: ${LINE:+1}: error: while statement cannot be interleaved - while True: - delay_mu(1) diff --git a/artiq/test/lit/iodelay/error_builtinfn.py b/artiq/test/lit/iodelay/error_builtinfn.py deleted file mode 100644 index b1b89a78f..000000000 --- a/artiq/test/lit/iodelay/error_builtinfn.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - x = 1 - # CHECK-L: ${LINE:+1}: error: call cannot be interleaved because an argument cannot be statically evaluated - delay_mu(x) - -def g(): - x = 1.0 - # CHECK-L: ${LINE:+1}: error: call cannot be interleaved - delay(x) - - diff --git a/artiq/test/lit/iodelay/error_call_nested.py b/artiq/test/lit/iodelay/error_call_nested.py deleted file mode 100644 index 2c12af9bd..000000000 --- a/artiq/test/lit/iodelay/error_call_nested.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - # CHECK-L: ${LINE:+1}: error: call cannot be interleaved - delay(1.0**2) - -def g(): - # CHECK-L: ${LINE:+1}: note: function called here - f() - f() diff --git a/artiq/test/lit/iodelay/error_call_subst.py b/artiq/test/lit/iodelay/error_call_subst.py deleted file mode 100644 index 0d1ba0843..000000000 --- a/artiq/test/lit/iodelay/error_call_subst.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def pulse(len): - # "on" - delay_mu(len) - # "off" - delay_mu(len) - -def f(): - a = 100 - # CHECK-L: ${LINE:+1}: error: call cannot be interleaved - pulse(a) diff --git a/artiq/test/lit/iodelay/error_control_flow.py b/artiq/test/lit/iodelay/error_control_flow.py deleted file mode 100644 index c179c95b9..000000000 --- a/artiq/test/lit/iodelay/error_control_flow.py +++ /dev/null @@ -1,23 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - # CHECK-L: ${LINE:+1}: error: while statement cannot be interleaved - while True: - delay_mu(1) - -def g(): - # CHECK-L: ${LINE:+1}: error: if statement cannot be interleaved - if True: - delay_mu(1) - -def h(): - # CHECK-L: ${LINE:+1}: error: if expression cannot be interleaved - delay_mu(1) if True else delay_mu(2) - -def i(): - # CHECK-L: ${LINE:+1}: error: try statement cannot be interleaved - try: - delay_mu(1) - finally: - pass diff --git a/artiq/test/lit/iodelay/error_for.py b/artiq/test/lit/iodelay/error_for.py deleted file mode 100644 index aae5d47f3..000000000 --- a/artiq/test/lit/iodelay/error_for.py +++ /dev/null @@ -1,9 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - r = range(10) - # CHECK-L: ${LINE:+2}: error: for statement cannot be interleaved because iteration count is indeterminate - # CHECK-L: ${LINE:+1}: note: this value is not a constant range literal - for _ in r: - delay_mu(1) diff --git a/artiq/test/lit/iodelay/error_goto.py b/artiq/test/lit/iodelay/error_goto.py deleted file mode 100644 index 714db7610..000000000 --- a/artiq/test/lit/iodelay/error_goto.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - for _ in range(10): - delay_mu(10) - # CHECK-L: ${LINE:+1}: error: loop iteration count is indeterminate because of control flow - break - -def g(): - for _ in range(10): - delay_mu(10) - # CHECK-L: ${LINE:+1}: error: loop iteration count is indeterminate because of control flow - continue diff --git a/artiq/test/lit/iodelay/error_iterable.py b/artiq/test/lit/iodelay/error_iterable.py deleted file mode 100644 index 6429236db..000000000 --- a/artiq/test/lit/iodelay/error_iterable.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = 1 - -def f(): - # CHECK-L: ${LINE:+2}: error: for statement cannot be interleaved because iteration count is indeterminate - # CHECK-L: ${LINE:+1}: note: this expression is not supported in an iterable used in a for loop that is being interleaved - for _ in range(x if True else x): - delay_mu(10) diff --git a/artiq/test/lit/iodelay/error_return.py b/artiq/test/lit/iodelay/error_return.py deleted file mode 100644 index c047de2b0..000000000 --- a/artiq/test/lit/iodelay/error_return.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - if True: - # CHECK-L: ${LINE:+1}: error: only return statement at the end of the function can be interleaved - return 1 - delay_mu(1) diff --git a/artiq/test/lit/iodelay/error_unify.py b/artiq/test/lit/iodelay/error_unify.py deleted file mode 100644 index 723b5e393..000000000 --- a/artiq/test/lit/iodelay/error_unify.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag +delay %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(): - delay_mu(10) - -# CHECK-L: ${LINE:+1}: fatal: delay delay(20 mu) was inferred for this function, but its delay is already constrained externally to delay(10 mu) -def g(): - delay_mu(20) - -x = f if True else g diff --git a/artiq/test/lit/iodelay/goto.py b/artiq/test/lit/iodelay/goto.py deleted file mode 100644 index d80de43f9..000000000 --- a/artiq/test/lit/iodelay/goto.py +++ /dev/null @@ -1,14 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: ()->NoneType delay(10 mu) -def f(): - delay_mu(10) - for _ in range(10): - break - -# CHECK-L: g: ()->NoneType delay(10 mu) -def g(): - delay_mu(10) - for _ in range(10): - continue diff --git a/artiq/test/lit/iodelay/interleave.py b/artiq/test/lit/iodelay/interleave.py deleted file mode 100644 index 76e30b7a7..000000000 --- a/artiq/test/lit/iodelay/interleave.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: (a:numpy.int64, b:numpy.int64)->NoneType delay(max(a, b) mu) -def f(a, b): - with interleave: - delay_mu(a) - delay_mu(b) - -# CHECK-L: g: (a:numpy.int64)->NoneType delay(max(a, 200) mu) -def g(a): - with interleave: - delay_mu(100) - delay_mu(200) - delay_mu(a) diff --git a/artiq/test/lit/iodelay/linear.py b/artiq/test/lit/iodelay/linear.py deleted file mode 100644 index cb2bed204..000000000 --- a/artiq/test/lit/iodelay/linear.py +++ /dev/null @@ -1,12 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: ()->NoneType delay(1001000 mu) -def f(): - delay(1.0) - delay_mu(1000) - -# CHECK-L: g: ()->NoneType delay(3 mu) -def g(): - delay_mu(1) - delay_mu(2) diff --git a/artiq/test/lit/iodelay/loop.py b/artiq/test/lit/iodelay/loop.py deleted file mode 100644 index 37bcb9e07..000000000 --- a/artiq/test/lit/iodelay/loop.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: ()->NoneType delay(30 mu) -def f(): - for _ in range(10): - delay_mu(3) - -# CHECK-L: g: ()->NoneType delay(60 mu) -def g(): - for _ in range(10): - for _ in range(2): - delay_mu(3) diff --git a/artiq/test/lit/iodelay/order_invariance.py b/artiq/test/lit/iodelay/order_invariance.py deleted file mode 100644 index e4deb96c5..000000000 --- a/artiq/test/lit/iodelay/order_invariance.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: g: ()->NoneType delay(2 mu) -def g(): - f() - f() - -def f(): - delay_mu(1) diff --git a/artiq/test/lit/iodelay/range.py b/artiq/test/lit/iodelay/range.py deleted file mode 100644 index c5592927e..000000000 --- a/artiq/test/lit/iodelay/range.py +++ /dev/null @@ -1,21 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: (a:numpy.int32)->NoneType delay(3 * a mu) -def f(a): - for _ in range(a): - delay_mu(3) - -# CHECK-L: g: (a:numpy.int32, b:numpy.int32)->NoneType delay(3 * (b - a) mu) -def g(a, b): - for _ in range(a, b): - delay_mu(3) - -# CHECK-L: h: (a:numpy.int32, b:numpy.int32, c:numpy.int32)->NoneType delay(3 * (b - a) // c mu) -def h(a, b, c): - for _ in range(a, b, c): - delay_mu(3) - -f(1) -g(1,2) -h(1,2,3) diff --git a/artiq/test/lit/iodelay/return.py b/artiq/test/lit/iodelay/return.py deleted file mode 100644 index 9eefe1f54..000000000 --- a/artiq/test/lit/iodelay/return.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: ()->numpy.int32 delay(30 mu) -def f(): - for _ in range(10): - delay_mu(3) - return 10 - -# CHECK-L: g: (x:float)->numpy.int32 -# CHECK-NOT-L: delay -def g(x): - if x > 1.0: - return 1 - return 0 - -g(1.0) diff --git a/artiq/test/lit/iodelay/sequential.py b/artiq/test/lit/iodelay/sequential.py deleted file mode 100644 index 5be751126..000000000 --- a/artiq/test/lit/iodelay/sequential.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: f: (a:numpy.int64, b:numpy.int64)->NoneType delay(a + b mu) -def f(a, b): - with sequential: - delay_mu(a) - delay_mu(b) diff --git a/artiq/test/lit/lit.cfg b/artiq/test/lit/lit.cfg deleted file mode 100644 index 693383297..000000000 --- a/artiq/test/lit/lit.cfg +++ /dev/null @@ -1,33 +0,0 @@ -# -*- python -*- - -import os -import sys -import lit.util -import lit.formats - -root = os.path.join(os.path.dirname(__file__), "..") - -config.name = "ARTIQ" -config.test_format = lit.formats.ShTest() -config.suffixes = [".py"] -config.excludes = ["not.py", "device_db.py"] - -if os.getenv("COVERAGE"): - config.environment["COVERAGE_FILE"] = os.path.join(root, "..", ".coverage") - python = "coverage run --parallel-mode --source=artiq" -else: - python = sys.executable -config.substitutions.append( ("%python", python) ) - -if os.getenv("PYTHONPATH"): - config.environment["PYTHONPATH"] = os.getenv("PYTHONPATH") - -not_ = "{} {}".format(sys.executable, os.path.join(root, "lit", "not.py")) -config.substitutions.append( ("%not", not_) ) - -if os.name == "posix": - config.environment["LIBARTIQ_SUPPORT"] = os.getenv("LIBARTIQ_SUPPORT") - config.environment["RUST_BACKTRACE"] = "1" - - config.available_features.add("exceptions") - config.available_features.add("time") diff --git a/artiq/test/lit/local_access/invalid_closure.py b/artiq/test/lit/local_access/invalid_closure.py deleted file mode 100644 index 75948fb57..000000000 --- a/artiq/test/lit/local_access/invalid_closure.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -if False: - t = 1 - -# CHECK-L: ${LINE:+1}: error: variable 't' can be captured in a closure uninitialized -l = lambda: t - -# CHECK-L: ${LINE:+1}: error: variable 't' can be captured in a closure uninitialized -def f(): - return t - -l() -f() diff --git a/artiq/test/lit/local_access/invalid_flow.py b/artiq/test/lit/local_access/invalid_flow.py deleted file mode 100644 index 7b99958b4..000000000 --- a/artiq/test/lit/local_access/invalid_flow.py +++ /dev/null @@ -1,20 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature +diag %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = 1 -if x > 10: - y = 1 -# CHECK-L: ${LINE:+1}: error: variable 'y' is not always initialized -x + y - -for z in [1]: - pass -# CHECK-L: ${LINE:+1}: error: variable 'z' is not always initialized --z - -if True: - pass -else: - t = 1 -# CHECK-L: ${LINE:+1}: error: variable 't' is not always initialized --t diff --git a/artiq/test/lit/local_access/multiple_asgn.py b/artiq/test/lit/local_access/multiple_asgn.py deleted file mode 100644 index b316a2005..000000000 --- a/artiq/test/lit/local_access/multiple_asgn.py +++ /dev/null @@ -1,5 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s - -def f(): - x, y = [0], [0] - x[0], y[0] diff --git a/artiq/test/lit/local_access/parallel.py b/artiq/test/lit/local_access/parallel.py deleted file mode 100644 index 001049117..000000000 --- a/artiq/test/lit/local_access/parallel.py +++ /dev/null @@ -1,6 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t - -with interleave: - delay(1.0) - t0 = now_mu() -print(t0) diff --git a/artiq/test/lit/local_access/valid.py b/artiq/test/lit/local_access/valid.py deleted file mode 100644 index 3c5fd0208..000000000 --- a/artiq/test/lit/local_access/valid.py +++ /dev/null @@ -1,7 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t - -if False: - x = 1 -else: - x = 2 --x diff --git a/artiq/test/lit/local_demotion/closure.py b/artiq/test/lit/local_demotion/closure.py deleted file mode 100644 index 5786eb20e..000000000 --- a/artiq/test/lit/local_demotion/closure.py +++ /dev/null @@ -1,15 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.irgen %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def x(y): pass - -# CHECK-L: NoneType input.a(environment(...) %ARG.ENV, NoneType %ARG.self) { -# CHECK-L: setlocal('self') %ENV, NoneType %ARG.self -# CHECK-NOT-L: call (y:NoneType)->NoneType %LOC.x, NoneType %ARG.self - -def a(self): - def b(): - pass - x(self) - -a(None) diff --git a/artiq/test/lit/local_demotion/demotion.py b/artiq/test/lit/local_demotion/demotion.py deleted file mode 100644 index c9b4ed0c5..000000000 --- a/artiq/test/lit/local_demotion/demotion.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.irgen %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def x(y): pass - -# CHECK-L: NoneType input.a(environment(...) %ARG.ENV, NoneType %ARG.self) { -# CHECK-NOT-L: getlocal('self') %ENV -# CHECK-L: call (y:NoneType)->NoneType %LOC.x, NoneType %ARG.self - -def a(self): - x(self) - -a(None) diff --git a/artiq/test/lit/monomorphism/bug_1242.py b/artiq/test/lit/monomorphism/bug_1242.py deleted file mode 100644 index 8918a30cc..000000000 --- a/artiq/test/lit/monomorphism/bug_1242.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = 0x100000000 -# CHECK-L: x: numpy.int64 - -y = int32(x) -# CHECK-L: y: numpy.int32 diff --git a/artiq/test/lit/monomorphism/bug_1252.py b/artiq/test/lit/monomorphism/bug_1252.py deleted file mode 100644 index e00a899ab..000000000 --- a/artiq/test/lit/monomorphism/bug_1252.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.irgen %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: %BLT.round = numpy.int64 builtin(round) float -def frequency_to_ftw(frequency): - return int64(round(1e-9*frequency)) - -frequency_to_ftw(1e9) diff --git a/artiq/test/lit/monomorphism/coercion.py b/artiq/test/lit/monomorphism/coercion.py deleted file mode 100644 index bbd67e936..000000000 --- a/artiq/test/lit/monomorphism/coercion.py +++ /dev/null @@ -1,10 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -def f(x): - x = int64(0) - return x - -# CHECK-L: g: ()->numpy.int64 -def g(): - return f(1 + 0) diff --git a/artiq/test/lit/monomorphism/integers.py b/artiq/test/lit/monomorphism/integers.py deleted file mode 100644 index e6760fffb..000000000 --- a/artiq/test/lit/monomorphism/integers.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.signature %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -x = 1 -# CHECK-L: x: numpy.int32 - -y = int(1) -# CHECK-L: y: numpy.int32 diff --git a/artiq/test/lit/monomorphism/round.py b/artiq/test/lit/monomorphism/round.py deleted file mode 100644 index 74df18401..000000000 --- a/artiq/test/lit/monomorphism/round.py +++ /dev/null @@ -1,11 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.inferencer +mono %s >%t -# RUN: OutputCheck %s --file-to-check=%t - -# CHECK-L: round:(1.0:float):numpy.int32 -round(1.0) - -# CHECK-L: round:(2.0:float):numpy.int32 -int32(round(2.0)) - -# CHECK-L: round:(3.0:float):numpy.int64 -int64(round(3.0)) diff --git a/artiq/test/lit/not.py b/artiq/test/lit/not.py deleted file mode 100644 index 855a36416..000000000 --- a/artiq/test/lit/not.py +++ /dev/null @@ -1,7 +0,0 @@ -import sys, subprocess - -def main(): - exit(not subprocess.call(sys.argv[1:])) - -if __name__ == "__main__": - main() diff --git a/artiq/test/lit/regression/bug_659.py b/artiq/test/lit/regression/bug_659.py deleted file mode 100644 index 096839725..000000000 --- a/artiq/test/lit/regression/bug_659.py +++ /dev/null @@ -1,17 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -import numpy as np - -@kernel -def rotate(array): - '''Rotates an array, deleting the oldest value''' - length = len(array) - for i in range(np.int64(len(array)) - 1): - array[length - i - 1] = array[length - i - 2] - array[0] = 0 - -@kernel -def entrypoint(): - rotate([1,2,3,4]) diff --git a/artiq/test/lit/regression/device_db.py b/artiq/test/lit/regression/device_db.py deleted file mode 100644 index e39c83c09..000000000 --- a/artiq/test/lit/regression/device_db.py +++ /dev/null @@ -1,8 +0,0 @@ -device_db = { - "core": { - "type": "local", - "module": "artiq.coredevice.core", - "class": "Core", - "arguments": {"host": None, "ref_period": 1e-9} - } -} diff --git a/artiq/test/lit/regression/issue_1506.py b/artiq/test/lit/regression/issue_1506.py deleted file mode 100644 index 88d8831bd..000000000 --- a/artiq/test/lit/regression/issue_1506.py +++ /dev/null @@ -1,44 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s - -# -# Check various sret-ized return types integrate properly with try/finally, which lowers -# to `invoke` on the LLVM level (code adapted from GitHub #1506). -# - -LIST = [1, 2] - - -def get_tuple(): - return (1, 2) - - -def get_list(): - return LIST - - -def get_range(): - return range(10) - - -def main(): - try: - a, b = get_tuple() - assert a == 1 - assert b == 2 - finally: - pass - - try: - for _ in get_list(): - pass - finally: - pass - - try: - for _ in get_range(): - pass - finally: - pass - - -main() diff --git a/artiq/test/lit/regression/issue_1627.py b/artiq/test/lit/regression/issue_1627.py deleted file mode 100644 index 828ef3a9f..000000000 --- a/artiq/test/lit/regression/issue_1627.py +++ /dev/null @@ -1,13 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -import numpy as np - -n = 2 -data = np.zeros((n, n)) - - -@kernel -def entrypoint(): - print(data[:n]) diff --git a/artiq/test/lit/regression/issue_1632.py b/artiq/test/lit/regression/issue_1632.py deleted file mode 100644 index c7f36f96b..000000000 --- a/artiq/test/lit/regression/issue_1632.py +++ /dev/null @@ -1,19 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.embedding %s - -from artiq.language.core import * -from artiq.language.types import * -import numpy as np - -class A: - def __init__(self): - self.n = 2 - - @kernel - def run(self): - print([1, 2, 3][:self.n]) - -a = A() - -@kernel -def entrypoint(): - a.run() diff --git a/artiq/test/lit/time/advance.py b/artiq/test/lit/time/advance.py deleted file mode 100644 index 7a6a894aa..000000000 --- a/artiq/test/lit/time/advance.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# REQUIRES: time - -assert now_mu() == 0 -delay(100.0) -assert now_mu() == 100000000 -at_mu(12345000000) -assert now_mu() == 12345000000 diff --git a/artiq/test/lit/time/advance_mu.py b/artiq/test/lit/time/advance_mu.py deleted file mode 100644 index d674afbec..000000000 --- a/artiq/test/lit/time/advance_mu.py +++ /dev/null @@ -1,8 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# REQUIRES: time - -assert now_mu() == 0 -delay_mu(100) -assert now_mu() == 100 -at_mu(12345) -assert now_mu() == 12345 diff --git a/artiq/test/lit/time/parallel.py b/artiq/test/lit/time/parallel.py deleted file mode 100644 index c54261f48..000000000 --- a/artiq/test/lit/time/parallel.py +++ /dev/null @@ -1,18 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s -# REQUIRES: time - -with parallel: - with sequential: - assert now_mu() == 0 - delay_mu(10) - assert now_mu() == 10 - with sequential: - assert now_mu() == 0 - delay_mu(20) - assert now_mu() == 20 - with sequential: - assert now_mu() == 0 - delay_mu(15) - assert now_mu() == 15 - assert now_mu() == 0 -assert now_mu() == 20 diff --git a/artiq/test/lit/try_loop/try_finally_while_try_finally_break.py b/artiq/test/lit/try_loop/try_finally_while_try_finally_break.py deleted file mode 100644 index c66d88ceb..000000000 --- a/artiq/test/lit/try_loop/try_finally_while_try_finally_break.py +++ /dev/null @@ -1,42 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def run(): - loop = 0 - print("start") - try: - while True: - print("loop") - try: - if loop == 0: - loop += 1 - continue - func() - break - except RuntimeError: - print("except") - return False - finally: - print("finally2") - print("after-while") - finally: - print("finally1") - print("exit") - return True - - -def func(): - print("func") - -# CHECK-L: start -# CHECK-NEXT-L: loop -# CHECK-NEXT-L: finally2 -# CHECK-NEXT-L: loop -# CHECK-NEXT-L: func -# CHECK-NEXT-L: finally2 -# CHECK-NEXT-L: after-while -# CHECK-NEXT-L: finally1 -# CHECK-NEXT-L: exit - -run() diff --git a/artiq/test/lit/try_loop/try_finally_while_try_reraise.py b/artiq/test/lit/try_loop/try_finally_while_try_reraise.py deleted file mode 100644 index f7c6db84f..000000000 --- a/artiq/test/lit/try_loop/try_finally_while_try_reraise.py +++ /dev/null @@ -1,42 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def run(): - print("start") - try: - try: - while True: - print("loop") - try: - print("try") - func() - print("unreachable") - return True - except RuntimeError: - print("except1") - raise - print("unreachable") - finally: - print("finally1") - print("unreachable") - return False - except RuntimeError: - print("except2") - raise - finally: - print("finally2") - return True - - -def func(): - raise RuntimeError("Test") - -# CHECK-L: start -# CHECK-NEXT-L: loop -# CHECK-NEXT-L: try -# CHECK-NEXT-L: except1 -# CHECK-NEXT-L: finally1 -# CHECK-NEXT-L: except2 -# CHECK-NEXT-L: finally2 -run() diff --git a/artiq/test/lit/try_loop/try_finally_while_try_return.py b/artiq/test/lit/try_loop/try_finally_while_try_return.py deleted file mode 100644 index 92a547f02..000000000 --- a/artiq/test/lit/try_loop/try_finally_while_try_return.py +++ /dev/null @@ -1,29 +0,0 @@ -# RUN: %python -m artiq.compiler.testbench.jit %s >%t -# RUN: OutputCheck %s --file-to-check=%t -# REQUIRES: exceptions - -def run(): - try: - while True: - try: - print("try") - func() - return True - except RuntimeError: - print("except") - return False - print("unreachable") - finally: - print("finally") - print("unreachable") - return False - - -def func(): - pass - -# CHECK-L: try -# CHECK-NOT-L: except -# CHECK-NOT-L: unreachable -# CHECK-L: finally -run() diff --git a/artiq/tools.py b/artiq/tools.py index e11afe2bb..63b55e79a 100644 --- a/artiq/tools.py +++ b/artiq/tools.py @@ -134,6 +134,9 @@ def file_import(filename, prefix="file_import_"): importlib.machinery.SourceFileLoader(modname, str(filename)), ) module = importlib.util.module_from_spec(spec) + # Add to sys.modules, otherwise inspect thinks it is a built-in module and often breaks. + # This must take place before module execution because NAC3 decorators call inspect. + sys.modules[modname] = module spec.loader.exec_module(module) finally: sys.path.remove(path) diff --git a/doc/manual/conf.py b/doc/manual/conf.py index b1f7c64fc..3c5806cee 100644 --- a/doc/manual/conf.py +++ b/doc/manual/conf.py @@ -20,12 +20,6 @@ from unittest.mock import Mock import sphinx_rtd_theme -# Ensure that ARTIQ-Python types are correctly printed -# See: https://github.com/m-labs/artiq/issues/741 -import builtins -builtins.__in_sphinx__ = True - - # we cannot use autodoc_mock_imports (does not help with argparse) mock_modules = ["artiq.gui.waitingspinnerwidget", "artiq.gui.flowlayout", @@ -34,6 +28,7 @@ mock_modules = ["artiq.gui.waitingspinnerwidget", "artiq.compiler.embedding", "artiq.dashboard.waveform", "artiq.coredevice.jsondesc", + "nac3artiq", "qasync", "lmdb", "dateutil.parser", "prettytable", "PyQt6", "h5py", "llvmlite", "pythonparser", "tqdm", "jsonschema", "platformdirs"] @@ -337,4 +332,4 @@ render_using_wavedrompy = True # tikz_transparent = True # these are the defaults -tikz_tikzlibraries = 'positioning, shapes, arrows.meta' \ No newline at end of file +tikz_tikzlibraries = 'positioning, shapes, arrows.meta' diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index 56d6cd46d..fb5b09aee 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -136,12 +136,6 @@ DAC/ADC drivers .. automodule:: artiq.coredevice.sampler :members: -:mod:`artiq.coredevice.novogorny` module -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -.. automodule:: artiq.coredevice.novogorny - :members: - :mod:`artiq.coredevice.fastino` module ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 1624f83f2..9ac1dd76f 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -1,7 +1,11 @@ Installing ARTIQ ================ +<<<<<<< HEAD M-Labs recommends installing ARTIQ through Nix (on Linux) or MSYS2 (on Windows). It is also possible to use Conda (on either platform), but this is not preferred, and likely to become unsupported in the near future. +======= +ARTIQ can be installed using the Nix package manager on Linux, and using the MSYS2 software distribution on Windows. +>>>>>>> upstream/nac3 Installing via Nix (Linux) -------------------------- @@ -176,7 +180,7 @@ Alternatively, you may install `MSYS2 `_, then edit ``C:\MING [artiq] SigLevel = Optional TrustAll - Server = https://msys2.m-labs.hk/artiq-beta + Server = https://msys2.m-labs.hk/artiq-nac3 Launch ``MSYS2 CLANG64`` from the Windows Start menu to open the MSYS2 shell, and enter the following commands: :: @@ -189,40 +193,6 @@ As above in the Nix section, you may find yourself wanting to add other useful p For more see the `MSYS2 documentation `_ on package management. If your favorite package is not available with MSYS2, contact M-Labs using the helpdesk@ email. -Installing via Conda [DEPRECATED] ---------------------------------- - -.. warning:: - Installing ARTIQ via Conda is not recommended. Instead, Linux users should install it via Nix and Windows users should install it via MSYS2. Conda support may be removed in future ARTIQ releases and M-Labs can only provide very limited technical support for Conda. - -First, install `Anaconda `_ or the more minimalistic `Miniconda `_. After installing either Anaconda or Miniconda, open a new terminal and verify that the following command works:: - - $ conda - -Executing just ``conda`` should print the help of the ``conda`` command. If your shell cannot find the ``conda`` command, make sure that the Conda binaries are in your ``$PATH``. If ``$ echo $PATH`` does not show the Conda directories, add them: execute e.g. ``$ export PATH=$HOME/miniconda3/bin:$PATH`` if you installed Conda into ``~/miniconda3``. - -Controllers for third-party devices (e.g. Thorlabs TCube, Lab Brick Digital Attenuator, etc.) that are not shipped with ARTIQ can also be installed with this script. Browse `Hydra `_ or see the list of NDSPs in this manual to find the names of the corresponding packages, and list them at the beginning of the script. - -Set up the Conda channel and install ARTIQ into a new Conda environment: :: - - $ conda config --prepend channels https://conda.m-labs.hk/artiq-beta - $ conda config --append channels conda-forge - $ conda create -n artiq artiq - -.. note:: - On Windows, if the last command that creates and installs the ARTIQ environment fails with an error similar to "seeking backwards is not allowed", try re-running the command with admin rights. - -.. note:: - For commercial use you might need a license for Anaconda/Miniconda or for using the Anaconda package channel. `Miniforge `_ might be an alternative in a commercial environment as it does not include the Anaconda package channel by default. If you want to use Anaconda/Miniconda/Miniforge in a commercial environment, please check the license and the latest terms of service. - -After the installation, activate the newly created environment by name. :: - - $ conda activate artiq - -This activation has to be performed in every new shell you open to make the ARTIQ tools from that environment available. - -.. _installing-upgrading: - Upgrading ARTIQ --------------- @@ -241,15 +211,5 @@ Upgrading with MSYS2 Run ``pacman -Syu`` to update all MSYS2 packages, including ARTIQ. If you get a message telling you that the shell session must be restarted after a partial update, open the shell again after the partial update and repeat the command. See the `MSYS2 `__ and `Pacman `_ manuals for more information, including how to update individual packages if required. -Upgrading with Conda -^^^^^^^^^^^^^^^^^^^^ +You may need to reflash the gateware and firmware of the core device to keep it synchronized with the software. -When upgrading ARTIQ or when testing different versions it is recommended that new Conda environments are created instead of upgrading the packages in existing environments. As a rule, keep previous environments around unless you are certain that they are no longer needed and the new environment is working correctly. - -To install the latest version, simply select a different environment name and run the installation commands again. - -Switching between Conda environments using commands such as ``$ conda deactivate artiq-7`` and ``$ conda activate artiq-8`` is the recommended way to roll back to previous versions of ARTIQ. - -You can list the environments you have created using:: - - $ conda env list diff --git a/doc/manual/introduction.rst b/doc/manual/introduction.rst index 78929480a..00654afba 100644 --- a/doc/manual/introduction.rst +++ b/doc/manual/introduction.rst @@ -12,7 +12,7 @@ The system features a high-level programming language, capable of describing com ARTIQ uses FPGA hardware to perform its time-critical tasks. The `Sinara hardware `_, and in particular the Kasli FPGA carrier, are designed to work with ARTIQ. ARTIQ is designed to be portable to hardware platforms from different vendors and FPGA manufacturers. Several different configurations of a `FPGA evaluation kit `_ and a `Zynq evaluation kit `_ are also used and supported. FPGA platforms can be combined with any number of additional peripherals, either already accessible from ARTIQ or made accessible with little effort. -ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). See `the manual `_ for installation instructions. Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration. Like any open-source software ARTIQ can equally be built and installed directly from `source `_. +ARTIQ and its dependencies are available in the form of Nix packages (for Linux) and MSYS2 packages (for Windows). Packages containing pre-compiled binary images to be loaded onto the hardware platforms are supplied for each configuration. Like any open-source software ARTIQ can equally be built and installed directly from `source `_. 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. diff --git a/flake.lock b/flake.lock index 4aa44b1d3..04ded1030 100644 --- a/flake.lock +++ b/flake.lock @@ -4,6 +4,7 @@ "inputs": { "flake-utils": "flake-utils", "nixpkgs": [ + "nac3", "nixpkgs" ], "sipyco": [ @@ -42,13 +43,39 @@ "type": "github" } }, + "nac3": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1739177615, + "narHash": "sha256-JuP4B91xgKgg+/yAdjr8D+eDT1IV7WszbV+bpu1xvQ8=", + "ref": "refs/heads/master", + "rev": "82a580c5c6e68e0c8f92ecd44da2a18b1adf7d0a", + "revCount": 1536, + "type": "git", + "url": "https://git.m-labs.hk/m-labs/nac3.git" + }, + "original": { + "type": "git", + "url": "https://git.m-labs.hk/m-labs/nac3.git" + } + }, "nixpkgs": { "locked": { +<<<<<<< HEAD "lastModified": 1736883708, "narHash": "sha256-uQ+NQ0/xYU0N1CnXsa2zghgNaOPxWpMJXSUJJ9W7140=", "owner": "NixOS", "repo": "nixpkgs", "rev": "eb62e6aa39ea67e0b8018ba8ea077efe65807dc8", +======= + "lastModified": 1738680400, + "narHash": "sha256-ooLh+XW8jfa+91F1nhf9OF7qhuA/y1ChLx6lXDNeY5U=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "799ba5bffed04ced7067a91798353d360788b30d", +>>>>>>> upstream/nac3 "type": "github" }, "original": { @@ -61,17 +88,17 @@ "root": { "inputs": { "artiq-comtools": "artiq-comtools", - "nixpkgs": "nixpkgs", + "nac3": "nac3", "rust-overlay": "rust-overlay", "sipyco": "sipyco", "src-migen": "src-migen", - "src-misoc": "src-misoc", - "src-pythonparser": "src-pythonparser" + "src-misoc": "src-misoc" } }, "rust-overlay": { "inputs": { "nixpkgs": [ + "nac3", "nixpkgs" ] }, @@ -93,6 +120,7 @@ "sipyco": { "inputs": { "nixpkgs": [ + "nac3", "nixpkgs" ] }, @@ -113,11 +141,19 @@ "src-migen": { "flake": false, "locked": { +<<<<<<< HEAD "lastModified": 1737001477, "narHash": "sha256-5Rv7R8OO/CsjdDreo+vCUO7dIrTD+70meV5rIvHOGDk=", "owner": "m-labs", "repo": "migen", "rev": "28e913e7114dae485747ccd8f9fd436ada2195f0", +======= + "lastModified": 1738906518, + "narHash": "sha256-GproDJowtcgbccsT+I0mObzFhE483shcS8MSszKXwlc=", + "owner": "m-labs", + "repo": "migen", + "rev": "2828df54594673653a641ab551caf6c6b1bfeee5", +>>>>>>> upstream/nac3 "type": "github" }, "original": { @@ -129,11 +165,19 @@ "src-misoc": { "flake": false, "locked": { +<<<<<<< HEAD "lastModified": 1737003678, "narHash": "sha256-YGswRUz5QH28FsVK0j4n/Op6ptFTPPdEOir0JkxfSa0=", "ref": "refs/heads/master", "rev": "3c2054c43309d2fa3b72c5435f90c2357f2908af", "revCount": 2465, +======= + "lastModified": 1738745832, + "narHash": "sha256-mPZs3J3hIk5XJthhHX56bUY1kyGb/kHg9ZlSHWanpXA=", + "ref": "refs/heads/master", + "rev": "3529bddf5c950985532d27a3dbc907cfe42d7b48", + "revCount": 2472, +>>>>>>> upstream/nac3 "submodules": true, "type": "git", "url": "https://github.com/m-labs/misoc.git" @@ -144,22 +188,6 @@ "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" - } - }, "systems": { "locked": { "lastModified": 1681028828, diff --git a/flake.nix b/flake.nix index 5aa14ec50..b05639be9 100644 --- a/flake.nix +++ b/flake.nix @@ -2,22 +2,25 @@ description = "A leading-edge control system for quantum information experiments"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable"; + nac3 = { + url = "https://git.m-labs.hk/m-labs/nac3.git"; + type = "git"; + }; rust-overlay = { url = "github:oxalica/rust-overlay?ref=snapshot/2024-08-01"; - inputs.nixpkgs.follows = "nixpkgs"; + inputs.nixpkgs.follows = "nac3/nixpkgs"; }; artiq-comtools = { url = "github:m-labs/artiq-comtools"; - inputs.nixpkgs.follows = "nixpkgs"; + inputs.nixpkgs.follows = "nac3/nixpkgs"; inputs.sipyco.follows = "sipyco"; }; sipyco = { url = "github:m-labs/sipyco"; - inputs.nixpkgs.follows = "nixpkgs"; + inputs.nixpkgs.follows = "nac3/nixpkgs"; }; src-migen = { @@ -31,30 +34,24 @@ submodules = true; flake = false; }; - - src-pythonparser = { - url = "github:m-labs/pythonparser"; - flake = false; - }; }; outputs = { self, - nixpkgs, rust-overlay, sipyco, - src-pythonparser, + nac3, artiq-comtools, src-migen, src-misoc, }: let - pkgs = import nixpkgs { + pkgs = import nac3.inputs.nixpkgs { system = "x86_64-linux"; overlays = [(import rust-overlay)]; }; - pkgs-aarch64 = import nixpkgs {system = "aarch64-linux";}; + pkgs-aarch64 = import nac3.inputs.nixpkgs {system = "aarch64-linux";}; - artiqVersionMajor = 9; + artiqVersionMajor = 10; artiqVersionMinor = self.sourceInfo.revCount or 0; artiqVersionId = self.sourceInfo.shortRev or "unknown"; artiqVersion = (builtins.toString artiqVersionMajor) + "." + (builtins.toString artiqVersionMinor) + "+" + artiqVersionId + ".beta"; @@ -101,14 +98,6 @@ fontconfig ]; - pythonparser = pkgs.python3Packages.buildPythonPackage { - pname = "pythonparser"; - version = "1.4"; - src = src-pythonparser; - doCheck = false; - propagatedBuildInputs = with pkgs.python3Packages; [regex]; - }; - qasync = pkgs.python3Packages.buildPythonPackage rec { pname = "qasync"; version = "0.27.1"; @@ -129,6 +118,7 @@ disabledTestPaths = ["tests/test_qeventloop.py"]; }; +<<<<<<< HEAD libartiq-support = pkgs.stdenv.mkDerivation { name = "libartiq-support"; src = self; @@ -169,6 +159,8 @@ ''; }; +======= +>>>>>>> upstream/nac3 artiq-upstream = pkgs.python3Packages.buildPythonPackage rec { pname = "artiq"; version = artiqVersion; @@ -180,10 +172,11 @@ ''; nativeBuildInputs = [pkgs.qt6.wrapQtAppsHook]; - # keep llvm_x and lld_x in sync with llvmlite + + # keep llvm_x in sync with nac3 propagatedBuildInputs = - [pkgs.llvm_15 pkgs.lld_15 sipyco.packages.x86_64-linux.sipyco pythonparser llvmlite-new pkgs.qt6.qtsvg artiq-comtools.packages.x86_64-linux.artiq-comtools] - ++ (with pkgs.python3Packages; [pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial levenshtein h5py pyqt6 qasync tqdm lmdb jsonschema platformdirs]); + [pkgs.llvm_14 nac3.packages.x86_64-linux.nac3artiq-pgo sipyco.packages.x86_64-linux.sipyco pkgs.qt6.qtsvg artiq-comtools.packages.x86_64-linux.artiq-comtools] + ++ (with pkgs.python3Packages; [pyqtgraph pygit2 numpy dateutil scipy prettytable pyserial h5py pyqt6 qasync tqdm lmdb jsonschema platformdirs]); dontWrapQtApps = true; postFixup = '' @@ -204,16 +197,12 @@ "--set FONTCONFIG_FILE ${pkgs.fontconfig.out}/etc/fonts/fonts.conf" ]; - # FIXME: automatically propagate lld_15 llvm_15 dependencies + # FIXME: automatically propagate llvm_x dependency # cacert is required in the check stage only, as certificates are to be # obtained from system elsewhere - nativeCheckInputs = with pkgs; [lld_15 llvm_15 lit outputcheck cacert] ++ [libartiq-support]; + nativeCheckInputs = [pkgs.llvm_14 pkgs.cacert]; 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` lit -v $TESTDIR/lit ''; }; @@ -292,9 +281,9 @@ nativeBuildInputs = [ (pkgs.python3.withPackages (ps: [migen misoc (artiq.withExperimentalFeatures experimentalFeatures) ps.packaging])) rust - pkgs.llvmPackages_15.clang-unwrapped - pkgs.llvm_15 - pkgs.lld_15 + pkgs.llvmPackages_14.clang-unwrapped + pkgs.llvm_14 + pkgs.lld_14 vivado rustPlatform.cargoSetupHook ]; @@ -396,8 +385,9 @@ done ''; in rec { - packages.x86_64-linux = { - inherit pythonparser qasync artiq; + packages.x86_64-linux = rec { + inherit (nac3.packages.x86_64-linux) python3-mimalloc; + inherit qasync artiq; inherit migen misoc asyncserial microscope vivadoEnv vivado; openocd-bscanspi = openocd-bscanspi-f pkgs; artiq-board-kc705-nist_clock = makeArtiqBoardPackage { @@ -470,9 +460,14 @@ }; }; + packages.x86_64-w64-mingw32 = import ./windows { + inherit sipyco nac3 artiq-comtools; + artiq = self; + }; + inherit qtPaths makeArtiqBoardPackage openocd-bscanspi-f; - packages.x86_64-linux.default = pkgs.python3.withPackages (_: [packages.x86_64-linux.artiq]); + defaultPackage.x86_64-linux = packages.x86_64-linux.python3-mimalloc.withPackages (_: [packages.x86_64-linux.artiq]); formatter.x86_64-linux = pkgs.alejandra; @@ -487,10 +482,9 @@ [ git lit - lld_15 - llvm_15 - llvmPackages_15.clang-unwrapped - outputcheck + lld_14 + llvm_14 + llvmPackages_14.clang-unwrapped pdf2svg python3Packages.sphinx @@ -499,23 +493,19 @@ python3Packages.sphinxcontrib-wavedrom python3Packages.sphinx_rtd_theme - (python3.withPackages (ps: [migen misoc microscope ps.packaging ps.paramiko] ++ artiq.propagatedBuildInputs)) + (packages.x86_64-linux.python3-mimalloc.withPackages (ps: [migen misoc microscope ps.packaging] ++ artiq.propagatedBuildInputs)) ] ++ [ latex-artiq-manual rust artiq-frontend-dev-wrappers - # To manually run compiler tests: - libartiq-support - # 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 ]; shellHook = '' - export LIBARTIQ_SUPPORT=`libartiq-support` export QT_PLUGIN_PATH=${qtPaths.QT_PLUGIN_PATH} export QML2_IMPORT_PATH=${qtPaths.QML2_IMPORT_PATH} export PYTHONPATH=`git rev-parse --show-toplevel`:$PYTHONPATH @@ -527,14 +517,14 @@ packages = [ rust - pkgs.llvmPackages_15.clang-unwrapped - pkgs.llvm_15 - pkgs.lld_15 + pkgs.llvmPackages_14.clang-unwrapped + pkgs.llvm_14 + pkgs.lld_14 packages.x86_64-linux.vivado packages.x86_64-linux.openocd-bscanspi - (pkgs.python3.withPackages (ps: [migen misoc artiq ps.packaging ps.paramiko])) + (pkgs.python3.withPackages (ps: [migen misoc artiq ps.packaging])) ]; }; }; @@ -545,6 +535,11 @@ hydraJobs = { inherit (packages.x86_64-linux) artiq artiq-board-kc705-nist_clock artiq-board-efc-shuttler openocd-bscanspi; + sipyco-msys2-pkg = packages.x86_64-w64-mingw32.sipyco-pkg; + artiq-comtools-msys2-pkg = packages.x86_64-w64-mingw32.artiq-comtools-pkg; + artiq-msys2-pkg = packages.x86_64-w64-mingw32.artiq-pkg; + msys2-repos = packages.x86_64-w64-mingw32.msys2-repos; + inherit (packages.x86_64-linux) artiq-manual-html artiq-manual-pdf; gateware-sim = pkgs.stdenvNoCC.mkDerivation { name = "gateware-sim"; buildInputs = [ @@ -573,8 +568,7 @@ ] ++ ps.paramiko.optional-dependencies.ed25519 )) - pkgs.llvm_15 - pkgs.lld_15 + pkgs.llvm_14 pkgs.openssh packages.x86_64-linux.openocd-bscanspi # for the bscanspi bitstreams ]; @@ -619,7 +613,6 @@ touch $out ''; }; - inherit (packages.x86_64-linux) artiq-manual-html artiq-manual-pdf; }; }; diff --git a/setup.py b/setup.py index 23ff6707c..34a13c2da 100755 --- a/setup.py +++ b/setup.py @@ -10,15 +10,6 @@ if sys.version_info[:2] < (3, 11): raise Exception("You need Python 3.11+") -# Depends on PyQt6, but setuptools cannot check for it. -requirements = [ - "numpy", "scipy", - "python-dateutil", "prettytable", "h5py", "lmdb", - "qasync", "pyqtgraph", "pygit2", - "llvmlite", "pythonparser", "levenshtein", - "platformdirs", -] - console_scripts = [ "artiq_client = artiq.frontend.artiq_client:main", "artiq_compile = artiq.frontend.artiq_compile:main", @@ -63,13 +54,11 @@ 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.7 +Programming Language :: Python :: 3.9 Topic :: Scientific/Engineering :: Physics Topic :: System :: Hardware """.splitlines(), - install_requires=requirements, - extras_require={}, - packages=find_namespace_packages(exclude=["artiq.test.lit", "artiq.test.lit.*", "doc.manual"], ), + packages=find_namespace_packages(exclude=["doc.manual"]), namespace_packages=[], include_package_data=True, ext_modules=[], diff --git a/versioneer.py b/versioneer.py index eae28b2c6..343e802e4 100644 --- a/versioneer.py +++ b/versioneer.py @@ -11,7 +11,7 @@ def get_rev(): """ def get_version(): - return os.getenv("VERSIONEER_OVERRIDE", default="9.0+unknown.beta") + return os.getenv("VERSIONEER_OVERRIDE", default="10.0+unknown.beta") def get_rev(): return os.getenv("VERSIONEER_REV", default="unknown") diff --git a/windows/PKGBUILD.artiq b/windows/PKGBUILD.artiq new file mode 100644 index 000000000..012767bdb --- /dev/null +++ b/windows/PKGBUILD.artiq @@ -0,0 +1,26 @@ +pkgbase="mingw-w64-artiq" +pkgname="mingw-w64-clang-x86_64-artiq" +pkgrel=1 +pkgdesc="A leading-edge control system for quantum information experiments" +license=("LGPL") +depends=( + "mingw-w64-clang-x86_64-python" + "mingw-w64-clang-x86_64-python-h5py" + "mingw-w64-clang-x86_64-python-lmdb" + "mingw-w64-clang-x86_64-python-pyqt6" + "mingw-w64-clang-x86_64-qt6-svg" + "mingw-w64-clang-x86_64-python-qasync" + "mingw-w64-clang-x86_64-python-pyqtgraph" + "mingw-w64-clang-x86_64-python-numpy" + "mingw-w64-clang-x86_64-python-scipy" + "mingw-w64-clang-x86_64-python-dateutil" + "mingw-w64-clang-x86_64-python-prettytable" + "mingw-w64-clang-x86_64-python-tqdm" + "mingw-w64-clang-x86_64-python-pygit2" + "mingw-w64-clang-x86_64-python-sipyco" + "mingw-w64-clang-x86_64-python-jsonschema" + "mingw-w64-clang-x86_64-nac3artiq" + "mingw-w64-clang-x86_64-artiq-comtools" +) + +source PKGBUILD.common diff --git a/windows/PKGBUILD.artiq-comtools b/windows/PKGBUILD.artiq-comtools new file mode 100644 index 000000000..11598c88a --- /dev/null +++ b/windows/PKGBUILD.artiq-comtools @@ -0,0 +1,13 @@ +pkgbase="mingw-w64-artiq-comtools" +pkgname="mingw-w64-clang-x86_64-artiq-comtools" +pkgrel=1 +pkgdesc="Lightweight ARTIQ communication tools" +license=("LGPL") +depends=( + "mingw-w64-clang-x86_64-python" + "mingw-w64-clang-x86_64-python-sipyco" + "mingw-w64-clang-x86_64-python-numpy" + "mingw-w64-clang-x86_64-python-aiohttp" +) + +source PKGBUILD.common diff --git a/windows/PKGBUILD.common b/windows/PKGBUILD.common new file mode 100644 index 000000000..4463788eb --- /dev/null +++ b/windows/PKGBUILD.common @@ -0,0 +1,29 @@ +arch=("any") +mingw_arch=("clang64") +pkgver=${DRV_VERSION} +url="https://m-labs.hk" +source=("source.tar") +sha256sums=("SKIP") + +build() { + mkdir clang64 + export PYTHONPATH=`pwd`/clang64/lib/python3.12/site-packages + chmod +w -R source + cd source + wine-msys2-build python setup.py install --single-version-externally-managed --prefix=../clang64 --record=setuptools-sucks.txt + cd .. + + # patch broken shebangs (Z:/nix/store/...) + for entrypoint in clang64/bin/*-script.py; do + [ -f "$entrypoint" ] || continue + sed -i "1s|#!.*|#!python|" $entrypoint + done + for entrypoint in clang64/bin/*-script.pyw; do + [ -f "$entrypoint" ] || continue + sed -i "1s|#!.*|#!pythonw|" $entrypoint + done +} + +package() { + cp -R clang64 ${pkgdir} +} diff --git a/windows/PKGBUILD.sipyco b/windows/PKGBUILD.sipyco new file mode 100644 index 000000000..ebaa740b4 --- /dev/null +++ b/windows/PKGBUILD.sipyco @@ -0,0 +1,12 @@ +pkgbase="mingw-w64-python-sipyco" +pkgname="mingw-w64-clang-x86_64-python-sipyco" +pkgrel=1 +pkgdesc="Simple Python Communications" +license=("LGPL") +depends=( + "mingw-w64-clang-x86_64-python" + "mingw-w64-clang-x86_64-python-numpy" + "mingw-w64-clang-x86_64-python-pybase64" +) + +source PKGBUILD.common diff --git a/windows/default.nix b/windows/default.nix new file mode 100644 index 000000000..68b764fb0 --- /dev/null +++ b/windows/default.nix @@ -0,0 +1,61 @@ +{ sipyco, nac3, artiq-comtools, artiq }: +let + pkgs = import nac3.inputs.nixpkgs { system = "x86_64-linux"; }; + makeMsys2 = { name, version, src }: pkgs.stdenvNoCC.mkDerivation { + pname = "${name}-msys2-pkg"; + inherit version; + nativeBuildInputs = [ + pkgs.pacman pkgs.fakeroot pkgs.libarchive pkgs.zstd pkgs.file + nac3.packages.x86_64-w64-mingw32.wine-msys2-build + ]; + inherit src; + phases = [ "buildPhase" "installPhase" ]; + buildPhase = + '' + export DRV_VERSION=${version} + ln -s ${./PKGBUILD.${name}} PKGBUILD + ln -s ${./PKGBUILD.common} PKGBUILD.common + ln -s $src source + tar cfh source.tar source + rm source + makepkg --config ${./makepkg.conf} --nodeps + ''; + installPhase = + '' + mkdir $out $out/nix-support + cp *.pkg.tar.zst $out + echo file msys2 $out/*.pkg.tar.zst >> $out/nix-support/hydra-build-products + ''; + }; +in rec { + sipyco-pkg = makeMsys2 { + name = "sipyco"; + src = sipyco; + inherit (sipyco.packages.x86_64-linux.sipyco) version; + }; + artiq-comtools-pkg = makeMsys2 { + name = "artiq-comtools"; + src = artiq-comtools; + inherit (artiq-comtools.packages.x86_64-linux.artiq-comtools) version; + }; + artiq-pkg = makeMsys2 { + name = "artiq"; + src = artiq; + inherit (artiq.packages.x86_64-linux.artiq) version; + }; + msys2-repos = pkgs.stdenvNoCC.mkDerivation { + name = "msys2-repos"; + nativeBuildInputs = [ pkgs.pacman ]; + phases = [ "buildPhase" ]; + buildPhase = + '' + mkdir $out + cd $out + ln -s ${sipyco-pkg}/*.pkg.tar.zst . + ln -s ${artiq-comtools-pkg}/*.pkg.tar.zst . + ln -s ${nac3.packages.x86_64-w64-mingw32.nac3artiq-pkg}/*.pkg.tar.zst . + ln -s ${artiq-pkg}/*.pkg.tar.zst . + repo-add artiq.db.tar.gz *.pkg.tar.zst + ''; + }; +} diff --git a/windows/makepkg.conf b/windows/makepkg.conf new file mode 100644 index 000000000..3238344eb --- /dev/null +++ b/windows/makepkg.conf @@ -0,0 +1,2 @@ +PKGEXT='.pkg.tar.zst' +SRCEXT='.src.tar.gz'