Merge branch 'master' into phaser

* master: (26 commits)
  fastino: documentation and eem pass-through
  kasli2: forward sma_clkin to si5324
  test: relax test_dma_playback_time on Zynq
  rpc: fixed _write_bool
  fastino: document/cleanup
  build_soc: remove assertion that was used for test runs
  metlino_sayma_ttl: Fix RTIO frequency & demo code (#1516)
  Revert "test: temporarily disable test_async_throughput"
  build_soc: rename identifier_str to gateware_identifier_str
  test: relax loopback gate timing
  test: temporarily disable test_async_throughput
  test: relax test_pulse_rate on Zynq
  test: skip NonexistentI2CBus if I2C is not supported
  build_soc: override identifier_str only for gateware
  examples: add Metlino master, Sayma satellite with TTLOuts via FMC
  sayma_amc: add support for 4x DIO output channels via FMC
  fmcdio_vhdci_eem: fix pin naming
  build_soc: add identifier_str override option
  RPC: optimization by caching
  test: improved test_performance
  ...
This commit is contained in:
Robert Jördens 2020-09-22 16:02:25 +00:00
commit 50b4eb4840
21 changed files with 925 additions and 189 deletions

View File

@ -11,6 +11,7 @@ Highlights:
* Performance improvements: * Performance improvements:
- #1432: SERDES TTL inputs can now detect edges on pulses that are shorter - #1432: SERDES TTL inputs can now detect edges on pulses that are shorter
than the RTIO period than the RTIO period
- Improved performance for kernel RPC involving list and array.
* Coredevice SI to mu conversions now always return valid codes, or raise a `ValueError`. * Coredevice SI to mu conversions now always return valid codes, or raise a `ValueError`.
* Zotino now exposes `voltage_to_mu()` * Zotino now exposes `voltage_to_mu()`
* `ad9910`: The maximum amplitude scale factor is now `0x3fff` (was `0x3ffe` * `ad9910`: The maximum amplitude scale factor is now `0x3fff` (was `0x3ffe`
@ -24,6 +25,8 @@ Highlights:
* Core device: ``panic_reset 1`` now correctly resets the kernel CPU as well if * Core device: ``panic_reset 1`` now correctly resets the kernel CPU as well if
communication CPU panic occurs. communication CPU panic occurs.
* NumberValue accepts a ``type`` parameter specifying the output as ``int`` or ``float`` * NumberValue accepts a ``type`` parameter specifying the output as ``int`` or ``float``
* A parameter `--identifier-str` has been added to many targets to aid
with reproducible builds.
Breaking changes: Breaking changes:

View File

@ -44,15 +44,14 @@ class ReprogrammableIdentifier(Module, AutoCSR):
p_INIT=sum(1 << j if c & (1 << i) else 0 for j, c in enumerate(contents))) p_INIT=sum(1 << j if c & (1 << i) else 0 for j, c in enumerate(contents)))
def add_identifier(soc, *args, **kwargs): def add_identifier(soc, *args, gateware_identifier_str=None, **kwargs):
if hasattr(soc, "identifier"): if hasattr(soc, "identifier"):
raise ValueError raise ValueError
identifier_str = get_identifier_string(soc, *args, **kwargs) identifier_str = get_identifier_string(soc, *args, **kwargs)
soc.submodules.identifier = ReprogrammableIdentifier(identifier_str) soc.submodules.identifier = ReprogrammableIdentifier(gateware_identifier_str or identifier_str)
soc.config["IDENTIFIER_STR"] = identifier_str soc.config["IDENTIFIER_STR"] = identifier_str
def build_artiq_soc(soc, argdict): def build_artiq_soc(soc, argdict):
firmware_dir = os.path.join(artiq_dir, "firmware") firmware_dir = os.path.join(artiq_dir, "firmware")
builder = Builder(soc, **argdict) builder = Builder(soc, **argdict)

View File

@ -43,9 +43,11 @@ class Reply(Enum):
class UnsupportedDevice(Exception): class UnsupportedDevice(Exception):
pass pass
class LoadError(Exception): class LoadError(Exception):
pass pass
class RPCReturnValueError(ValueError): class RPCReturnValueError(ValueError):
pass pass
@ -53,6 +55,105 @@ class RPCReturnValueError(ValueError):
RPCKeyword = namedtuple('RPCKeyword', ['name', 'value']) RPCKeyword = namedtuple('RPCKeyword', ['name', 'value'])
def _receive_fraction(kernel, embedding_map):
numerator = kernel._read_int64()
denominator = kernel._read_int64()
return Fraction(numerator, denominator)
def _receive_list(kernel, embedding_map):
length = kernel._read_int32()
tag = chr(kernel._read_int8())
if tag == "b":
buffer = kernel._read(length)
return list(buffer)
elif tag == "i":
buffer = kernel._read(4 * length)
return list(struct.unpack(">%sl" % length, buffer))
elif tag == "I":
buffer = kernel._read(8 * length)
return list(struct.unpack(">%sq" % length, buffer))
elif tag == "f":
buffer = kernel._read(8 * length)
return list(struct.unpack(">%sd" % length, buffer))
else:
fn = receivers[tag]
elems = []
for _ in range(length):
# discard tag, as our device would still send the tag for each
# non-primitive elements.
kernel._read_int8()
item = fn(kernel, embedding_map)
elems.append(item)
return elems
def _receive_array(kernel, embedding_map):
num_dims = kernel._read_int8()
shape = tuple(kernel._read_int32() for _ in range(num_dims))
tag = chr(kernel._read_int8())
fn = receivers[tag]
length = numpy.prod(shape)
if tag == "b":
buffer = kernel._read(length)
elems = numpy.ndarray((length, ), 'B', buffer)
elif tag == "i":
buffer = kernel._read(4 * length)
elems = numpy.ndarray((length, ), '>i4', buffer)
elif tag == "I":
buffer = kernel._read(8 * length)
elems = numpy.ndarray((length, ), '>i8', buffer)
elif tag == "f":
buffer = kernel._read(8 * length)
elems = numpy.ndarray((length, ), '>d', buffer)
else:
fn = receivers[tag]
elems = []
for _ in range(numpy.prod(shape)):
# discard the tag
kernel._read_int8()
item = fn(kernel, embedding_map)
elems.append(item)
elems = numpy.array(elems)
return elems.reshape(shape)
def _receive_range(kernel, embedding_map):
start = kernel._receive_rpc_value(embedding_map)
stop = kernel._receive_rpc_value(embedding_map)
step = kernel._receive_rpc_value(embedding_map)
return range(start, stop, step)
def _receive_keyword(kernel, embedding_map):
name = kernel._read_string()
value = kernel._receive_rpc_value(embedding_map)
return RPCKeyword(name, value)
receivers = {
"\x00": lambda kernel, embedding_map: kernel._rpc_sentinel,
"t": lambda kernel, embedding_map:
tuple(kernel._receive_rpc_value(embedding_map)
for _ in range(kernel._read_int8())),
"n": lambda kernel, embedding_map: None,
"b": lambda kernel, embedding_map: bool(kernel._read_int8()),
"i": lambda kernel, embedding_map: numpy.int32(kernel._read_int32()),
"I": lambda kernel, embedding_map: numpy.int64(kernel._read_int64()),
"f": lambda kernel, embedding_map: kernel._read_float64(),
"s": lambda kernel, embedding_map: kernel._read_string(),
"B": lambda kernel, embedding_map: kernel._read_bytes(),
"A": lambda kernel, embedding_map: kernel._read_bytes(),
"O": lambda kernel, embedding_map:
embedding_map.retrieve_object(kernel._read_int32()),
"F": _receive_fraction,
"l": _receive_list,
"a": _receive_array,
"r": _receive_range,
"k": _receive_keyword
}
class CommKernelDummy: class CommKernelDummy:
def __init__(self): def __init__(self):
pass pass
@ -77,6 +178,17 @@ class CommKernel:
self._read_type = None self._read_type = None
self.host = host self.host = host
self.port = port self.port = port
self.read_buffer = bytearray()
self.write_buffer = bytearray()
self.unpack_int32 = struct.Struct(">l").unpack
self.unpack_int64 = struct.Struct(">q").unpack
self.unpack_float64 = struct.Struct(">d").unpack
self.pack_header = struct.Struct(">lB").pack
self.pack_int32 = struct.Struct(">l").pack
self.pack_int64 = struct.Struct(">q").pack
self.pack_float64 = struct.Struct(">d").pack
def open(self): def open(self):
if hasattr(self, "socket"): if hasattr(self, "socket"):
@ -97,13 +209,18 @@ class CommKernel:
# #
def _read(self, length): def _read(self, length):
r = bytes() # cache the reads to avoid frequent call to recv
while len(r) < length: while len(self.read_buffer) < length:
rn = self.socket.recv(min(8192, length - len(r))) # the number is just the maximum amount
if not rn: # when there is not much data, it would return earlier
raise ConnectionResetError("Connection closed") diff = length - len(self.read_buffer)
r += rn flag = 0
return r if diff > 8192:
flag |= socket.MSG_WAITALL
self.read_buffer += self.socket.recv(8192, flag)
result = self.read_buffer[:length]
self.read_buffer = self.read_buffer[length:]
return result
def _read_header(self): def _read_header(self):
self.open() self.open()
@ -111,14 +228,14 @@ class CommKernel:
# Wait for a synchronization sequence, 5a 5a 5a 5a. # Wait for a synchronization sequence, 5a 5a 5a 5a.
sync_count = 0 sync_count = 0
while sync_count < 4: while sync_count < 4:
(sync_byte, ) = struct.unpack("B", self._read(1)) sync_byte = self._read(1)[0]
if sync_byte == 0x5a: if sync_byte == 0x5a:
sync_count += 1 sync_count += 1
else: else:
sync_count = 0 sync_count = 0
# Read message header. # Read message header.
(raw_type, ) = struct.unpack("B", self._read(1)) raw_type = self._read(1)[0]
self._read_type = Reply(raw_type) self._read_type = Reply(raw_type)
logger.debug("receiving message: type=%r", logger.debug("receiving message: type=%r",
@ -134,19 +251,18 @@ class CommKernel:
self._read_expect(ty) self._read_expect(ty)
def _read_int8(self): def _read_int8(self):
(value, ) = struct.unpack("B", self._read(1)) return self._read(1)[0]
return value
def _read_int32(self): def _read_int32(self):
(value, ) = struct.unpack(">l", self._read(4)) (value, ) = self.unpack_int32(self._read(4))
return value return value
def _read_int64(self): def _read_int64(self):
(value, ) = struct.unpack(">q", self._read(8)) (value, ) = self.unpack_int64(self._read(8))
return value return value
def _read_float64(self): def _read_float64(self):
(value, ) = struct.unpack(">d", self._read(8)) (value, ) = self.unpack_float64(self._read(8))
return value return value
def _read_bool(self): def _read_bool(self):
@ -163,7 +279,15 @@ class CommKernel:
# #
def _write(self, data): def _write(self, data):
self.socket.sendall(data) self.write_buffer += data
# if the buffer is already pretty large, send it
# the block size is arbitrary, tuning it may improve performance
if len(self.write_buffer) > 4096:
self._flush()
def _flush(self):
self.socket.sendall(self.write_buffer)
self.write_buffer.clear()
def _write_header(self, ty): def _write_header(self, ty):
self.open() self.open()
@ -171,7 +295,7 @@ class CommKernel:
logger.debug("sending message: type=%r", ty) logger.debug("sending message: type=%r", ty)
# Write synchronization sequence and header. # Write synchronization sequence and header.
self._write(struct.pack(">lB", 0x5a5a5a5a, ty.value)) self._write(self.pack_header(0x5a5a5a5a, ty.value))
def _write_empty(self, ty): def _write_empty(self, ty):
self._write_header(ty) self._write_header(ty)
@ -180,19 +304,19 @@ class CommKernel:
self._write(chunk) self._write(chunk)
def _write_int8(self, value): def _write_int8(self, value):
self._write(struct.pack("B", value)) self._write(value)
def _write_int32(self, value): def _write_int32(self, value):
self._write(struct.pack(">l", value)) self._write(self.pack_int32(value))
def _write_int64(self, value): def _write_int64(self, value):
self._write(struct.pack(">q", value)) self._write(self.pack_int64(value))
def _write_float64(self, value): def _write_float64(self, value):
self._write(struct.pack(">d", value)) self._write(self.pack_float64(value))
def _write_bool(self, value): def _write_bool(self, value):
self._write(struct.pack("B", value)) self._write(b'\x01' if value else b'\x00')
def _write_bytes(self, value): def _write_bytes(self, value):
self._write_int32(len(value)) self._write_int32(len(value))
@ -207,6 +331,7 @@ class CommKernel:
def check_system_info(self): def check_system_info(self):
self._write_empty(Request.SystemInfo) self._write_empty(Request.SystemInfo)
self._flush()
self._read_header() self._read_header()
self._read_expect(Reply.SystemInfo) self._read_expect(Reply.SystemInfo)
@ -231,6 +356,7 @@ class CommKernel:
def load(self, kernel_library): def load(self, kernel_library):
self._write_header(Request.LoadKernel) self._write_header(Request.LoadKernel)
self._write_bytes(kernel_library) self._write_bytes(kernel_library)
self._flush()
self._read_header() self._read_header()
if self._read_type == Reply.LoadFailed: if self._read_type == Reply.LoadFailed:
@ -240,6 +366,7 @@ class CommKernel:
def run(self): def run(self):
self._write_empty(Request.RunKernel) self._write_empty(Request.RunKernel)
self._flush()
logger.debug("running kernel") logger.debug("running kernel")
_rpc_sentinel = object() _rpc_sentinel = object()
@ -247,50 +374,8 @@ class CommKernel:
# See rpc_proto.rs and compiler/ir.py:rpc_tag. # See rpc_proto.rs and compiler/ir.py:rpc_tag.
def _receive_rpc_value(self, embedding_map): def _receive_rpc_value(self, embedding_map):
tag = chr(self._read_int8()) tag = chr(self._read_int8())
if tag == "\x00": if tag in receivers:
return self._rpc_sentinel return receivers.get(tag)(self, embedding_map)
elif tag == "t":
length = self._read_int8()
return tuple(self._receive_rpc_value(embedding_map) for _ in range(length))
elif tag == "n":
return None
elif tag == "b":
return bool(self._read_int8())
elif tag == "i":
return numpy.int32(self._read_int32())
elif tag == "I":
return numpy.int64(self._read_int64())
elif tag == "f":
return self._read_float64()
elif tag == "F":
numerator = self._read_int64()
denominator = self._read_int64()
return Fraction(numerator, denominator)
elif tag == "s":
return self._read_string()
elif tag == "B":
return self._read_bytes()
elif tag == "A":
return self._read_bytes()
elif tag == "l":
length = self._read_int32()
return [self._receive_rpc_value(embedding_map) for _ in range(length)]
elif tag == "a":
num_dims = self._read_int8()
shape = tuple(self._read_int32() for _ in range(num_dims))
elems = [self._receive_rpc_value(embedding_map) for _ in range(numpy.prod(shape))]
return numpy.array(elems).reshape(shape)
elif tag == "r":
start = self._receive_rpc_value(embedding_map)
stop = self._receive_rpc_value(embedding_map)
step = self._receive_rpc_value(embedding_map)
return range(start, stop, step)
elif tag == "k":
name = self._read_string()
value = self._receive_rpc_value(embedding_map)
return RPCKeyword(name, value)
elif tag == "O":
return embedding_map.retrieve_object(self._read_int32())
else: else:
raise IOError("Unknown RPC value tag: {}".format(repr(tag))) raise IOError("Unknown RPC value tag: {}".format(repr(tag)))
@ -340,7 +425,7 @@ class CommKernel:
elif tag == "b": elif tag == "b":
check(isinstance(value, bool), check(isinstance(value, bool),
lambda: "bool") lambda: "bool")
self._write_int8(value) self._write_bool(value)
elif tag == "i": elif tag == "i":
check(isinstance(value, (int, numpy.int32)) and check(isinstance(value, (int, numpy.int32)) and
(-2**31 < value < 2**31-1), (-2**31 < value < 2**31-1),
@ -378,6 +463,16 @@ class CommKernel:
check(isinstance(value, list), check(isinstance(value, list),
lambda: "list") lambda: "list")
self._write_int32(len(value)) self._write_int32(len(value))
tag_element = chr(tags[0])
if tag_element == "b":
self._write(bytes(value))
elif tag_element == "i":
self._write(struct.pack(">%sl" % len(value), *value))
elif tag_element == "I":
self._write(struct.pack(">%sq" % len(value), *value))
elif tag_element == "f":
self._write(struct.pack(">%sd" % len(value), *value))
else:
for elt in value: for elt in value:
tags_copy = bytearray(tags) tags_copy = bytearray(tags)
self._send_rpc_value(tags_copy, elt, root, function) self._send_rpc_value(tags_copy, elt, root, function)
@ -390,6 +485,19 @@ class CommKernel:
lambda: "{}-dimensional numpy.ndarray".format(num_dims)) lambda: "{}-dimensional numpy.ndarray".format(num_dims))
for s in value.shape: for s in value.shape:
self._write_int32(s) self._write_int32(s)
tag_element = chr(tags[0])
if tag_element == "b":
self._write(value.reshape((-1,), order="C").tobytes())
elif tag_element == "i":
array = value.reshape((-1,), order="C").astype('>i4')
self._write(array.tobytes())
elif tag_element == "I":
array = value.reshape((-1,), order="C").astype('>i8')
self._write(array.tobytes())
elif tag_element == "f":
array = value.reshape((-1,), order="C").astype('>d')
self._write(array.tobytes())
else:
for elt in value.reshape((-1,), order="C"): for elt in value.reshape((-1,), order="C"):
tags_copy = bytearray(tags) tags_copy = bytearray(tags)
self._send_rpc_value(tags_copy, elt, root, function) self._send_rpc_value(tags_copy, elt, root, function)
@ -420,7 +528,7 @@ class CommKernel:
return_tags = self._read_bytes() return_tags = self._read_bytes()
if service_id == 0: if service_id == 0:
service = lambda obj, attr, value: setattr(obj, attr, value) def service(obj, attr, value): return setattr(obj, attr, value)
else: else:
service = embedding_map.retrieve_object(service_id) service = embedding_map.retrieve_object(service_id)
logger.debug("rpc service: [%d]%r%s %r %r -> %s", service_id, service, logger.debug("rpc service: [%d]%r%s %r %r -> %s", service_id, service,
@ -432,15 +540,19 @@ class CommKernel:
try: try:
result = service(*args, **kwargs) result = service(*args, **kwargs)
logger.debug("rpc service: %d %r %r = %r", service_id, args, kwargs, result) logger.debug("rpc service: %d %r %r = %r",
service_id, args, kwargs, result)
self._write_header(Request.RPCReply) self._write_header(Request.RPCReply)
self._write_bytes(return_tags) self._write_bytes(return_tags)
self._send_rpc_value(bytearray(return_tags), result, result, service) self._send_rpc_value(bytearray(return_tags),
result, result, service)
self._flush()
except RPCReturnValueError as exn: except RPCReturnValueError as exn:
raise raise
except Exception as exn: except Exception as exn:
logger.debug("rpc service: %d %r %r ! %r", service_id, args, kwargs, exn) logger.debug("rpc service: %d %r %r ! %r",
service_id, args, kwargs, exn)
self._write_header(Request.RPCException) self._write_header(Request.RPCException)
@ -481,6 +593,7 @@ class CommKernel:
self._write_int32(line) self._write_int32(line)
self._write_int32(-1) # column not known self._write_int32(-1) # column not known
self._write_string(function) self._write_string(function)
self._flush()
def _serve_exception(self, embedding_map, symbolizer, demangler): def _serve_exception(self, embedding_map, symbolizer, demangler):
name = self._read_string() name = self._read_string()

View File

@ -1,26 +1,44 @@
"""RTIO driver for the Fastino 32channel, 16 bit, 2.5 MS/s per channel, """RTIO driver for the Fastino 32channel, 16 bit, 2.5 MS/s per channel,
streaming DAC. streaming DAC.
TODO: Example, describe update/hold
""" """
from artiq.language.core import kernel, portable, delay from artiq.language.core import kernel, portable, delay
from artiq.coredevice.rtio import rtio_output, rtio_output_wide, rtio_input_data from artiq.coredevice.rtio import (rtio_output, rtio_output_wide,
rtio_input_data)
from artiq.language.units import us from artiq.language.units import us
from artiq.language.types import TInt32, TList, TFloat from artiq.language.types import TInt32, TList
class Fastino: class Fastino:
"""Fastino 32-channel, 16-bit, 2.5 MS/s per channel streaming DAC """Fastino 32-channel, 16-bit, 2.5 MS/s per channel streaming DAC
The RTIO PHY supports staging DAC data before transmitting them by writing
to the DAC RTIO addresses, if a channel is not "held" by setting its bit
using :meth:`set_hold`, the next frame will contain the update. For the
DACs held, the update is triggered explicitly by setting the corresponding
bit using :meth:`set_update`. Update is self-clearing. This enables atomic
DAC updates synchronized to a frame edge.
The `log2_width=0` RTIO layout uses one DAC channel per RTIO address and a
dense RTIO address space. The RTIO words are narrow. (32 bit) and
few-channel updates are efficient. There is the least amount of DAC state
tracking in kernels, at the cost of more DMA and RTIO data.
The setting here and in the RTIO PHY (gateware) must match.
Other `log2_width` (up to `log2_width=5`) settings pack multiple
(in powers of two) DAC channels into one group and into one RTIO write.
The RTIO data width increases accordingly. The `log2_width`
LSBs of the RTIO address for a DAC channel write must be zero and the
address space is sparse. For `log2_width=5` the RTIO data is 512 bit wide.
If `log2_width` is zero, the :meth:`set_dac`/:meth:`set_dac_mu` interface
must be used. If non-zero, the :meth:`set_group`/:meth:`set_group_mu`
interface must be used.
:param channel: RTIO channel number :param channel: RTIO channel number
:param core_device: Core device name (default: "core") :param core_device: Core device name (default: "core")
:param log2_width: Width of DAC channel group (power of two, :param log2_width: Width of DAC channel group (logarithm base 2).
see the RTIO PHY for details). If zero, the Value must match the corresponding value in the RTIO PHY (gateware).
:meth:`set_dac`/:meth:`set_dac_mu` interface must be used.
If non-zero, the :meth:`set_group`/:meth:`set_group_mu`
interface must be used. Value must match the corresponding value
in the RTIO PHY.
""" """
kernel_invariants = {"core", "channel", "width"} kernel_invariants = {"core", "channel", "width"}
@ -94,7 +112,10 @@ class Fastino:
:param voltage: Voltage in SI Volts. :param voltage: Voltage in SI Volts.
:return: DAC data word in machine units, 16 bit integer. :return: DAC data word in machine units, 16 bit integer.
""" """
return int(round((0x8000/10.)*voltage)) + 0x8000 data = int(round((0x8000/10.)*voltage)) + 0x8000
if data < 0 or data > 0xffff:
raise ValueError("DAC voltage out of bounds")
return data
@portable @portable
def voltage_group_to_mu(self, voltage, data): def voltage_group_to_mu(self, voltage, data):

View File

@ -0,0 +1,95 @@
core_addr = "192.168.1.65"
device_db = {
"core": {
"type": "local",
"module": "artiq.coredevice.core",
"class": "Core",
"arguments": {"host": core_addr, "ref_period": 1/(8*150e6)}
},
"core_log": {
"type": "controller",
"host": "::1",
"port": 1068,
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
},
"core_cache": {
"type": "local",
"module": "artiq.coredevice.cache",
"class": "CoreCache"
},
"core_dma": {
"type": "local",
"module": "artiq.coredevice.dma",
"class": "CoreDMA"
}
}
# master peripherals
for i in range(4):
device_db["led" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": i},
}
# DEST#1 peripherals
amc_base = 0x070000
rtm_base = 0x020000
for i in range(4):
device_db["led" + str(4+i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + i},
}
#DIO (EEM0) starting at RTIO channel 0x000056
for i in range(8):
device_db["ttl" + str(i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + 0x000056 + i},
}
#DIO (EEM1) starting at RTIO channel 0x00005e
for i in range(8):
device_db["ttl" + str(8+i)] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + 0x00005e + i},
}
device_db["fmcdio_dirctl_clk"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + 0x000066}
}
device_db["fmcdio_dirctl_ser"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + 0x000067}
}
device_db["fmcdio_dirctl_latch"] = {
"type": "local",
"module": "artiq.coredevice.ttl",
"class": "TTLOut",
"arguments": {"channel": amc_base + 0x000068}
}
device_db["fmcdio_dirctl"] = {
"type": "local",
"module": "artiq.coredevice.shiftreg",
"class": "ShiftReg",
"arguments": {"clk": "fmcdio_dirctl_clk",
"ser": "fmcdio_dirctl_ser",
"latch": "fmcdio_dirctl_latch"}
}

View File

@ -0,0 +1,129 @@
import sys
import os
import select
from artiq.experiment import *
from artiq.coredevice.fmcdio_vhdci_eem import *
def chunker(seq, size):
res = []
for el in seq:
res.append(el)
if len(res) == size:
yield res
res = []
if res:
yield res
def is_enter_pressed() -> TBool:
if os.name == "nt":
if msvcrt.kbhit() and msvcrt.getch() == b"\r":
return True
else:
return False
else:
if select.select([sys.stdin, ], [], [], 0.0)[0]:
sys.stdin.read(1)
return True
else:
return False
class Demo(EnvExperiment):
def build(self):
self.setattr_device("core")
self.setattr_device("fmcdio_dirctl")
self.leds = dict()
self.ttl_outs = dict()
ddb = self.get_device_db()
for name, desc in ddb.items():
if isinstance(desc, dict) and desc["type"] == "local":
module, cls = desc["module"], desc["class"]
if (module, cls) == ("artiq.coredevice.ttl", "TTLOut"):
dev = self.get_device(name)
if "led" in name: # guess
self.leds[name] = dev
elif "ttl" in name: # to exclude fmcdio_dirctl
self.ttl_outs[name] = dev
self.leds = sorted(self.leds.items(), key=lambda x: x[1].channel)
self.ttl_outs = sorted(self.ttl_outs.items(), key=lambda x: x[1].channel)
self.dirctl_word = (
shiftreg_bits(0, dio_bank0_out_pins | dio_bank1_out_pins) |
shiftreg_bits(1, dio_bank0_out_pins | dio_bank1_out_pins)
)
@kernel
def init(self):
self.core.break_realtime()
print("*** Waiting for DRTIO ready...")
drtio_indices = [7]
for i in drtio_indices:
while not self.drtio_is_up(i):
pass
self.fmcdio_dirctl.set(self.dirctl_word)
@kernel
def drtio_is_up(self, drtio_index):
if not self.core.get_rtio_destination_status(drtio_index):
return False
print("DRTIO #", drtio_index, "is ready\n")
return True
@kernel
def test_led(self, led):
while not is_enter_pressed():
self.core.break_realtime()
# do not fill the FIFOs too much to avoid long response times
t = now_mu() - self.core.seconds_to_mu(0.2)
while self.core.get_rtio_counter_mu() < t:
pass
for i in range(3):
led.pulse(100*ms)
delay(100*ms)
def test_leds(self):
print("*** Testing LEDs.")
print("Check for blinking. Press ENTER when done.")
for led_name, led_dev in self.leds:
print("Testing LED: {}".format(led_name))
self.test_led(led_dev)
@kernel
def test_ttl_out_chunk(self, ttl_chunk):
while not is_enter_pressed():
self.core.break_realtime()
for _ in range(50000):
i = 0
for ttl in ttl_chunk:
i += 1
for _ in range(i):
ttl.pulse(1*us)
delay(1*us)
delay(10*us)
def test_ttl_outs(self):
print("*** Testing TTL outputs.")
print("Outputs are tested in groups of 4. Touch each TTL connector")
print("with the oscilloscope probe tip, and check that the number of")
print("pulses corresponds to its number in the group.")
print("Press ENTER when done.")
for ttl_chunk in chunker(self.ttl_outs, 4):
print("Testing TTL outputs: {}.".format(", ".join(name for name, dev in ttl_chunk)))
self.test_ttl_out_chunk([dev for name, dev in ttl_chunk])
def run(self):
self.core.reset()
if self.leds:
self.test_leds()
if self.ttl_outs:
self.test_ttl_outs()

View File

@ -11,6 +11,7 @@ extern crate cslice;
#[macro_use] #[macro_use]
extern crate log; extern crate log;
extern crate byteorder;
extern crate io; extern crate io;
extern crate dyld; extern crate dyld;

View File

@ -1,6 +1,7 @@
use core::str; use core::str;
use core::slice;
use cslice::{CSlice, CMutSlice}; use cslice::{CSlice, CMutSlice};
use byteorder::{NetworkEndian, ByteOrder};
use io::{ProtoRead, Read, Write, ProtoWrite, Error}; use io::{ProtoRead, Read, Write, ProtoWrite, Error};
use self::tag::{Tag, TagIterator, split_tag}; use self::tag::{Tag, TagIterator, split_tag};
@ -53,14 +54,35 @@ unsafe fn recv_value<R, E>(reader: &mut R, tag: Tag, data: &mut *mut (),
struct List { elements: *mut (), length: u32 }; struct List { elements: *mut (), length: u32 };
consume_value!(List, |ptr| { consume_value!(List, |ptr| {
(*ptr).length = reader.read_u32()?; (*ptr).length = reader.read_u32()?;
let length = (*ptr).length as usize;
let tag = it.clone().next().expect("truncated tag"); let tag = it.clone().next().expect("truncated tag");
(*ptr).elements = alloc(tag.size() * (*ptr).length as usize)?; (*ptr).elements = alloc(tag.size() * (*ptr).length as usize)?;
let mut data = (*ptr).elements; let mut data = (*ptr).elements;
for _ in 0..(*ptr).length as usize { match tag {
Tag::Bool => {
let dest = slice::from_raw_parts_mut(data as *mut u8, length);
reader.read_exact(dest)?;
},
Tag::Int32 => {
let dest = slice::from_raw_parts_mut(data as *mut u8, length * 4);
reader.read_exact(dest)?;
let dest = slice::from_raw_parts_mut(data as *mut i32, length);
NetworkEndian::from_slice_i32(dest);
},
Tag::Int64 | Tag::Float64 => {
let dest = slice::from_raw_parts_mut(data as *mut u8, length * 8);
reader.read_exact(dest)?;
let dest = slice::from_raw_parts_mut(data as *mut i64, length);
NetworkEndian::from_slice_i64(dest);
},
_ => {
for _ in 0..length {
recv_value(reader, tag, &mut data, alloc)? recv_value(reader, tag, &mut data, alloc)?
} }
}
}
Ok(()) Ok(())
}) })
} }
@ -72,14 +94,35 @@ unsafe fn recv_value<R, E>(reader: &mut R, tag: Tag, data: &mut *mut (),
total_len *= len; total_len *= len;
consume_value!(u32, |ptr| *ptr = len ) consume_value!(u32, |ptr| *ptr = len )
} }
let length = total_len as usize;
let elt_tag = it.clone().next().expect("truncated tag"); let elt_tag = it.clone().next().expect("truncated tag");
*buffer = alloc(elt_tag.size() * total_len as usize)?; *buffer = alloc(elt_tag.size() * total_len as usize)?;
let mut data = *buffer; let mut data = *buffer;
for _ in 0..total_len { match elt_tag {
Tag::Bool => {
let dest = slice::from_raw_parts_mut(data as *mut u8, length);
reader.read_exact(dest)?;
},
Tag::Int32 => {
let dest = slice::from_raw_parts_mut(data as *mut u8, length * 4);
reader.read_exact(dest)?;
let dest = slice::from_raw_parts_mut(data as *mut i32, length);
NetworkEndian::from_slice_i32(dest);
},
Tag::Int64 | Tag::Float64 => {
let dest = slice::from_raw_parts_mut(data as *mut u8, length * 8);
reader.read_exact(dest)?;
let dest = slice::from_raw_parts_mut(data as *mut i64, length);
NetworkEndian::from_slice_i64(dest);
},
_ => {
for _ in 0..length {
recv_value(reader, elt_tag, &mut data, alloc)? recv_value(reader, elt_tag, &mut data, alloc)?
} }
}
}
Ok(()) Ok(())
}) })
} }
@ -155,12 +198,34 @@ unsafe fn send_value<W>(writer: &mut W, tag: Tag, data: &mut *const ())
#[repr(C)] #[repr(C)]
struct List { elements: *const (), length: u32 }; struct List { elements: *const (), length: u32 };
consume_value!(List, |ptr| { consume_value!(List, |ptr| {
let length = (*ptr).length as usize;
writer.write_u32((*ptr).length)?; writer.write_u32((*ptr).length)?;
let tag = it.clone().next().expect("truncated tag"); let tag = it.clone().next().expect("truncated tag");
let mut data = (*ptr).elements; let mut data = (*ptr).elements;
for _ in 0..(*ptr).length as usize { writer.write_u8(tag.as_u8())?;
match tag {
Tag::Bool => {
let slice = slice::from_raw_parts(data as *const u8, length);
writer.write_all(slice)?;
},
Tag::Int32 => {
let slice = slice::from_raw_parts(data as *const u32, length);
for v in slice.iter() {
writer.write_u32(*v)?;
}
},
Tag::Int64 | Tag::Float64 => {
let slice = slice::from_raw_parts(data as *const u64, length);
for v in slice.iter() {
writer.write_u64(*v)?;
}
},
_ => {
for _ in 0..length {
send_value(writer, tag, &mut data)?; send_value(writer, tag, &mut data)?;
} }
}
}
Ok(()) Ok(())
}) })
} }
@ -176,10 +241,32 @@ unsafe fn send_value<W>(writer: &mut W, tag: Tag, data: &mut *const ())
total_len *= *len; total_len *= *len;
}) })
} }
let length = total_len as usize;
let mut data = *buffer; let mut data = *buffer;
for _ in 0..total_len as usize { writer.write_u8(elt_tag.as_u8())?;
match elt_tag {
Tag::Bool => {
let slice = slice::from_raw_parts(data as *const u8, length);
writer.write_all(slice)?;
},
Tag::Int32 => {
let slice = slice::from_raw_parts(data as *const u32, length);
for v in slice.iter() {
writer.write_u32(*v)?;
}
},
Tag::Int64 | Tag::Float64 => {
let slice = slice::from_raw_parts(data as *const u64, length);
for v in slice.iter() {
writer.write_u64(*v)?;
}
},
_ => {
for _ in 0..length {
send_value(writer, elt_tag, &mut data)?; send_value(writer, elt_tag, &mut data)?;
} }
}
}
Ok(()) Ok(())
}) })
} }

