diff --git a/RELEASE_NOTES.rst b/RELEASE_NOTES.rst index 7ea7ce122..4c905ea5a 100644 --- a/RELEASE_NOTES.rst +++ b/RELEASE_NOTES.rst @@ -10,3 +10,5 @@ Release notes * Core device flash storage has moved due to increased runtime size. This requires reflashing the runtime and the flash storage filesystem image or erase and rewrite its entries. +* RTIOCollisionError has been renamed to RTIOCollision + \ No newline at end of file diff --git a/artiq/compiler/embedding.py b/artiq/compiler/embedding.py index 7cd53eaaa..1058c6a70 100644 --- a/artiq/compiler/embedding.py +++ b/artiq/compiler/embedding.py @@ -295,6 +295,102 @@ class StitchingInferencer(Inferencer): super().__init__(engine) self.value_map = value_map self.quote = quote + self.attr_type_cache = {} + + def _compute_value_type(self, object_value, object_type, object_loc, attr_name, loc): + if not hasattr(object_value, attr_name): + if attr_name.startswith('_'): + names = set(filter(lambda name: not name.startswith('_'), + dir(object_value))) + else: + names = set(dir(object_value)) + suggestion = suggest_identifier(attr_name, names) + + note = diagnostic.Diagnostic("note", + "attribute accessed here", {}, + loc) + if suggestion is not None: + diag = diagnostic.Diagnostic("error", + "host object does not have an attribute '{attr}'; " + "did you mean '{suggestion}'?", + {"attr": attr_name, "suggestion": suggestion}, + object_loc, notes=[note]) + else: + diag = diagnostic.Diagnostic("error", + "host object does not have an attribute '{attr}'", + {"attr": attr_name}, + object_loc, notes=[note]) + self.engine.process(diag) + return + + # Figure out what ARTIQ type does the value of the attribute have. + # We do this by quoting it, as if to serialize. This has some + # overhead (i.e. synthesizing a source buffer), but has the advantage + # of having the host-to-ARTIQ mapping code in only one place and + # also immediately getting proper diagnostics on type errors. + attr_value = getattr(object_value, attr_name) + if inspect.ismethod(attr_value) and types.is_instance(object_type): + # In cases like: + # class c: + # @kernel + # def f(self): pass + # we want f to be defined on the class, not on the instance. + attributes = object_type.constructor.attributes + attr_value = attr_value.__func__ + else: + attributes = object_type.attributes + + attr_value_type = None + + if isinstance(attr_value, list): + # Fast path for lists of scalars. + IS_FLOAT = 1 + IS_INT32 = 2 + IS_INT64 = 4 + + state = 0 + for elt in attr_value: + if elt.__class__ == float: + state |= IS_FLOAT + elif elt.__class__ == int: + if -2**31 < elt < 2**31-1: + state |= IS_INT32 + elif -2**63 < elt < 2**63-1: + state |= IS_INT64 + else: + state = -1 + break + else: + state = -1 + + if state == IS_FLOAT: + attr_value_type = builtins.TList(builtins.TFloat()) + elif state == IS_INT32: + attr_value_type = builtins.TList(builtins.TInt32()) + elif state == IS_INT64: + attr_value_type = builtins.TList(builtins.TInt64()) + + if attr_value_type is None: + # Slow path. We don't know what exactly is the attribute value, + # so we quote it only for the error message that may possibly result. + ast = self.quote(attr_value, object_loc.expanded_from) + + def proxy_diagnostic(diag): + note = diagnostic.Diagnostic("note", + "while inferring a type for an attribute '{attr}' of a host object", + {"attr": attr_name}, + loc) + diag.notes.append(note) + + self.engine.process(diag) + + proxy_engine = diagnostic.Engine() + proxy_engine.process = proxy_diagnostic + Inferencer(engine=proxy_engine).visit(ast) + IntMonomorphizer(engine=proxy_engine).visit(ast) + attr_value_type = ast.type + + return attributes, attr_value_type def _unify_attribute(self, result_type, value_node, attr_name, attr_loc, loc): # The inferencer can only observe types, not values; however, @@ -304,108 +400,15 @@ class StitchingInferencer(Inferencer): # its type, we now interrogate every host object we have to ensure # that we can successfully serialize the value of the attribute we # are now adding at the code generation stage. - # - # FIXME: We perform exhaustive checks of every known host object every - # time an attribute access is visited, which is potentially quadratic. - # This is done because it is simpler than performing the checks only when: - # * a previously unknown attribute is encountered, - # * a previously unknown host object is encountered; - # which would be the optimal solution. - object_type = value_node.type.find() for object_value, object_loc in self.value_map[object_type]: - attr_value_type = None - if not hasattr(object_value, attr_name): - if attr_name.startswith('_'): - names = set(filter(lambda name: not name.startswith('_'), - dir(object_value))) - else: - names = set(dir(object_value)) - suggestion = suggest_identifier(attr_name, names) - - note = diagnostic.Diagnostic("note", - "attribute accessed here", {}, - loc) - if suggestion is not None: - diag = diagnostic.Diagnostic("error", - "host object does not have an attribute '{attr}'; " - "did you mean '{suggestion}'?", - {"attr": attr_name, "suggestion": suggestion}, - object_loc, notes=[note]) - else: - diag = diagnostic.Diagnostic("error", - "host object does not have an attribute '{attr}'", - {"attr": attr_name}, - object_loc, notes=[note]) - self.engine.process(diag) - return - - # Figure out what ARTIQ type does the value of the attribute have. - # We do this by quoting it, as if to serialize. This has some - # overhead (i.e. synthesizing a source buffer), but has the advantage - # of having the host-to-ARTIQ mapping code in only one place and - # also immediately getting proper diagnostics on type errors. - attr_value = getattr(object_value, attr_name) - if inspect.ismethod(attr_value) and types.is_instance(object_type): - # In cases like: - # class c: - # @kernel - # def f(self): pass - # we want f to be defined on the class, not on the instance. - attributes = object_type.constructor.attributes - attr_value = attr_value.__func__ - is_method = True - else: - attributes = object_type.attributes - is_method = False - - if isinstance(attr_value, list): - # Fast path for lists of scalars. - IS_FLOAT = 1 - IS_INT32 = 2 - IS_INT64 = 4 - - state = 0 - for elt in attr_value: - if elt.__class__ == float: - state |= IS_FLOAT - elif elt.__class__ == int: - if -2**31 < elt < 2**31-1: - state |= IS_INT32 - elif -2**63 < elt < 2**63-1: - state |= IS_INT64 - else: - state = -1 - break - else: - state = -1 - - if state == IS_FLOAT: - attr_value_type = builtins.TList(builtins.TFloat()) - elif state == IS_INT32: - attr_value_type = builtins.TList(builtins.TInt32()) - elif state == IS_INT64: - attr_value_type = builtins.TList(builtins.TInt64()) - - if attr_value_type is None: - # Slow path. We don't know what exactly is the attribute value, - # so we quote it only for the error message that may possibly result. - ast = self.quote(attr_value, object_loc.expanded_from) - - def proxy_diagnostic(diag): - note = diagnostic.Diagnostic("note", - "while inferring a type for an attribute '{attr}' of a host object", - {"attr": attr_name}, - loc) - diag.notes.append(note) - - self.engine.process(diag) - - proxy_engine = diagnostic.Engine() - proxy_engine.process = proxy_diagnostic - Inferencer(engine=proxy_engine).visit(ast) - IntMonomorphizer(engine=proxy_engine).visit(ast) - attr_value_type = ast.type + attr_type_key = (id(object_value), attr_name) + try: + attributes, attr_value_type = self.attr_type_cache[attr_type_key] + except KeyError: + attributes, attr_value_type = \ + self._compute_value_type(object_value, object_type, object_loc, attr_name, loc) + self.attr_type_cache[attr_type_key] = attributes, attr_value_type if attr_name not in attributes: # We just figured out what the type should be. Add it. @@ -739,6 +742,16 @@ class Stitcher: else: if hasattr(function, "artiq_embedded"): if function.artiq_embedded.function is not None: + if function.__name__ == "": + note = diagnostic.Diagnostic("note", + "lambda created here", {}, + self._function_loc(function.artiq_embedded.function)) + diag = diagnostic.Diagnostic("fatal", + "lambdas cannot be used as kernel functions", {}, + loc, + notes=[note]) + self.engine.process(diag) + # Insert the typed AST for the new function and restart inference. # It doesn't really matter where we insert as long as it is before # the final call. diff --git a/artiq/compiler/transforms/asttyped_rewriter.py b/artiq/compiler/transforms/asttyped_rewriter.py index 70790a613..43455143f 100644 --- a/artiq/compiler/transforms/asttyped_rewriter.py +++ b/artiq/compiler/transforms/asttyped_rewriter.py @@ -477,7 +477,7 @@ class ASTTypedRewriter(algorithm.Transformer): 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) + else_loc=node.else_loc, else_colon_loc=node.else_colon_loc, loc=node.loc) return node def visit_withitem(self, node): diff --git a/artiq/compiler/transforms/llvm_ir_generator.py b/artiq/compiler/transforms/llvm_ir_generator.py index 6f8e0633a..c59e16689 100644 --- a/artiq/compiler/transforms/llvm_ir_generator.py +++ b/artiq/compiler/transforms/llvm_ir_generator.py @@ -485,7 +485,7 @@ class LLVMIRGenerator: ]) llrpcattr.global_constant = True llrpcattr.unnamed_addr = True - llrpcattr.linkage = 'internal' + llrpcattr.linkage = 'private' return llrpcattr @@ -499,14 +499,14 @@ class LLVMIRGenerator: llrpcattrs + [ll.Constant(llrpcattrty.as_pointer(), None)]) llrpcattrary.global_constant = True llrpcattrary.unnamed_addr = True - llrpcattrary.linkage = 'internal' + llrpcattrary.linkage = 'private' llobjectaryty = ll.ArrayType(llptr, len(llobjects[typ]) + 1) llobjectary = ll.GlobalVariable(self.llmodule, llobjectaryty, name="objects.{}".format(type_name)) llobjectary.initializer = ll.Constant(llobjectaryty, llobjects[typ] + [ll.Constant(llptr, None)]) - llobjectary.linkage = 'internal' + llobjectary.linkage = 'private' lldesc = ll.GlobalVariable(self.llmodule, lldescty, name="desc.{}".format(type_name)) @@ -515,7 +515,7 @@ class LLVMIRGenerator: llobjectary.bitcast(llptr.as_pointer()) ]) lldesc.global_constant = True - lldesc.linkage = 'internal' + lldesc.linkage = 'private' lldescs.append(lldesc) llglobaldescty = ll.ArrayType(lldescty.as_pointer(), len(lldescs) + 1) @@ -529,7 +529,7 @@ class LLVMIRGenerator: self.llfunction = self.map(func) if func.is_internal: - self.llfunction.linkage = 'internal' + self.llfunction.linkage = 'private' self.llfunction.attributes.add('uwtable') @@ -655,8 +655,11 @@ class LLVMIRGenerator: def process_SetLocal(self, insn): env = insn.environment() - llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) llvalue = self.map(insn.value()) + if isinstance(llvalue.type, ll.VoidType): + # We store NoneType as {} but return it as void. So, bail out here. + return ll.Constant(ll.LiteralStructType([]), []) + llptr = self.llptr_to_var(self.map(env), env.type, insn.var_name) if isinstance(llvalue, ll.Block): llvalue = ll.BlockAddress(self.llfunction, llvalue) if llptr.type.pointee != llvalue.type: diff --git a/artiq/compiler/types.py b/artiq/compiler/types.py index f5adf79e5..ec4701852 100644 --- a/artiq/compiler/types.py +++ b/artiq/compiler/types.py @@ -527,7 +527,7 @@ class TDelay(Type): elif self.is_fixed() and other.is_fixed() and \ self.duration.fold() == other.duration.fold(): pass - else: + elif self is not other: raise UnificationError(self, other) def fold(self, accum, fn): @@ -694,42 +694,48 @@ class TypePrinter(object): self.map = {} self.recurse_guard = set() - def name(self, typ): + 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: + 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) - attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr])) - for attr in typ.attributes]) - return "".format(typ.name, attrs) + return "".format(typ.name) elif isinstance(typ, TMono): if typ.params == {}: return typ.name else: return "%s(%s)" % (typ.name, ", ".join( - ["%s=%s" % (k, self.name(typ.params[k])) for k in typ.params])) + ["%s=%s" % (k, self.name(typ.params[k], depth + 1)) for k in typ.params])) elif isinstance(typ, TTuple): if len(typ.elts) == 1: - return "(%s,)" % self.name(typ.elts[0]) + return "(%s,)" % self.name(typ.elts[0], depth + 1) else: - return "(%s)" % ", ".join(list(map(self.name, typ.elts))) + return "(%s)" % ", ".join([self.name(typ, depth + 1) for typ in typ.elts]) elif isinstance(typ, (TFunction, TRPCFunction, TCFunction)): args = [] - args += [ "%s:%s" % (arg, self.name(typ.args[arg])) for arg in typ.args] - args += ["?%s:%s" % (arg, self.name(typ.optargs[arg])) for arg in typ.optargs] - signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret)) + 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)) + signature += " delay({})".format(self.name(delay, depth + 1)) elif not (delay.is_fixed() and iodelay.is_zero(delay.duration)): - signature += " " + self.name(delay) + signature += " " + self.name(delay, depth + 1) if isinstance(typ, TRPCFunction): return "[rpc #{}]{}".format(typ.service, signature) @@ -740,13 +746,17 @@ class TypePrinter(object): elif isinstance(typ, TBuiltinFunction): return "".format(typ.name) elif isinstance(typ, (TConstructor, TExceptionConstructor)): - if typ in self.recurse_guard: + if typ in self.recurse_guard or depth >= max_depth: return "".format(typ.name) - else: + elif len(typ.attributes) > 0: self.recurse_guard.add(typ) - attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr])) + 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): diff --git a/artiq/coredevice/__init__.py b/artiq/coredevice/__init__.py index 76e782a3f..da8c72b1d 100644 --- a/artiq/coredevice/__init__.py +++ b/artiq/coredevice/__init__.py @@ -1,12 +1,12 @@ -from artiq.coredevice import exceptions, dds +from artiq.coredevice import exceptions, dds, spi from artiq.coredevice.exceptions import (RTIOUnderflow, RTIOSequenceError, - RTIOCollisionError, RTIOOverflow, + RTIOCollision, RTIOOverflow, DDSBatchError, CacheError) from artiq.coredevice.dds import (PHASE_MODE_CONTINUOUS, PHASE_MODE_ABSOLUTE, PHASE_MODE_TRACKING) __all__ = [] -__all__ += ["RTIOUnderflow", "RTIOSequenceError", "RTIOCollisionError", +__all__ += ["RTIOUnderflow", "RTIOSequenceError", "RTIOCollision", "RTIOOverflow", "DDSBatchError", "CacheError"] __all__ += ["PHASE_MODE_CONTINUOUS", "PHASE_MODE_ABSOLUTE", "PHASE_MODE_TRACKING"] diff --git a/artiq/coredevice/ad5360.py b/artiq/coredevice/ad5360.py new file mode 100644 index 000000000..b2dbe79c6 --- /dev/null +++ b/artiq/coredevice/ad5360.py @@ -0,0 +1,175 @@ +from artiq.language.core import (kernel, portable, delay_mu, delay, + seconds_to_mu) +from artiq.language.units import ns, us +from artiq.coredevice import spi + +# Designed from the data sheets and somewhat after the linux kernel +# iio driver. + +_AD5360_SPI_CONFIG = (0*spi.SPI_OFFLINE | 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) + +_AD5360_CMD_DATA = 3 << 22 +_AD5360_CMD_OFFSET = 2 << 22 +_AD5360_CMD_GAIN = 1 << 22 +_AD5360_CMD_SPECIAL = 0 << 22 + + +@portable +def _AD5360_WRITE_CHANNEL(c): + return (c + 8) << 16 + +_AD5360_SPECIAL_NOP = 0 << 16 +_AD5360_SPECIAL_CONTROL = 1 << 16 +_AD5360_SPECIAL_OFS0 = 2 << 16 +_AD5360_SPECIAL_OFS1 = 3 << 16 +_AD5360_SPECIAL_READ = 5 << 16 + + +@portable +def _AD5360_READ_CHANNEL(ch): + return (ch + 8) << 7 + +_AD5360_READ_X1A = 0x000 << 7 +_AD5360_READ_X1B = 0x040 << 7 +_AD5360_READ_OFFSET = 0x080 << 7 +_AD5360_READ_GAIN = 0x0c0 << 7 +_AD5360_READ_CONTROL = 0x101 << 7 +_AD5360_READ_OFS0 = 0x102 << 7 +_AD5360_READ_OFS1 = 0x103 << 7 + + +class AD5360: + """ + Support for the Analog devices AD53[67][0123] + multi-channel Digital to Analog Converters + + :param spi_device: Name of the SPI bus this device is on. + :param ldac_device: Name of the TTL device that LDAC is connected to + (optional). Needs to be explicitly initialized to high. + :param chip_select: Value to drive on the chip select lines + during transactions. + """ + + def __init__(self, dmgr, spi_device, ldac_device=None, chip_select=1): + self.core = dmgr.get("core") + self.bus = dmgr.get(spi_device) + if ldac_device is not None: + self.ldac = dmgr.get(ldac_device) + self.chip_select = chip_select + + @kernel + def setup_bus(self, write_div=4, read_div=7): + """Configure the SPI bus and the SPI transaction parameters + for this device. This method has to be called before any other method + if the bus has been used to access a different device in the meantime. + + This method advances the timeline by the duration of two + RTIO-to-Wishbone bus transactions. + + :param write_div: Write clock divider. + :param read_div: Read clock divider. + """ + # write: 2*8ns >= 10ns = t_6 (clk falling to cs_n rising) + # read: 4*8*ns >= 25ns = t_22 (clk falling to miso valid) + self.bus.set_config_mu(_AD5360_SPI_CONFIG, write_div, read_div) + self.bus.set_xfer(self.chip_select, 24, 0) + + @kernel + def write(self, data): + """Write 24 bits of data. + + This method advances the timeline by the duration of the SPI transfer + and the required CS high time. + """ + self.bus.write(data << 8) + delay_mu(self.bus.ref_period_mu) # get to 20ns min cs high + + @kernel + def write_offsets(self, value=0x1fff): + """Write the OFS0 and OFS1 offset DACs. + + This method advances the timeline by twice the duration of + :meth:`write`. + + :param value: Value to set both offset registers to. + """ + value &= 0x3fff + self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_OFS0 | value) + self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_OFS1 | value) + + @kernel + def write_channel(self, channel=0, value=0, op=_AD5360_CMD_DATA): + """Write to a channel register. + + This method advances the timeline by the duration of :meth:`write`. + + :param channel: Channel number to write to. + :param value: 16 bit value to write to the register. + :param op: Operation to perform, one of :const:`_AD5360_CMD_DATA`, + :const:`_AD5360_CMD_OFFSET`, :const:`_AD5360_CMD_GAIN` + (default: :const:`_AD5360_CMD_DATA`). + """ + channel &= 0x3f + value &= 0xffff + self.write(op | _AD5360_WRITE_CHANNEL(channel) | value) + + @kernel + def read_channel_sync(self, channel=0, op=_AD5360_READ_X1A): + """Read a channel register. + + This method advances the timeline by the duration of :meth:`write` plus + three RTIO-to-Wishbone transactions. + + :param channel: Channel number to read from. + :param op: Operation to perform, one of :const:`_AD5360_READ_X1A`, + :const:`_AD5360_READ_X1B`, :const:`_AD5360_READ_OFFSET`, + :const:`_AD5360_READ_GAIN` (default: :const:`_AD5360_READ_X1A`). + :return: The 16 bit register value. + """ + channel &= 0x3f + self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_READ | op | + _AD5360_READ_CHANNEL(channel)) + self.bus.set_xfer(self.chip_select, 0, 24) + self.write(_AD5360_CMD_SPECIAL | _AD5360_SPECIAL_NOP) + self.bus.read_async() + self.bus.set_xfer(self.chip_select, 24, 0) + return self.bus.input_async() & 0xffff + + @kernel + def load(self): + """Pulse the LDAC line. + + This method advances the timeline by two RTIO clock periods (16 ns). + """ + self.ldac.off() + # t13 = 10ns ldac pulse width low + delay_mu(2*self.bus.ref_period_mu) + self.ldac.on() + + @kernel + def set(self, values, op=_AD5360_CMD_DATA): + """Write to several channels and pulse LDAC to update the channels. + + This method does not advance the timeline. Write events are scheduled + in the past. The DACs will synchronously start changing their output + levels `now`. + + :param values: List of 16 bit values to write to the channels. + :param op: Operation to perform, one of :const:`_AD5360_CMD_DATA`, + :const:`_AD5360_CMD_OFFSET`, :const:`_AD5360_CMD_GAIN` + (default: :const:`_AD5360_CMD_DATA`). + """ + # compensate all delays that will be applied + delay_mu(-len(values)*(self.bus.xfer_period_mu + + self.bus.write_period_mu + + self.bus.ref_period_mu) - + 3*self.bus.ref_period_mu - + seconds_to_mu(1.5*us)) + for i in range(len(values)): + self.write_channel(i, values[i], op) + delay_mu(3*self.bus.ref_period_mu + # latency alignment ttl to spi + seconds_to_mu(1.5*us)) # t10 max busy low for one channel + self.load() + delay_mu(-2*self.bus.ref_period_mu) # load(), t13 diff --git a/artiq/coredevice/comm_generic.py b/artiq/coredevice/comm_generic.py index b4059fdcc..f4e1d2eee 100644 --- a/artiq/coredevice/comm_generic.py +++ b/artiq/coredevice/comm_generic.py @@ -158,7 +158,7 @@ class CommGeneric: return self._read_chunk(self._read_int32()) def _read_string(self): - return self._read_bytes()[:-1].decode('utf-8') + return self._read_bytes()[:-1].decode("utf-8") # # Writer interface @@ -242,7 +242,7 @@ class CommGeneric: self._read_header() self._read_expect(_D2HMsgType.LOG_REPLY) - return self._read_chunk(self._read_length).decode("utf-8") + return self._read_chunk(self._read_length).decode("utf-8", "replace") def clear_log(self): self._write_empty(_H2DMsgType.LOG_CLEAR) diff --git a/artiq/coredevice/core.py b/artiq/coredevice/core.py index ba03b223b..32c2501bd 100644 --- a/artiq/coredevice/core.py +++ b/artiq/coredevice/core.py @@ -60,11 +60,17 @@ class Core: The time machine unit is equal to this period. :param external_clock: whether the core device should switch to its external RTIO clock input instead of using its internal oscillator. + :param ref_multiplier: ratio between the RTIO fine timestamp frequency + and the RTIO coarse timestamp frequency (e.g. SERDES multiplication + factor). :param comm_device: name of the device used for communications. """ - def __init__(self, dmgr, ref_period, external_clock=False, comm_device="comm"): + def __init__(self, dmgr, ref_period, external_clock=False, + ref_multiplier=8, comm_device="comm"): self.ref_period = ref_period self.external_clock = external_clock + self.ref_multiplier = ref_multiplier + self.coarse_ref_period = ref_period*ref_multiplier self.comm = dmgr.get(comm_device) self.first_run = True diff --git a/artiq/coredevice/exceptions.py b/artiq/coredevice/exceptions.py index 05d9d1c29..aa00ad784 100644 --- a/artiq/coredevice/exceptions.py +++ b/artiq/coredevice/exceptions.py @@ -86,7 +86,7 @@ class RTIOSequenceError(Exception): """ artiq_builtin = True -class RTIOCollisionError(Exception): +class RTIOCollision(Exception): """Raised when an event is submitted on a given channel with the same coarse timestamp as the previous one but with a different fine timestamp. @@ -113,3 +113,7 @@ class DDSBatchError(Exception): or when too many commands are batched. """ artiq_builtin = True + +class I2CError(Exception): + """Raised with a I2C transaction fails.""" + artiq_builtin = True diff --git a/artiq/coredevice/i2c.py b/artiq/coredevice/i2c.py new file mode 100644 index 000000000..e9de36161 --- /dev/null +++ b/artiq/coredevice/i2c.py @@ -0,0 +1,112 @@ +from artiq.language.core import syscall, kernel +from artiq.language.types import TBool, TInt32, TNone +from artiq.coredevice.exceptions import I2CError + + +@syscall +def i2c_init(busno: TInt32) -> TNone: + raise NotImplementedError("syscall not simulated") + + +@syscall +def i2c_start(busno: TInt32) -> TNone: + raise NotImplementedError("syscall not simulated") + + +@syscall +def i2c_stop(busno: TInt32) -> TNone: + raise NotImplementedError("syscall not simulated") + + +@syscall +def i2c_write(busno: TInt32, b: TInt32) -> TBool: + raise NotImplementedError("syscall not simulated") + + +@syscall +def i2c_read(busno: TInt32, ack: TBool) -> TInt32: + raise NotImplementedError("syscall not simulated") + + +class PCA9548: + """Driver for the PCA9548 I2C bus switch. + + On the KC705, this chip is used for selecting the I2C buses on the two FMC + connectors. HPC=1, LPC=2. + """ + def __init__(self, dmgr, busno=0, address=0xe8): + self.core = dmgr.get("core") + self.busno = busno + self.address = address + + @kernel + def set(self, channel): + """Select one channel. + + Selecting multiple channels at the same time is not supported by this + driver. + + :param channel: channel number (0-7) + """ + i2c_init(self.busno) + i2c_start(self.busno) + try: + if not i2c_write(self.busno, self.address): + raise I2CError("PCA9548 failed to ack address") + if not i2c_write(self.busno, 1 << channel): + raise I2CError("PCA9548 failed to ack control word") + finally: + i2c_stop(self.busno) + + @kernel + def readback(self): + i2c_init(self.busno) + i2c_start(self.busno) + r = 0 + try: + if not i2c_write(self.busno, self.address | 1): + raise I2CError("PCA9548 failed to ack address") + r = i2c_read(self.busno, False) + finally: + i2c_stop(self.busno) + return r + + +class TCA6424A: + """Driver for the TCA6424A I2C I/O expander. + + On the NIST QC2 hardware, this chip is used for switching the directions + of TTL buffers.""" + def __init__(self, dmgr, busno=0, address=0x44): + self.core = dmgr.get("core") + self.busno = busno + self.address = address + + @kernel + def _write24(self, command, value): + i2c_init(self.busno) + i2c_start(self.busno) + try: + if not i2c_write(self.busno, self.address): + raise I2CError("TCA6424A failed to ack address") + if not i2c_write(self.busno, command): + raise I2CError("TCA6424A failed to ack command") + for i in range(3): + if not i2c_write(self.busno, value >> 16): + raise I2CError("TCA6424A failed to ack data") + value <<= 8 + finally: + i2c_stop(self.busno) + + @kernel + def set(self, outputs): + """Drive all pins of the chip to the levels given by the + specified 24-bit word. + + On the QC2 hardware, the LSB of the word determines the direction of + TTL0 (on a given FMC card) and the MSB that of TTL23. + + A bit set to 1 means the TTL is an output. + """ + self._write24(0x8c, 0) # set all directions to output + self._write24(0x84, output) # set levels diff --git a/artiq/coredevice/rtio.py b/artiq/coredevice/rtio.py new file mode 100644 index 000000000..978d80fd1 --- /dev/null +++ b/artiq/coredevice/rtio.py @@ -0,0 +1,18 @@ +from artiq.language.core import syscall +from artiq.language.types import TInt64, TInt32, TNone + + +@syscall +def rtio_output(time_mu: TInt64, channel: TInt32, addr: TInt32, data: TInt32 + ) -> TNone: + raise NotImplementedError("syscall not simulated") + + +@syscall +def rtio_input_timestamp(timeout_mu: TInt64, channel: TInt32) -> TInt64: + raise NotImplementedError("syscall not simulated") + + +@syscall +def rtio_input_data(channel: TInt32) -> TInt32: + raise NotImplementedError("syscall not simulated") diff --git a/artiq/coredevice/spi.py b/artiq/coredevice/spi.py new file mode 100644 index 000000000..6221c8f53 --- /dev/null +++ b/artiq/coredevice/spi.py @@ -0,0 +1,248 @@ +from artiq.language.core import (kernel, portable, seconds_to_mu, now_mu, + delay_mu, int) +from artiq.language.units import MHz +from artiq.coredevice.rtio import rtio_output, rtio_input_data + + +SPI_DATA_ADDR, SPI_XFER_ADDR, SPI_CONFIG_ADDR = range(3) +( + SPI_OFFLINE, + SPI_ACTIVE, + SPI_PENDING, + SPI_CS_POLARITY, + SPI_CLK_POLARITY, + SPI_CLK_PHASE, + SPI_LSB_FIRST, + SPI_HALF_DUPLEX, +) = (1 << i for i in range(8)) + +SPI_RT2WB_READ = 1 << 2 + + +class SPIMaster: + """Core device Serial Peripheral Interface (SPI) bus master. + Owns one SPI bus. + + **Transfer Sequence**: + + * If desired, write the ``config`` register (:meth:`set_config`) + to configure and activate the core. + * If desired, write the ``xfer`` register (:meth:`set_xfer`) + to set ``cs_n``, ``write_length``, and ``read_length``. + * :meth:`write` to the ``data`` register (also for transfers with + zero bits to be written). Writing starts the transfer. + * If desired, :meth:`read_sync` (or :meth:`read_async` followed by a + :meth:`input_async` later) the ``data`` register corresponding to + the last completed transfer. + * If desired, :meth:`set_xfer` for the next transfer. + * If desired, :meth:`write` ``data`` queuing the next + (possibly chained) transfer. + + :param channel: RTIO channel number of the SPI bus to control. + """ + def __init__(self, dmgr, channel): + self.core = dmgr.get("core") + self.ref_period_mu = seconds_to_mu(self.core.coarse_ref_period, + self.core) + self.channel = channel + self.write_period_mu = int(0, 64) + self.read_period_mu = int(0, 64) + self.xfer_period_mu = int(0, 64) + # A full transfer takes write_period_mu + xfer_period_mu. + # Chained transfers can happen every xfer_period_mu. + # The second transfer of a chain can be written 2*ref_period_mu + # after the first. Read data is available every xfer_period_mu starting + # a bit after xfer_period_mu (depending on clk_phase). + # To chain transfers together, new data must be written before + # pending transfer's read data becomes available. + + @portable + def frequency_to_div(self, f): + return int(1/(f*self.ref_period)) + 1 + + @kernel + def set_config(self, flags=0, write_freq=20*MHz, read_freq=20*MHz): + """Set the configuration register. + + * If ``config.cs_polarity`` == 0 (```cs`` active low, the default), + "``cs_n`` all deasserted" means "all ``cs_n`` bits high". + * ``cs_n`` is not mandatory in the pads supplied to the gateware core. + Framing and chip selection can also be handled independently + through other means, e.g. ``TTLOut``. + * If there is a ``miso`` wire in the pads supplied in the gateware, + input and output may be two signals ("4-wire SPI"), + otherwise ``mosi`` must be used for both output and input + ("3-wire SPI") and ``config.half_duplex`` must to be set + when reading data is desired or when the slave drives the + ``mosi`` signal at any point. + * The first bit output on ``mosi`` is always the MSB/LSB (depending + on ``config.lsb_first``) of the ``data`` register, independent of + ``xfer.write_length``. The last bit input from ``miso`` always ends + up in the LSB/MSB (respectively) of the ``data`` register, + independent of ``xfer.read_length``. + * Writes to the ``config`` register take effect immediately. + + **Configuration flags**: + + * :const:`SPI_OFFLINE`: all pins high-z (reset=1) + * :const:`SPI_ACTIVE`: transfer in progress (read-only) + * :const:`SPI_PENDING`: transfer pending in intermediate buffer + (read-only) + * :const:`SPI_CS_POLARITY`: active level of ``cs_n`` (reset=0) + * :const:`SPI_CLK_POLARITY`: idle level of ``clk`` (reset=0) + * :const:`SPI_CLK_PHASE`: first edge after ``cs`` assertion to sample + data on (reset=0). In Motorola/Freescale SPI language + (:const:`SPI_CLK_POLARITY`, :const:`SPI_CLK_PHASE`) == (CPOL, CPHA): + + - (0, 0): idle low, output on falling, input on rising + - (0, 1): idle low, output on rising, input on falling + - (1, 0): idle high, output on rising, input on falling + - (1, 1): idle high, output on falling, input on rising + * :const:`SPI_LSB_FIRST`: LSB is the first bit on the wire (reset=0) + * :const:`SPI_HALF_DUPLEX`: 3-wire SPI, in/out on ``mosi`` (reset=0) + + This method advances the timeline by the duration of the + RTIO-to-Wishbone bus transaction (three RTIO clock cycles). + + :param flags: A bit map of `SPI_*` flags. + :param write_freq: Desired SPI clock frequency during write bits. + :param read_freq: Desired SPI clock frequency during read bits. + """ + self.set_config_mu(flags, self.frequency_to_div(write_freq), + self.frequency_to_div(read_freq)) + + @kernel + def set_config_mu(self, flags=0, write_div=6, read_div=6): + """Set the ``config`` register (in SPI bus machine units). + + .. seealso:: :meth:`set_config` + + :param write_div: Counter load value to divide the RTIO + clock by to generate the SPI write clk. (minimum=2, reset=2) + ``f_rtio_clk/f_spi_write == write_div``. If ``write_div`` is odd, + the setup phase of the SPI clock is biased to longer lengths + by one RTIO clock cycle. + :param read_div: Ditto for the read clock. + """ + rtio_output(now_mu(), self.channel, SPI_CONFIG_ADDR, flags | + ((write_div - 2) << 16) | ((read_div - 2) << 24)) + self.write_period_mu = int(write_div*self.ref_period_mu) + self.read_period_mu = int(read_div*self.ref_period_mu) + delay_mu(3*self.ref_period_mu) + + @kernel + def set_xfer(self, chip_select=0, write_length=0, read_length=0): + """Set the ``xfer`` register. + + * Every transfer consists of a write of ``write_length`` bits + immediately followed by a read of ``read_length`` bits. + * ``cs_n`` is asserted at the beginning and deasserted at the end + of the transfer if there is no other transfer pending. + * ``cs_n`` handling is agnostic to whether it is one-hot or decoded + somewhere downstream. If it is decoded, "``cs_n`` all deasserted" + should be handled accordingly (no slave selected). + If it is one-hot, asserting multiple slaves should only be attempted + if ``miso`` is either not connected between slaves, or open + collector, or correctly multiplexed externally. + * For 4-wire SPI only the sum of ``read_length`` and ``write_length`` + matters. The behavior is the same (except for clock speeds) no matter + how the total transfer length is divided between the two. For + 3-wire SPI, the direction of ``mosi`` is switched from output to + input after ``write_length`` bits. + * Data output on ``mosi`` in 4-wire SPI during the read cycles is what + is found in the data register at the time. + Data in the ``data`` register outside the least/most (depending + on ``config.lsb_first``) significant ``read_length`` bits is what is + seen on ``miso`` (or ``mosi`` if ``config.half_duplex``) + during the write cycles. + * Writes to ``xfer`` are synchronized to the start of the next + (possibly chained) transfer. + + This method advances the timeline by the duration of the + RTIO-to-Wishbone bus transaction (three RTIO clock cycles). + + :param chip_select: Bit mask of chip selects to assert. Or number of + the chip select to assert if ``cs`` is decoded downstream. + (reset=0) + :param write_length: Number of bits to write during the next transfer. + (reset=0) + :param read_length: Number of bits to read during the next transfer. + (reset=0) + """ + rtio_output(now_mu(), self.channel, SPI_XFER_ADDR, + chip_select | (write_length << 16) | (read_length << 24)) + self.xfer_period_mu = int(write_length*self.write_period_mu + + read_length*self.read_period_mu) + delay_mu(3*self.ref_period_mu) + + @kernel + def write(self, data=0): + """Write data to data register. + + * The ``data`` register and the shift register are 32 bits wide. + If there are no writes to the register, ``miso`` data reappears on + ``mosi`` after 32 cycles. + * A wishbone data register write is acknowledged when the + transfer has been written to the intermediate buffer. + It will be started when there are no other transactions being + executed, either beginning a new SPI transfer of chained + to an in-flight transfer. + * Writes take three ``ref_period`` cycles unless another + chained transfer is pending and the transfer being + executed is not complete. + * The SPI ``data`` register is double-buffered: Once a transfer has + started, new write data can be written, queuing a new transfer. + Transfers submitted this way are chained and executed without + deasserting ``cs`` in between. Once a transfer completes, + the previous transfer's read data is available in the + ``data`` register. + + This method advances the timeline by the duration of the SPI transfer. + If a transfer is to be chained, the timeline needs to be rewound. + """ + rtio_output(now_mu(), self.channel, SPI_DATA_ADDR, data) + delay_mu(self.xfer_period_mu + self.write_period_mu) + + @kernel + def read_async(self): + """Trigger an asynchronous read from the ``data`` register. + + Reads always finish in two cycles. + + Every data register read triggered by a :meth:`read_async` + must be matched by a :meth:`input_async` to retrieve the data. + + This method advances the timeline by the duration of the + RTIO-to-Wishbone bus transaction (three RTIO clock cycles). + """ + rtio_output(now_mu(), self.channel, SPI_DATA_ADDR | SPI_RT2WB_READ, 0) + delay_mu(3*self.ref_period_mu) + + @kernel + def input_async(self): + """Retrieves data read asynchronously from the ``data`` register. + + :meth:`input_async` must match a preeeding :meth:`read_async`. + """ + return rtio_input_data(self.channel) + + @kernel + def read_sync(self): + """Read the ``data`` register synchronously. + + This is a shortcut for :meth:`read_async` followed by + :meth:`input_async`. + """ + self.read_async() + return self.input_async() + + @kernel + def _get_xfer_sync(self): + rtio_output(now_mu(), self.channel, SPI_XFER_ADDR | SPI_RT2WB_READ, 0) + return rtio_input_data(self.channel) + + @kernel + def _get_config_sync(self): + rtio_output(now_mu(), self.channel, SPI_CONFIG_ADDR | SPI_RT2WB_READ, + 0) + return rtio_input_data(self.channel) diff --git a/artiq/coredevice/ttl.py b/artiq/coredevice/ttl.py index be410d919..9ac12ca48 100644 --- a/artiq/coredevice/ttl.py +++ b/artiq/coredevice/ttl.py @@ -1,26 +1,6 @@ from artiq.language.core import * from artiq.language.types import * - - -@syscall -def ttl_set_o(time_mu: TInt64, channel: TInt32, enabled: TBool) -> TNone: - raise NotImplementedError("syscall not simulated") - -@syscall -def ttl_set_oe(time_mu: TInt64, channel: TInt32, enabled: TBool) -> TNone: - raise NotImplementedError("syscall not simulated") - -@syscall -def ttl_set_sensitivity(time_mu: TInt64, channel: TInt32, sensitivity: TInt32) -> TNone: - raise NotImplementedError("syscall not simulated") - -@syscall -def ttl_get(channel: TInt32, time_limit_mu: TInt64) -> TInt64: - raise NotImplementedError("syscall not simulated") - -@syscall -def ttl_clock_set(time_mu: TInt64, channel: TInt32, ftw: TInt32) -> TNone: - raise NotImplementedError("syscall not simulated") +from artiq.coredevice.rtio import rtio_output, rtio_input_timestamp class TTLOut: @@ -37,9 +17,13 @@ class TTLOut: # in RTIO cycles self.o_previous_timestamp = int(0, width=64) + @kernel + def output(self): + pass + @kernel def set_o(self, o): - ttl_set_o(now_mu(), self.channel, o) + rtio_output(now_mu(), self.channel, 0, 1 if o else 0) self.o_previous_timestamp = now_mu() @kernel @@ -91,6 +75,11 @@ class TTLInOut: This should be used with bidirectional channels. + Note that the channel is in input mode by default. If you need to drive a + signal, you must call ``output``. If the channel is in output mode most of + the time in your setup, it is a good idea to call ``output`` in the + startup kernel. + :param channel: channel number """ def __init__(self, dmgr, channel): @@ -103,21 +92,27 @@ class TTLInOut: @kernel def set_oe(self, oe): - ttl_set_oe(now_mu(), self.channel, oe) + rtio_output(now_mu(), self.channel, 1, 1 if oe else 0) @kernel def output(self): - """Set the direction to output.""" + """Set the direction to output. + + There must be a delay of at least one RTIO clock cycle before any + other command can be issued.""" self.set_oe(True) @kernel def input(self): - """Set the direction to input.""" + """Set the direction to input. + + There must be a delay of at least one RTIO clock cycle before any + other command can be issued.""" self.set_oe(False) @kernel def set_o(self, o): - ttl_set_o(now_mu(), self.channel, o) + rtio_output(now_mu(), self.channel, 0, 1 if o else 0) self.o_previous_timestamp = now_mu() @kernel @@ -159,7 +154,7 @@ class TTLInOut: @kernel def _set_sensitivity(self, value): - ttl_set_sensitivity(now_mu(), self.channel, value) + rtio_output(now_mu(), self.channel, 2, value) self.i_previous_timestamp = now_mu() @kernel @@ -215,7 +210,7 @@ class TTLInOut: """Poll the RTIO input during all the previously programmed gate openings, and returns the number of registered events.""" count = 0 - while ttl_get(self.channel, self.i_previous_timestamp) >= 0: + while rtio_input_timestamp(self.i_previous_timestamp, self.channel) >= 0: count += 1 return count @@ -226,7 +221,7 @@ class TTLInOut: If the gate is permanently closed, returns a negative value. """ - return ttl_get(self.channel, self.i_previous_timestamp) + return rtio_input_timestamp(self.i_previous_timestamp, self.channel) class TTLClockGen: @@ -243,21 +238,21 @@ class TTLClockGen: # in RTIO cycles self.previous_timestamp = int(0, width=64) - self.acc_width = 24 + self.acc_width = int(24, width=64) @portable def frequency_to_ftw(self, frequency): """Returns the frequency tuning word corresponding to the given frequency. """ - return round(2**self.acc_width*frequency*self.core.ref_period) + return round(2**self.acc_width*frequency*self.core.coarse_ref_period) @portable def ftw_to_frequency(self, ftw): """Returns the frequency corresponding to the given frequency tuning word. """ - return ftw/self.core.ref_period/2**self.acc_width + return ftw/self.core.coarse_ref_period/2**self.acc_width @kernel def set_mu(self, frequency): @@ -277,7 +272,7 @@ class TTLClockGen: that are not powers of two cause jitter of one RTIO clock cycle at the output. """ - ttl_clock_set(now_mu(), self.channel, frequency) + rtio_output(now_mu(), self.channel, 0, frequency) self.previous_timestamp = now_mu() @kernel diff --git a/artiq/frontend/artiq_flash.py b/artiq/frontend/artiq_flash.py index 82cb27939..b29128a35 100755 --- a/artiq/frontend/artiq_flash.py +++ b/artiq/frontend/artiq_flash.py @@ -6,7 +6,6 @@ import os import subprocess import tempfile -import artiq from artiq import __artiq_dir__ as artiq_dir from artiq.frontend.bit2bin import bit2bin @@ -18,13 +17,13 @@ def get_argparser(): epilog="""\ Valid actions: - * proxy: load the flash proxy bitstream - * bitstream: write bitstream to flash + * proxy: load the flash proxy gateware bitstream + * gateware: write gateware bitstream to flash * bios: write bios to flash * runtime: write runtime to flash * storage: write storage image to flash - * load: load bitstream into device (volatile but fast) - * start: trigger the target to (re)load its bitstream from flash + * load: load gateware bitstream into device (volatile but fast) + * start: trigger the target to (re)load its gateware bitstream from flash Prerequisites: @@ -37,12 +36,12 @@ Prerequisites: """) parser.add_argument("-t", "--target", default="kc705", help="target board, default: %(default)s") - parser.add_argument("-m", "--adapter", default="qc2", + parser.add_argument("-m", "--adapter", default="clock", help="target adapter, default: %(default)s") parser.add_argument("-f", "--storage", help="write file to storage area") parser.add_argument("-d", "--dir", help="look for files in this directory") parser.add_argument("ACTION", nargs="*", - default="proxy bitstream bios runtime start".split(), + default="proxy gateware bios runtime start".split(), help="actions to perform, default: %(default)s") return parser @@ -55,7 +54,7 @@ def main(): "kc705": { "chip": "xc7k325t", "start": "xc7_program xc7.tap", - "bitstream": 0x000000, + "gateware": 0x000000, "bios": 0xaf0000, "runtime": 0xb00000, "storage": 0xb80000, @@ -63,7 +62,7 @@ def main(): "pipistrello": { "chip": "xc6slx45", "start": "xc6s_program xc6s.tap", - "bitstream": 0x000000, + "gateware": 0x000000, "bios": 0x170000, "runtime": 0x180000, "storage": 0x200000, @@ -83,23 +82,23 @@ def main(): proxy_base = "bscan_spi_{}.bit".format(config["chip"]) proxy = None for p in [opts.dir, os.path.expanduser("~/.migen"), - "/usr/local/share/migen", "/usr/share/migen"]: + "/usr/local/share/migen", "/usr/share/migen"]: proxy_ = os.path.join(p, proxy_base) if os.access(proxy_, os.R_OK): proxy = "jtagspi_init 0 {}".format(proxy_) break if not proxy: raise SystemExit( - "proxy bitstream {} not found".format(proxy_base)) + "proxy gateware bitstream {} not found".format(proxy_base)) prog.append(proxy) - elif action == "bitstream": + elif action == "gateware": bin = os.path.join(opts.dir, "top.bin") if not os.access(bin, os.R_OK): bin = tempfile.mkstemp()[1] bit = os.path.join(opts.dir, "top.bit") conv = True prog.append("jtagspi_program {} 0x{:x}".format( - bin, config["bitstream"])) + bin, config["gateware"])) elif action == "bios": prog.append("jtagspi_program {} 0x{:x}".format( os.path.join(opts.dir, "bios.bin"), config["bios"])) diff --git a/artiq/frontend/artiq_rpctool.py b/artiq/frontend/artiq_rpctool.py index 58514f881..4c63f7428 100755 --- a/artiq/frontend/artiq_rpctool.py +++ b/artiq/frontend/artiq_rpctool.py @@ -3,10 +3,12 @@ import argparse import textwrap import sys +import traceback import numpy as np # Needed to use numpy in RPC call arguments on cmd line +import readline # This makes input() nicer import pprint -from artiq.protocols.pc_rpc import AutoTarget, Client +from artiq.protocols.pc_rpc import AutoTarget, Client, RemoteError def get_argparser(): @@ -17,7 +19,6 @@ def get_argparser(): parser.add_argument("port", type=int, help="TCP port to use to connect to the controller") subparsers = parser.add_subparsers(dest="action") - subparsers.required = True subparsers.add_parser("list-targets", help="list existing targets") parser_list_methods = subparsers.add_parser("list-methods", help="list target's methods") @@ -27,6 +28,10 @@ def get_argparser(): parser_call.add_argument("method", help="method name") parser_call.add_argument("args", nargs=argparse.REMAINDER, help="arguments") + parser_interactive = subparsers.add_parser("interactive", + help="enter interactive mode " + "(default)") + parser_interactive.add_argument("-t", "--target", help="target name") return parser @@ -81,8 +86,35 @@ def call_method(remote, method_name, args): pprint.pprint(ret) +def interactive(remote): + while True: + try: + cmd = input("({}) ".format(remote.get_selected_target())) + except EOFError: + return + class RemoteDict: + def __getitem__(self, k): + if k == "np": + return np + else: + return getattr(remote, k) + try: + ret = eval(cmd, {}, RemoteDict()) + except Exception as e: + if isinstance(e, RemoteError): + print("Remote exception:") + print(str(e)) + else: + traceback.print_exc() + else: + if ret is not None: + pprint.pprint(ret) + + def main(): args = get_argparser().parse_args() + if not args.action: + args.target = None remote = Client(args.server, args.port, None) targets, description = remote.get_rpc_id() @@ -98,6 +130,8 @@ def main(): list_methods(remote) elif args.action == "call": call_method(remote, args.method, args.args) + elif args.action == "interactive" or not args.action: + interactive(remote) else: print("Unrecognized action: {}".format(args.action)) diff --git a/artiq/gateware/nist_clock.py b/artiq/gateware/nist_clock.py index 363a339b5..b6db05efe 100644 --- a/artiq/gateware/nist_clock.py +++ b/artiq/gateware/nist_clock.py @@ -9,7 +9,7 @@ fmc_adapter_io = [ ("ttl", 4, Pins("LPC:LA01_CC_N"), IOStandard("LVTTL")), ("ttl", 5, Pins("LPC:LA06_P"), IOStandard("LVTTL")), ("ttl", 6, Pins("LPC:LA06_N"), IOStandard("LVTTL")), - ("ttl", 7, Pins("LPC:LA27_P"), IOStandard("LVTTL")), + ("ttl", 7, Pins("LPC:LA01_CC_P"), IOStandard("LVTTL")), ("ttl", 8, Pins("LPC:LA10_P"), IOStandard("LVTTL")), ("ttl", 9, Pins("LPC:LA05_N"), IOStandard("LVTTL")), ("ttl", 10, Pins("LPC:LA05_P"), IOStandard("LVTTL")), @@ -38,8 +38,8 @@ fmc_adapter_io = [ Subsignal("rst", Pins("LPC:LA25_P")), IOStandard("LVTTL")), - ("i2c", 0, - Subsignal("scl", Pins("LPC:IIC_SLC")), + ("i2c_fmc", 0, + Subsignal("scl", Pins("LPC:IIC_SCL")), Subsignal("sda", Pins("LPC:IIC_SDA")), IOStandard("LVCMOS25")), @@ -48,29 +48,27 @@ fmc_adapter_io = [ Subsignal("n", Pins("LPC:CLK1_M2C_N")), IOStandard("LVDS")), - ("la32", 0, - Subsignal("p", Pins("LPC:LA32_P")), - Subsignal("n", Pins("LPC:LA32_N")), - IOStandard("LVDS")), + ("la32_p", 0, Pins("LPC:LA32_P"), IOStandard("LVTTL")), + ("la32_n", 0, Pins("LPC:LA32_N"), IOStandard("LVTTL")), ("spi", 0, Subsignal("clk", Pins("LPC:LA13_N")), - Subsignal("ce", Pins("LPC:LA14_N")), + Subsignal("cs_n", Pins("LPC:LA14_N")), Subsignal("mosi", Pins("LPC:LA17_CC_P")), Subsignal("miso", Pins("LPC:LA17_CC_N")), IOStandard("LVTTL")), ("spi", 1, Subsignal("clk", Pins("LPC:LA23_N")), - Subsignal("ce", Pins("LPC:LA23_P")), + Subsignal("cs_n", Pins("LPC:LA23_P")), Subsignal("mosi", Pins("LPC:LA18_CC_N")), Subsignal("miso", Pins("LPC:LA18_CC_P")), IOStandard("LVTTL")), ("spi", 2, - Subsignal("clk", Pins("LPC:LA27_P")), - Subsignal("ce", Pins("LPC:LA26_P")), - Subsignal("mosi", Pins("LPC:LA27_N")), - Subsignal("miso", Pins("LPC:LA26_N")), + Subsignal("clk", Pins("LPC:LA26_N")), + Subsignal("cs_n", Pins("LPC:LA27_N")), + Subsignal("mosi", Pins("LPC:LA26_P")), + Subsignal("miso", Pins("LPC:LA27_P")), IOStandard("LVTTL")), ] diff --git a/artiq/gateware/nist_qc2.py b/artiq/gateware/nist_qc2.py index 442d66ee6..9fba61b16 100644 --- a/artiq/gateware/nist_qc2.py +++ b/artiq/gateware/nist_qc2.py @@ -48,7 +48,7 @@ fmc_adapter_io = [ Subsignal("rst", Pins("LPC:LA25_P")), IOStandard("LVTTL")), - ("i2c", 0, + ("i2c_fmc", 0, Subsignal("scl", Pins("LPC:IIC_SCL")), Subsignal("sda", Pins("LPC:IIC_SDA")), IOStandard("LVCMOS25")), diff --git a/artiq/gateware/rtio/analyzer.py b/artiq/gateware/rtio/analyzer.py index 22eac2e35..ca66ee7bb 100644 --- a/artiq/gateware/rtio/analyzer.py +++ b/artiq/gateware/rtio/analyzer.py @@ -82,7 +82,7 @@ class MessageEncoder(Module, AutoCSR): rtio_core.counter.value_sys << rtio_core.fine_ts_width), ] for ename in ("o_underflow_reset", "o_sequence_error_reset", - "o_collision_error_reset", "i_overflow_reset"): + "o_collision_reset", "i_overflow_reset"): self.comb += \ If(getattr(kcsrs, ename).re, exception_stb.eq(1), diff --git a/artiq/gateware/rtio/core.py b/artiq/gateware/rtio/core.py index e258b2a04..8c2d1b237 100644 --- a/artiq/gateware/rtio/core.py +++ b/artiq/gateware/rtio/core.py @@ -103,7 +103,7 @@ class _OutputManager(Module): self.underflow = Signal() # valid 1 cycle after we, pulsed self.sequence_error = Signal() - self.collision_error = Signal() + self.collision = Signal() # # # @@ -126,24 +126,28 @@ class _OutputManager(Module): # Special cases replace = Signal() sequence_error = Signal() - collision_error = Signal() + collision = Signal() any_error = Signal() nop = Signal() self.sync.rsys += [ - # Note: replace does not perform any RTLink address checks, - # i.e. a write to a different address will be silently replaced - # as well. + # Note: replace may be asserted at the same time as collision + # when addresses are different. In that case, it is a collision. replace.eq(self.ev.timestamp == buf.timestamp), # Detect sequence errors on coarse timestamps only # so that they are mutually exclusive with collision errors. sequence_error.eq(self.ev.timestamp[fine_ts_width:] < buf.timestamp[fine_ts_width:]) ] + if hasattr(self.ev, "a"): + different_addresses = self.ev.a != buf.a + else: + different_addresses = 0 if fine_ts_width: - self.sync.rsys += collision_error.eq( + self.sync.rsys += collision.eq( (self.ev.timestamp[fine_ts_width:] == buf.timestamp[fine_ts_width:]) - & (self.ev.timestamp[:fine_ts_width] != buf.timestamp[:fine_ts_width])) - self.comb += any_error.eq(sequence_error | collision_error) + & ((self.ev.timestamp[:fine_ts_width] != buf.timestamp[:fine_ts_width]) + |different_addresses)) + self.comb += any_error.eq(sequence_error | collision) if interface.suppress_nop: # disable NOP at reset: do not suppress a first write with all 0s nop_en = Signal(reset=0) @@ -163,7 +167,7 @@ class _OutputManager(Module): ] self.comb += [ self.sequence_error.eq(self.we & sequence_error), - self.collision_error.eq(self.we & collision_error) + self.collision.eq(self.we & collision) ] # Buffer read and FIFO write @@ -335,7 +339,7 @@ class _KernelCSRs(AutoCSR): self.o_status = CSRStatus(4) self.o_underflow_reset = CSR() self.o_sequence_error_reset = CSR() - self.o_collision_error_reset = CSR() + self.o_collision_reset = CSR() if data_width: self.i_data = CSRStatus(data_width) @@ -422,22 +426,22 @@ class RTIO(Module): underflow = Signal() sequence_error = Signal() - collision_error = Signal() + collision = Signal() self.sync.rsys += [ If(selected & self.kcsrs.o_underflow_reset.re, underflow.eq(0)), If(selected & self.kcsrs.o_sequence_error_reset.re, sequence_error.eq(0)), - If(selected & self.kcsrs.o_collision_error_reset.re, - collision_error.eq(0)), + If(selected & self.kcsrs.o_collision_reset.re, + collision.eq(0)), If(o_manager.underflow, underflow.eq(1)), If(o_manager.sequence_error, sequence_error.eq(1)), - If(o_manager.collision_error, collision_error.eq(1)) + If(o_manager.collision, collision.eq(1)) ] o_statuses.append(Cat(~o_manager.writable, underflow, sequence_error, - collision_error)) + collision)) if channel.interface.i is not None: i_manager = _InputManager(channel.interface.i, self.counter, diff --git a/artiq/gateware/rtio/phy/spi.py b/artiq/gateware/rtio/phy/spi.py new file mode 100644 index 000000000..92ab7a548 --- /dev/null +++ b/artiq/gateware/rtio/phy/spi.py @@ -0,0 +1,13 @@ +from migen import * + +from artiq.gateware.spi import SPIMaster as SPIMasterWB +from artiq.gateware.rtio.phy.wishbone import RT2WB + + +class SPIMaster(Module): + def __init__(self, pads, **kwargs): + self.submodules._ll = ClockDomainsRenamer("rio")( + SPIMasterWB(pads, **kwargs)) + self.submodules._rt2wb = RT2WB(2, self._ll.bus) + self.rtlink = self._rt2wb.rtlink + self.probes = [] diff --git a/artiq/gateware/soc.py b/artiq/gateware/soc.py index 4487101aa..b1b84f25e 100644 --- a/artiq/gateware/soc.py +++ b/artiq/gateware/soc.py @@ -1,7 +1,12 @@ +import os + from misoc.integration.soc_core import mem_decoder from misoc.cores import timer +from misoc.interconnect import wishbone +from misoc.integration.builder import * from artiq.gateware import amp +from artiq import __artiq_dir__ as artiq_dir class AMPSoC: @@ -29,3 +34,28 @@ class AMPSoC: self.mailbox.i2) self.add_memory_region("mailbox", self.mem_map["mailbox"] | 0x80000000, 4) + + self.submodules.timer_kernel = timer.Timer() + self.register_kernel_cpu_csrdevice("timer_kernel") + + def register_kernel_cpu_csrdevice(self, name): + # make sure the device is not getting connected to the comms-CPU already + assert self.csr_map[name] is None + + csrs = getattr(self, name).get_csrs() + bank = wishbone.CSRBank(csrs) + self.submodules += bank + self.kernel_cpu.add_wb_slave(mem_decoder(self.mem_map[name]), + bank.bus) + self.add_csr_region(name, + self.mem_map[name] | 0x80000000, 32, + csrs) + + +def build_artiq_soc(soc, argdict): + builder = Builder(soc, **argdict) + builder.add_extra_software_packages() + builder.add_software_package("liblwip", os.path.join(artiq_dir, "runtime", + "liblwip")) + builder.add_software_package("runtime", os.path.join(artiq_dir, "runtime")) + builder.build() diff --git a/artiq/gateware/spi.py b/artiq/gateware/spi.py new file mode 100644 index 000000000..e476adbb9 --- /dev/null +++ b/artiq/gateware/spi.py @@ -0,0 +1,487 @@ +from itertools import product + +from migen import * +from migen.genlib.fsm import FSM, NextState +from misoc.interconnect import wishbone + + +class SPIClockGen(Module): + def __init__(self, width): + self.load = Signal(width) + self.bias = Signal() # bias this clock phase to longer times + self.edge = Signal() + self.clk = Signal(reset=1) + + cnt = Signal.like(self.load) + bias = Signal() + zero = Signal() + self.comb += [ + zero.eq(cnt == 0), + self.edge.eq(zero & ~bias), + ] + self.sync += [ + If(zero, + bias.eq(0), + ).Else( + cnt.eq(cnt - 1), + ), + If(self.edge, + cnt.eq(self.load[1:]), + bias.eq(self.load[0] & (self.clk ^ self.bias)), + self.clk.eq(~self.clk), + ) + ] + + +class SPIRegister(Module): + def __init__(self, width): + self.data = Signal(width) + self.o = Signal() + self.i = Signal() + self.lsb = Signal() + self.shift = Signal() + self.sample = Signal() + + self.comb += [ + self.o.eq(Mux(self.lsb, self.data[0], self.data[-1])), + ] + self.sync += [ + If(self.shift, + If(self.lsb, + self.data[:-1].eq(self.data[1:]), + ).Else( + self.data[1:].eq(self.data[:-1]), + ) + ), + If(self.sample, + If(self.lsb, + self.data[-1].eq(self.i), + ).Else( + self.data[0].eq(self.i), + ) + ) + ] + + +class SPIBitCounter(Module): + def __init__(self, width): + self.n_read = Signal(width) + self.n_write = Signal(width) + self.read = Signal() + self.write = Signal() + self.done = Signal() + + self.comb += [ + self.write.eq(self.n_write != 0), + self.read.eq(self.n_read != 0), + self.done.eq(~(self.write | self.read)), + ] + self.sync += [ + If(self.write, + self.n_write.eq(self.n_write - 1), + ).Elif(self.read, + self.n_read.eq(self.n_read - 1), + ) + ] + + +class SPIMachine(Module): + def __init__(self, data_width, clock_width, bits_width): + ce = CEInserter() + self.submodules.cg = ce(SPIClockGen(clock_width)) + self.submodules.reg = ce(SPIRegister(data_width)) + self.submodules.bits = ce(SPIBitCounter(bits_width)) + self.div_write = Signal.like(self.cg.load) + self.div_read = Signal.like(self.cg.load) + self.clk_phase = Signal() + self.start = Signal() + self.cs = Signal() + self.oe = Signal() + self.done = Signal() + + # # # + + fsm = CEInserter()(FSM("IDLE")) + self.submodules += fsm + + fsm.act("IDLE", + If(self.start, + If(self.clk_phase, + NextState("WAIT"), + ).Else( + NextState("SETUP"), + ) + ) + ) + fsm.act("SETUP", + self.reg.sample.eq(1), + NextState("HOLD"), + ) + fsm.act("HOLD", + If(self.bits.done & ~self.start, + If(self.clk_phase, + NextState("IDLE"), + ).Else( + NextState("WAIT"), + ) + ).Else( + self.reg.shift.eq(1), + NextState("SETUP"), + ) + ) + fsm.act("WAIT", + If(self.bits.done, + NextState("IDLE"), + ).Else( + NextState("SETUP"), + ) + ) + + write0 = Signal() + self.sync += [ + If(self.cg.edge & self.reg.shift, + write0.eq(self.bits.write), + ) + ] + self.comb += [ + self.cg.ce.eq(self.start | self.cs | ~self.cg.edge), + If(self.bits.write | ~self.bits.read, + self.cg.load.eq(self.div_write), + ).Else( + self.cg.load.eq(self.div_read), + ), + self.cg.bias.eq(self.clk_phase), + fsm.ce.eq(self.cg.edge), + self.cs.eq(~fsm.ongoing("IDLE")), + self.reg.ce.eq(self.cg.edge), + self.bits.ce.eq(self.cg.edge & self.reg.sample), + self.done.eq(self.cg.edge & self.bits.done & fsm.ongoing("HOLD")), + self.oe.eq(write0 | self.bits.write), + ] + + +class SPIMaster(Module): + """SPI Master. + + Notes: + * M = 32 is the data width (width of the data register, + maximum write bits, maximum read bits) + * Every transfer consists of a write_length 0-M bit write followed + by a read_length 0-M bit read. + * cs_n is asserted at the beginning and deasserted at the end of the + transfer if there is no other transfer pending. + * cs_n handling is agnostic to whether it is one-hot or decoded + somewhere downstream. If it is decoded, "cs_n all deasserted" + should be handled accordingly (no slave selected). + If it is one-hot, asserting multiple slaves should only be attempted + if miso is either not connected between slaves, or open collector, + or correctly multiplexed externally. + * If config.cs_polarity == 0 (cs active low, the default), + "cs_n all deasserted" means "all cs_n bits high". + * cs is not mandatory in pads. Framing and chip selection can also + be handled independently through other means. + * If there is a miso wire in pads, the input and output can be done + with two signals (a.k.a. 4-wire SPI), else mosi must be used for + both output and input (a.k.a. 3-wire SPI) and config.half_duplex + must to be set when reading data is desired. + * For 4-wire SPI only the sum of read_length and write_length matters. + The behavior is the same no matter how the total transfer length is + divided between the two. For 3-wire SPI, the direction of mosi/miso + is switched from output to input after write_len cycles, at the + "shift_out" clk edge corresponding to bit write_length + 1 of the + transfer. + * The first bit output on mosi is always the MSB/LSB (depending on + config.lsb_first) of the data register, independent of + xfer.write_len. The last bit input from miso always ends up in + the LSB/MSB (respectively) of the data register, independent of + read_len. + * Data output on mosi in 4-wire SPI during the read cycles is what + is found in the data register at the time. + Data in the data register outside the least/most (depending + on config.lsb_first) significant read_length bits is what is + seen on miso during the write cycles. + * The SPI data register is double-buffered: Once a transfer has + started, new write data can be written, queuing a new transfer. + Transfers submitted this way are chained and executed without + deasserting cs. Once a transfer completes, the previous transfer's + read data is available in the data register. + * Writes to the config register take effect immediately. Writes to xfer + and data are synchronized to the start of a transfer. + * A wishbone data register write is ack-ed when the transfer has + been written to the intermediate buffer. It will be started when + there are no other transactions being executed, either starting + a new SPI transfer of chained to an in-flight transfer. + Writes take two cycles unless the write is to the data register + and another chained transfer is pending and the transfer being + executed is not complete. Reads always finish in two cycles. + + Transaction Sequence: + * If desired, write the config register to set up the core. + * If desired, write the xfer register to change lengths and cs_n. + * Write the data register (also for zero-length writes), + writing triggers the transfer and when the transfer is accepted to + the inermediate buffer, the write is ack-ed. + * If desired, read the data register corresponding to the last + completed transfer. + * If desired, change xfer register for the next transfer. + * If desired, write data queuing the next (possibly chained) transfer. + + Register address and bit map: + + config (address 2): + 1 offline: all pins high-z (reset=1) + 1 active: cs/transfer active (read-only) + 1 pending: transfer pending in intermediate buffer (read-only) + 1 cs_polarity: active level of chip select (reset=0) + 1 clk_polarity: idle level of clk (reset=0) + 1 clk_phase: first edge after cs assertion to sample data on (reset=0) + (clk_polarity, clk_phase) == (CPOL, CPHA) in Freescale language. + (0, 0): idle low, output on falling, input on rising + (0, 1): idle low, output on rising, input on falling + (1, 0): idle high, output on rising, input on falling + (1, 1): idle high, output on falling, input on rising + There is never a clk edge during a cs edge. + 1 lsb_first: LSB is the first bit on the wire (reset=0) + 1 half_duplex: 3-wire SPI, in/out on mosi (reset=0) + 8 undefined + 8 div_write: counter load value to divide this module's clock + to generate the SPI write clk (reset=0) + f_clk/f_spi_write == div_write + 2 + 8 div_read: ditto for the read clock + + xfer (address 1): + 16 cs: active high bit mask of chip selects to assert (reset=0) + 6 write_len: 0-M bits (reset=0) + 2 undefined + 6 read_len: 0-M bits (reset=0) + 2 undefined + + data (address 0): + M write/read data (reset=0) + """ + def __init__(self, pads, bus=None): + if bus is None: + bus = wishbone.Interface(data_width=32) + self.bus = bus + + ### + + # Wishbone + config = Record([ + ("offline", 1), + ("active", 1), + ("pending", 1), + ("cs_polarity", 1), + ("clk_polarity", 1), + ("clk_phase", 1), + ("lsb_first", 1), + ("half_duplex", 1), + ("padding", 8), + ("div_write", 8), + ("div_read", 8), + ]) + config.offline.reset = 1 + assert len(config) <= len(bus.dat_w) + + xfer = Record([ + ("cs", 16), + ("write_length", 6), + ("padding0", 2), + ("read_length", 6), + ("padding1", 2), + ]) + assert len(xfer) <= len(bus.dat_w) + + self.submodules.spi = spi = SPIMachine( + data_width=len(bus.dat_w), + clock_width=len(config.div_read), + bits_width=len(xfer.read_length)) + + pending = Signal() + cs = Signal.like(xfer.cs) + data_read = Signal.like(spi.reg.data) + data_write = Signal.like(spi.reg.data) + + self.comb += [ + bus.dat_r.eq( + Array([data_read, xfer.raw_bits(), config.raw_bits() + ])[bus.adr]), + spi.start.eq(pending & (~spi.cs | spi.done)), + spi.clk_phase.eq(config.clk_phase), + spi.reg.lsb.eq(config.lsb_first), + spi.div_write.eq(config.div_write), + spi.div_read.eq(config.div_read), + ] + self.sync += [ + If(spi.done, + data_read.eq(spi.reg.data), + ), + If(spi.start, + cs.eq(xfer.cs), + spi.bits.n_write.eq(xfer.write_length), + spi.bits.n_read.eq(xfer.read_length), + spi.reg.data.eq(data_write), + pending.eq(0), + ), + # wb.ack a transaction if any of the following: + # a) reading, + # b) writing to non-data register + # c) writing to data register and no pending transfer + # d) writing to data register and pending and swapping buffers + bus.ack.eq(bus.cyc & bus.stb & + (~bus.we | (bus.adr != 0) | ~pending | spi.done)), + If(bus.ack, + bus.ack.eq(0), + If(bus.we, + Array([data_write, xfer.raw_bits(), config.raw_bits() + ])[bus.adr].eq(bus.dat_w), + If(bus.adr == 0, # data register + pending.eq(1), + ), + ), + ), + config.active.eq(spi.cs), + config.pending.eq(pending), + ] + + # I/O + if hasattr(pads, "cs_n"): + cs_n_t = TSTriple(len(pads.cs_n)) + self.specials += cs_n_t.get_tristate(pads.cs_n) + self.comb += [ + cs_n_t.oe.eq(~config.offline), + cs_n_t.o.eq((cs & Replicate(spi.cs, len(cs))) ^ + Replicate(~config.cs_polarity, len(cs))), + ] + + clk_t = TSTriple() + self.specials += clk_t.get_tristate(pads.clk) + self.comb += [ + clk_t.oe.eq(~config.offline), + clk_t.o.eq((spi.cg.clk & spi.cs) ^ config.clk_polarity), + ] + + mosi_t = TSTriple() + self.specials += mosi_t.get_tristate(pads.mosi) + self.comb += [ + mosi_t.oe.eq(~config.offline & spi.cs & + (spi.oe | ~config.half_duplex)), + mosi_t.o.eq(spi.reg.o), + spi.reg.i.eq(Mux(config.half_duplex, mosi_t.i, + getattr(pads, "miso", mosi_t.i))), + ] + + +SPI_DATA_ADDR, SPI_XFER_ADDR, SPI_CONFIG_ADDR = range(3) +( + SPI_OFFLINE, + SPI_ACTIVE, + SPI_PENDING, + SPI_CS_POLARITY, + SPI_CLK_POLARITY, + SPI_CLK_PHASE, + SPI_LSB_FIRST, + SPI_HALF_DUPLEX, +) = (1 << i for i in range(8)) + + +def SPI_DIV_WRITE(i): + return i << 16 + + +def SPI_DIV_READ(i): + return i << 24 + + +def SPI_CS(i): + return i << 0 + + +def SPI_WRITE_LENGTH(i): + return i << 16 + + +def SPI_READ_LENGTH(i): + return i << 24 + + +def _test_xfer(bus, cs, wlen, rlen, wdata): + yield from bus.write(SPI_XFER_ADDR, SPI_CS(cs) | + SPI_WRITE_LENGTH(wlen) | SPI_READ_LENGTH(rlen)) + yield from bus.write(SPI_DATA_ADDR, wdata) + yield + + +def _test_read(bus, sync=SPI_ACTIVE | SPI_PENDING): + while (yield from bus.read(SPI_CONFIG_ADDR)) & sync: + pass + return (yield from bus.read(SPI_DATA_ADDR)) + + +def _test_gen(bus): + yield from bus.write(SPI_CONFIG_ADDR, + 0*SPI_CLK_PHASE | 0*SPI_LSB_FIRST | + 1*SPI_HALF_DUPLEX | + SPI_DIV_WRITE(3) | SPI_DIV_READ(5)) + yield from _test_xfer(bus, 0b01, 4, 0, 0x90000000) + print(hex((yield from _test_read(bus)))) + yield from _test_xfer(bus, 0b10, 0, 4, 0x90000000) + print(hex((yield from _test_read(bus)))) + yield from _test_xfer(bus, 0b11, 4, 4, 0x81000000) + print(hex((yield from _test_read(bus)))) + yield from _test_xfer(bus, 0b01, 8, 32, 0x87654321) + yield from _test_xfer(bus, 0b01, 0, 32, 0x12345678) + print(hex((yield from _test_read(bus, SPI_PENDING)))) + print(hex((yield from _test_read(bus, SPI_ACTIVE)))) + return + for cpol, cpha, lsb, clk in product( + (0, 1), (0, 1), (0, 1), (0, 1)): + yield from bus.write(SPI_CONFIG_ADDR, + cpol*SPI_CLK_POLARITY | cpha*SPI_CLK_PHASE | + lsb*SPI_LSB_FIRST | SPI_DIV_WRITE(clk) | + SPI_DIV_READ(clk)) + for wlen, rlen, wdata in product((0, 8, 32), (0, 8, 32), + (0, 0xffffffff, 0xdeadbeef)): + rdata = (yield from _test_xfer(bus, 0b1, wlen, rlen, wdata, True)) + len = (wlen + rlen) % 32 + mask = (1 << len) - 1 + if lsb: + shift = (wlen + rlen) % 32 + else: + shift = 0 + a = (wdata >> wshift) & wmask + b = (rdata >> rshift) & rmask + if a != b: + print("ERROR", end=" ") + print(cpol, cpha, lsb, clk, wlen, rlen, + hex(wdata), hex(rdata), hex(a), hex(b)) + + +class _TestPads: + def __init__(self): + self.cs_n = Signal(2) + self.clk = Signal() + self.mosi = Signal() + self.miso = Signal() + + +class _TestTristate(Module): + def __init__(self, t): + oe = Signal() + self.comb += [ + t.target.eq(t.o), + oe.eq(t.oe), + t.i.eq(t.o), + ] + +if __name__ == "__main__": + from migen.fhdl.specials import Tristate + + pads = _TestPads() + dut = SPIMaster(pads) + dut.comb += pads.miso.eq(pads.mosi) + # from migen.fhdl.verilog import convert + # print(convert(dut)) + + Tristate.lower = _TestTristate + run_simulation(dut, _test_gen(dut.bus), vcd_name="spi_master.vcd") diff --git a/artiq/gateware/targets/kc705.py b/artiq/gateware/targets/kc705.py index 719876220..8f766ecce 100755 --- a/artiq/gateware/targets/kc705.py +++ b/artiq/gateware/targets/kc705.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3.5 import argparse -import os from migen import * from migen.genlib.resetsync import AsyncResetSynchronizer @@ -9,18 +8,18 @@ from migen.genlib.cdc import MultiReg from migen.build.generic_platform import * from migen.build.xilinx.vivado import XilinxVivadoToolchain from migen.build.xilinx.ise import XilinxISEToolchain +from migen.fhdl.specials import Keep from misoc.interconnect.csr import * from misoc.interconnect import wishbone from misoc.cores import gpio from misoc.integration.soc_core import mem_decoder -from misoc.integration.builder import * from misoc.targets.kc705 import MiniSoC, soc_kc705_args, soc_kc705_argdict +from misoc.integration.builder import builder_args, builder_argdict -from artiq.gateware.soc import AMPSoC +from artiq.gateware.soc import AMPSoC, build_artiq_soc from artiq.gateware import rtio, nist_qc1, nist_clock, nist_qc2 -from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_7series, dds -from artiq import __artiq_dir__ as artiq_dir +from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_7series, dds, spi from artiq import __version__ as artiq_version @@ -80,9 +79,24 @@ class _RTIOCRG(Module, AutoCSR): ] +_ams101_dac = [ + ("ams101_dac", 0, + Subsignal("ldac", Pins("XADC:GPIO0")), + Subsignal("clk", Pins("XADC:GPIO1")), + Subsignal("mosi", Pins("XADC:GPIO2")), + Subsignal("cs_n", Pins("XADC:GPIO3")), + IOStandard("LVTTL") + ) +] + + class _NIST_Ions(MiniSoC, AMPSoC): csr_map = { - "rtio": None, # mapped on Wishbone instead + # mapped on Wishbone instead + "timer_kernel": None, + "rtio": None, + "i2c": None, + "rtio_crg": 13, "kernel_cpu": 14, "rtio_moninj": 15, @@ -90,8 +104,10 @@ class _NIST_Ions(MiniSoC, AMPSoC): } csr_map.update(MiniSoC.csr_map) mem_map = { - "rtio": 0x20000000, # (shadow @0xa0000000) - "mailbox": 0x70000000 # (shadow @0xf0000000) + "timer_kernel": 0x10000000, # (shadow @0x90000000) + "rtio": 0x20000000, # (shadow @0xa0000000) + "i2c": 0x30000000, # (shadow @0xb0000000) + "mailbox": 0x70000000 # (shadow @0xf0000000) } mem_map.update(MiniSoC.mem_map) @@ -115,33 +131,36 @@ class _NIST_Ions(MiniSoC, AMPSoC): self.platform.request("user_led", 0), self.platform.request("user_led", 1))) + self.platform.add_extension(_ams101_dac) + + i2c = self.platform.request("i2c") + self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda]) + self.register_kernel_cpu_csrdevice("i2c") + self.config["I2C_BUS_COUNT"] = 1 + def add_rtio(self, rtio_channels): self.submodules.rtio_crg = _RTIOCRG(self.platform, self.crg.cd_sys.clk) self.submodules.rtio = rtio.RTIO(rtio_channels) + self.register_kernel_cpu_csrdevice("rtio") self.config["RTIO_FINE_TS_WIDTH"] = self.rtio.fine_ts_width self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) - if isinstance(self.platform.toolchain, XilinxVivadoToolchain): - self.platform.add_platform_command(""" -create_clock -name rsys_clk -period 8.0 [get_nets {rsys_clk}] -create_clock -name rio_clk -period 8.0 [get_nets {rio_clk}] -set_false_path -from [get_clocks rsys_clk] -to [get_clocks rio_clk] -set_false_path -from [get_clocks rio_clk] -to [get_clocks rsys_clk] -""", rsys_clk=self.rtio.cd_rsys.clk, rio_clk=self.rtio.cd_rio.clk) - if isinstance(self.platform.toolchain, XilinxISEToolchain): - self.platform.add_platform_command(""" -NET "sys_clk" TNM_NET = "GRPrsys_clk"; -NET "{rio_clk}" TNM_NET = "GRPrio_clk"; -TIMESPEC "TSfix_cdc1" = FROM "GRPrsys_clk" TO "GRPrio_clk" TIG; -TIMESPEC "TSfix_cdc2" = FROM "GRPrio_clk" TO "GRPrsys_clk" TIG; -""", rio_clk=self.rtio_crg.cd_rtio.clk) + self.specials += [ + Keep(self.rtio.cd_rsys.clk), + Keep(self.rtio_crg.cd_rtio.clk), + Keep(self.ethphy.crg.cd_eth_rx.clk), + Keep(self.ethphy.crg.cd_eth_tx.clk), + ] - rtio_csrs = self.rtio.get_csrs() - self.submodules.rtiowb = wishbone.CSRBank(rtio_csrs) - self.kernel_cpu.add_wb_slave(mem_decoder(self.mem_map["rtio"]), - self.rtiowb.bus) - self.add_csr_region("rtio", self.mem_map["rtio"] | 0x80000000, 32, - rtio_csrs) + self.platform.add_period_constraint(self.rtio.cd_rsys.clk, 8.) + self.platform.add_period_constraint(self.rtio_crg.cd_rtio.clk, 8.) + self.platform.add_period_constraint(self.ethphy.crg.cd_eth_rx.clk, 8.) + self.platform.add_period_constraint(self.ethphy.crg.cd_eth_tx.clk, 8.) + self.platform.add_false_path_constraints( + self.rtio.cd_rsys.clk, + self.rtio_crg.cd_rtio.clk, + self.ethphy.crg.cd_eth_rx.clk, + self.ethphy.crg.cd_eth_tx.clk) self.submodules.rtio_analyzer = rtio.Analyzer(self.rtio, self.get_native_sdram_if()) @@ -235,8 +254,29 @@ class NIST_CLOCK(_NIST_Ions): phy = ttl_simple.Output(platform.request("user_led", 2)) self.submodules += phy rtio_channels.append(rtio.Channel.from_phy(phy)) + + ams101_dac = self.platform.request("ams101_dac", 0) + phy = ttl_simple.Output(ams101_dac.ldac) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy)) self.config["RTIO_REGULAR_TTL_COUNT"] = len(rtio_channels) + phy = ttl_simple.ClockGen(platform.request("la32_p")) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy(phy)) + + phy = spi.SPIMaster(ams101_dac) + self.submodules += phy + self.config["RTIO_FIRST_SPI_CHANNEL"] = len(rtio_channels) + rtio_channels.append(rtio.Channel.from_phy( + phy, ofifo_depth=4, ififo_depth=4)) + + for i in range(3): + phy = spi.SPIMaster(self.platform.request("spi", i)) + self.submodules += phy + rtio_channels.append(rtio.Channel.from_phy( + phy, ofifo_depth=128, ififo_depth=128)) + self.config["RTIO_DDS_CHANNEL"] = len(rtio_channels) self.config["DDS_CHANNEL_COUNT"] = 11 self.config["DDS_AD9914"] = True @@ -258,7 +298,7 @@ class NIST_CLOCK(_NIST_Ions): class NIST_QC2(_NIST_Ions): """ NIST QC2 hardware, as used in Quantum I and Quantum II, with new backplane - and 12 DDS channels. Current implementation for single backplane. + and 12 DDS channels. Current implementation for single backplane. """ def __init__(self, cpu_type="or1k", **kwargs): _NIST_Ions.__init__(self, cpu_type, **kwargs) @@ -315,7 +355,7 @@ def main(): "+ NIST Ions QC1/CLOCK/QC2 hardware adapters") builder_args(parser) soc_kc705_args(parser) - parser.add_argument("-H", "--hw-adapter", default="qc1", + parser.add_argument("-H", "--hw-adapter", default="clock", help="hardware adapter type: qc1/clock/qc2 " "(default: %(default)s)") args = parser.parse_args() @@ -333,11 +373,7 @@ def main(): sys.exit(1) soc = cls(**soc_kc705_argdict(args)) - builder = Builder(soc, **builder_argdict(args)) - builder.add_software_package("liblwip", os.path.join(artiq_dir, "runtime", - "liblwip")) - builder.add_software_package("runtime", os.path.join(artiq_dir, "runtime")) - builder.build() + build_artiq_soc(soc, builder_argdict(args)) if __name__ == "__main__": diff --git a/artiq/gateware/targets/pipistrello.py b/artiq/gateware/targets/pipistrello.py index 288a7b3c8..80985c04a 100755 --- a/artiq/gateware/targets/pipistrello.py +++ b/artiq/gateware/targets/pipistrello.py @@ -4,7 +4,6 @@ # Copyright (C) 2014, 2015 M-Labs Limited import argparse -import os from fractions import Fraction from migen import * @@ -15,12 +14,13 @@ from misoc.interconnect.csr import * from misoc.interconnect import wishbone from misoc.cores import gpio from misoc.integration.soc_core import mem_decoder -from misoc.targets.pipistrello import * +from misoc.targets.pipistrello import (BaseSoC, soc_pipistrello_args, + soc_pipistrello_argdict) +from misoc.integration.builder import builder_args, builder_argdict -from artiq.gateware.soc import AMPSoC +from artiq.gateware.soc import AMPSoC, build_artiq_soc from artiq.gateware import rtio, nist_qc1 -from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_spartan6, dds -from artiq import __artiq_dir__ as artiq_dir +from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_spartan6, dds, spi from artiq import __version__ as artiq_version @@ -103,6 +103,7 @@ TIMESPEC "TSfix_ise4" = FROM "GRPsys_clk" TO "GRPrtio_clk" TIG; class NIST_QC1(BaseSoC, AMPSoC): csr_map = { + "timer_kernel": None, # mapped on Wishbone instead "rtio": None, # mapped on Wishbone instead "rtio_crg": 10, "kernel_cpu": 11, @@ -111,8 +112,9 @@ class NIST_QC1(BaseSoC, AMPSoC): } csr_map.update(BaseSoC.csr_map) mem_map = { - "rtio": 0x20000000, # (shadow @0xa0000000) - "mailbox": 0x70000000 # (shadow @0xf0000000) + "timer_kernel": 0x10000000, # (shadow @0x90000000) + "rtio": 0x20000000, # (shadow @0xa0000000) + "mailbox": 0x70000000 # (shadow @0xf0000000) } mem_map.update(BaseSoC.mem_map) @@ -152,12 +154,12 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=512, ofifo_depth=4)) - # ttl2 can run on a 8x serdes if xtrig is not used + # the last TTL is used for ClockGen for i in range(15): if i in (0, 1): phy = ttl_serdes_spartan6.Output_4X(platform.request("ttl", i), self.rtio_crg.rtiox4_stb) - elif i in (2,): + elif i in (2,): # ttl2 can run on a 8x serdes if xtrig is not used phy = ttl_serdes_spartan6.Output_8X(platform.request("ttl", i), self.rtio_crg.rtiox8_stb) else: @@ -192,23 +194,27 @@ trce -v 12 -fastpaths -tsi {build_name}.tsi -o {build_name}.twr {build_name}.ncd ofifo_depth=512, ififo_depth=4)) + pmod = self.platform.request("pmod", 0) + spi_pins = Module() + spi_pins.clk = pmod.d[0] + spi_pins.mosi = pmod.d[1] + spi_pins.miso = pmod.d[2] + spi_pins.cs_n = pmod.d[3:] + phy = spi.SPIMaster(spi_pins) + self.submodules += phy + self.config["RTIO_FIRST_SPI_CHANNEL"] = len(rtio_channels) + rtio_channels.append(rtio.Channel.from_phy( + phy, ofifo_depth=4, ififo_depth=4)) + self.config["RTIO_LOG_CHANNEL"] = len(rtio_channels) rtio_channels.append(rtio.LogChannel()) - # RTIO core + # RTIO logic self.submodules.rtio = rtio.RTIO(rtio_channels) + self.register_kernel_cpu_csrdevice("rtio") self.config["RTIO_FINE_TS_WIDTH"] = self.rtio.fine_ts_width self.config["DDS_RTIO_CLK_RATIO"] = 8 >> self.rtio.fine_ts_width self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) - - # CPU connections - rtio_csrs = self.rtio.get_csrs() - self.submodules.rtiowb = wishbone.CSRBank(rtio_csrs) - self.kernel_cpu.add_wb_slave(mem_decoder(self.mem_map["rtio"]), - self.rtiowb.bus) - self.add_csr_region("rtio", self.mem_map["rtio"] | 0x80000000, 32, - rtio_csrs) - self.submodules.rtio_analyzer = rtio.Analyzer(self.rtio, self.get_native_sdram_if()) @@ -222,11 +228,7 @@ def main(): args = parser.parse_args() soc = NIST_QC1(**soc_pipistrello_argdict(args)) - builder = Builder(soc, **builder_argdict(args)) - builder.add_software_package("liblwip", os.path.join(artiq_dir, "runtime", - "liblwip")) - builder.add_software_package("runtime", os.path.join(artiq_dir, "runtime")) - builder.build() + build_artiq_soc(soc, builder_argdict(args)) if __name__ == "__main__": diff --git a/artiq/gui/moninj.py b/artiq/gui/moninj.py index 9e5d0a733..43892876a 100644 --- a/artiq/gui/moninj.py +++ b/artiq/gui/moninj.py @@ -29,7 +29,7 @@ class _TTLWidget(QtWidgets.QFrame): QtWidgets.QFrame.__init__(self) - self.setFrameShape(QtWidgets.QFrame.Panel) + self.setFrameShape(QtWidgets.QFrame.Box) self.setFrameShadow(QtWidgets.QFrame.Raised) grid = QtWidgets.QGridLayout() diff --git a/artiq/master/scheduler.py b/artiq/master/scheduler.py index 8b7c52553..6e41a8d48 100644 --- a/artiq/master/scheduler.py +++ b/artiq/master/scheduler.py @@ -198,7 +198,7 @@ class PrepareStage(TaskObject): await self.pool.state_changed.wait() elif isinstance(run, float): await asyncio_wait_or_cancel([self.pool.state_changed.wait()], - timeout=run) + timeout=run) else: if run.flush: run.status = RunStatus.flushing diff --git a/artiq/master/worker_db.py b/artiq/master/worker_db.py index 487257077..fd41647b3 100644 --- a/artiq/master/worker_db.py +++ b/artiq/master/worker_db.py @@ -90,7 +90,7 @@ def _create_device(desc, device_mgr): if ty == "local": module = importlib.import_module(desc["module"]) device_class = getattr(module, desc["class"]) - return device_class(device_mgr, **desc["arguments"]) + return device_class(device_mgr, **desc.get("arguments", {})) elif ty == "controller": if desc.get("best_effort", False): cls = BestEffortClient diff --git a/artiq/protocols/analyzer.py b/artiq/protocols/analyzer.py index 440c3b593..46ca712ee 100644 --- a/artiq/protocols/analyzer.py +++ b/artiq/protocols/analyzer.py @@ -15,6 +15,6 @@ class ExceptionType(Enum): o_underflow_reset = 0b010000 o_sequence_error_reset = 0b010001 - o_collision_error_reset = 0b010010 + o_collision_reset = 0b010010 i_overflow_reset = 0b100000 diff --git a/artiq/protocols/pc_rpc.py b/artiq/protocols/pc_rpc.py index 962fe10de..c9f53b286 100644 --- a/artiq/protocols/pc_rpc.py +++ b/artiq/protocols/pc_rpc.py @@ -108,6 +108,7 @@ class Client: server_identification = self.__recv() self.__target_names = server_identification["targets"] self.__description = server_identification["description"] + self.__selected_target = None if target_name is not None: self.select_rpc_target(target_name) except: @@ -119,6 +120,12 @@ class Client: exactly once if the object was created with ``target_name=None``.""" target_name = _validate_target_name(target_name, self.__target_names) self.__socket.sendall((target_name + "\n").encode()) + self.__selected_target = target_name + + def get_selected_target(self): + """Returns the selected target, or ``None`` if no target has been + selected yet.""" + return self.__selected_target def get_rpc_id(self): """Returns a tuple (target_names, description) containing the @@ -197,6 +204,7 @@ class AsyncioClient: server_identification = await self.__recv() self.__target_names = server_identification["targets"] self.__description = server_identification["description"] + self.__selected_target = None if target_name is not None: self.select_rpc_target(target_name) except: @@ -209,6 +217,12 @@ class AsyncioClient: """ target_name = _validate_target_name(target_name, self.__target_names) self.__writer.write((target_name + "\n").encode()) + self.__selected_target = target_name + + def get_selected_target(self): + """Returns the selected target, or ``None`` if no target has been + selected yet.""" + return self.__selected_target def get_rpc_id(self): """Returns a tuple (target_names, description) containing the diff --git a/artiq/protocols/pipe_ipc.py b/artiq/protocols/pipe_ipc.py index 4ac416a09..c32f74b8f 100644 --- a/artiq/protocols/pipe_ipc.py +++ b/artiq/protocols/pipe_ipc.py @@ -109,7 +109,6 @@ else: # windows # mode or not. self.address = "\\\\.\\pipe\\artiq-{}-{}".format(os.getpid(), next(_pipe_count)) - self.server = None self.ready = asyncio.Event() self.write_buffer = b"" @@ -118,11 +117,12 @@ else: # windows async def _autoclose(self): await self.process.wait() - if self.server is not None: - self.server[0].close() - self.server = None + self.server[0].close() + del self.server if self.ready.is_set(): self.writer.close() + del self.reader + del self.writer async def create_subprocess(self, *args, **kwargs): loop = asyncio.get_event_loop() @@ -150,8 +150,12 @@ else: # windows # There is still a race condition in the AsyncioParentComm # creation/destruction, but it is unlikely to cause problems # in most practical cases. - assert self.server is not None - self.server = None + if self.ready.is_set(): + # A child already connected before. We should have shut down + # the server, but asyncio won't let us do that. + # Drop connections immediately instead. + writer.close() + return self.reader = reader self.writer = writer if self.write_buffer: diff --git a/artiq/runtime/Makefile b/artiq/runtime/Makefile index 6c13b631b..58fca022f 100644 --- a/artiq/runtime/Makefile +++ b/artiq/runtime/Makefile @@ -7,7 +7,7 @@ OBJECTS := isr.o clock.o rtiocrg.o flash_storage.o mailbox.o \ session.o log.o analyzer.o moninj.o net_server.o bridge_ctl.o \ ksupport_data.o kloader.o test_mode.o main.o OBJECTS_KSUPPORT := ksupport.o artiq_personality.o mailbox.o \ - bridge.o rtio.o ttl.o dds.o + bridge.o rtio.o dds.o i2c.o CFLAGS += -I$(LIBALLOC_DIRECTORY) \ -I$(MISOC_DIRECTORY)/software/include/dyld \ diff --git a/artiq/runtime/bridge.c b/artiq/runtime/bridge.c index 708e35473..d1157cf65 100644 --- a/artiq/runtime/bridge.c +++ b/artiq/runtime/bridge.c @@ -7,15 +7,20 @@ #define TIME_BUFFER (8000 << CONFIG_RTIO_FINE_TS_WIDTH) -static void dds_write(int addr, int data) +static void rtio_output_blind(int channel, int addr, int data) { - rtio_chan_sel_write(CONFIG_RTIO_DDS_CHANNEL); + rtio_chan_sel_write(channel); rtio_o_address_write(addr); rtio_o_data_write(data); rtio_o_timestamp_write(rtio_get_counter() + TIME_BUFFER); rtio_o_we_write(1); } +static void dds_write(int addr, int data) +{ + rtio_output_blind(CONFIG_RTIO_DDS_CHANNEL, addr, data); +} + static int dds_read(int addr) { int r; @@ -54,7 +59,7 @@ void bridge_main(void) struct msg_brg_ttl_out *msg; msg = (struct msg_brg_ttl_out *)umsg; - ttl_set_oe(rtio_get_counter() + TIME_BUFFER, msg->channel, msg->value); + rtio_output_blind(msg->channel, TTL_OE_ADDR, msg->value); mailbox_acknowledge(); break; } @@ -62,7 +67,7 @@ void bridge_main(void) struct msg_brg_ttl_out *msg; msg = (struct msg_brg_ttl_out *)umsg; - ttl_set_o(rtio_get_counter() + TIME_BUFFER, msg->channel, msg->value); + rtio_output_blind(msg->channel, TTL_O_ADDR, msg->value); mailbox_acknowledge(); break; } diff --git a/artiq/runtime/dds.c b/artiq/runtime/dds.c index 396e652eb..3f8e728c1 100644 --- a/artiq/runtime/dds.c +++ b/artiq/runtime/dds.c @@ -26,10 +26,7 @@ #endif #define DDS_WRITE(addr, data) do { \ - rtio_o_address_write(addr); \ - rtio_o_data_write(data); \ - rtio_o_timestamp_write(now); \ - rtio_write_and_process_status(now, CONFIG_RTIO_DDS_CHANNEL); \ + rtio_output(now, CONFIG_RTIO_DDS_CHANNEL, addr, data); \ now += DURATION_WRITE; \ } while(0) @@ -37,8 +34,6 @@ void dds_init(long long int timestamp, int channel) { long long int now; - rtio_chan_sel_write(CONFIG_RTIO_DDS_CHANNEL); - now = timestamp - DURATION_INIT; #ifdef CONFIG_DDS_ONEHOT_SEL @@ -94,10 +89,10 @@ static void dds_set_one(long long int now, long long int ref_time, unsigned int { unsigned int channel_enc; - if(channel >= CONFIG_DDS_CHANNEL_COUNT) { - core_log("Attempted to set invalid DDS channel\n"); - return; - } + if(channel >= CONFIG_DDS_CHANNEL_COUNT) { + core_log("Attempted to set invalid DDS channel\n"); + return; + } #ifdef CONFIG_DDS_ONEHOT_SEL channel_enc = 1 << channel; #else @@ -190,7 +185,6 @@ void dds_batch_exit(void) if(!batch_mode) artiq_raise_from_c("DDSBatchError", "DDS batch error", 0, 0, 0); - rtio_chan_sel_write(CONFIG_RTIO_DDS_CHANNEL); /* + FUD time */ now = batch_ref_time - batch_count*(DURATION_PROGRAM + DURATION_WRITE); for(i=0;i + +#include "artiq_personality.h" +#include "rtio.h" +#include "i2c.h" + + +static void i2c_halfperiod() +{ + timer_kernel_en_write(0); + timer_kernel_load_write(CONFIG_CLOCK_FREQUENCY/10000); + timer_kernel_reload_write(0); + timer_kernel_en_write(1); + + timer_kernel_update_value_write(1); + while(timer_kernel_value_read() != 0) + timer_kernel_update_value_write(1); +} + +#if (defined CONFIG_I2C_BUS_COUNT) && (CONFIG_I2C_BUS_COUNT > 0) + +#define SDA_BIT (1 << (2*busno + 1)) +#define SCL_BIT (1 << (2*busno)) + +static int i2c_sda_i(int busno) +{ + if(busno >= CONFIG_I2C_BUS_COUNT) + return 1; + else + return i2c_in_read() & SDA_BIT; +} + +static void i2c_sda_oe(int busno, int oe) +{ + int reg; + + reg = i2c_oe_read(); + if(oe) + reg |= SDA_BIT; + else + reg &= ~SDA_BIT; + i2c_oe_write(reg); +} + +static void i2c_sda_o(int busno, int o) +{ + int reg; + + reg = i2c_out_read(); + if(o) + reg |= SDA_BIT; + else + reg &= ~SDA_BIT; + i2c_out_write(reg); +} + +static void i2c_scl_oe(int busno, int oe) +{ + int reg; + + reg = i2c_oe_read(); + if(oe) + reg |= SCL_BIT; + else + reg &= ~SCL_BIT; + i2c_oe_write(reg); +} + +static void i2c_scl_o(int busno, int o) +{ + int reg; + + reg = i2c_out_read(); + if(o) + reg |= SCL_BIT; + else + reg &= ~SCL_BIT; + i2c_out_write(reg); +} + +#else + +static int i2c_sda_i(int busno) +{ + return 1; +} +static void i2c_sda_oe(int busno, int oe) {} +static void i2c_sda_o(int busno, int o) {} +static void i2c_scl_oe(int busno, int oe) {} +static void i2c_scl_o(int busno, int o) {} + +#endif + + +void i2c_init(int busno) +{ + /* Set SCL as output, and high level */ + i2c_scl_o(busno, 1); + i2c_scl_oe(busno, 1); + /* Prepare a zero level on SDA so that i2c_sda_oe pulls it down */ + i2c_sda_o(busno, 0); + /* Release SDA */ + i2c_sda_oe(busno, 0); + + /* Check the I2C bus is ready */ + i2c_halfperiod(); + i2c_halfperiod(); + if(!i2c_sda_i(busno)) + artiq_raise_from_c("I2CError", "SDA is stuck low", 0, 0, 0); +} + +void i2c_start(int busno) +{ + /* Set SCL high then SDA low */ + i2c_scl_o(busno, 1); + i2c_halfperiod(); + i2c_sda_oe(busno, 1); + i2c_halfperiod(); +} + +void i2c_stop(int busno) +{ + /* First, make sure SCL is low, so that the target releases the SDA line */ + i2c_scl_o(busno, 0); + i2c_halfperiod(); + /* Set SCL high then SDA high */ + i2c_sda_oe(busno, 1); + i2c_scl_o(busno, 1); + i2c_halfperiod(); + i2c_sda_oe(busno, 0); + i2c_halfperiod(); +} + +int i2c_write(int busno, int b) +{ + int i; + + /* MSB first */ + for(i=7;i>=0;i--) { + /* Set SCL low and set our bit on SDA */ + i2c_scl_o(busno, 0); + i2c_sda_oe(busno, b & (1 << i) ? 0 : 1); + i2c_halfperiod(); + /* Set SCL high ; data is shifted on the rising edge of SCL */ + i2c_scl_o(busno, 1); + i2c_halfperiod(); + } + /* Check ack */ + /* Set SCL low, then release SDA so that the I2C target can respond */ + i2c_scl_o(busno, 0); + i2c_halfperiod(); + i2c_sda_oe(busno, 0); + /* Set SCL high and check for ack */ + i2c_scl_o(busno, 1); + i2c_halfperiod(); + /* returns 1 if acked (I2C target pulled SDA low) */ + return !i2c_sda_i(busno); +} + +int i2c_read(int busno, int ack) +{ + int i; + unsigned char b; + + /* Set SCL low first, otherwise setting SDA as input may cause a transition + * on SDA with SCL high which will be interpreted as START/STOP condition. + */ + i2c_scl_o(busno, 0); + i2c_halfperiod(); /* make sure SCL has settled low */ + i2c_sda_oe(busno, 0); + + b = 0; + /* MSB first */ + for(i=7;i>=0;i--) { + i2c_scl_o(busno, 0); + i2c_halfperiod(); + /* Set SCL high and shift data */ + i2c_scl_o(busno, 1); + i2c_halfperiod(); + if(i2c_sda_i(busno)) b |= (1 << i); + } + /* Send ack */ + /* Set SCL low and pull SDA low when acking */ + i2c_scl_o(busno, 0); + if(ack) + i2c_sda_oe(busno, 1); + i2c_halfperiod(); + /* then set SCL high */ + i2c_scl_o(busno, 1); + i2c_halfperiod(); + + return b; +} diff --git a/artiq/runtime/i2c.h b/artiq/runtime/i2c.h new file mode 100644 index 000000000..c7aab6e22 --- /dev/null +++ b/artiq/runtime/i2c.h @@ -0,0 +1,10 @@ +#ifndef __I2C_H +#define __I2C_H + +void i2c_init(int busno); +void i2c_start(int busno); +void i2c_stop(int busno); +int i2c_write(int busno, int b); +int i2c_read(int busno, int ack); + +#endif diff --git a/artiq/runtime/ksupport.c b/artiq/runtime/ksupport.c index d5825f746..b61f956ee 100644 --- a/artiq/runtime/ksupport.c +++ b/artiq/runtime/ksupport.c @@ -13,9 +13,9 @@ #include "messages.h" #include "bridge.h" #include "artiq_personality.h" -#include "ttl.h" -#include "dds.h" #include "rtio.h" +#include "dds.h" +#include "i2c.h" double round(double x); @@ -109,18 +109,21 @@ static const struct symbol runtime_exports[] = { /* direct syscalls */ {"rtio_get_counter", &rtio_get_counter}, {"rtio_log", &rtio_log}, - - {"ttl_set_o", &ttl_set_o}, - {"ttl_set_oe", &ttl_set_oe}, - {"ttl_set_sensitivity", &ttl_set_sensitivity}, - {"ttl_get", &ttl_get}, - {"ttl_clock_set", &ttl_clock_set}, + {"rtio_output", &rtio_output}, + {"rtio_input_timestamp", &rtio_input_timestamp}, + {"rtio_input_data", &rtio_input_data}, {"dds_init", &dds_init}, {"dds_batch_enter", &dds_batch_enter}, {"dds_batch_exit", &dds_batch_exit}, {"dds_set", &dds_set}, + {"i2c_init", &i2c_init}, + {"i2c_start", &i2c_start}, + {"i2c_stop", &i2c_stop}, + {"i2c_write", &i2c_write}, + {"i2c_read", &i2c_read}, + {"cache_get", &cache_get}, {"cache_put", &cache_put}, diff --git a/artiq/runtime/main.c b/artiq/runtime/main.c index 372176732..5897b1eef 100644 --- a/artiq/runtime/main.c +++ b/artiq/runtime/main.c @@ -137,9 +137,9 @@ static void network_init(void) struct ip4_addr gateway_ip; init_macadr(); - fsip_or_default(&local_ip, "ip", 192, 168, 0, 42); + fsip_or_default(&local_ip, "ip", 192, 168, 1, 50); fsip_or_default(&netmask, "netmask", 255, 255, 255, 0); - fsip_or_default(&gateway_ip, "gateway", 192, 168, 0, 1); + fsip_or_default(&gateway_ip, "gateway", 192, 168, 1, 1); lwip_init(); diff --git a/artiq/runtime/rtio.c b/artiq/runtime/rtio.c index 76514d19a..90dd7fb5e 100644 --- a/artiq/runtime/rtio.c +++ b/artiq/runtime/rtio.c @@ -1,5 +1,6 @@ #include +#include "artiq_personality.h" #include "rtio.h" void rtio_init(void) @@ -15,7 +16,8 @@ long long int rtio_get_counter(void) return rtio_counter_read(); } -void rtio_process_exceptional_status(int status, long long int timestamp, int channel) +static void rtio_process_exceptional_status( + long long int timestamp, int channel, int status) { if(status & RTIO_O_STATUS_FULL) while(rtio_o_status_read() & RTIO_O_STATUS_FULL); @@ -31,14 +33,87 @@ void rtio_process_exceptional_status(int status, long long int timestamp, int ch "RTIO sequence error at {0} mu, channel {1}", timestamp, channel, 0); } - if(status & RTIO_O_STATUS_COLLISION_ERROR) { - rtio_o_collision_error_reset_write(1); - artiq_raise_from_c("RTIOCollisionError", - "RTIO collision error at {0} mu, channel {1}", + if(status & RTIO_O_STATUS_COLLISION) { + rtio_o_collision_reset_write(1); + artiq_raise_from_c("RTIOCollision", + "RTIO collision at {0} mu, channel {1}", timestamp, channel, 0); } } + +void rtio_output(long long int timestamp, int channel, unsigned int addr, + unsigned int data) +{ + int status; + + rtio_chan_sel_write(channel); + rtio_o_timestamp_write(timestamp); + rtio_o_address_write(addr); + rtio_o_data_write(data); + rtio_o_we_write(1); + status = rtio_o_status_read(); + if(status) + rtio_process_exceptional_status(timestamp, channel, status); +} + + +long long int rtio_input_timestamp(long long int timeout, int channel) +{ + long long int r; + int status; + + rtio_chan_sel_write(channel); + while((status = rtio_i_status_read())) { + if(status & RTIO_I_STATUS_OVERFLOW) { + rtio_i_overflow_reset_write(1); + break; + } + if(rtio_get_counter() >= timeout) { + /* check empty flag again to prevent race condition. + * now we are sure that the time limit has been exceeded. + */ + status = rtio_i_status_read(); + if(status & RTIO_I_STATUS_EMPTY) + break; + } + /* input FIFO is empty - keep waiting */ + } + + if (status & RTIO_I_STATUS_OVERFLOW) + artiq_raise_from_c("RTIOOverflow", + "RTIO input overflow on channel {0}", + channel, 0, 0); + if (status & RTIO_I_STATUS_EMPTY) + return -1; + + r = rtio_i_timestamp_read(); + rtio_i_re_write(1); + return r; +} + + +unsigned int rtio_input_data(int channel) +{ + unsigned int data; + int status; + + rtio_chan_sel_write(channel); + while((status = rtio_i_status_read())) { + if(status & RTIO_I_STATUS_OVERFLOW) { + rtio_i_overflow_reset_write(1); + artiq_raise_from_c("RTIOOverflow", + "RTIO input overflow on channel {0}", + channel, 0, 0); + } + } + + data = rtio_i_data_read(); + rtio_i_re_write(1); + return data; +} + + void rtio_log_va(long long int timestamp, const char *fmt, va_list args) { // This executes on the kernel CPU's stack, which is specifically designed diff --git a/artiq/runtime/rtio.h b/artiq/runtime/rtio.h index 87d8a9f37..c32dca484 100644 --- a/artiq/runtime/rtio.h +++ b/artiq/runtime/rtio.h @@ -2,30 +2,31 @@ #define __RTIO_H #include -#include -#include "artiq_personality.h" #define RTIO_O_STATUS_FULL 1 #define RTIO_O_STATUS_UNDERFLOW 2 #define RTIO_O_STATUS_SEQUENCE_ERROR 4 -#define RTIO_O_STATUS_COLLISION_ERROR 8 +#define RTIO_O_STATUS_COLLISION 8 #define RTIO_I_STATUS_EMPTY 1 #define RTIO_I_STATUS_OVERFLOW 2 void rtio_init(void); long long int rtio_get_counter(void); -void rtio_process_exceptional_status(int status, long long int timestamp, int channel); void rtio_log(long long int timestamp, const char *format, ...); void rtio_log_va(long long int timestamp, const char *format, va_list args); +void rtio_output(long long int timestamp, int channel, unsigned int address, + unsigned int data); -static inline void rtio_write_and_process_status(long long int timestamp, int channel) -{ - int status; +/* + * Waits at least until timeout and returns the timestamp of the first + * input event on the chanel, -1 if there was no event. + */ +long long int rtio_input_timestamp(long long int timeout, int channel); - rtio_o_we_write(1); - status = rtio_o_status_read(); - if(status) - rtio_process_exceptional_status(status, timestamp, channel); -} +/* + * Assumes that there is or will be an event in the channel and returns only + * its data. + */ +unsigned int rtio_input_data(int channel); #endif /* __RTIO_H */ diff --git a/artiq/runtime/session.c b/artiq/runtime/session.c index 5c4333480..fb6e4dcaf 100644 --- a/artiq/runtime/session.c +++ b/artiq/runtime/session.c @@ -1027,7 +1027,6 @@ static int process_kmsg(struct msg_base *umsg) struct cache_row *row = NULL; for(struct cache_row *iter = cache; iter; iter = iter->next) { if(!strcmp(iter->key, request->key)) { - free(iter->elements); row = iter; break; } @@ -1042,11 +1041,14 @@ static int process_kmsg(struct msg_base *umsg) } if(!row->borrowed) { - if(request->length != 0) { - row->length = request->length; + row->length = request->length; + if(row->length != 0) { row->elements = calloc(row->length, sizeof(int32_t)); memcpy(row->elements, request->elements, sizeof(int32_t) * row->length); + } else { + free(row->elements); + row->elements = NULL; } reply.succeeded = 1; diff --git a/artiq/runtime/ttl.c b/artiq/runtime/ttl.c deleted file mode 100644 index 577ab1eeb..000000000 --- a/artiq/runtime/ttl.c +++ /dev/null @@ -1,67 +0,0 @@ -#include - -#include "artiq_personality.h" -#include "rtio.h" -#include "ttl.h" - -void ttl_set_o(long long int timestamp, int channel, int value) -{ - rtio_chan_sel_write(channel); - rtio_o_timestamp_write(timestamp); - rtio_o_address_write(0); - rtio_o_data_write(value); - rtio_write_and_process_status(timestamp, channel); -} - -void ttl_set_oe(long long int timestamp, int channel, int oe) -{ - rtio_chan_sel_write(channel); - rtio_o_timestamp_write(timestamp); - rtio_o_address_write(1); - rtio_o_data_write(oe); - rtio_write_and_process_status(timestamp, channel); -} - -void ttl_set_sensitivity(long long int timestamp, int channel, int sensitivity) -{ - rtio_chan_sel_write(channel); - rtio_o_timestamp_write(timestamp); - rtio_o_address_write(2); - rtio_o_data_write(sensitivity); - rtio_write_and_process_status(timestamp, channel); -} - -long long int ttl_get(int channel, long long int time_limit) -{ - long long int r; - int status; - - rtio_chan_sel_write(channel); - while((status = rtio_i_status_read())) { - if(rtio_i_status_read() & RTIO_I_STATUS_OVERFLOW) { - rtio_i_overflow_reset_write(1); - artiq_raise_from_c("RTIOOverflow", - "RTIO overflow at channel {0}", - channel, 0, 0); - } - if(rtio_get_counter() >= time_limit) { - /* check empty flag again to prevent race condition. - * now we are sure that the time limit has been exceeded. - */ - if(rtio_i_status_read() & RTIO_I_STATUS_EMPTY) - return -1; - } - /* input FIFO is empty - keep waiting */ - } - r = rtio_i_timestamp_read(); - rtio_i_re_write(1); - return r; -} - -void ttl_clock_set(long long int timestamp, int channel, int ftw) -{ - rtio_chan_sel_write(channel); - rtio_o_timestamp_write(timestamp); - rtio_o_data_write(ftw); - rtio_write_and_process_status(timestamp, channel); -} diff --git a/artiq/runtime/ttl.h b/artiq/runtime/ttl.h index 9d95a32f2..87fa74b7c 100644 --- a/artiq/runtime/ttl.h +++ b/artiq/runtime/ttl.h @@ -1,10 +1,8 @@ #ifndef __TTL_H #define __TTL_H -void ttl_set_o(long long int timestamp, int channel, int value); -void ttl_set_oe(long long int timestamp, int channel, int oe); -void ttl_set_sensitivity(long long int timestamp, int channel, int sensitivity); -long long int ttl_get(int channel, long long int time_limit); -void ttl_clock_set(long long int timestamp, int channel, int ftw); +#define TTL_O_ADDR 0 +#define TTL_OE_ADDR 1 +#define TTL_SENSITIVITY_ADDR 2 #endif /* __TTL_H */ diff --git a/artiq/test/coredevice/test_i2c.py b/artiq/test/coredevice/test_i2c.py new file mode 100644 index 000000000..45955c132 --- /dev/null +++ b/artiq/test/coredevice/test_i2c.py @@ -0,0 +1,25 @@ +import os, unittest + +from artiq.experiment import * +from artiq.test.hardware_testbench import ExperimentCase + + +class I2CSwitch(EnvExperiment): + def build(self): + self.setattr_device("core") + self.setattr_device("i2c_switch") + + @kernel + def run(self): + passed = True + for i in range(8): + self.i2c_switch.set(i) + if self.i2c_switch.readback() != 1 << i: + passed = False + self.set_dataset("passed", passed) + + +class I2CTest(ExperimentCase): + def test_i2c_switch(self): + self.execute(I2CSwitch) + self.assertTrue(self.dataset_mgr.get("passed")) diff --git a/artiq/test/coredevice/test_rtio.py b/artiq/test/coredevice/test_rtio.py index ba05b8f73..8f5dcb48c 100644 --- a/artiq/test/coredevice/test_rtio.py +++ b/artiq/test/coredevice/test_rtio.py @@ -8,6 +8,7 @@ from math import sqrt from artiq.experiment import * from artiq.test.hardware_testbench import ExperimentCase + artiq_low_latency = os.getenv("ARTIQ_LOW_LATENCY") @@ -101,6 +102,29 @@ class Watchdog(EnvExperiment): pass +class LoopbackCount(EnvExperiment): + def build(self): + self.setattr_device("core") + self.setattr_device("loop_in") + self.setattr_device("loop_out") + self.setattr_argument("npulses") + + def set_count(self, count): + self.set_dataset("count", count) + + @kernel + def run(self): + self.loop_out.output() + delay(5*us) + with parallel: + 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()) + + class Underflow(EnvExperiment): def build(self): self.setattr_device("core") @@ -126,7 +150,7 @@ class SequenceError(EnvExperiment): self.ttl_out.pulse(25*us) -class CollisionError(EnvExperiment): +class Collision(EnvExperiment): def build(self): self.setattr_device("core") self.setattr_device("ttl_out_serdes") @@ -139,6 +163,17 @@ class CollisionError(EnvExperiment): delay_mu(1) +class AddressCollision(EnvExperiment): + def build(self): + self.setattr_device("core") + self.setattr_device("loop_in") + + @kernel + def run(self): + self.loop_in.input() + self.loop_in.pulse(10*us) + + class TimeKeepsRunning(EnvExperiment): def build(self): self.setattr_device("core") @@ -168,7 +203,7 @@ class CoredeviceTest(ExperimentCase): rtt = self.dataset_mgr.get("rtt") print(rtt) self.assertGreater(rtt, 0*ns) - self.assertLess(rtt, 50*ns) + self.assertLess(rtt, 60*ns) def test_clock_generator_loopback(self): self.execute(ClockGeneratorLoopback) @@ -182,6 +217,12 @@ class CoredeviceTest(ExperimentCase): self.assertGreater(rate, 100*ns) self.assertLess(rate, 2500*ns) + def test_loopback_count(self): + npulses = 2 + self.execute(LoopbackCount, npulses=npulses) + count = self.dataset_mgr.get("count") + self.assertEqual(count, npulses) + def test_underflow(self): with self.assertRaises(RTIOUnderflow): self.execute(Underflow) @@ -190,9 +231,13 @@ class CoredeviceTest(ExperimentCase): with self.assertRaises(RTIOSequenceError): self.execute(SequenceError) - def test_collision_error(self): - with self.assertRaises(RTIOCollisionError): - self.execute(CollisionError) + def test_collision(self): + with self.assertRaises(RTIOCollision): + self.execute(Collision) + + def test_address_collision(self): + with self.assertRaises(RTIOCollision): + self.execute(AddressCollision) def test_watchdog(self): # watchdog only works on the device @@ -204,8 +249,11 @@ class CoredeviceTest(ExperimentCase): def test_time_keeps_running(self): self.execute(TimeKeepsRunning) t1 = self.dataset_mgr.get("time_at_start") + + self.device_mgr.get("comm").close() # start a new session self.execute(TimeKeepsRunning) t2 = self.dataset_mgr.get("time_at_start") + dead_time = mu_to_seconds(t2 - t1, self.device_mgr.get("core")) print(dead_time) self.assertGreater(dead_time, 1*ms) diff --git a/artiq/test/lit/codegen/assign_none.py b/artiq/test/lit/codegen/assign_none.py new file mode 100644 index 000000000..000e87ac7 --- /dev/null +++ b/artiq/test/lit/codegen/assign_none.py @@ -0,0 +1,6 @@ +# RUN: %python -m artiq.compiler.testbench.llvmgen %s + +def f(): + pass +def g(): + a = f() diff --git a/artiq/test/lit/inferencer/error_with_arity.py b/artiq/test/lit/inferencer/error_with_arity.py index b056073ae..c91a3996b 100644 --- a/artiq/test/lit/inferencer/error_with_arity.py +++ b/artiq/test/lit/inferencer/error_with_arity.py @@ -9,7 +9,7 @@ class contextmgr: 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:+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 index 5aa8ed1ec..27d6b1b54 100644 --- a/artiq/test/lit/inferencer/error_with_exn.py +++ b/artiq/test/lit/inferencer/error_with_exn.py @@ -11,6 +11,6 @@ class contextmgr: def foo(): # CHECK-L: ${LINE:+2}: error: cannot unify int(width='a) 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:int(width='a))->NoneType delay('b)' will always be None + # 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:int(width='a))->NoneType delay('b)' will always be None with contextmgr(): pass diff --git a/artiq/test/lit/iodelay/class.py b/artiq/test/lit/iodelay/class.py index 6bcb9339b..63fab1862 100644 --- a/artiq/test/lit/iodelay/class.py +++ b/artiq/test/lit/iodelay/class.py @@ -1,7 +1,7 @@ # RUN: %python -m artiq.compiler.testbench.signature %s >%t # RUN: OutputCheck %s --file-to-check=%t -# CHECK-L: g: (i:)->NoneType delay(1000000 mu) +# CHECK-L: g: (i:)->NoneType delay(1000000 mu) def g(i): i.f(1.0) diff --git a/artiq/test/test_worker.py b/artiq/test/test_worker.py index efc2fa6e2..73c09b352 100644 --- a/artiq/test/test_worker.py +++ b/artiq/test/test_worker.py @@ -87,8 +87,12 @@ class WorkerCase(unittest.TestCase): _run_experiment("SimpleExperiment") def test_exception(self): - with self.assertRaises(WorkerInternalException): - _run_experiment("ExceptionTermination") + with self.assertLogs() as logs: + with self.assertRaises(WorkerInternalException): + _run_experiment("ExceptionTermination") + self.assertEqual(len(logs.records), 1) + self.assertIn("Terminating with exception (TypeError)", + logs.output[0]) def test_watchdog_no_timeout(self): _run_experiment("WatchdogNoTimeout") diff --git a/doc/manual/core_device.rst b/doc/manual/core_device.rst index f22763151..c53ccbe23 100644 --- a/doc/manual/core_device.rst +++ b/doc/manual/core_device.rst @@ -29,6 +29,9 @@ KC705 The main target board for the ARTIQ core device is the KC705 development board from Xilinx. It supports the NIST QC1 hardware via an adapter, and the NIST CLOCK and QC2 hardware (FMC). +NIST QC1 +++++++++ + With the QC1 hardware, the TTL lines are mapped as follows: +--------------+------------+--------------+ @@ -47,6 +50,9 @@ With the QC1 hardware, the TTL lines are mapped as follows: | 19 | TTL15 | Clock | +--------------+------------+--------------+ +NIST CLOCK +++++++++++ + With the CLOCK hardware, the TTL lines are mapped as follows: +--------------------+-----------------------+--------------+ @@ -64,6 +70,24 @@ With the CLOCK hardware, the TTL lines are mapped as follows: +--------------------+-----------------------+--------------+ | 19 | LED | Output | +--------------------+-----------------------+--------------+ +| 20 | AMS101_LDAC_B | Output | ++--------------------+-----------------------+--------------+ +| 21 | LA32_P | Clock | ++--------------------+-----------------------+--------------+ + + +NIST QC2 +++++++++ + +With the QC2 hardware, the TTL lines are mapped as follows: + +TODO + +The QC2 hardware uses TCA6424A I2C I/O expanders to define the directions of its TTL buffers. There is one such expander per FMC card, and they are selected using the PCA9548 on the KC705. + +To avoid I/O contention, the startup kernel should first program the TCA6424A expanders and then call ``output()`` on all ``TTLInOut`` channels that should be configured as outputs. + +See :mod:`artiq.coredevice.i2c` for more details. Pipistrello @@ -99,6 +123,4 @@ When plugged to an adapter, the NIST QC1 hardware can be used. The TTL lines are The input only limitation on channels 0 and 1 comes from the QC-DAQ adapter. When the adapter is not used (and physically unplugged from the Pipistrello board), the corresponding pins on the Pipistrello can be used as outputs. Do not configure these channels as outputs when the adapter is plugged, as this would cause electrical contention. -The board can accept an external RTIO clock connected to PMT2. If the DDS box -does not drive the PMT2 pair, use XTRIG and patch the XTRIG transceiver output -on the adapter board onto C:15 disconnecting PMT2. +The board can accept an external RTIO clock connected to PMT2. If the DDS box does not drive the PMT2 pair, use XTRIG and patch the XTRIG transceiver output on the adapter board onto C:15 disconnecting PMT2. diff --git a/doc/manual/core_drivers_reference.rst b/doc/manual/core_drivers_reference.rst index 604d823b5..e174930e4 100644 --- a/doc/manual/core_drivers_reference.rst +++ b/doc/manual/core_drivers_reference.rst @@ -19,11 +19,29 @@ These drivers are for the core device and the peripherals closely integrated int ---------------------------------- .. autoclass:: artiq.coredevice.dds._DDSGeneric - :members: + :members: .. automodule:: artiq.coredevice.dds :members: +:mod:`artiq.coredevice.spi` module +---------------------------------- + +.. automodule:: artiq.coredevice.spi + :members: + +:mod:`artiq.coredevice.ad5360` module +------------------------------------- + +.. automodule:: artiq.coredevice.ad5360 + :members: + +:mod:`artiq.coredevice.i2c` module +---------------------------------- + +.. automodule:: artiq.coredevice.i2c + :members: + :mod:`artiq.coredevice.exceptions` module ----------------------------------------- diff --git a/doc/manual/getting_started_core.rst b/doc/manual/getting_started_core.rst index 2189e65bd..4dde970cc 100644 --- a/doc/manual/getting_started_core.rst +++ b/doc/manual/getting_started_core.rst @@ -108,6 +108,10 @@ Create a new file ``rtio.py`` containing the following: :: delay(2*us) +.. note:: + If ``ttl0`` is a bidirectional channel (``TTLInOut``), it is in input (non-driving) mode by default. You need to call ``self.ttl0.output()`` as explained above for the LED. + + Connect an oscilloscope or logic analyzer to TTL0 and run ``artiq_run.py led.py``. Notice that the generated signal's period is precisely 4 microseconds, and that it has a duty cycle of precisely 50%. This is not what you would expect if the delay and the pulse were implemented with CPU-controlled GPIO: overhead from the loop management, function calls, etc. would increase the signal's period, and asymmetry in the overhead would cause duty cycle distortion. Instead, inside the core device, output timing is generated by the gateware and the CPU only programs switching commands with certain timestamps that the CPU computes. This guarantees precise timing as long as the CPU can keep generating timestamps that are increasing fast enough. In case it fails to do that (and attempts to program an event with a timestamp in the past), the :class:`artiq.coredevice.exceptions.RTIOUnderflow` exception is raised. The kernel causing it may catch it (using a regular ``try... except...`` construct), or it will be propagated to the host. diff --git a/doc/manual/installing.rst b/doc/manual/installing.rst index 751480058..43f7540dd 100644 --- a/doc/manual/installing.rst +++ b/doc/manual/installing.rst @@ -40,13 +40,16 @@ If your ``$PATH`` misses reference the ``miniconda3/bin`` or ``anaconda3/bin`` y $ export PATH=$HOME/miniconda3/bin:$PATH -Installing the host side software -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Installing the ARTIQ packages +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ For this, you need to add our Anaconda repository to your conda configuration:: $ conda config --add channels http://conda.anaconda.org/m-labs/channel/main - $ conda config --add channels http://conda.anaconda.org/m-labs/channel/dev + +.. note:: + To use the development versions of ARTIQ, also add the ``dev`` channel (http://conda.anaconda.org/m-labs/channel/dev). + Development versions contain more features, but are not as well-tested and are more likely to contain bugs or inconsistencies. Then you can install the ARTIQ package, it will pull all the necessary dependencies. @@ -60,7 +63,12 @@ Then you can install the ARTIQ package, it will pull all the necessary dependenc $ ENV=$(date +artiq-%Y-%m-%d); conda create -n $ENV artiq-kc705-nist_qc1; \ echo "Created environment $ENV for ARTIQ" -* For the KC705 board with the FMC backplane and AD9914 DDS chips:: +* For the KC705 board with the "clock" FMC backplane and AD9914 DDS chips:: + + $ ENV=$(date +artiq-%Y-%m-%d); conda create -n $ENV artiq-kc705-nist_clock; \ + echo "Created environment $ENV for ARTIQ" + +* For the KC705 board with the QC2 FMC backplane and AD9914 DDS chips:: $ ENV=$(date +artiq-%Y-%m-%d); conda create -n $ENV artiq-kc705-nist_qc2; \ echo "Created environment $ENV for ARTIQ" @@ -85,11 +93,13 @@ Preparing the core device FPGA board You now need to flash 3 things on the FPGA board: -1. The FPGA bitstream +1. The FPGA gateware bitstream 2. The BIOS 3. The ARTIQ runtime -First you need to :ref:`install openocd `. Then, you can flash the board: +They are all shipped in our Conda packages, along with the required flash proxy gateware bitstreams. + +First you need to install OpenOCD. Then, you can flash the board: * For the Pipistrello board:: @@ -97,11 +107,9 @@ First you need to :ref:`install openocd `. Then, you can flash * For the KC705 board:: - $ artiq_flash + $ artiq_flash -m [qc1/clock/qc2] -Next step (for KC705) is to flash MAC and IP addresses to the board: - -* See :ref:`those instructions ` to flash MAC and IP addresses. +For the KC705, the next step is to flash the MAC and IP addresses to the board. See :ref:`those instructions `. .. _install-from-sources: @@ -161,11 +169,11 @@ and the ARTIQ kernels. Preparing the core device FPGA board ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -These steps are required to generate bitstream (``.bit``) files, build the MiSoC BIOS and ARTIQ runtime, and flash FPGA boards. If the board is already flashed, you may skip those steps and go directly to `Installing the host-side software`. +These steps are required to generate gateware bitstream (``.bit``) files, build the MiSoC BIOS and ARTIQ runtime, and flash FPGA boards. If the board is already flashed, you may skip those steps and go directly to `Installing the host-side software`. * Install the FPGA vendor tools (e.g. Xilinx ISE and/or Vivado): - * Get Xilinx tools from http://www.xilinx.com/support/download/index.htm. ISE can build bitstreams both for boards using the Spartan-6 (Pipistrello) and 7-series devices (KC705), while Vivado supports only boards using 7-series devices. + * Get Xilinx tools from http://www.xilinx.com/support/download/index.htm. ISE can build gateware bitstreams both for boards using the Spartan-6 (Pipistrello) and 7-series devices (KC705), while Vivado supports only boards using 7-series devices. * The Pipistrello is supported by Webpack, the KC705 is not. @@ -200,9 +208,9 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC .. _install-flash-proxy: -* Install the required flash proxy bitstreams: +* Install the required flash proxy gateware bitstreams: - The purpose of the flash proxy bitstream is to give programming software fast JTAG access to the flash connected to the FPGA. + The purpose of the flash proxy gateware bitstream is to give programming software fast JTAG access to the flash connected to the FPGA. * Pipistrello and KC705: @@ -235,7 +243,7 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC :ref:`installing the host-side software `. -* Build the bitstream, BIOS and runtime by running: +* Build the gateware bitstream, BIOS and runtime by running: :: $ cd ~/artiq-dev @@ -262,7 +270,7 @@ These steps are required to generate bitstream (``.bit``) files, build the MiSoC .. note:: The `-t` option specifies the board your are targeting. Available options are ``kc705`` and ``pipistrello``. -* Check that the board boots by running a serial terminal program (you may need to press its FPGA reconfiguration button or power-cycle it to load the bitstream that was newly written into the flash): :: +* Check that the board boots by running a serial terminal program (you may need to press its FPGA reconfiguration button or power-cycle it to load the gateware bitstream that was newly written into the flash): :: $ make -C ~/artiq-dev/misoc/tools # do only once $ ~/artiq-dev/misoc/tools/flterm --port /dev/ttyUSB1 diff --git a/examples/artiq_ipython_notebook.ipynb b/examples/artiq_ipython_notebook.ipynb index e4a58d237..1d6026090 100644 --- a/examples/artiq_ipython_notebook.ipynb +++ b/examples/artiq_ipython_notebook.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 2, "metadata": { "collapsed": false }, @@ -24,6 +24,7 @@ "import asyncio\n", "import datetime\n", "import glob\n", + "from pprint import pprint\n", "\n", "import numpy as np\n", "np.set_printoptions(precision=3)\n", @@ -39,19 +40,6 @@ "from artiq.master.worker_db import DeviceManager" ] }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "# let's assume artiq_master and artiq_ctlmgr are already running\n", - "# move to a location where we have our artiq setup\n", - "os.chdir(os.path.expanduser(\"~/work/nist/artiq/run\"))" - ] - }, { "cell_type": "code", "execution_count": 3, @@ -60,21 +48,35 @@ }, "outputs": [], "source": [ - "# you can directly use the artiq controller infrastructure\n", + "# let's assume artiq_master and artiq_ctlmgr are already running\n", + "# then move to a location where we have our artiq setup\n", + "os.chdir(os.path.expanduser(\"~/work/nist/artiq/run\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# we can directly use the artiq controller infrastructure\n", "# and access any artiq device\n", "\n", - "# you can have artiq prepare that for you:\n", - "\n", + "# we can have artiq prepare that connection for us:\n", "ddb = DeviceDB(\"device_db.pyon\")\n", "devmgr = DeviceManager(ddb)\n", "lda = devmgr.get(\"lda\")\n", "lda.set_attenuation(42)\n", "assert lda.get_attenuation() == 42\n", "\n", - "# ... or you can wire it up yourself if you know where it is\n", + "# ... or we can wire it up ourselves if you know where it is\n", "assert ddb.get(\"lda\")[\"host\"] == \"::1\"\n", "assert ddb.get(\"lda\")[\"port\"] == 3253\n", "\n", + "# there are different Client types tailored to different use cases:\n", + "\n", "# synchronous\n", "lda = Client(\"::1\", 3253)\n", "assert lda.get_attenuation() == 42\n", @@ -93,7 +95,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "metadata": { "collapsed": false }, @@ -102,8 +104,22 @@ "name": "stdout", "output_type": "stream", "text": [ - "current schedule {}\n", - "experiments ['__pycache__/', 'flopping_f_simulation.py', 'notebook_test.py', '.git/', 'idle.elf', 'transport.py', 'idle.py', 'speed_benchmark.py', 'test_raise.py']\n" + "current schedule\n", + "{}\n", + "experiments:\n", + "['ex/',\n", + " 'test_analyzer.py',\n", + " 'notebook_test.py',\n", + " 'speed_benchmark.py',\n", + " 'histograms.py',\n", + " 'arguments_demo.py',\n", + " '.git/',\n", + " '__pycache__/',\n", + " 'flopping_f_simulation.py',\n", + " 'test_crash.py',\n", + " 'run_forever.py',\n", + " 'transport.py',\n", + " 'pdq2_simple.py']\n" ] } ], @@ -114,13 +130,15 @@ " Client(\"::1\", 3251, \"master_\" + i) for i in\n", " \"schedule experiment_db dataset_db\".split()]\n", "\n", - "print(\"current schedule\", schedule.get_status())\n", - "print(\"experiments\", exps.list_directory(\"repository\"))" + "print(\"current schedule\")\n", + "pprint(schedule.get_status())\n", + "print(\"experiments:\")\n", + "pprint(exps.list_directory(\"repository\"))" ] }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "metadata": { "collapsed": false }, @@ -129,12 +147,22 @@ "name": "stdout", "output_type": "stream", "text": [ - "current schedule {131: {'priority': 0, 'status': 'preparing', 'repo_msg': None, 'pipeline': 'main', 'due_date': None, 'flush': False, 'expid': {'file': 'repository/flopping_f_simulation.py', 'arguments': {'noise_amplitude': 0.1, 'F0': 1500}, 'log_level': 30, 'class_name': 'FloppingF'}}}\n" + "current schedule\n", + "{4722: {'due_date': None,\n", + " 'expid': {'arguments': {'F0': 1500, 'noise_amplitude': 0.3},\n", + " 'class_name': 'FloppingF',\n", + " 'file': 'repository/flopping_f_simulation.py',\n", + " 'log_level': 30},\n", + " 'flush': False,\n", + " 'pipeline': 'main',\n", + " 'priority': 0,\n", + " 'repo_msg': None,\n", + " 'status': 'preparing'}}\n" ] } ], "source": [ - "# we can submit experiments to be run:\n", + "# we can submit experiments to be run\n", "\n", "expid = dict(\n", " file=\"repository/flopping_f_simulation.py\",\n", @@ -142,18 +170,19 @@ " log_level=logging.WARNING,\n", " arguments=dict(\n", " F0=1500,\n", - " noise_amplitude=.1,\n", + " noise_amplitude=.3,\n", " ),\n", ")\n", "if not schedule.get_status():\n", " rid = schedule.submit(pipeline_name=\"main\", expid=expid,\n", " priority=0, due_date=None, flush=False)\n", - "print(\"current schedule\", schedule.get_status())" + "print(\"current schedule\")\n", + "pprint(schedule.get_status())" ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 7, "metadata": { "collapsed": false }, @@ -167,7 +196,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 8, "metadata": { "collapsed": false }, @@ -176,14 +205,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "flopping_f 1499.996784076909\n" + "flopping_f: 1499.944285221012\n" ] }, { "data": { - "image/png": "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\n", + "image/png": "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\nJKD/D7Ip5VrpmcohAAAAAElFTkSuQmCC\n", "text/plain": [ - "" + "" ] }, "metadata": {}, @@ -197,12 +226,12 @@ "fig, ax = plt.subplots()\n", "d = datasets.get(\"flopping_f_brightness\")\n", "ax.plot(d)\n", - "print(\"flopping_f\", datasets.get(\"flopping_freq\"))" + "print(\"flopping_f:\", datasets.get(\"flopping_freq\"))" ] }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 9, "metadata": { "collapsed": false }, @@ -215,7 +244,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 10, "metadata": { "collapsed": false }, @@ -237,8 +266,8 @@ " \"results\", t.strftime(\"%Y-%m-%d\"), #t.strftime(\"%H-%M\"),\n", " \"*\", \"{:09d}-FloppingF.h5\".format(rid))\n", "\n", - "# we would usually like to use pandas but our data doe not comply\n", - "# with the pandas metadata\n", + "# we would usually like to use pandas but our data does not have\n", + "# the metadata pandas want\n", "#d = pd.HDFStore(glob.glob(f)[0])\n", "\n", "with h5py.File(glob.glob(f)[0]) as f:\n", @@ -248,7 +277,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 11, "metadata": { "collapsed": false }, @@ -264,7 +293,7 @@ "source": [ "%%writefile repository/notebook_test.py\n", "\n", - "# we can also write experiments in the notebook ans submit them\n", + "# we can also write experiments in the notebook and submit them\n", "# we don't have submit-by-content yet (and there would be questions\n", "# about other modules that would need to be imported) so we just export\n", "# this cell and submit it by filename\n", @@ -281,7 +310,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 12, "metadata": { "collapsed": false }, @@ -290,7 +319,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "133\n" + "4724\n" ] } ], diff --git a/examples/master/device_db.pyon b/examples/master/device_db.pyon index 59b32de2f..ee475911a 100644 --- a/examples/master/device_db.pyon +++ b/examples/master/device_db.pyon @@ -1,12 +1,12 @@ # This is an example device database that needs to be adapted to your setup. -# The RTIO channel numbers here are for NIST QC1 on KC705. +# The RTIO channel numbers here are for NIST CLOCK on KC705. { "comm": { "type": "local", "module": "artiq.coredevice.comm_tcp", "class": "Comm", - "arguments": {"host": "192.168.0.42"} + "arguments": {"host": "kc705.lab.m-labs.hk"} }, "core": { "type": "local", @@ -15,74 +15,110 @@ "arguments": {"ref_period": 1e-9} }, - "pmt0": { + "i2c_switch": { "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLInOut", - "arguments": {"channel": 0} - }, - "pmt1": { - "type": "local", - "module": "artiq.coredevice.ttl", - "class": "TTLInOut", - "arguments": {"channel": 1} + "module": "artiq.coredevice.i2c", + "class": "PCA9548" }, "ttl0": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 2}, + "arguments": {"channel": 0}, "comment": "This is a fairly long comment to test word wrapping in GUI." }, "ttl1": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 3}, + "arguments": {"channel": 1}, "comment": "Hello World" }, "ttl2": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 4} + "arguments": {"channel": 2} }, "ttl3": { "type": "local", "module": "artiq.coredevice.ttl", - "class": "TTLOut", - "arguments": {"channel": 5} + "class": "TTLInOut", + "arguments": {"channel": 3} }, + "ttl4": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 6} + "arguments": {"channel": 4} }, "ttl5": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", + "arguments": {"channel": 5} + }, + "ttl6": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 6} + }, + "ttl7": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLInOut", "arguments": {"channel": 7} }, + + "ttl_sma": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLInOut", - "arguments": {"channel": 17} + "arguments": {"channel": 18} }, "led": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLOut", - "arguments": {"channel": 18} + "arguments": {"channel": 19} }, - "ttl15": { + + "ttl_ams101_ldac": { + "type": "local", + "module": "artiq.coredevice.ttl", + "class": "TTLOut", + "arguments": {"channel": 20} + }, + "ttl_clock_la32_p": { "type": "local", "module": "artiq.coredevice.ttl", "class": "TTLClockGen", - "arguments": {"channel": 19} + "arguments": {"channel": 21} + }, + + "spi_ams101": { + "type": "local", + "module": "artiq.coredevice.spi", + "class": "SPIMaster", + "arguments": {"channel": 22} + }, + + "spi0": { + "type": "local", + "module": "artiq.coredevice.spi", + "class": "SPIMaster", + "arguments": {"channel": 23} + }, + + "dac0": { + "type": "local", + "module": "artiq.coredevice.ad5360", + "class": "AD5360", + "arguments": {"spi_device": "spi0", "ldac_device": "ttl0"} }, "dds_bus": { @@ -94,21 +130,21 @@ "dds0": { "type": "local", "module": "artiq.coredevice.dds", - "class": "AD9858", - "arguments": {"sysclk": 1e9, "channel": 0}, + "class": "AD9914", + "arguments": {"sysclk": 3e9, "channel": 0}, "comment": "Comments work in DDS panel as well" }, "dds1": { "type": "local", "module": "artiq.coredevice.dds", - "class": "AD9858", - "arguments": {"sysclk": 1e9, "channel": 1} + "class": "AD9914", + "arguments": {"sysclk": 3e9, "channel": 1} }, "dds2": { "type": "local", "module": "artiq.coredevice.dds", - "class": "AD9858", - "arguments": {"sysclk": 1e9, "channel": 2} + "class": "AD9914", + "arguments": {"sysclk": 3e9, "channel": 2} }, "qc_q1_0": { @@ -160,7 +196,12 @@ "ttl_out": "ttl0", "ttl_out_serdes": "ttl0", - "pmt": "pmt0", + "loop_out": "ttl0", + "loop_in": "ttl3", + "loop_clock_out": "ttl_clock_la32_p", + "loop_clock_in": "ttl7", + + "pmt": "ttl3", "bd_dds": "dds0", "bd_sw": "ttl0", "bdd_dds": "dds1", diff --git a/examples/master/repository/coredevice_examples/simple/ad5360.py b/examples/master/repository/coredevice_examples/simple/ad5360.py new file mode 100644 index 000000000..5dd731c04 --- /dev/null +++ b/examples/master/repository/coredevice_examples/simple/ad5360.py @@ -0,0 +1,17 @@ +from artiq.experiment import * + + +class AD5360Test(EnvExperiment): + def build(self): + self.setattr_device("core") + self.dac = self.get_device("dac0") + self.setattr_device("led") + + @kernel + def run(self): + self.dac.setup_bus(write_div=30, read_div=40) + self.dac.write_offsets() + self.led.on() + delay(400*us) + self.led.off() + self.dac.set([i << 10 for i in range(40)]) diff --git a/examples/master/repository/coredevice_examples/tdr.py b/examples/master/repository/coredevice_examples/tdr.py index a71628645..d0c435753 100644 --- a/examples/master/repository/coredevice_examples/tdr.py +++ b/examples/master/repository/coredevice_examples/tdr.py @@ -39,6 +39,7 @@ class TDR(EnvExperiment): n = 1000 # repetitions latency = 50e-9 # calibrated latency without a transmission line pulse = 1e-6 # pulse length, larger than rtt + self.t = [0 for i in range(2)] try: self.many(n, seconds_to_mu(pulse, self.core)) except PulseNotReceivedError: @@ -53,21 +54,19 @@ class TDR(EnvExperiment): @kernel def many(self, n, p): - t = [0 for i in range(2)] self.core.break_realtime() for i in range(n): - self.one(t, p) - self.t = t + self.one(p) @kernel - def one(self, t, p): + def one(self, p): t0 = now_mu() with parallel: self.pmt0.gate_both_mu(2*p) self.ttl2.pulse_mu(p) - for i in range(len(t)): + for i in range(len(self.t)): ti = self.pmt0.timestamp_mu() if ti <= 0: - raise PulseNotReceivedError - t[i] += ti - t0 + raise PulseNotReceivedError() + self.t[i] = int(self.t[i] + ti - t0) self.pmt0.count() # flush