Merge branch 'master' into scanwidget

* master: (125 commits)
  rtio: when rtlink addresses are different, issue collision not replace (fixes #320)
  rtio: collision_error -> collision
  test/coredevice/time_keeps_running: start new session to prevent now_mu save/restore
  runtime: fix sloppy memory management in cache_put.
  test_loopback: bump RTT limit to 60ns.
  soc: use add_extra_software_packages, factor builder code
  doc/PCA9548: clarify channel selection
  doc: I2C/QC2
  test: I2C/PCA9548 unittest
  examples/device_db: add PCA9548
  runtime/i2c_read: fix MSB
  ad5360: add documentation and an example
  ad5360: t16 is a max
  ad5360: un-factor write_channels
  ad5360: add busy and update timings
  ad5360: style
  ad5360: add batched zero-length multi-channel set()
  spi/ad5360: refactor, small fixes
  coredevice/i2c: fix exception message
  coredevice/PCA9548: fix I2C address
  ...
This commit is contained in:
Robert Jördens 2016-03-08 11:14:57 +01:00
commit ef217f7fe2
58 changed files with 2146 additions and 505 deletions

View File

@ -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

View File

@ -295,26 +295,9 @@ class StitchingInferencer(Inferencer):
super().__init__(engine)
self.value_map = value_map
self.quote = quote
self.attr_type_cache = {}
def _unify_attribute(self, result_type, value_node, attr_name, attr_loc, loc):
# The inferencer can only observe types, not values; however,
# when we work with host objects, we have to get the values
# somewhere, since host interpreter does not have types.
# Since we have categorized every host object we quoted according to
# 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
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('_'),
@ -354,10 +337,10 @@ class StitchingInferencer(Inferencer):
# 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
attr_value_type = None
if isinstance(attr_value, list):
# Fast path for lists of scalars.
@ -407,6 +390,26 @@ class StitchingInferencer(Inferencer):
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,
# when we work with host objects, we have to get the values
# somewhere, since host interpreter does not have types.
# Since we have categorized every host object we quoted according to
# 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.
object_type = value_node.type.find()
for object_value, object_loc in self.value_map[object_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.
attributes[attr_name] = attr_value_type
@ -739,6 +742,16 @@ class Stitcher:
else:
if hasattr(function, "artiq_embedded"):
if function.artiq_embedded.function is not None:
if function.__name__ == "<lambda>":
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.

View File

@ -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):

View File

@ -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:

View File

@ -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 "<instance {}>".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 "<instance {} {{\n\t\t{}\n\t}}>".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 "<instance {} {{{}}}>".format(typ.name, attrs)
return "<instance {} {{}}>".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 "<function {}>".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 "<constructor {}>".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 "<constructor {} {{{}}}>".format(typ.name, attrs)
else:
self.recurse_guard.add(typ)
return "<constructor {} {{}}>".format(typ.name)
elif isinstance(typ, TBuiltin):
return "<builtin {}>".format(typ.name)
elif isinstance(typ, TValue):

View File

@ -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"]

175
artiq/coredevice/ad5360.py Normal file
View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

112
artiq/coredevice/i2c.py Normal file
View File

@ -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

18
artiq/coredevice/rtio.py Normal file
View File

@ -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")

248
artiq/coredevice/spi.py Normal file
View File

@ -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)

View File

@ -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

View File

@ -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,
@ -90,16 +89,16 @@ def main():
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"]))

View File

@ -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))

View File

@ -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")),
]

View File

@ -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")),

View File

@ -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),

View File

@ -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,

View File

@ -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 = []

View File

@ -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()

487
artiq/gateware/spi.py Normal file
View File

@ -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")

View File

@ -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,7 +104,9 @@ class _NIST_Ions(MiniSoC, AMPSoC):
}
csr_map.update(MiniSoC.csr_map)
mem_map = {
"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
@ -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__":

View File

@ -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,6 +112,7 @@ class NIST_QC1(BaseSoC, AMPSoC):
}
csr_map.update(BaseSoC.csr_map)
mem_map = {
"timer_kernel": 0x10000000, # (shadow @0x90000000)
"rtio": 0x20000000, # (shadow @0xa0000000)
"mailbox": 0x70000000 # (shadow @0xf0000000)
}
@ -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__":

View File

@ -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()

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
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:

View File

@ -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 \

View File

@ -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;
}

View File

@ -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
@ -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<batch_count;i++) {
@ -216,7 +210,6 @@ void dds_set(long long int timestamp, int channel,
batch[batch_count].amplitude = amplitude;
batch_count++;
} else {
rtio_chan_sel_write(CONFIG_RTIO_DDS_CHANNEL);
dds_set_one(timestamp - DURATION_PROGRAM, timestamp, channel, ftw, pow, phase_mode,
amplitude);
}

193
artiq/runtime/i2c.c Normal file
View File

@ -0,0 +1,193 @@
#include <generated/csr.h>
#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;
}

10
artiq/runtime/i2c.h Normal file
View File

@ -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

View File

@ -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},

View File

@ -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();

View File

@ -1,5 +1,6 @@
#include <generated/csr.h>
#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

View File

@ -2,30 +2,31 @@
#define __RTIO_H
#include <stdarg.h>
#include <generated/csr.h>
#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 */

View File

@ -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;
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;

View File

@ -1,67 +0,0 @@
#include <generated/csr.h>
#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);
}

View File

@ -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 */

View File

@ -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"))

View File

@ -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)

View File

@ -0,0 +1,6 @@
# RUN: %python -m artiq.compiler.testbench.llvmgen %s
def f():
pass
def g():
a = f()

View File

@ -9,7 +9,7 @@ class contextmgr:
pass
def foo():
# CHECK-L: ${LINE:+2}: error: function '__enter__(self:<instance contextmgr {}>, n1:'a)->NoneType delay('b)' must accept 1 positional argument and no optional arguments
# CHECK-L: ${LINE:+2}: error: function '__enter__(self:<instance contextmgr>, n1:'a)->NoneType delay('b)' must accept 1 positional argument and no optional arguments
# CHECK-L: ${LINE:+1}: error: function '__exit__(self:<instance contextmgr>, n1:'c, n2:'d)->NoneType delay('e)' must accept 4 positional arguments and no optional arguments
with contextmgr():
pass

View File

@ -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:<instance contextmgr {}>, 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:<instance contextmgr>, n1:NoneType, n2:NoneType, n3:int(width='a))->NoneType delay('b)' will always be None
with contextmgr():
pass

View File

@ -1,7 +1,7 @@
# RUN: %python -m artiq.compiler.testbench.signature %s >%t
# RUN: OutputCheck %s --file-to-check=%t
# CHECK-L: g: (i:<instance c {}>)->NoneType delay(1000000 mu)
# CHECK-L: g: (i:<instance c>)->NoneType delay(1000000 mu)
def g(i):
i.f(1.0)

View File

@ -87,8 +87,12 @@ class WorkerCase(unittest.TestCase):
_run_experiment("SimpleExperiment")
def test_exception(self):
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")

View File

@ -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.

View File

@ -24,6 +24,24 @@ These drivers are for the core device and the peripherals closely integrated int
.. 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
-----------------------------------------

View File

@ -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.

View File

@ -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 <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 <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 <flash-mac-ip-addr>` 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 <flash-mac-ip-addr>`.
.. _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 <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

File diff suppressed because one or more lines are too long

View File

@ -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",

View File

@ -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)])

View File

@ -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