View File

@ -107,6 +107,9 @@ fn startup() {
io_expander1 = board_misoc::io_expander::IoExpander::new(1); io_expander1 = board_misoc::io_expander::IoExpander::new(1);
io_expander0.init().expect("I2C I/O expander #0 initialization failed"); io_expander0.init().expect("I2C I/O expander #0 initialization failed");
io_expander1.init().expect("I2C I/O expander #1 initialization failed"); io_expander1.init().expect("I2C I/O expander #1 initialization failed");
io_expander0.set_oe(0, 1 << 1).unwrap();
io_expander0.set(0, 1, false);
io_expander0.service().unwrap();
} }
rtio_clocking::init(); rtio_clocking::init();

View File

@ -475,6 +475,9 @@ pub extern fn main() -> i32 {
io_expander1.set(1, 7, true); io_expander1.set(1, 7, true);
io_expander1.service().unwrap(); io_expander1.service().unwrap();
} }
io_expander0.set_oe(0, 1 << 1).unwrap();
io_expander0.set(0, 1, false);
io_expander0.service().unwrap();
} }
#[cfg(has_si5324)] #[cfg(has_si5324)]

View File

@ -619,12 +619,12 @@ class Fastino(_EEM):
) for pol in "pn"] ) for pol in "pn"]
@classmethod @classmethod
def add_std(cls, target, eem, iostandard="LVDS_25"): def add_std(cls, target, eem, log2_width, iostandard="LVDS_25"):
cls.add_extension(target, eem, iostandard=iostandard) cls.add_extension(target, eem, iostandard=iostandard)
phy = fastino.Fastino(target.platform.request("fastino{}_ser_p".format(eem)), phy = fastino.Fastino(target.platform.request("fastino{}_ser_p".format(eem)),
target.platform.request("fastino{}_ser_n".format(eem)), target.platform.request("fastino{}_ser_n".format(eem)),
log2_width=0) log2_width=log2_width)
target.submodules += phy target.submodules += phy
target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4)) target.rtio_channels.append(rtio.Channel.from_phy(phy, ififo_depth=4))

View File

@ -19,8 +19,9 @@ def _get_connectors():
for j, pair in enumerate(eem_fmc_connections[i]): for j, pair in enumerate(eem_fmc_connections[i]):
for pn in "n", "p": for pn in "n", "p":
cc = "cc_" if j == 0 else "" cc = "cc_" if j == 0 else ""
lpc_cc = "CC_" if eem_fmc_connections[i][j] in (0, 1, 17, 18) else ""
connections["d{}_{}{}".format(j, cc, pn)] = \ connections["d{}_{}{}".format(j, cc, pn)] = \
"LPC:LA{:02d}_{}{}".format(pair, cc.upper(), pn.upper()) "LPC:LA{:02d}_{}{}".format(pair, lpc_cc, pn.upper())
connectors.append(("eem{}".format(i), connections)) connectors.append(("eem{}".format(i), connections))
return connectors return connectors

View File

@ -79,6 +79,21 @@ class _RTIOCRG(Module, AutoCSR):
] ]
class SMAClkinForward(Module):
def __init__(self, platform):
sma_clkin = platform.request("sma_clkin")
sma_clkin_se = Signal()
sma_clkin_buffered = Signal()
cdr_clk_se = Signal()
cdr_clk = platform.request("cdr_clk")
self.specials += [
Instance("IBUFDS", i_I=sma_clkin.p, i_IB=sma_clkin.n, o_O=sma_clkin_se),
Instance("BUFIO", i_I=sma_clkin_se, o_O=sma_clkin_buffered),
Instance("ODDR", i_C=sma_clkin_buffered, i_CE=1, i_D1=0, i_D2=1, o_Q=cdr_clk_se),
Instance("OBUFDS", i_I=cdr_clk_se, o_O=cdr_clk.p, o_OB=cdr_clk.n)
]
def fix_serdes_timing_path(platform): def fix_serdes_timing_path(platform):
# ignore timing of path from OSERDESE2 through the pad to ISERDESE2 # ignore timing of path from OSERDESE2 through the pad to ISERDESE2
platform.add_platform_command( platform.add_platform_command(
@ -99,7 +114,7 @@ class StandaloneBase(MiniSoC, AMPSoC):
} }
mem_map.update(MiniSoC.mem_map) mem_map.update(MiniSoC.mem_map)
def __init__(self, **kwargs): def __init__(self, gateware_identifier_str=None, **kwargs):
MiniSoC.__init__(self, MiniSoC.__init__(self,
cpu_type="or1k", cpu_type="or1k",
sdram_controller_type="minicon", sdram_controller_type="minicon",
@ -109,12 +124,13 @@ class StandaloneBase(MiniSoC, AMPSoC):
ethmac_ntxslots=4, ethmac_ntxslots=4,
**kwargs) **kwargs)
AMPSoC.__init__(self) AMPSoC.__init__(self)
add_identifier(self) add_identifier(self, gateware_identifier_str=gateware_identifier_str)
if self.platform.hw_rev == "v2.0": if self.platform.hw_rev == "v2.0":
self.submodules.error_led = gpio.GPIOOut(Cat( self.submodules.error_led = gpio.GPIOOut(Cat(
self.platform.request("error_led"))) self.platform.request("error_led")))
self.csr_devices.append("error_led") self.csr_devices.append("error_led")
self.submodules += SMAClkinForward(self.platform)
i2c = self.platform.request("i2c") i2c = self.platform.request("i2c")
self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda]) self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda])
@ -280,7 +296,7 @@ class MasterBase(MiniSoC, AMPSoC):
} }
mem_map.update(MiniSoC.mem_map) mem_map.update(MiniSoC.mem_map)
def __init__(self, rtio_clk_freq=125e6, enable_sata=False, **kwargs): def __init__(self, rtio_clk_freq=125e6, enable_sata=False, gateware_identifier_str=None, **kwargs):
MiniSoC.__init__(self, MiniSoC.__init__(self,
cpu_type="or1k", cpu_type="or1k",
sdram_controller_type="minicon", sdram_controller_type="minicon",
@ -290,10 +306,13 @@ class MasterBase(MiniSoC, AMPSoC):
ethmac_ntxslots=4, ethmac_ntxslots=4,
**kwargs) **kwargs)
AMPSoC.__init__(self) AMPSoC.__init__(self)
add_identifier(self) add_identifier(self, gateware_identifier_str=gateware_identifier_str)
platform = self.platform platform = self.platform
if platform.hw_rev == "v2.0":
self.submodules += SMAClkinForward(platform)
i2c = self.platform.request("i2c") i2c = self.platform.request("i2c")
self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda]) self.submodules.i2c = gpio.GPIOTristate([i2c.scl, i2c.sda])
self.csr_devices.append("i2c") self.csr_devices.append("i2c")
@ -453,13 +472,13 @@ class SatelliteBase(BaseSoC):
} }
mem_map.update(BaseSoC.mem_map) mem_map.update(BaseSoC.mem_map)
def __init__(self, rtio_clk_freq=125e6, enable_sata=False, *, with_wrpll=False, **kwargs): def __init__(self, rtio_clk_freq=125e6, enable_sata=False, *, with_wrpll=False, gateware_identifier_str=None, **kwargs):
BaseSoC.__init__(self, BaseSoC.__init__(self,
cpu_type="or1k", cpu_type="or1k",
sdram_controller_type="minicon", sdram_controller_type="minicon",
l2_size=128*1024, l2_size=128*1024,
**kwargs) **kwargs)
add_identifier(self) add_identifier(self, gateware_identifier_str=gateware_identifier_str)
platform = self.platform platform = self.platform
@ -674,11 +693,14 @@ def main():
help="variant: {} (default: %(default)s)".format( help="variant: {} (default: %(default)s)".format(
"/".join(sorted(VARIANTS.keys())))) "/".join(sorted(VARIANTS.keys()))))
parser.add_argument("--with-wrpll", default=False, action="store_true") parser.add_argument("--with-wrpll", default=False, action="store_true")
parser.add_argument("--gateware-identifier-str", default=None,
help="Override ROM identifier")
args = parser.parse_args() args = parser.parse_args()
argdict = dict() argdict = dict()
if args.with_wrpll: if args.with_wrpll:
argdict["with_wrpll"] = True argdict["with_wrpll"] = True
argdict["gateware_identifier_str"] = args.gateware_identifier_str
variant = args.variant.lower() variant = args.variant.lower()
try: try:

View File

@ -109,7 +109,8 @@ def peripheral_mirny(module, peripheral):
def peripheral_fastino(module, peripheral): def peripheral_fastino(module, peripheral):
if len(peripheral["ports"]) != 1: if len(peripheral["ports"]) != 1:
raise ValueError("wrong number of ports") raise ValueError("wrong number of ports")
eem.Fastino.add_std(module, peripheral["ports"][0]) eem.Fastino.add_std(module, peripheral["ports"][0],
peripheral.get("log2_width", 0))
def peripheral_phaser(module, peripheral): def peripheral_phaser(module, peripheral):
@ -259,6 +260,8 @@ def main():
parser.set_defaults(output_dir="artiq_kasli") parser.set_defaults(output_dir="artiq_kasli")
parser.add_argument("description", metavar="DESCRIPTION", parser.add_argument("description", metavar="DESCRIPTION",
help="JSON system description file") help="JSON system description file")
parser.add_argument("--gateware-identifier-str", default=None,
help="Override ROM identifier")
args = parser.parse_args() args = parser.parse_args()
with open(args.description, "r") as f: with open(args.description, "r") as f:
@ -276,7 +279,7 @@ def main():
else: else:
raise ValueError("Invalid base") raise ValueError("Invalid base")
soc = cls(description, **soc_kasli_argdict(args)) soc = cls(description, gateware_identifier_str=args.gateware_identifier_str, **soc_kasli_argdict(args))
args.variant = description["variant"] args.variant = description["variant"]
build_artiq_soc(soc, builder_argdict(args)) build_artiq_soc(soc, builder_argdict(args))

View File

@ -119,7 +119,7 @@ class _StandaloneBase(MiniSoC, AMPSoC):
} }
mem_map.update(MiniSoC.mem_map) mem_map.update(MiniSoC.mem_map)
def __init__(self, **kwargs): def __init__(self, gateware_identifier_str=None, **kwargs):
MiniSoC.__init__(self, MiniSoC.__init__(self,
cpu_type="or1k", cpu_type="or1k",
sdram_controller_type="minicon", sdram_controller_type="minicon",
@ -129,7 +129,7 @@ class _StandaloneBase(MiniSoC, AMPSoC):
ethmac_ntxslots=4, ethmac_ntxslots=4,
**kwargs) **kwargs)
AMPSoC.__init__(self) AMPSoC.__init__(self)
add_identifier(self) add_identifier(self, gateware_identifier_str=gateware_identifier_str)
if isinstance(self.platform.toolchain, XilinxVivadoToolchain): if isinstance(self.platform.toolchain, XilinxVivadoToolchain):
self.platform.toolchain.bitstream_commands.extend([ self.platform.toolchain.bitstream_commands.extend([
@ -416,6 +416,8 @@ def main():
help="variant: " help="variant: "
"nist_clock/nist_qc2/sma_spi " "nist_clock/nist_qc2/sma_spi "
"(default: %(default)s)") "(default: %(default)s)")
parser.add_argument("--gateware-identifier-str", default=None,
help="Override ROM identifier")
args = parser.parse_args() args = parser.parse_args()
variant = args.variant.lower() variant = args.variant.lower()
@ -424,7 +426,7 @@ def main():
except KeyError: except KeyError:
raise SystemExit("Invalid variant (-V/--variant)") raise SystemExit("Invalid variant (-V/--variant)")
soc = cls(**soc_kc705_argdict(args)) soc = cls(gateware_identifier_str=args.gateware_identifier_str, **soc_kc705_argdict(args))
build_artiq_soc(soc, builder_argdict(args)) build_artiq_soc(soc, builder_argdict(args))

View File

@ -38,7 +38,7 @@ class Master(MiniSoC, AMPSoC):
} }
mem_map.update(MiniSoC.mem_map) mem_map.update(MiniSoC.mem_map)
def __init__(self, **kwargs): def __init__(self, gateware_identifier_str=None, **kwargs):
MiniSoC.__init__(self, MiniSoC.__init__(self,
cpu_type="or1k", cpu_type="or1k",
sdram_controller_type="minicon", sdram_controller_type="minicon",
@ -49,7 +49,7 @@ class Master(MiniSoC, AMPSoC):
csr_address_width=15, csr_address_width=15,
**kwargs) **kwargs)
AMPSoC.__init__(self) AMPSoC.__init__(self)
add_identifier(self) add_identifier(self, gateware_identifier_str=gateware_identifier_str)
platform = self.platform platform = self.platform
rtio_clk_freq = 150e6 rtio_clk_freq = 150e6
@ -164,9 +164,11 @@ def main():
builder_args(parser) builder_args(parser)
soc_sdram_args(parser) soc_sdram_args(parser)
parser.set_defaults(output_dir="artiq_metlino") parser.set_defaults(output_dir="artiq_metlino")
parser.add_argument("--gateware-identifier-str", default=None,
help="Override ROM identifier")
args = parser.parse_args() args = parser.parse_args()
args.variant = "master" args.variant = "master"
soc = Master(**soc_sdram_argdict(args)) soc = Master(gateware_identifier_str=args.gateware_identifier_str, **soc_sdram_argdict(args))
build_artiq_soc(soc, builder_argdict(args)) build_artiq_soc(soc, builder_argdict(args))

View File

@ -12,8 +12,10 @@ from misoc.interconnect.csr import *
from misoc.targets.sayma_amc import * from misoc.targets.sayma_amc import *
from artiq.gateware.amp import AMPSoC from artiq.gateware.amp import AMPSoC
from artiq.gateware import eem
from artiq.gateware import rtio from artiq.gateware import rtio
from artiq.gateware import jesd204_tools from artiq.gateware import jesd204_tools
from artiq.gateware import fmcdio_vhdci_eem
from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_ultrascale, sawg from artiq.gateware.rtio.phy import ttl_simple, ttl_serdes_ultrascale, sawg
from artiq.gateware.drtio.transceiver import gth_ultrascale from artiq.gateware.drtio.transceiver import gth_ultrascale
from artiq.gateware.drtio.siphaser import SiPhaser7Series from artiq.gateware.drtio.siphaser import SiPhaser7Series
@ -50,7 +52,7 @@ class SatelliteBase(MiniSoC):
} }
mem_map.update(MiniSoC.mem_map) mem_map.update(MiniSoC.mem_map)
def __init__(self, rtio_clk_freq=125e6, identifier_suffix="", with_sfp=False, *, with_wrpll, **kwargs): def __init__(self, rtio_clk_freq=125e6, identifier_suffix="", gateware_identifier_str=None, with_sfp=False, *, with_wrpll, **kwargs):
MiniSoC.__init__(self, MiniSoC.__init__(self,
cpu_type="or1k", cpu_type="or1k",
sdram_controller_type="minicon", sdram_controller_type="minicon",
@ -59,7 +61,7 @@ class SatelliteBase(MiniSoC):
ethmac_nrxslots=4, ethmac_nrxslots=4,
ethmac_ntxslots=4, ethmac_ntxslots=4,
**kwargs) **kwargs)
add_identifier(self, suffix=identifier_suffix) add_identifier(self, suffix=identifier_suffix, gateware_identifier_str=gateware_identifier_str)
self.rtio_clk_freq = rtio_clk_freq self.rtio_clk_freq = rtio_clk_freq
platform = self.platform platform = self.platform
@ -284,7 +286,7 @@ class JDCGSyncDDS(Module, AutoCSR):
class Satellite(SatelliteBase): class Satellite(SatelliteBase):
""" """
DRTIO satellite with local DAC/SAWG channels. DRTIO satellite with local DAC/SAWG channels, as well as TTL channels via FMC and VHDCI carrier.
""" """
def __init__(self, jdcg_type, **kwargs): def __init__(self, jdcg_type, **kwargs):
SatelliteBase.__init__(self, 150e6, SatelliteBase.__init__(self, 150e6,
@ -307,7 +309,7 @@ class Satellite(SatelliteBase):
self.csr_devices.append("slave_fpga_cfg") self.csr_devices.append("slave_fpga_cfg")
self.config["SLAVE_FPGA_GATEWARE"] = 0x200000 self.config["SLAVE_FPGA_GATEWARE"] = 0x200000
rtio_channels = [] self.rtio_channels = rtio_channels = []
for i in range(4): for i in range(4):
phy = ttl_simple.Output(platform.request("user_led", i)) phy = ttl_simple.Output(platform.request("user_led", i))
self.submodules += phy self.submodules += phy
@ -343,6 +345,27 @@ class Satellite(SatelliteBase):
self.jdcg_1.sawgs self.jdcg_1.sawgs
for phy in sawg.phys) for phy in sawg.phys)
# FMC-VHDCI-EEM DIOs x 2 (all OUTPUTs)
platform.add_connectors(fmcdio_vhdci_eem.connectors)
eem.DIO.add_std(self, 0,
ttl_simple.Output, ttl_simple.Output, iostandard="LVDS")
eem.DIO.add_std(self, 1,
ttl_simple.Output, ttl_simple.Output, iostandard="LVDS")
# FMC-DIO-32ch-LVDS-a Direction Control Pins (via shift register) as TTLs x 3
platform.add_extension(fmcdio_vhdci_eem.io)
print("fmcdio_vhdci_eem.[CLK, SER, LATCH] starting at RTIO channel 0x{:06x}"
.format(len(rtio_channels)))
fmcdio_dirctl = platform.request("fmcdio_dirctl", 0)
fmcdio_dirctl_phys = [
ttl_simple.Output(fmcdio_dirctl.clk),
ttl_simple.Output(fmcdio_dirctl.ser),
ttl_simple.Output(fmcdio_dirctl.latch)
]
for phy in fmcdio_dirctl_phys:
self.submodules += phy
rtio_channels.append(rtio.Channel.from_phy(phy))
workaround_us_lvds_tristate(platform)
self.add_rtio(rtio_channels) self.add_rtio(rtio_channels)
self.submodules.sysref_sampler = jesd204_tools.SysrefSampler( self.submodules.sysref_sampler = jesd204_tools.SysrefSampler(
@ -403,14 +426,24 @@ def main():
help="Change type of signal generator. This is used exclusively for " help="Change type of signal generator. This is used exclusively for "
"development and debugging.") "development and debugging.")
parser.add_argument("--with-wrpll", default=False, action="store_true") parser.add_argument("--with-wrpll", default=False, action="store_true")
parser.add_argument("--gateware-identifier-str", default=None,
help="Override ROM identifier")
args = parser.parse_args() args = parser.parse_args()
variant = args.variant.lower() variant = args.variant.lower()
if variant == "satellite": if variant == "satellite":
soc = Satellite(with_sfp=args.sfp, jdcg_type=args.jdcg_type, with_wrpll=args.with_wrpll, soc = Satellite(
with_sfp=args.sfp,
jdcg_type=args.jdcg_type,
with_wrpll=args.with_wrpll,
gateware_identifier_str=args.gateware_identifier_str,
**soc_sayma_amc_argdict(args)) **soc_sayma_amc_argdict(args))
elif variant == "simplesatellite": elif variant == "simplesatellite":
soc = SimpleSatellite(with_sfp=args.sfp, with_wrpll=args.with_wrpll, **soc_sayma_amc_argdict(args)) soc = SimpleSatellite(
with_sfp=args.sfp,
with_wrpll=args.with_wrpll,
gateware_identifier_str=args.gateware_identifier_str,
**soc_sayma_amc_argdict(args))
else: else:
raise SystemExit("Invalid variant (-V/--variant)") raise SystemExit("Invalid variant (-V/--variant)")

View File

@ -75,11 +75,11 @@ class _SatelliteBase(BaseSoC):
} }
mem_map.update(BaseSoC.mem_map) mem_map.update(BaseSoC.mem_map)
def __init__(self, rtio_clk_freq, *, with_wrpll, **kwargs): def __init__(self, rtio_clk_freq, *, with_wrpll, gateware_identifier_str, **kwargs):
BaseSoC.__init__(self, BaseSoC.__init__(self,
cpu_type="or1k", cpu_type="or1k",
**kwargs) **kwargs)
add_identifier(self) add_identifier(self, gateware_identifier_str=gateware_identifier_str)
self.rtio_clk_freq = rtio_clk_freq self.rtio_clk_freq = rtio_clk_freq
platform = self.platform platform = self.platform
@ -299,11 +299,15 @@ def main():
parser.add_argument("--rtio-clk-freq", parser.add_argument("--rtio-clk-freq",
default=150, type=int, help="RTIO clock frequency in MHz") default=150, type=int, help="RTIO clock frequency in MHz")
parser.add_argument("--with-wrpll", default=False, action="store_true") parser.add_argument("--with-wrpll", default=False, action="store_true")
parser.add_argument("--gateware-identifier-str", default=None,
help="Override ROM identifier")
parser.set_defaults(output_dir=os.path.join("artiq_sayma", "rtm")) parser.set_defaults(output_dir=os.path.join("artiq_sayma", "rtm"))
args = parser.parse_args() args = parser.parse_args()
soc = Satellite( soc = Satellite(
rtio_clk_freq=1e6*args.rtio_clk_freq, with_wrpll=args.with_wrpll, rtio_clk_freq=1e6*args.rtio_clk_freq,
with_wrpll=args.with_wrpll,
gateware_identifier_str=args.gateware_identifier_str,
**soc_sayma_rtm_argdict(args)) **soc_sayma_rtm_argdict(args))
builder = SatmanSoCBuilder(soc, **builder_argdict(args)) builder = SatmanSoCBuilder(soc, **builder_argdict(args))
try: try:

View File

@ -24,6 +24,7 @@ class I2CSwitch(EnvExperiment):
class NonexistentI2CBus(EnvExperiment): class NonexistentI2CBus(EnvExperiment):
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.setattr_device("i2c_switch") # HACK: only run this test on boards with I2C
self.broken_switch = PCA9548(self._HasEnvironment__device_mgr, 255) self.broken_switch = PCA9548(self._HasEnvironment__device_mgr, 255)
@kernel @kernel

View File

@ -1,72 +1,273 @@
import os import os
import time import time
import unittest import unittest
import numpy
from artiq.experiment import * from artiq.experiment import *
from artiq.test.hardware_testbench import ExperimentCase from artiq.test.hardware_testbench import ExperimentCase
# large: 1MB payload
# small: 1KB payload
bytes_large = b"\x00" * (1 << 20)
bytes_small = b"\x00" * (1 << 10)
list_large = [123] * (1 << 18)
list_small = [123] * (1 << 8)
array_large = numpy.array(list_large, numpy.int32)
array_small = numpy.array(list_small, numpy.int32)
byte_list_large = [True] * (1 << 20)
byte_list_small = [True] * (1 << 10)
received_bytes = 0
time_start = 0
time_end = 0
class _Transfer(EnvExperiment): class _Transfer(EnvExperiment):
def build(self): def build(self):
self.setattr_device("core") self.setattr_device("core")
self.data = b"\x00"*(10**6) self.count = 10
self.h2d = [0.0] * self.count
self.d2h = [0.0] * self.count
@rpc @rpc
def source(self) -> TBytes: def get_bytes(self, large: TBool) -> TBytes:
return self.data if large:
return bytes_large
else:
return bytes_small
@rpc(flags={"async"}) @rpc
def get_list(self, large: TBool) -> TList(TInt32):
if large:
return list_large
else:
return list_small
@rpc
def get_byte_list(self, large: TBool) -> TList(TBool):
if large:
return byte_list_large
else:
return byte_list_small
@rpc
def get_array(self, large: TBool) -> TArray(TInt32):
if large:
return array_large
else:
return array_small
@rpc
def get_string_list(self) -> TList(TStr):
return string_list
@rpc
def sink(self, data): def sink(self, data):
assert data == self.data pass
@rpc(flags={"async"}) @rpc(flags={"async"})
def sink_array(self, data): def sink_async(self, data):
assert data == [0]*(1 << 15) global received_bytes, time_start, time_end
if received_bytes == 0:
time_start = time.time()
received_bytes += len(data)
if received_bytes == (1024 ** 2)*128:
time_end = time.time()
@rpc
def get_async_throughput(self) -> TFloat:
return 128.0 / (time_end - time_start)
@kernel @kernel
def host_to_device(self): def test_bytes(self, large):
def inner():
t0 = self.core.get_rtio_counter_mu() t0 = self.core.get_rtio_counter_mu()
data = self.source() data = self.get_bytes(large)
t1 = self.core.get_rtio_counter_mu() t1 = self.core.get_rtio_counter_mu()
return len(data)/self.core.mu_to_seconds(t1-t0) self.sink(data)
t2 = self.core.get_rtio_counter_mu()
self.h2d[i] = self.core.mu_to_seconds(t1 - t0)
self.d2h[i] = self.core.mu_to_seconds(t2 - t1)
for i in range(self.count):
inner()
return (self.h2d, self.d2h)
@kernel @kernel
def device_to_host(self): def test_byte_list(self, large):
def inner():
t0 = self.core.get_rtio_counter_mu() t0 = self.core.get_rtio_counter_mu()
self.sink(self.data) data = self.get_byte_list(large)
t1 = self.core.get_rtio_counter_mu() t1 = self.core.get_rtio_counter_mu()
return len(self.data)/self.core.mu_to_seconds(t1-t0) self.sink(data)
t2 = self.core.get_rtio_counter_mu()
self.h2d[i] = self.core.mu_to_seconds(t1 - t0)
self.d2h[i] = self.core.mu_to_seconds(t2 - t1)
for i in range(self.count):
inner()
return (self.h2d, self.d2h)
@kernel @kernel
def device_to_host_array(self): def test_list(self, large):
#data = [[0]*8 for _ in range(1 << 12)] def inner():
data = [0]*(1 << 15)
t0 = self.core.get_rtio_counter_mu() t0 = self.core.get_rtio_counter_mu()
self.sink_array(data) data = self.get_list(large)
t1 = self.core.get_rtio_counter_mu() t1 = self.core.get_rtio_counter_mu()
return ((len(data)*4)/ self.sink(data)
self.core.mu_to_seconds(t1-t0)) t2 = self.core.get_rtio_counter_mu()
self.h2d[i] = self.core.mu_to_seconds(t1 - t0)
self.d2h[i] = self.core.mu_to_seconds(t2 - t1)
for i in range(self.count):
inner()
return (self.h2d, self.d2h)
@kernel
def test_array(self, large):
def inner():
t0 = self.core.get_rtio_counter_mu()
data = self.get_array(large)
t1 = self.core.get_rtio_counter_mu()
self.sink(data)
t2 = self.core.get_rtio_counter_mu()
self.h2d[i] = self.core.mu_to_seconds(t1 - t0)
self.d2h[i] = self.core.mu_to_seconds(t2 - t1)
for i in range(self.count):
inner()
return (self.h2d, self.d2h)
@kernel
def test_async(self):
data = self.get_bytes(True)
for _ in range(128):
self.sink_async(data)
return self.get_async_throughput()
class TransferTest(ExperimentCase): class TransferTest(ExperimentCase):
def test_host_to_device(self): @classmethod
exp = self.create(_Transfer) def setUpClass(self):
host_to_device_rate = exp.host_to_device() self.results = []
print(host_to_device_rate/(1024*1024), "MiB/s")
self.assertGreater(host_to_device_rate, 2.0e6)
def test_device_to_host(self): @classmethod
exp = self.create(_Transfer) def tearDownClass(self):
device_to_host_rate = exp.device_to_host() if len(self.results) == 0:
print(device_to_host_rate/(1024*1024), "MiB/s") return
self.assertGreater(device_to_host_rate, 2.2e6) max_length = max(max(len(row[0]) for row in self.results), len("Test"))
def test_device_to_host_array(self): def pad(name):
exp = self.create(_Transfer) nonlocal max_length
rate = exp.device_to_host_array() return name + " " * (max_length - len(name))
print(rate/(1024*1024), "MiB/s") print()
self.assertGreater(rate, .15e6) print("| {} | Mean (MiB/s) | std (MiB/s) |".format(pad("Test")))
print("| {} | ------------ | ------------ |".format("-" * max_length))
for v in self.results:
print("| {} | {:>12.2f} | {:>12.2f} |".format(
pad(v[0]), v[1], v[2]))
def test_bytes_large(self):
exp = self.create(_Transfer)
results = exp.test_bytes(True)
host_to_device = (1 << 20) / numpy.array(results[0], numpy.float64)
device_to_host = (1 << 20) / numpy.array(results[1], numpy.float64)
host_to_device /= 1024*1024
device_to_host /= 1024*1024
self.results.append(["Bytes (1MB) H2D", host_to_device.mean(),
host_to_device.std()])
self.results.append(["Bytes (1MB) D2H", device_to_host.mean(),
device_to_host.std()])
def test_bytes_small(self):
exp = self.create(_Transfer)
results = exp.test_bytes(False)
host_to_device = (1 << 10) / numpy.array(results[0], numpy.float64)
device_to_host = (1 << 10) / numpy.array(results[1], numpy.float64)
host_to_device /= 1024*1024
device_to_host /= 1024*1024
self.results.append(["Bytes (1KB) H2D", host_to_device.mean(),
host_to_device.std()])
self.results.append(["Bytes (1KB) D2H", device_to_host.mean(),
device_to_host.std()])
def test_byte_list_large(self):
exp = self.create(_Transfer)
results = exp.test_byte_list(True)
host_to_device = (1 << 20) / numpy.array(results[0], numpy.float64)
device_to_host = (1 << 20) / numpy.array(results[1], numpy.float64)
host_to_device /= 1024*1024
device_to_host /= 1024*1024
self.results.append(["Bytes List (1MB) H2D", host_to_device.mean(),
host_to_device.std()])
self.results.append(["Bytes List (1MB) D2H", device_to_host.mean(),
device_to_host.std()])
def test_byte_list_small(self):
exp = self.create(_Transfer)
results = exp.test_byte_list(False)
host_to_device = (1 << 10) / numpy.array(results[0], numpy.float64)
device_to_host = (1 << 10) / numpy.array(results[1], numpy.float64)
host_to_device /= 1024*1024
device_to_host /= 1024*1024
self.results.append(["Bytes List (1KB) H2D", host_to_device.mean(),
host_to_device.std()])
self.results.append(["Bytes List (1KB) D2H", device_to_host.mean(),
device_to_host.std()])
def test_list_large(self):
exp = self.create(_Transfer)
results = exp.test_list(True)
host_to_device = (1 << 20) / numpy.array(results[0], numpy.float64)
device_to_host = (1 << 20) / numpy.array(results[1], numpy.float64)
host_to_device /= 1024*1024
device_to_host /= 1024*1024
self.results.append(["I32 List (1MB) H2D", host_to_device.mean(),
host_to_device.std()])
self.results.append(["I32 List (1MB) D2H", device_to_host.mean(),
device_to_host.std()])
def test_list_small(self):
exp = self.create(_Transfer)
results = exp.test_list(False)
host_to_device = (1 << 10) / numpy.array(results[0], numpy.float64)
device_to_host = (1 << 10) / numpy.array(results[1], numpy.float64)
host_to_device /= 1024*1024
device_to_host /= 1024*1024
self.results.append(["I32 List (1KB) H2D", host_to_device.mean(),
host_to_device.std()])
self.results.append(["I32 List (1KB) D2H", device_to_host.mean(),
device_to_host.std()])
def test_array_large(self):
exp = self.create(_Transfer)
results = exp.test_array(True)
host_to_device = (1 << 20) / numpy.array(results[0], numpy.float64)
device_to_host = (1 << 20) / numpy.array(results[1], numpy.float64)
host_to_device /= 1024*1024
device_to_host /= 1024*1024
self.results.append(["I32 Array (1MB) H2D", host_to_device.mean(),
host_to_device.std()])
self.results.append(["I32 Array (1MB) D2H", device_to_host.mean(),
device_to_host.std()])
def test_array_small(self):
exp = self.create(_Transfer)
results = exp.test_array(False)
host_to_device = (1 << 10) / numpy.array(results[0], numpy.float64)
device_to_host = (1 << 10) / numpy.array(results[1], numpy.float64)
host_to_device /= 1024*1024
device_to_host /= 1024*1024
self.results.append(["I32 Array (1KB) H2D", host_to_device.mean(),
host_to_device.std()])
self.results.append(["I32 Array (1KB) D2H", device_to_host.mean(),
device_to_host.std()])
def test_async_throughput(self):
exp = self.create(_Transfer)
results = exp.test_async()
print("Async throughput: {:>6.2f}MiB/s".format(results))
class _KernelOverhead(EnvExperiment): class _KernelOverhead(EnvExperiment):
def build(self): def build(self):

View File

@ -12,6 +12,7 @@ from artiq.coredevice import exceptions
from artiq.coredevice.comm_mgmt import CommMgmt from artiq.coredevice.comm_mgmt import CommMgmt
from artiq.coredevice.comm_analyzer import (StoppedMessage, OutputMessage, InputMessage, from artiq.coredevice.comm_analyzer import (StoppedMessage, OutputMessage, InputMessage,
decode_dump, get_analyzer_dump) decode_dump, get_analyzer_dump)
from artiq.compiler.targets import CortexA9Target
artiq_low_latency = os.getenv("ARTIQ_LOW_LATENCY") artiq_low_latency = os.getenv("ARTIQ_LOW_LATENCY")
@ -230,17 +231,18 @@ class LoopbackGateTiming(EnvExperiment):
# With the exact delay known, make sure tight gate timings work. # With the exact delay known, make sure tight gate timings work.
# In the most common configuration, 24 mu == 24 ns == 3 coarse periods, # In the most common configuration, 24 mu == 24 ns == 3 coarse periods,
# which should be plenty of slack. # which should be plenty of slack.
# FIXME: ZC706 with NIST_QC2 needs 48ns - hw problem?
delay_mu(10000) delay_mu(10000)
gate_start_mu = now_mu() gate_start_mu = now_mu()
self.loop_in.gate_both_mu(24) self.loop_in.gate_both_mu(48) # XXX
gate_end_mu = now_mu() gate_end_mu = now_mu()
# gateware latency offset between gate and input # gateware latency offset between gate and input
lat_offset = 11*8 lat_offset = 11*8
out_mu = gate_start_mu - loop_delay_mu + lat_offset out_mu = gate_start_mu - loop_delay_mu + lat_offset
at_mu(out_mu) at_mu(out_mu)
self.loop_out.pulse_mu(24) self.loop_out.pulse_mu(48) # XXX
in_mu = self.loop_in.timestamp_mu(gate_end_mu) in_mu = self.loop_in.timestamp_mu(gate_end_mu)
print("timings: ", gate_start_mu, in_mu - lat_offset, gate_end_mu) print("timings: ", gate_start_mu, in_mu - lat_offset, gate_end_mu)
@ -460,10 +462,14 @@ class CoredeviceTest(ExperimentCase):
def test_pulse_rate(self): def test_pulse_rate(self):
"""Minimum interval for sustained TTL output switching""" """Minimum interval for sustained TTL output switching"""
self.execute(PulseRate) exp = self.execute(PulseRate)
rate = self.dataset_mgr.get("pulse_rate") rate = self.dataset_mgr.get("pulse_rate")
print(rate) print(rate)
self.assertGreater(rate, 100*ns) self.assertGreater(rate, 100*ns)
if exp.core.target_cls == CortexA9Target:
# Crappy AXI PS/PL interface from Xilinx is slow.
self.assertLess(rate, 810*ns)
else:
self.assertLess(rate, 480*ns) self.assertLess(rate, 480*ns)
def test_pulse_rate_ad9914_dds(self): def test_pulse_rate_ad9914_dds(self):
@ -621,11 +627,13 @@ class _DMA(EnvExperiment):
self.delta = now_mu() - start self.delta = now_mu() - start
@kernel @kernel
def playback_many(self, n): def playback_many(self, n, add_delay=False):
handle = self.core_dma.get_handle(self.trace_name) handle = self.core_dma.get_handle(self.trace_name)
self.core.break_realtime() self.core.break_realtime()
t1 = self.core.get_rtio_counter_mu() t1 = self.core.get_rtio_counter_mu()
for i in range(n): for i in range(n):
if add_delay:
delay(2*us)
self.core_dma.playback_handle(handle) self.core_dma.playback_handle(handle)
t2 = self.core.get_rtio_counter_mu() t2 = self.core.get_rtio_counter_mu()
self.set_dataset("dma_playback_time", self.core.mu_to_seconds(t2 - t1)) self.set_dataset("dma_playback_time", self.core.mu_to_seconds(t2 - t1))
@ -718,12 +726,17 @@ class DMATest(ExperimentCase):
self.device_mgr.get_desc("ad9914dds0") self.device_mgr.get_desc("ad9914dds0")
except KeyError: except KeyError:
raise unittest.SkipTest("skipped on Kasli for now") raise unittest.SkipTest("skipped on Kasli for now")
exp = self.create(_DMA) exp = self.create(_DMA)
is_zynq = exp.core.target_cls == CortexA9Target
count = 20000 count = 20000
exp.record_many(40) exp.record_many(40)
exp.playback_many(count) exp.playback_many(count, is_zynq)
dt = self.dataset_mgr.get("dma_playback_time") dt = self.dataset_mgr.get("dma_playback_time")
print("dt={}, dt/count={}".format(dt, dt/count)) print("dt={}, dt/count={}".format(dt, dt/count))
if is_zynq:
self.assertLess(dt/count, 6.2*us)
else:
self.assertLess(dt/count, 4.5*us) self.assertLess(dt/count, 4.5*us)
def test_dma_underflow(self): def test_dma_underflow(self):