Merge branch 'master' into ad9910

This commit is contained in:
occheung 2021-12-07 13:57:30 +08:00
commit 41b2c8d07a
40 changed files with 760 additions and 312 deletions

View File

@ -20,6 +20,8 @@ Highlights:
- Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs. - Exposes upconverter calibration and enabling/disabling of upconverter LO & RF outputs.
- Add helpers to align Phaser updates to the RTIO timeline (``get_next_frame_mu()``) - Add helpers to align Phaser updates to the RTIO timeline (``get_next_frame_mu()``)
* ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912 * ``get()``, ``get_mu()``, ``get_att()``, and ``get_att_mu()`` functions added for AD9910 and AD9912
* On Kasli, the number of FIFO lanes in the scalable events dispatcher (SED) can now be configured in
the JSON hardware description file.
* New hardware support: * New hardware support:
- HVAMP_8CH 8 channel HV amplifier for Fastino / Zotino - HVAMP_8CH 8 channel HV amplifier for Fastino / Zotino
* ``artiq_ddb_template`` generates edge-counter keys that start with the key of the corresponding * ``artiq_ddb_template`` generates edge-counter keys that start with the key of the corresponding
@ -28,6 +30,13 @@ Highlights:
repository when building the list of experiments. repository when building the list of experiments.
* The configuration entry ``rtio_clock`` supports multiple clocking settings, deprecating the usage * The configuration entry ``rtio_clock`` supports multiple clocking settings, deprecating the usage
of compile-time options. of compile-time options.
* DRTIO: added support for 100MHz clock.
* Previously detected RTIO async errors are reported to the host after each kernel terminates and a
warning is logged. The warning is additional to the one already printed in the core device log upon
detection of the error.
* HDF5 options can now be passed when creating datasets with ``set_dataset``. This allows
in particular to use transparent compression filters as follows:
``set_dataset(name, value, hdf5_options={"compression": "gzip"})``.
Breaking changes: Breaking changes:
@ -38,6 +47,11 @@ Breaking changes:
* Phaser: fixed coarse mixer frequency configuration * Phaser: fixed coarse mixer frequency configuration
* Mirny: Added extra delays in ``ADF5356.sync()``. This avoids the need of an extra delay before * Mirny: Added extra delays in ``ADF5356.sync()``. This avoids the need of an extra delay before
calling `ADF5356.init()`. calling `ADF5356.init()`.
* DRTIO: Changed message alignment from 32-bits to 64-bits.
* The deprecated ``set_dataset(..., save=...)`` is no longer supported.
* The internal dataset representation was changed to support tracking HDF5 options like e.g.
a compression method. This requires changes to code reading the dataset persistence file
(``dataset_db.pyon``) and to custom applets.
ARTIQ-6 ARTIQ-6
@ -107,9 +121,13 @@ Breaking changes:
* ``quamash`` has been replaced with ``qasync``. * ``quamash`` has been replaced with ``qasync``.
* Protocols are updated to use device endian. * Protocols are updated to use device endian.
* Analyzer dump format includes a byte for device endianness. * Analyzer dump format includes a byte for device endianness.
* To support variable numbers of Urukul cards in the future, the
``artiq.coredevice.suservo.SUServo`` constructor now accepts two device name lists,
``cpld_devices`` and ``dds_devices``, rather than four individual arguments.
* Experiment classes with underscore-prefixed names are now ignored when ``artiq_client`` * Experiment classes with underscore-prefixed names are now ignored when ``artiq_client``
determines which experiment to submit (consistent with ``artiq_run``). determines which experiment to submit (consistent with ``artiq_run``).
ARTIQ-5 ARTIQ-5
------- -------

View File

@ -13,7 +13,7 @@ class NumberWidget(QtWidgets.QLCDNumber):
def data_changed(self, data, mods): def data_changed(self, data, mods):
try: try:
n = float(data[self.dataset_name][1]) n = float(data[self.dataset_name]["value"])
except (KeyError, ValueError, TypeError): except (KeyError, ValueError, TypeError):
n = "---" n = "---"
self.display(n) self.display(n)

View File

@ -13,7 +13,7 @@ class Image(pyqtgraph.ImageView):
def data_changed(self, data, mods): def data_changed(self, data, mods):
try: try:
img = data[self.args.img][1] img = data[self.args.img]["value"]
except KeyError: except KeyError:
return return
self.setImage(img) self.setImage(img)

View File

@ -17,11 +17,11 @@ class HistogramPlot(pyqtgraph.PlotWidget):
def data_changed(self, data, mods, title): def data_changed(self, data, mods, title):
try: try:
y = data[self.args.y][1] y = data[self.args.y]["value"]
if self.args.x is None: if self.args.x is None:
x = None x = None
else: else:
x = data[self.args.x][1] x = data[self.args.x]["value"]
except KeyError: except KeyError:
return return
if x is None: if x is None:

View File

@ -6,6 +6,7 @@ from PyQt5.QtCore import QTimer
import pyqtgraph import pyqtgraph
from artiq.applets.simple import TitleApplet from artiq.applets.simple import TitleApplet
from artiq.master.databases import make_dataset as empty_dataset
class XYPlot(pyqtgraph.PlotWidget): class XYPlot(pyqtgraph.PlotWidget):
@ -21,14 +22,14 @@ class XYPlot(pyqtgraph.PlotWidget):
def data_changed(self, data, mods, title): def data_changed(self, data, mods, title):
try: try:
y = data[self.args.y][1] y = data[self.args.y]["value"]
except KeyError: except KeyError:
return return
x = data.get(self.args.x, (False, None))[1] x = data.get(self.args.x, empty_dataset())["value"]
if x is None: if x is None:
x = np.arange(len(y)) x = np.arange(len(y))
error = data.get(self.args.error, (False, None))[1] error = data.get(self.args.error, empty_dataset())["value"]
fit = data.get(self.args.fit, (False, None))[1] fit = data.get(self.args.fit, empty_dataset())["value"]
if not len(y) or len(y) != len(x): if not len(y) or len(y) != len(x):
self.mismatch['X values'] = True self.mismatch['X values'] = True

View File

@ -126,9 +126,9 @@ class XYHistPlot(QtWidgets.QSplitter):
def data_changed(self, data, mods): def data_changed(self, data, mods):
try: try:
xs = data[self.args.xs][1] xs = data[self.args.xs]["value"]
histogram_bins = data[self.args.histogram_bins][1] histogram_bins = data[self.args.histogram_bins]["value"]
histograms_counts = data[self.args.histograms_counts][1] histograms_counts = data[self.args.histograms_counts]["value"]
except KeyError: except KeyError:
return return
if len(xs) != histograms_counts.shape[0]: if len(xs) != histograms_counts.shape[0]:

View File

@ -10,6 +10,7 @@ from sipyco.sync_struct import Subscriber, process_mod
from sipyco import pyon from sipyco import pyon
from sipyco.pipe_ipc import AsyncioChildComm from sipyco.pipe_ipc import AsyncioChildComm
from artiq.master.databases import make_dataset as empty_dataset
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -251,7 +252,7 @@ class TitleApplet(SimpleApplet):
def emit_data_changed(self, data, mod_buffer): def emit_data_changed(self, data, mod_buffer):
if self.args.title is not None: if self.args.title is not None:
title_values = {k.replace(".", "/"): data.get(k, (False, None))[1] title_values = {k.replace(".", "/"): data.get(k, empty_dataset())["value"]
for k in self.dataset_title} for k in self.dataset_title}
try: try:
title = self.args.title.format(**title_values) title = self.args.title.format(**title_values)

View File

@ -104,8 +104,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
idx = self.table_model_filter.mapToSource(idx[0]) idx = self.table_model_filter.mapToSource(idx[0])
key = self.table_model.index_to_key(idx) key = self.table_model.index_to_key(idx)
if key is not None: if key is not None:
persist, value = self.table_model.backing_store[key] dataset = self.table_model.backing_store[key]
asyncio.ensure_future(self._upload_dataset(key, value)) asyncio.ensure_future(self._upload_dataset(key, dataset["value"]))
def save_state(self): def save_state(self):
return bytes(self.table.header().saveState()) return bytes(self.table.header().saveState())

View File

@ -1174,26 +1174,32 @@ class LLVMIRGenerator:
else: else:
llfun = self.map(insn.static_target_function) llfun = self.map(insn.static_target_function)
llenv = self.llbuilder.extract_value(llclosure, 0, name="env.fun") llenv = self.llbuilder.extract_value(llclosure, 0, name="env.fun")
return llfun, [llenv] + list(llargs) return llfun, [llenv] + list(llargs), {}
def _prepare_ffi_call(self, insn): def _prepare_ffi_call(self, insn):
llargs = [] llargs = []
byvals = [] llarg_attrs = {}
for i, arg in enumerate(insn.arguments()): for i, arg in enumerate(insn.arguments()):
llarg = self.map(arg) llarg = self.map(arg)
if isinstance(llarg.type, (ll.LiteralStructType, ll.IdentifiedStructType)): if isinstance(llarg.type, (ll.LiteralStructType, ll.IdentifiedStructType)):
llslot = self.llbuilder.alloca(llarg.type) llslot = self.llbuilder.alloca(llarg.type)
self.llbuilder.store(llarg, llslot) self.llbuilder.store(llarg, llslot)
llargs.append(llslot) llargs.append(llslot)
byvals.append(i) llarg_attrs[i] = "byval"
else: else:
llargs.append(llarg) llargs.append(llarg)
llretty = self.llty_of_type(insn.type, for_return=True)
is_sret = self.needs_sret(llretty)
if is_sret:
llarg_attrs = {i + 1: a for (i, a) in llarg_attrs.items()}
llarg_attrs[0] = "sret"
llfunname = insn.target_function().type.name llfunname = insn.target_function().type.name
llfun = self.llmodule.globals.get(llfunname) llfun = self.llmodule.globals.get(llfunname)
if llfun is None: if llfun is None:
llretty = self.llty_of_type(insn.type, for_return=True) # Function has not been declared in the current LLVM module, do it now.
if self.needs_sret(llretty): if is_sret:
llfunty = ll.FunctionType(llvoid, [llretty.as_pointer()] + llfunty = ll.FunctionType(llvoid, [llretty.as_pointer()] +
[llarg.type for llarg in llargs]) [llarg.type for llarg in llargs])
else: else:
@ -1201,17 +1207,14 @@ class LLVMIRGenerator:
llfun = ll.Function(self.llmodule, llfunty, llfun = ll.Function(self.llmodule, llfunty,
insn.target_function().type.name) insn.target_function().type.name)
if self.needs_sret(llretty): for idx, attr in llarg_attrs.items():
llfun.args[0].add_attribute('sret') llfun.args[idx].add_attribute(attr)
byvals = [i + 1 for i in byvals]
for i in byvals:
llfun.args[i].add_attribute('byval')
if 'nounwind' in insn.target_function().type.flags: if 'nounwind' in insn.target_function().type.flags:
llfun.attributes.add('nounwind') llfun.attributes.add('nounwind')
if 'nowrite' in insn.target_function().type.flags: if 'nowrite' in insn.target_function().type.flags:
llfun.attributes.add('inaccessiblememonly') llfun.attributes.add('inaccessiblememonly')
return llfun, list(llargs) return llfun, list(llargs), llarg_attrs
def _build_rpc(self, fun_loc, fun_type, args, llnormalblock, llunwindblock): def _build_rpc(self, fun_loc, fun_type, args, llnormalblock, llunwindblock):
llservice = ll.Constant(lli32, fun_type.service) llservice = ll.Constant(lli32, fun_type.service)
@ -1347,20 +1350,21 @@ class LLVMIRGenerator:
insn.arguments(), insn.arguments(),
llnormalblock=None, llunwindblock=None) llnormalblock=None, llunwindblock=None)
elif types.is_external_function(functiontyp): elif types.is_external_function(functiontyp):
llfun, llargs = self._prepare_ffi_call(insn) llfun, llargs, llarg_attrs = self._prepare_ffi_call(insn)
else: else:
llfun, llargs = self._prepare_closure_call(insn) llfun, llargs, llarg_attrs = self._prepare_closure_call(insn)
if self.has_sret(functiontyp): if self.has_sret(functiontyp):
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), []) llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [])
llresultslot = self.llbuilder.alloca(llfun.type.pointee.args[0].pointee) llresultslot = self.llbuilder.alloca(llfun.type.pointee.args[0].pointee)
llcall = self.llbuilder.call(llfun, [llresultslot] + llargs) self.llbuilder.call(llfun, [llresultslot] + llargs, arg_attrs=llarg_attrs)
llresult = self.llbuilder.load(llresultslot) llresult = self.llbuilder.load(llresultslot)
self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr]) self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr])
else: else:
llcall = llresult = self.llbuilder.call(llfun, llargs, name=insn.name) llresult = self.llbuilder.call(llfun, llargs, name=insn.name,
arg_attrs=llarg_attrs)
if isinstance(llresult.type, ll.VoidType): if isinstance(llresult.type, ll.VoidType):
# We have NoneType-returning functions return void, but None is # We have NoneType-returning functions return void, but None is
@ -1379,16 +1383,17 @@ class LLVMIRGenerator:
insn.arguments(), insn.arguments(),
llnormalblock, llunwindblock) llnormalblock, llunwindblock)
elif types.is_external_function(functiontyp): elif types.is_external_function(functiontyp):
llfun, llargs = self._prepare_ffi_call(insn) llfun, llargs, llarg_attrs = self._prepare_ffi_call(insn)
else: else:
llfun, llargs = self._prepare_closure_call(insn) llfun, llargs, llarg_attrs = self._prepare_closure_call(insn)
if self.has_sret(functiontyp): if self.has_sret(functiontyp):
llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), []) llstackptr = self.llbuilder.call(self.llbuiltin("llvm.stacksave"), [])
llresultslot = self.llbuilder.alloca(llfun.type.pointee.args[0].pointee) llresultslot = self.llbuilder.alloca(llfun.type.pointee.args[0].pointee)
llcall = self.llbuilder.invoke(llfun, [llresultslot] + llargs, llcall = self.llbuilder.invoke(llfun, [llresultslot] + llargs,
llnormalblock, llunwindblock, name=insn.name) llnormalblock, llunwindblock, name=insn.name,
arg_attrs=llarg_attrs)
self.llbuilder.position_at_start(llnormalblock) self.llbuilder.position_at_start(llnormalblock)
llresult = self.llbuilder.load(llresultslot) llresult = self.llbuilder.load(llresultslot)
@ -1396,7 +1401,7 @@ class LLVMIRGenerator:
self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr]) self.llbuilder.call(self.llbuiltin("llvm.stackrestore"), [llstackptr])
else: else:
llcall = self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock, llcall = self.llbuilder.invoke(llfun, llargs, llnormalblock, llunwindblock,
name=insn.name) name=insn.name, arg_attrs=llarg_attrs)
llresult = llcall llresult = llcall
# The !tbaa metadata is not legal to use with the invoke instruction, # The !tbaa metadata is not legal to use with the invoke instruction,

View File

@ -374,18 +374,25 @@ class AD9910:
data[(n - preload) + i] = self.bus.read() data[(n - preload) + i] = self.bus.read()
@kernel @kernel
def set_cfr1(self, power_down: TInt32 = 0b0000, def set_cfr1(self,
power_down: TInt32 = 0b0000,
phase_autoclear: TInt32 = 0, phase_autoclear: TInt32 = 0,
drg_load_lrr: TInt32 = 0, drg_autoclear: TInt32 = 0, drg_load_lrr: TInt32 = 0,
internal_profile: TInt32 = 0, ram_destination: TInt32 = 0, drg_autoclear: TInt32 = 0,
ram_enable: TInt32 = 0, manual_osk_external: TInt32 = 0, phase_clear: TInt32 = 0,
osk_enable: TInt32 = 0, select_auto_osk: TInt32 = 0): internal_profile: TInt32 = 0,
ram_destination: TInt32 = 0,
ram_enable: TInt32 = 0,
manual_osk_external: TInt32 = 0,
osk_enable: TInt32 = 0,
select_auto_osk: TInt32 = 0):
"""Set CFR1. See the AD9910 datasheet for parameter meanings. """Set CFR1. See the AD9910 datasheet for parameter meanings.
This method does not pulse IO_UPDATE. This method does not pulse IO_UPDATE.
:param power_down: Power down bits. :param power_down: Power down bits.
:param phase_autoclear: Autoclear phase accumulator. :param phase_autoclear: Autoclear phase accumulator.
:param phase_clear: Asynchronous, static reset of the phase accumulator.
:param drg_load_lrr: Load digital ramp generator LRR. :param drg_load_lrr: Load digital ramp generator LRR.
:param drg_autoclear: Autoclear digital ramp generator. :param drg_autoclear: Autoclear digital ramp generator.
:param internal_profile: Internal profile control. :param internal_profile: Internal profile control.
@ -405,11 +412,41 @@ class AD9910:
(drg_load_lrr << 15) | (drg_load_lrr << 15) |
(drg_autoclear << 14) | (drg_autoclear << 14) |
(phase_autoclear << 13) | (phase_autoclear << 13) |
(phase_clear << 11) |
(osk_enable << 9) | (osk_enable << 9) |
(select_auto_osk << 8) | (select_auto_osk << 8) |
(power_down << 4) | (power_down << 4) |
2) # SDIO input only, MSB first 2) # SDIO input only, MSB first
@kernel
def set_cfr2(self,
asf_profile_enable: TInt32 = 1,
drg_enable: TInt32 = 0,
effective_ftw: TInt32 = 1,
sync_validation_disable: TInt32 = 0,
matched_latency_enable: TInt32 = 0):
"""Set CFR2. See the AD9910 datasheet for parameter meanings.
This method does not pulse IO_UPDATE.
:param asf_profile_enable: Enable amplitude scale from single tone profiles.
:param drg_enable: Digital ramp enable.
:param effective_ftw: Read effective FTW.
:param sync_validation_disable: Disable the SYNC_SMP_ERR pin indicating
(active high) detection of a synchronization pulse sampling error.
:param matched_latency_enable: Simultaneous application of amplitude,
phase, and frequency changes to the DDS arrive at the output
* matched_latency_enable = 0: in the order listed
* matched_latency_enable = 1: simultaneously.
"""
self.write32(_AD9910_REG_CFR2,
(asf_profile_enable << 24) |
(drg_enable << 19) |
(effective_ftw << 16) |
(matched_latency_enable << 7) |
(sync_validation_disable << 5))
@kernel @kernel
def init(self, blind: TBool = False): def init(self, blind: TBool = False):
"""Initialize and configure the DDS. """Initialize and configure the DDS.
@ -442,7 +479,7 @@ class AD9910:
# enable amplitude scale from profiles # enable amplitude scale from profiles
# read effective FTW # read effective FTW
# sync timing validation disable (enabled later) # sync timing validation disable (enabled later)
self.write32(_AD9910_REG_CFR2, 0x01010020) self.set_cfr2(sync_validation_disable=1)
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1 * us)
cfr3 = (0x0807c000 | (self.pll_vco << 24) | cfr3 = (0x0807c000 | (self.pll_vco << 24) |
(self.pll_cp << 19) | (self.pll_en << 8) | (self.pll_cp << 19) | (self.pll_en << 8) |
@ -465,7 +502,7 @@ class AD9910:
if i >= 100 - 1: if i >= 100 - 1:
raise ValueError("PLL lock timeout") raise ValueError("PLL lock timeout")
delay(10 * us) # slack delay(10 * us) # slack
if self.sync_data.sync_delay_seed >= 0: if self.sync_data.sync_delay_seed >= 0 and not blind:
self.tune_sync_delay(self.sync_data.sync_delay_seed) self.tune_sync_delay(self.sync_data.sync_delay_seed)
delay(1 * ms) delay(1 * ms)
@ -481,7 +518,7 @@ class AD9910:
@kernel @kernel
def set_mu(self, ftw: TInt32, pow_: TInt32 = 0, asf: TInt32 = 0x3fff, def set_mu(self, ftw: TInt32, pow_: TInt32 = 0, asf: TInt32 = 0x3fff,
phase_mode: TInt32 = _PHASE_MODE_DEFAULT, phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 7): ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 7) -> TInt32:
"""Set DDS data in machine units. """Set DDS data in machine units.
This uses machine units (FTW, POW, ASF). The frequency tuning word This uses machine units (FTW, POW, ASF). The frequency tuning word
@ -792,7 +829,7 @@ class AD9910:
@kernel @kernel
def set(self, frequency: TFloat, phase: TFloat = 0.0, def set(self, frequency: TFloat, phase: TFloat = 0.0,
amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT, amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 7): ref_time_mu: TInt64 = int64(-1), profile: TInt32 = 7) -> TFloat:
"""Set DDS data in SI units. """Set DDS data in SI units.
.. seealso:: :meth:`set_mu` .. seealso:: :meth:`set_mu`
@ -881,20 +918,26 @@ class AD9910:
self.cpld.cfg_sw(self.chip_select - 4, state) self.cpld.cfg_sw(self.chip_select - 4, state)
@kernel @kernel
def set_sync(self, in_delay: TInt32, window: TInt32): def set_sync(self,
in_delay: TInt32,
window: TInt32,
en_sync_gen: TInt32 = 0):
"""Set the relevant parameters in the multi device synchronization """Set the relevant parameters in the multi device synchronization
register. See the AD9910 datasheet for details. The SYNC clock register. See the AD9910 datasheet for details. The SYNC clock
generator preset value is set to zero, and the SYNC_OUT generator is generator preset value is set to zero, and the SYNC_OUT generator is
disabled. disabled by default.
:param in_delay: SYNC_IN delay tap (0-31) in steps of ~75ps :param in_delay: SYNC_IN delay tap (0-31) in steps of ~75ps
:param window: Symmetric SYNC_IN validation window (0-15) in :param window: Symmetric SYNC_IN validation window (0-15) in
steps of ~75ps for both hold and setup margin. steps of ~75ps for both hold and setup margin.
:param en_sync_gen: Whether to enable the DDS-internal sync generator
(SYNC_OUT, cf. sync_sel == 1). Should be left off for the normal
use case, where the SYNC clock is supplied by the core device.
""" """
self.write32(_AD9910_REG_SYNC, self.write32(_AD9910_REG_SYNC,
(window << 28) | # SYNC S/H validation delay (window << 28) | # SYNC S/H validation delay
(1 << 27) | # SYNC receiver enable (1 << 27) | # SYNC receiver enable
(0 << 26) | # SYNC generator disable (en_sync_gen << 26) | # SYNC generator enable
(0 << 25) | # SYNC generator SYS rising edge (0 << 25) | # SYNC generator SYS rising edge
(0 << 18) | # SYNC preset (0 << 18) | # SYNC preset
(0 << 11) | # SYNC output delay (0 << 11) | # SYNC output delay
@ -910,9 +953,10 @@ class AD9910:
Also modifies CFR2. Also modifies CFR2.
""" """
self.write32(_AD9910_REG_CFR2, 0x01010020) # clear SMP_ERR self.set_cfr2(sync_validation_disable=1) # clear SMP_ERR
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1 * us)
self.write32(_AD9910_REG_CFR2, 0x01010000) # enable SMP_ERR delay(10 * us) # slack
self.set_cfr2(sync_validation_disable=0) # enable SMP_ERR
self.cpld.io_update.pulse(1 * us) self.cpld.io_update.pulse(1 * us)
@kernel @kernel
@ -990,7 +1034,7 @@ class AD9910:
# set up DRG # set up DRG
self.set_cfr1(drg_load_lrr=1, drg_autoclear=1) self.set_cfr1(drg_load_lrr=1, drg_autoclear=1)
# DRG -> FTW, DRG enable # DRG -> FTW, DRG enable
self.write32(_AD9910_REG_CFR2, 0x01090000) self.set_cfr2(drg_enable=1)
# no limits # no limits
self.write64(_AD9910_REG_RAMP_LIMIT, -1, 0) self.write64(_AD9910_REG_RAMP_LIMIT, -1, 0)
# DRCTL=0, dt=1 t_SYNC_CLK # DRCTL=0, dt=1 t_SYNC_CLK
@ -1011,7 +1055,7 @@ class AD9910:
ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW
delay(100 * us) # slack delay(100 * us) # slack
# disable DRG # disable DRG
self.write32(_AD9910_REG_CFR2, 0x01010000) self.set_cfr2(drg_enable=0)
self.cpld.io_update.pulse_mu(8) self.cpld.io_update.pulse_mu(8)
return ftw & 1 return ftw & 1

View File

@ -156,7 +156,7 @@ class AD9912:
return self.cpld.get_channel_att(self.chip_select - 4) return self.cpld.get_channel_att(self.chip_select - 4)
@kernel @kernel
def set_mu(self, ftw: TInt64, pow_: TInt32): def set_mu(self, ftw: TInt64, pow_: TInt32 = 0):
"""Set profile 0 data in machine units. """Set profile 0 data in machine units.
After the SPI transfer, the shared IO update pin is pulsed to After the SPI transfer, the shared IO update pin is pulsed to

View File

@ -621,6 +621,7 @@ class CommKernel:
function = self._read_string() function = self._read_string()
backtrace = [self._read_int32() for _ in range(self._read_int32())] backtrace = [self._read_int32() for _ in range(self._read_int32())]
self._process_async_error()
traceback = list(reversed(symbolizer(backtrace))) + \ traceback = list(reversed(symbolizer(backtrace))) + \
[(filename, line, column, *demangler([function]), None)] [(filename, line, column, *demangler([function]), None)]
@ -635,6 +636,16 @@ class CommKernel:
python_exn.artiq_core_exception = core_exn python_exn.artiq_core_exception = core_exn
raise python_exn raise python_exn
def _process_async_error(self):
errors = self._read_int8()
if errors > 0:
map_name = lambda y, z: [f"{y}(s)"] if z else []
errors = map_name("collision", errors & 2 ** 0) + \
map_name("busy error", errors & 2 ** 1) + \
map_name("sequence error", errors & 2 ** 2)
logger.warning(f"{(', '.join(errors[:-1]) + ' and ') if len(errors) > 1 else ''}{errors[-1]} "
f"reported during kernel execution")
def serve(self, embedding_map, symbolizer, demangler): def serve(self, embedding_map, symbolizer, demangler):
while True: while True:
self._read_header() self._read_header()
@ -646,4 +657,5 @@ class CommKernel:
raise exceptions.ClockFailure raise exceptions.ClockFailure
else: else:
self._read_expect(Reply.KernelFinished) self._read_expect(Reply.KernelFinished)
self._process_async_error()
return return

View File

@ -64,6 +64,13 @@
"type": "boolean", "type": "boolean",
"default": false "default": false
}, },
"sed_lanes": {
"type": "number",
"minimum": 1,
"maximum": 32,
"default": 8,
"description": "Number of FIFOs in the SED, must be a power of 2"
},
"peripherals": { "peripherals": {
"type": "array", "type": "array",
"items": { "items": {

View File

@ -57,32 +57,26 @@ class SUServo:
:param channel: RTIO channel number :param channel: RTIO channel number
:param pgia_device: Name of the Sampler PGIA gain setting SPI bus :param pgia_device: Name of the Sampler PGIA gain setting SPI bus
:param cpld0_device: Name of the first Urukul CPLD SPI bus :param cpld_devices: Names of the Urukul CPLD SPI buses
:param cpld1_device: Name of the second Urukul CPLD SPI bus :param dds_devices: Names of the AD9910 devices
:param dds0_device: Name of the AD9910 device for the DDS on the first
Urukul
:param dds1_device: Name of the AD9910 device for the DDS on the second
Urukul
:param gains: Initial value for PGIA gains shift register :param gains: Initial value for PGIA gains shift register
(default: 0x0000). Knowledge of this state is not transferred (default: 0x0000). Knowledge of this state is not transferred
between experiments. between experiments.
:param core_device: Core device name :param core_device: Core device name
""" """
kernel_invariants = {"channel", "core", "pgia", "cpld0", "cpld1", kernel_invariants = {"channel", "core", "pgia", "cplds", "ddses",
"dds0", "dds1", "ref_period_mu"} "ref_period_mu"}
def __init__(self, dmgr, channel, pgia_device, def __init__(self, dmgr, channel, pgia_device,
cpld0_device, cpld1_device, cpld_devices, dds_devices,
dds0_device, dds1_device,
gains=0x0000, core_device="core"): gains=0x0000, core_device="core"):
self.core = dmgr.get(core_device) self.core = dmgr.get(core_device)
self.pgia = dmgr.get(pgia_device) self.pgia = dmgr.get(pgia_device)
self.pgia.update_xfer_duration_mu(div=4, length=16) self.pgia.update_xfer_duration_mu(div=4, length=16)
self.dds0 = dmgr.get(dds0_device) assert len(dds_devices) == len(cpld_devices)
self.dds1 = dmgr.get(dds1_device) self.ddses = [dmgr.get(dds) for dds in dds_devices]
self.cpld0 = dmgr.get(cpld0_device) self.cplds = [dmgr.get(cpld) for cpld in cpld_devices]
self.cpld1 = dmgr.get(cpld1_device)
self.channel = channel self.channel = channel
self.gains = gains self.gains = gains
self.ref_period_mu = self.core.seconds_to_mu( self.ref_period_mu = self.core.seconds_to_mu(
@ -109,17 +103,15 @@ class SUServo:
sampler.SPI_CONFIG | spi.SPI_END, sampler.SPI_CONFIG | spi.SPI_END,
16, 4, sampler.SPI_CS_PGIA) 16, 4, sampler.SPI_CS_PGIA)
self.cpld0.init(blind=True) for i in range(len(self.cplds)):
cfg0 = self.cpld0.cfg_reg cpld = self.cplds[i]
self.cpld0.cfg_write(cfg0 | (0xf << urukul.CFG_MASK_NU)) dds = self.ddses[i]
self.dds0.init(blind=True)
self.cpld0.cfg_write(cfg0)
self.cpld1.init(blind=True) cpld.init(blind=True)
cfg1 = self.cpld1.cfg_reg prev_cpld_cfg = cpld.cfg_reg
self.cpld1.cfg_write(cfg1 | (0xf << urukul.CFG_MASK_NU)) cpld.cfg_write(prev_cpld_cfg | (0xf << urukul.CFG_MASK_NU))
self.dds1.init(blind=True) dds.init(blind=True)
self.cpld1.cfg_write(cfg1) cpld.cfg_write(prev_cpld_cfg)
@kernel @kernel
def write(self, addr, value): def write(self, addr, value):
@ -257,9 +249,11 @@ class Channel:
self.servo = dmgr.get(servo_device) self.servo = dmgr.get(servo_device)
self.core = self.servo.core self.core = self.servo.core
self.channel = channel self.channel = channel
# FIXME: this assumes the mem channel is right after the control # This assumes the mem channel is right after the control channels
# channels # Make sure this is always the case in eem.py
self.servo_channel = self.channel + 8 - self.servo.channel self.servo_channel = (self.channel + 4 * len(self.servo.cplds) -
self.servo.channel)
self.dds = self.servo.ddses[self.servo_channel // 4]
@kernel @kernel
def set(self, en_out, en_iir=0, profile=0): def set(self, en_out, en_iir=0, profile=0):
@ -311,12 +305,8 @@ class Channel:
see :meth:`dds_offset_to_mu` see :meth:`dds_offset_to_mu`
:param phase: DDS phase in turns :param phase: DDS phase in turns
""" """
if self.servo_channel < 4: ftw = self.dds.frequency_to_ftw(frequency)
dds = self.servo.dds0 pow_ = self.dds.turns_to_pow(phase)
else:
dds = self.servo.dds1
ftw = dds.frequency_to_ftw(frequency)
pow_ = dds.turns_to_pow(phase)
offs = self.dds_offset_to_mu(offset) offs = self.dds_offset_to_mu(offset)
self.set_dds_mu(profile, ftw, offs, pow_) self.set_dds_mu(profile, ftw, offs, pow_)

View File

@ -146,16 +146,16 @@ class Creator(QtWidgets.QDialog):
class Model(DictSyncTreeSepModel): class Model(DictSyncTreeSepModel):
def __init__(self, init): def __init__(self, init):
DictSyncTreeSepModel.__init__(self, ".", DictSyncTreeSepModel.__init__(
["Dataset", "Persistent", "Value"], self, ".", ["Dataset", "Persistent", "Value"], init
init) )
def convert(self, k, v, column): def convert(self, k, v, column):
if column == 1: if column == 1:
return "Y" if v[0] else "N" return "Y" if v["persist"] else "N"
elif column == 2: elif column == 2:
return short_format(v[1]) return short_format(v["value"])
else: else:
raise ValueError raise ValueError
@ -223,8 +223,8 @@ class DatasetsDock(QtWidgets.QDockWidget):
idx = self.table_model_filter.mapToSource(idx[0]) idx = self.table_model_filter.mapToSource(idx[0])
key = self.table_model.index_to_key(idx) key = self.table_model.index_to_key(idx)
if key is not None: if key is not None:
persist, value = self.table_model.backing_store[key] dataset = self.table_model.backing_store[key]
t = type(value) t = type(dataset["value"])
if np.issubdtype(t, np.number): if np.issubdtype(t, np.number):
dialog_cls = NumberEditor dialog_cls = NumberEditor
elif np.issubdtype(t, np.bool_): elif np.issubdtype(t, np.bool_):
@ -235,7 +235,7 @@ class DatasetsDock(QtWidgets.QDockWidget):
logger.error("Cannot edit dataset %s: " logger.error("Cannot edit dataset %s: "
"type %s is not supported", key, t) "type %s is not supported", key, t)
return return
dialog_cls(self, self.dataset_ctl, key, value).open() dialog_cls(self, self.dataset_ctl, key, dataset["value"]).open()
def delete_clicked(self): def delete_clicked(self):
idx = self.table.selectedIndexes() idx = self.table.selectedIndexes()

View File

@ -191,10 +191,8 @@ device_db = {
"arguments": { "arguments": {
"channel": 24, "channel": 24,
"pgia_device": "spi_sampler0_pgia", "pgia_device": "spi_sampler0_pgia",
"cpld0_device": "urukul0_cpld", "cpld_devices": ["urukul0_cpld", "urukul1_cpld"],
"cpld1_device": "urukul1_cpld", "dds_devices": ["urukul0_dds", "urukul1_dds"],
"dds0_device": "urukul0_dds",
"dds1_device": "urukul1_dds"
} }
}, },

View File

@ -137,11 +137,10 @@ pub fn send(linkno: u8, packet: &Packet) -> Result<(), Error<!>> {
packet.write_to(&mut writer)?; packet.write_to(&mut writer)?;
let padding = 4 - (writer.position() % 4); // Pad till offset 4, insert checksum there
if padding != 4 { let padding = (12 - (writer.position() % 8)) % 8;
for _ in 0..padding { for _ in 0..padding {
writer.write_u8(0)?; writer.write_u8(0)?;
}
} }
let checksum = crc::crc32::checksum_ieee(&writer.get_ref()[0..writer.position()]); let checksum = crc::crc32::checksum_ieee(&writer.get_ref()[0..writer.position()]);

View File

@ -90,7 +90,9 @@ pub enum Reply<'a> {
LoadCompleted, LoadCompleted,
LoadFailed(&'a str), LoadFailed(&'a str),
KernelFinished, KernelFinished {
async_errors: u8
},
KernelStartupFailed, KernelStartupFailed,
KernelException { KernelException {
name: &'a str, name: &'a str,
@ -100,7 +102,8 @@ pub enum Reply<'a> {
line: u32, line: u32,
column: u32, column: u32,
function: &'a str, function: &'a str,
backtrace: &'a [usize] backtrace: &'a [usize],
async_errors: u8
}, },
RpcRequest { async: bool }, RpcRequest { async: bool },
@ -160,14 +163,16 @@ impl<'a> Reply<'a> {
writer.write_string(reason)?; writer.write_string(reason)?;
}, },
Reply::KernelFinished => { Reply::KernelFinished { async_errors } => {
writer.write_u8(7)?; writer.write_u8(7)?;
writer.write_u8(async_errors)?;
}, },
Reply::KernelStartupFailed => { Reply::KernelStartupFailed => {
writer.write_u8(8)?; writer.write_u8(8)?;
}, },
Reply::KernelException { Reply::KernelException {
name, message, param, file, line, column, function, backtrace name, message, param, file, line, column, function, backtrace,
async_errors
} => { } => {
writer.write_u8(9)?; writer.write_u8(9)?;
writer.write_string(name)?; writer.write_string(name)?;
@ -183,6 +188,7 @@ impl<'a> Reply<'a> {
for &addr in backtrace { for &addr in backtrace {
writer.write_u32(addr as u32)? writer.write_u32(addr as u32)?
} }
writer.write_u8(async_errors)?;
}, },
Reply::RpcRequest { async } => { Reply::RpcRequest { async } => {

View File

@ -326,6 +326,14 @@ pub mod drtio {
pub fn reset(_io: &Io, _aux_mutex: &Mutex) {} pub fn reset(_io: &Io, _aux_mutex: &Mutex) {}
} }
static mut SEEN_ASYNC_ERRORS: u8 = 0;
pub unsafe fn get_async_errors() -> u8 {
let mut errors = SEEN_ASYNC_ERRORS;
SEEN_ASYNC_ERRORS = 0;
errors
}
fn async_error_thread(io: Io) { fn async_error_thread(io: Io) {
loop { loop {
unsafe { unsafe {
@ -343,6 +351,7 @@ fn async_error_thread(io: Io) {
error!("RTIO sequence error involving channel {}", error!("RTIO sequence error involving channel {}",
csr::rtio_core::sequence_error_channel_read()); csr::rtio_core::sequence_error_channel_read());
} }
SEEN_ASYNC_ERRORS = errors;
csr::rtio_core::async_error_write(errors); csr::rtio_core::async_error_write(errors);
} }
} }

View File

@ -9,6 +9,7 @@ use urc::Urc;
use sched::{ThreadHandle, Io, Mutex, TcpListener, TcpStream, Error as SchedError}; use sched::{ThreadHandle, Io, Mutex, TcpListener, TcpStream, Error as SchedError};
use rtio_clocking; use rtio_clocking;
use rtio_dma::Manager as DmaManager; use rtio_dma::Manager as DmaManager;
use rtio_mgt::get_async_errors;
use cache::Cache; use cache::Cache;
use kern_hwreq; use kern_hwreq;
use board_artiq::drtio_routing; use board_artiq::drtio_routing;
@ -431,7 +432,9 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex,
match stream { match stream {
None => return Ok(true), None => return Ok(true),
Some(ref mut stream) => Some(ref mut stream) =>
host_write(stream, host::Reply::KernelFinished).map_err(|e| e.into()) host_write(stream, host::Reply::KernelFinished {
async_errors: unsafe { get_async_errors() }
}).map_err(|e| e.into())
} }
} }
&kern::RunException { &kern::RunException {
@ -458,7 +461,8 @@ fn process_kern_message(io: &Io, aux_mutex: &Mutex,
line: line, line: line,
column: column, column: column,
function: function, function: function,
backtrace: backtrace backtrace: backtrace,
async_errors: unsafe { get_async_errors() }
}).map_err(|e| e.into()) }).map_err(|e| e.into())
} }
} }

View File

@ -447,6 +447,19 @@ const SI5324_SETTINGS: si5324::FrequencySettings
crystal_ref: true crystal_ref: true
}; };
#[cfg(all(has_si5324, rtio_frequency = "100.0"))]
const SI5324_SETTINGS: si5324::FrequencySettings
= si5324::FrequencySettings {
n1_hs : 5,
nc1_ls : 10,
n2_hs : 10,
n2_ls : 250,
n31 : 50,
n32 : 50,
bwsel : 4,
crystal_ref: true
};
#[no_mangle] #[no_mangle]
pub extern fn main() -> i32 { pub extern fn main() -> i32 {
extern { extern {

View File

@ -364,8 +364,7 @@ class PeripheralManager:
def process_suservo(self, rtio_offset, peripheral): def process_suservo(self, rtio_offset, peripheral):
suservo_name = self.get_name("suservo") suservo_name = self.get_name("suservo")
sampler_name = self.get_name("sampler") sampler_name = self.get_name("sampler")
urukul0_name = self.get_name("urukul") urukul_names = [self.get_name("urukul") for _ in range(2)]
urukul1_name = self.get_name("urukul")
channel = count(0) channel = count(0)
for i in range(8): for i in range(8):
self.gen(""" self.gen("""
@ -386,16 +385,14 @@ class PeripheralManager:
"arguments": {{ "arguments": {{
"channel": 0x{suservo_channel:06x}, "channel": 0x{suservo_channel:06x},
"pgia_device": "spi_{sampler_name}_pgia", "pgia_device": "spi_{sampler_name}_pgia",
"cpld0_device": "{urukul0_name}_cpld", "cpld_devices": {cpld_names_list},
"cpld1_device": "{urukul1_name}_cpld", "dds_devices": {dds_names_list}
"dds0_device": "{urukul0_name}_dds",
"dds1_device": "{urukul1_name}_dds"
}} }}
}}""", }}""",
suservo_name=suservo_name, suservo_name=suservo_name,
sampler_name=sampler_name, sampler_name=sampler_name,
urukul0_name=urukul0_name, cpld_names_list=[urukul_name + "_cpld" for urukul_name in urukul_names],
urukul1_name=urukul1_name, dds_names_list=[urukul_name + "_dds" for urukul_name in urukul_names],
suservo_channel=rtio_offset+next(channel)) suservo_channel=rtio_offset+next(channel))
self.gen(""" self.gen("""
device_db["spi_{sampler_name}_pgia"] = {{ device_db["spi_{sampler_name}_pgia"] = {{
@ -407,7 +404,7 @@ class PeripheralManager:
sampler_name=sampler_name, sampler_name=sampler_name,
sampler_channel=rtio_offset+next(channel)) sampler_channel=rtio_offset+next(channel))
pll_vco = peripheral.get("pll_vco") pll_vco = peripheral.get("pll_vco")
for urukul_name in (urukul0_name, urukul1_name): for urukul_name in urukul_names:
self.gen(""" self.gen("""
device_db["spi_{urukul_name}"] = {{ device_db["spi_{urukul_name}"] = {{
"type": "local", "type": "local",

View File

@ -362,7 +362,10 @@ def main():
variants.remove("rtm") variants.remove("rtm")
except ValueError: except ValueError:
pass pass
if len(variants) == 0: if all(action in ["rtm_gateware", "storage", "rtm_load", "erase", "start"]
for action in args.action) and args.action:
pass
elif len(variants) == 0:
raise FileNotFoundError("no variants found, did you install a board binary package?") raise FileNotFoundError("no variants found, did you install a board binary package?")
elif len(variants) == 1: elif len(variants) == 1:
variant = variants[0] variant = variants[0]

View File

@ -229,6 +229,17 @@ class SinaraTester(EnvExperiment):
self.core.break_realtime() self.core.break_realtime()
cpld.init() cpld.init()
@kernel
def test_urukul_att(self, cpld):
self.core.break_realtime()
for i in range(32):
test_word = 1 << i
cpld.set_all_att_mu(test_word)
readback_word = cpld.get_att_mu()
if readback_word != test_word:
print(readback_word, test_word)
raise ValueError
@kernel @kernel
def calibrate_urukul(self, channel): def calibrate_urukul(self, channel):
self.core.break_realtime() self.core.break_realtime()
@ -268,11 +279,12 @@ class SinaraTester(EnvExperiment):
def test_urukuls(self): def test_urukuls(self):
print("*** Testing Urukul DDSes.") print("*** Testing Urukul DDSes.")
print("Initializing CPLDs...")
for name, cpld in sorted(self.urukul_cplds.items(), key=lambda x: x[0]): for name, cpld in sorted(self.urukul_cplds.items(), key=lambda x: x[0]):
print(name + "...") print(name + ": initializing CPLD...")
self.init_urukul(cpld) self.init_urukul(cpld)
print("...done") print(name + ": testing attenuator digital control...")
self.test_urukul_att(cpld)
print(name + ": done")
print("Calibrating inter-device synchronization...") print("Calibrating inter-device synchronization...")
for channel_name, channel_dev in self.urukuls: for channel_name, channel_dev in self.urukuls:

View File

@ -4,7 +4,7 @@ from migen.genlib.cdc import MultiReg, PulseSynchronizer
from misoc.interconnect.csr import * from misoc.interconnect.csr import *
# This code assumes 125/62.5MHz reference clock and 125MHz or 150MHz RTIO # This code assumes 125/62.5MHz reference clock and 100MHz, 125MHz or 150MHz RTIO
# frequency. # frequency.
class SiPhaser7Series(Module, AutoCSR): class SiPhaser7Series(Module, AutoCSR):
@ -15,9 +15,9 @@ class SiPhaser7Series(Module, AutoCSR):
self.phase_shift_done = CSRStatus(reset=1) self.phase_shift_done = CSRStatus(reset=1)
self.error = CSR() self.error = CSR()
assert rtio_clk_freq in (125e6, 150e6) assert rtio_clk_freq in (100e6, 125e6, 150e6)
# 125MHz/62.5MHz reference clock to 125MHz/150MHz. VCO @ 750MHz. # 125MHz/62.5MHz reference clock to 100MHz/125MHz/150MHz. VCO @ 750MHz.
# Used to provide a startup clock to the transceiver through the Si, # Used to provide a startup clock to the transceiver through the Si,
# we do not use the crystal reference so that the PFD (f3) frequency # we do not use the crystal reference so that the PFD (f3) frequency
# can be high. # can be high.
@ -43,8 +43,8 @@ class SiPhaser7Series(Module, AutoCSR):
else: else:
mmcm_freerun_output = mmcm_freerun_output_raw mmcm_freerun_output = mmcm_freerun_output_raw
# 125MHz/150MHz to 125MHz/150MHz with controllable phase shift, # 100MHz/125MHz/150MHz to 100MHz/125MHz/150MHz with controllable phase shift,
# VCO @ 1000MHz/1200MHz. # VCO @ 800MHz/1000MHz/1200MHz.
# Inserted between CDR and output to Si, used to correct # Inserted between CDR and output to Si, used to correct
# non-determinstic skew of Si5324. # non-determinstic skew of Si5324.
mmcm_ps_fb = Signal() mmcm_ps_fb = Signal()

View File

@ -0,0 +1,10 @@
"""Gateware implementation of the Sampler-Urukul (AD9910) DDS amplitude servo.
General conventions:
- ``t_...`` signals and constants refer to time spans measured in the gateware
module's default clock (typically a 125 MHz RTIO clock).
- ``start`` signals cause modules to proceed with the next servo iteration iff
they are currently idle (i.e. their value is irrelevant while the module is
busy, so they are not necessarily one-clock-period strobes).
"""

View File

@ -1,9 +1,7 @@
from collections import namedtuple from collections import namedtuple
import logging import logging
from migen import * from migen import *
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -222,31 +220,30 @@ class IIR(Module):
assert w.word <= w.coeff # same memory assert w.word <= w.coeff # same memory
assert w.state + w.coeff + 3 <= w.accu assert w.state + w.coeff + 3 <= w.accu
# m_coeff of active profiles should only be accessed during # m_coeff of active profiles should only be accessed externally during
# ~processing # ~processing
self.specials.m_coeff = Memory( self.specials.m_coeff = Memory(
width=2*w.coeff, # Cat(pow/ftw/offset, cfg/a/b) width=2*w.coeff, # Cat(pow/ftw/offset, cfg/a/b)
depth=4 << w.profile + w.channel) depth=4 << w.profile + w.channel)
# m_state[x] should only be read during ~(shifting | # m_state[x] should only be read externally during ~(shifting | loading)
# loading) # m_state[y] of active profiles should only be read externally during
# m_state[y] of active profiles should only be read during
# ~processing # ~processing
self.specials.m_state = Memory( self.specials.m_state = Memory(
width=w.state, # y1,x0,x1 width=w.state, # y1,x0,x1
depth=(1 << w.profile + w.channel) + (2 << w.channel)) depth=(1 << w.profile + w.channel) + (2 << w.channel))
# ctrl should only be updated synchronously # ctrl should only be updated synchronously
self.ctrl = [Record([ self.ctrl = [Record([
("profile", w.profile), ("profile", w.profile),
("en_out", 1), ("en_out", 1),
("en_iir", 1), ("en_iir", 1),
("clip", 1), ("clip", 1),
("stb", 1)]) ("stb", 1)])
for i in range(1 << w.channel)] for i in range(1 << w.channel)]
# only update during ~loading # only update during ~loading
self.adc = [Signal((w.adc, True), reset_less=True) self.adc = [Signal((w.adc, True), reset_less=True)
for i in range(1 << w.channel)] for i in range(1 << w.channel)]
# Cat(ftw0, ftw1, pow, asf) # Cat(ftw0, ftw1, pow, asf)
# only read during ~processing # only read externally during ~processing
self.dds = [Signal(4*w.word, reset_less=True) self.dds = [Signal(4*w.word, reset_less=True)
for i in range(1 << w.channel)] for i in range(1 << w.channel)]
# perform one IIR iteration, start with loading, # perform one IIR iteration, start with loading,
@ -270,100 +267,116 @@ class IIR(Module):
en_iirs = Array([ch.en_iir for ch in self.ctrl]) en_iirs = Array([ch.en_iir for ch in self.ctrl])
clips = Array([ch.clip for ch in self.ctrl]) clips = Array([ch.clip for ch in self.ctrl])
# state counter # Main state machine sequencing the steps of each servo iteration. The
state = Signal(w.channel + 2) # module IDLEs until self.start is asserted, and then runs through LOAD,
# pipeline group activity flags (SR) # PROCESS and SHIFT in order (see description of corresponding flags
stage = Signal(3) # above). The steps share the same memory ports, and are executed
# strictly sequentially.
#
# LOAD/SHIFT just read/write one address per cycle; the duration needed
# to iterate over all channels is determined by counting cycles.
#
# The PROCESSing step is split across a three-stage pipeline, where each
# stage has up to four clock cycles latency. We feed the first stage
# using the (MSBs of) t_current_step, and, after all channels have been
# covered, proceed once the pipeline has completely drained.
self.submodules.fsm = fsm = FSM("IDLE") self.submodules.fsm = fsm = FSM("IDLE")
state_clr = Signal() t_current_step = Signal(w.channel + 2)
stage_en = Signal() t_current_step_clr = Signal()
# pipeline group activity flags (SR)
# 0: load from memory
# 1: compute
# 2: write to output registers (DDS profiles, clip flags)
stages_active = Signal(3)
fsm.act("IDLE", fsm.act("IDLE",
self.done.eq(1), self.done.eq(1),
state_clr.eq(1), t_current_step_clr.eq(1),
If(self.start, If(self.start,
NextState("LOAD") NextState("LOAD")
) )
) )
fsm.act("LOAD", fsm.act("LOAD",
self.loading.eq(1), self.loading.eq(1),
If(state == (1 << w.channel) - 1, If(t_current_step == (1 << w.channel) - 1,
state_clr.eq(1), t_current_step_clr.eq(1),
stage_en.eq(1), NextValue(stages_active[0], 1),
NextState("PROCESS") NextState("PROCESS")
) )
) )
fsm.act("PROCESS", fsm.act("PROCESS",
self.processing.eq(1), self.processing.eq(1),
# this is technically wasting three cycles # this is technically wasting three cycles
# (one for setting stage, and phase=2,3 with stage[2]) # (one for setting stages_active, and phase=2,3 with stages_active[2])
If(stage == 0, If(stages_active == 0,
state_clr.eq(1), t_current_step_clr.eq(1),
NextState("SHIFT") NextState("SHIFT"),
) )
) )
fsm.act("SHIFT", fsm.act("SHIFT",
self.shifting.eq(1), self.shifting.eq(1),
If(state == (2 << w.channel) - 1, If(t_current_step == (2 << w.channel) - 1,
NextState("IDLE") NextState("IDLE")
) )
) )
self.sync += [ self.sync += [
state.eq(state + 1), If(t_current_step_clr,
If(state_clr, t_current_step.eq(0)
state.eq(0), ).Else(
), t_current_step.eq(t_current_step + 1)
If(stage_en,
stage[0].eq(1)
) )
] ]
# pipeline group channel pointer # global pipeline phase (lower two bits of t_current_step)
pipeline_phase = Signal(2, reset_less=True)
# pipeline group channel pointer (SR)
# for each pipeline stage, this is the channel currently being # for each pipeline stage, this is the channel currently being
# processed # processed
channel = [Signal(w.channel, reset_less=True) for i in range(3)] channel = [Signal(w.channel, reset_less=True) for i in range(3)]
self.comb += Cat(pipeline_phase, channel[0]).eq(t_current_step)
self.sync += [
If(pipeline_phase == 3,
Cat(channel[1:]).eq(Cat(channel[:-1])),
stages_active[1:].eq(stages_active[:-1]),
If(channel[0] == (1 << w.channel) - 1,
stages_active[0].eq(0)
)
)
]
# pipeline group profile pointer (SR) # pipeline group profile pointer (SR)
# for each pipeline stage, this is the profile currently being # for each pipeline stage, this is the profile currently being
# processed # processed
profile = [Signal(w.profile, reset_less=True) for i in range(2)] profile = [Signal(w.profile, reset_less=True) for i in range(2)]
# pipeline phase (lower two bits of state)
phase = Signal(2, reset_less=True)
self.comb += Cat(phase, channel[0]).eq(state)
self.sync += [ self.sync += [
Case(phase, { If(pipeline_phase == 0,
0: [ profile[0].eq(profiles[channel[0]]),
profile[0].eq(profiles[channel[0]]), profile[1].eq(profile[0]),
profile[1].eq(profile[0]) )
],
3: [
Cat(channel[1:]).eq(Cat(channel[:-1])),
stage[1:].eq(stage[:-1]),
If(channel[0] == (1 << w.channel) - 1,
stage[0].eq(0)
)
]
})
] ]
m_coeff = self.m_coeff.get_port() m_coeff = self.m_coeff.get_port()
m_state = self.m_state.get_port(write_capable=True) # mode=READ_FIRST m_state = self.m_state.get_port(write_capable=True) # mode=READ_FIRST
self.specials += m_state, m_coeff self.specials += m_state, m_coeff
#
# Hook up main IIR filter.
#
dsp = DSP(w) dsp = DSP(w)
self.submodules += dsp self.submodules += dsp
offset_clr = Signal() offset_clr = Signal()
self.comb += [ self.comb += [
m_coeff.adr.eq(Cat(phase, profile[0], m_coeff.adr.eq(Cat(pipeline_phase, profile[0],
Mux(phase==0, channel[1], channel[0]))), Mux(pipeline_phase == 0, channel[1], channel[0]))),
dsp.offset[-w.coeff - 1:].eq(Mux(offset_clr, 0, dsp.offset[-w.coeff - 1:].eq(Mux(offset_clr, 0,
Cat(m_coeff.dat_r[:w.coeff], m_coeff.dat_r[w.coeff - 1]) Cat(m_coeff.dat_r[:w.coeff], m_coeff.dat_r[w.coeff - 1])
)), )),
dsp.coeff.eq(m_coeff.dat_r[w.coeff:]), dsp.coeff.eq(m_coeff.dat_r[w.coeff:]),
dsp.state.eq(m_state.dat_r), dsp.state.eq(m_state.dat_r),
Case(phase, { Case(pipeline_phase, {
0: dsp.accu_clr.eq(1), 0: dsp.accu_clr.eq(1),
2: [ 2: [
offset_clr.eq(1), offset_clr.eq(1),
@ -373,6 +386,11 @@ class IIR(Module):
}) })
] ]
#
# Arbitrate state memory access between steps.
#
# selected adc and profile delay (combinatorial from dat_r) # selected adc and profile delay (combinatorial from dat_r)
# both share the same coeff word (sel in the lower 8 bits) # both share the same coeff word (sel in the lower 8 bits)
sel_profile = Signal(w.channel) sel_profile = Signal(w.channel)
@ -389,13 +407,13 @@ class IIR(Module):
sel_profile.eq(m_coeff.dat_r[w.coeff:]), sel_profile.eq(m_coeff.dat_r[w.coeff:]),
dly_profile.eq(m_coeff.dat_r[w.coeff + 8:]), dly_profile.eq(m_coeff.dat_r[w.coeff + 8:]),
If(self.shifting, If(self.shifting,
m_state.adr.eq(state | (1 << w.profile + w.channel)), m_state.adr.eq(t_current_step | (1 << w.profile + w.channel)),
m_state.dat_w.eq(m_state.dat_r), m_state.dat_w.eq(m_state.dat_r),
m_state.we.eq(state[0]) m_state.we.eq(t_current_step[0])
), ),
If(self.loading, If(self.loading,
m_state.adr.eq((state << 1) | (1 << w.profile + w.channel)), m_state.adr.eq((t_current_step << 1) | (1 << w.profile + w.channel)),
m_state.dat_w[-w.adc - 1:-1].eq(Array(self.adc)[state]), m_state.dat_w[-w.adc - 1:-1].eq(Array(self.adc)[t_current_step]),
m_state.dat_w[-1].eq(m_state.dat_w[-2]), m_state.dat_w[-1].eq(m_state.dat_w[-2]),
m_state.we.eq(1) m_state.we.eq(1)
), ),
@ -405,16 +423,20 @@ class IIR(Module):
Cat(profile[1], channel[2]), Cat(profile[1], channel[2]),
# read old y # read old y
Cat(profile[0], channel[0]), Cat(profile[0], channel[0]),
# x0 (recent) # read x0 (recent)
0 | (sel_profile << 1) | (1 << w.profile + w.channel), 0 | (sel_profile << 1) | (1 << w.profile + w.channel),
# x1 (old) # read x1 (old)
1 | (sel << 1) | (1 << w.profile + w.channel), 1 | (sel << 1) | (1 << w.profile + w.channel),
])[phase]), ])[pipeline_phase]),
m_state.dat_w.eq(dsp.output), m_state.dat_w.eq(dsp.output),
m_state.we.eq((phase == 0) & stage[2] & en[1]), m_state.we.eq((pipeline_phase == 0) & stages_active[2] & en[1]),
) )
] ]
#
# Compute auxiliary signals (delayed servo enable, clip indicators, etc.).
#
# internal channel delay counters # internal channel delay counters
dlys = Array([Signal(w.dly) dlys = Array([Signal(w.dly)
for i in range(1 << w.channel)]) for i in range(1 << w.channel)])
@ -434,51 +456,65 @@ class IIR(Module):
en_out = Signal(reset_less=True) en_out = Signal(reset_less=True)
# latched channel en_iir # latched channel en_iir
en_iir = Signal(reset_less=True) en_iir = Signal(reset_less=True)
self.sync += [
Case(pipeline_phase, {
0: [
dly.eq(dlys[channel[0]]),
en_out.eq(en_outs[channel[0]]),
en_iir.eq(en_iirs[channel[0]]),
If(stages_active[2] & en[1] & dsp.clip,
clips[channel[2]].eq(1)
)
],
2: [
en[0].eq(0),
en[1].eq(en[0]),
sel.eq(sel_profile),
If(stages_active[0] & en_out,
If(dly != dly_profile,
dlys[channel[0]].eq(dly + 1)
).Elif(en_iir,
en[0].eq(1)
)
)
],
}),
]
#
# Update DDS profile with FTW/POW/ASF
# Stage 0 loads the POW, stage 1 the FTW, and stage 2 writes
# the ASF computed by the IIR filter.
#
# muxing # muxing
ddss = Array(self.dds) ddss = Array(self.dds)
self.sync += [ self.sync += [
Case(phase, { Case(pipeline_phase, {
0: [ 0: [
dly.eq(dlys[channel[0]]), If(stages_active[1],
en_out.eq(en_outs[channel[0]]), ddss[channel[1]][:w.word].eq(m_coeff.dat_r), # ftw0
en_iir.eq(en_iirs[channel[0]]), ),
If(stage[1], ],
ddss[channel[1]][:w.word].eq(m_coeff.dat_r) 1: [
), If(stages_active[1],
If(stage[2] & en[1] & dsp.clip, ddss[channel[1]][w.word:2 * w.word].eq(m_coeff.dat_r), # ftw1
clips[channel[2]].eq(1) ),
) If(stages_active[2],
], ddss[channel[2]][3*w.word:].eq( # asf
1: [ m_state.dat_r[w.state - w.asf - 1:w.state - 1])
If(stage[1], )
ddss[channel[1]][w.word:2*w.word].eq( ],
m_coeff.dat_r), 2: [
), If(stages_active[0],
If(stage[2], ddss[channel[0]][2*w.word:3*w.word].eq(m_coeff.dat_r), # pow
ddss[channel[2]][3*w.word:].eq( ),
m_state.dat_r[w.state - w.asf - 1:w.state - 1]) ],
) 3: [
], ],
2: [ }),
en[0].eq(0),
en[1].eq(en[0]),
sel.eq(sel_profile),
If(stage[0],
ddss[channel[0]][2*w.word:3*w.word].eq(
m_coeff.dat_r),
If(en_out,
If(dly != dly_profile,
dlys[channel[0]].eq(dly + 1)
).Elif(en_iir,
en[0].eq(1)
)
)
)
],
3: [
],
}),
] ]
def _coeff(self, channel, profile, coeff): def _coeff(self, channel, profile, coeff):

View File

@ -5,32 +5,76 @@ from .iir import IIR, IIRWidths
from .dds_ser import DDS, DDSParams from .dds_ser import DDS, DDSParams
def predict_timing(adc_p, iir_p, dds_p):
"""
The following is a sketch of the timing for 1 Sampler (8 ADCs) and N Urukuls
Shown here, the cycle duration is limited by the IIR loading+processing time.
ADC|CONVH|CONV|READ|RTT|IDLE|CONVH|CONV|READ|RTT|IDLE|CONVH|CONV|READ|RTT|...
|4 |57 |16 |8 | .. |4 |57 |16 |8 | .. |4 |57 |16 |8 |...
---+-------------------+------------------------+------------------------+---
IIR| |LOAD|PROC |SHIFT|LOAD|PROC |SHIFT|...
| |8 |16*N+9 |16 |8 |16*N+9 |16 |...
---+--------------------------------------+------------------------+---------
DDS| |CMD|PROF|WAIT|IO_UP|IDLE|CMD|PR...
| |16 |128 |1 |1 | .. |16 | ...
IIR loading starts once the ADC presents its data, the DDSes are updated
once the IIR processing is over. These are the only blocking processes.
IIR shifting happens in parallel to writing to the DDSes and ADC conversions
take place while the IIR filter is processing or the DDSes are being
written to, depending on the cycle duration (given by whichever module
takes the longest).
"""
t_adc = (adc_p.t_cnvh + adc_p.t_conv + adc_p.t_rtt +
adc_p.channels*adc_p.width//adc_p.lanes) + 1
# load adc_p.channels values, process dds_p.channels
# (4 processing phases and 2 additional stages à 4 phases
# to complete the processing of the last channel)
t_iir = adc_p.channels + 4*dds_p.channels + 8 + 1
t_dds = (dds_p.width*2 + 1)*dds_p.clk + 1
t_cycle = max(t_adc, t_iir, t_dds)
return t_adc, t_iir, t_dds, t_cycle
class Servo(Module): class Servo(Module):
def __init__(self, adc_pads, dds_pads, adc_p, iir_p, dds_p): def __init__(self, adc_pads, dds_pads, adc_p, iir_p, dds_p):
t_adc, t_iir, t_dds, t_cycle = predict_timing(adc_p, iir_p, dds_p)
assert t_iir + 2*adc_p.channels < t_cycle, "need shifting time"
self.submodules.adc = ADC(adc_pads, adc_p) self.submodules.adc = ADC(adc_pads, adc_p)
self.submodules.iir = IIR(iir_p) self.submodules.iir = IIR(iir_p)
self.submodules.dds = DDS(dds_pads, dds_p) self.submodules.dds = DDS(dds_pads, dds_p)
# adc channels are reversed on Sampler # adc channels are reversed on Sampler
for i, j, k, l in zip(reversed(self.adc.data), self.iir.adc, for iir, adc in zip(self.iir.adc, reversed(self.adc.data)):
self.iir.dds, self.dds.profile): self.comb += iir.eq(adc)
self.comb += j.eq(i), l.eq(k) for dds, iir in zip(self.dds.profile, self.iir.dds):
self.comb += dds.eq(iir)
t_adc = (adc_p.t_cnvh + adc_p.t_conv + adc_p.t_rtt +
adc_p.channels*adc_p.width//adc_p.lanes) + 1
t_iir = ((1 + 4 + 1) << iir_p.channel) + 1
t_dds = (dds_p.width*2 + 1)*dds_p.clk + 1
t_cycle = max(t_adc, t_iir, t_dds)
assert t_iir + (2 << iir_p.channel) < t_cycle, "need shifting time"
# If high, a new cycle is started if the current cycle (if any) is
# finished. Consequently, if low, servo iterations cease after the
# current cycle is finished. Don't care while the first step (ADC)
# is active.
self.start = Signal() self.start = Signal()
# Counter for delay between end of ADC cycle and start of next one,
# depending on the duration of the other steps.
t_restart = t_cycle - t_adc + 1 t_restart = t_cycle - t_adc + 1
assert t_restart > 1 assert t_restart > 1
cnt = Signal(max=t_restart) cnt = Signal(max=t_restart)
cnt_done = Signal() cnt_done = Signal()
active = Signal(3) active = Signal(3)
# Indicates whether different steps (0: ADC, 1: IIR, 2: DDS) are
# currently active (exposed for simulation only), with each bit being
# reset once the successor step is launched. Depending on the
# timing details of the different steps, any number can be concurrently
# active (e.g. ADC read from iteration n, IIR computation from iteration
# n - 1, and DDS write from iteration n - 2).
# Asserted once per cycle when the DDS write has been completed.
self.done = Signal() self.done = Signal()
self.sync += [ self.sync += [
If(self.dds.done, If(self.dds.done,
active[2].eq(0) active[2].eq(0)

View File

@ -135,12 +135,12 @@ class StandaloneBase(MiniSoC, AMPSoC):
self.config["HAS_SI5324"] = None self.config["HAS_SI5324"] = None
self.config["SI5324_SOFT_RESET"] = None self.config["SI5324_SOFT_RESET"] = None
def add_rtio(self, rtio_channels): def add_rtio(self, rtio_channels, sed_lanes=8):
self.submodules.rtio_crg = _RTIOCRG(self.platform) self.submodules.rtio_crg = _RTIOCRG(self.platform)
self.csr_devices.append("rtio_crg") self.csr_devices.append("rtio_crg")
fix_serdes_timing_path(self.platform) fix_serdes_timing_path(self.platform)
self.submodules.rtio_tsc = rtio.TSC("async", glbl_fine_ts_width=3) self.submodules.rtio_tsc = rtio.TSC("async", glbl_fine_ts_width=3)
self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels) self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels, lane_count=sed_lanes)
self.csr_devices.append("rtio_core") self.csr_devices.append("rtio_core")
self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc)
self.submodules.rtio_dma = ClockDomainsRenamer("sys_kernel")( self.submodules.rtio_dma = ClockDomainsRenamer("sys_kernel")(
@ -228,9 +228,9 @@ class SUServo(StandaloneBase):
ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X) ttl_serdes_7series.Output_8X, ttl_serdes_7series.Output_8X)
# EEM3/2: Sampler, EEM5/4: Urukul, EEM7/6: Urukul # EEM3/2: Sampler, EEM5/4: Urukul, EEM7/6: Urukul
eem.SUServo.add_std( eem.SUServo.add_std(self,
self, eems_sampler=(3, 2), eems_sampler=(3, 2),
eems_urukul0=(5, 4), eems_urukul1=(7, 6)) eems_urukul=[[5, 4], [7, 6]])
for i in (1, 2): for i in (1, 2):
sfp_ctl = self.platform.request("sfp_ctl", i) sfp_ctl = self.platform.request("sfp_ctl", i)
@ -375,13 +375,13 @@ class MasterBase(MiniSoC, AMPSoC):
self.csr_devices.append("rtio_crg") self.csr_devices.append("rtio_crg")
fix_serdes_timing_path(platform) fix_serdes_timing_path(platform)
def add_rtio(self, rtio_channels): def add_rtio(self, rtio_channels, sed_lanes=8):
# Only add MonInj core if there is anything to monitor # Only add MonInj core if there is anything to monitor
if any([len(c.probes) for c in rtio_channels]): if any([len(c.probes) for c in rtio_channels]):
self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) self.submodules.rtio_moninj = rtio.MonInj(rtio_channels)
self.csr_devices.append("rtio_moninj") self.csr_devices.append("rtio_moninj")
self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels) self.submodules.rtio_core = rtio.Core(self.rtio_tsc, rtio_channels, lane_count=sed_lanes)
self.csr_devices.append("rtio_core") self.csr_devices.append("rtio_core")
self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc) self.submodules.rtio = rtio.KernelInitiator(self.rtio_tsc)
@ -608,13 +608,13 @@ class SatelliteBase(BaseSoC):
self.csr_devices.append("rtio_crg") self.csr_devices.append("rtio_crg")
fix_serdes_timing_path(platform) fix_serdes_timing_path(platform)
def add_rtio(self, rtio_channels): def add_rtio(self, rtio_channels, sed_lanes=8):
# Only add MonInj core if there is anything to monitor # Only add MonInj core if there is anything to monitor
if any([len(c.probes) for c in rtio_channels]): if any([len(c.probes) for c in rtio_channels]):
self.submodules.rtio_moninj = rtio.MonInj(rtio_channels) self.submodules.rtio_moninj = rtio.MonInj(rtio_channels)
self.csr_devices.append("rtio_moninj") self.csr_devices.append("rtio_moninj")
self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels) self.submodules.local_io = SyncRTIO(self.rtio_tsc, rtio_channels, lane_count=sed_lanes)
self.comb += self.drtiosat.async_errors.eq(self.local_io.async_errors) self.comb += self.drtiosat.async_errors.eq(self.local_io.async_errors)
self.submodules.cri_con = rtio.CRIInterconnectShared( self.submodules.cri_con = rtio.CRIInterconnectShared(
[self.drtiosat.cri], [self.drtiosat.cri],

View File

@ -57,7 +57,8 @@ class GenericStandalone(StandaloneBase):
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels) self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
self.rtio_channels.append(rtio.LogChannel()) self.rtio_channels.append(rtio.LogChannel())
self.add_rtio(self.rtio_channels) self.add_rtio(self.rtio_channels, sed_lanes=description["sed_lanes"])
if has_grabber: if has_grabber:
self.config["HAS_GRABBER"] = None self.config["HAS_GRABBER"] = None
self.add_csr_group("grabber", self.grabber_csr_group) self.add_csr_group("grabber", self.grabber_csr_group)
@ -94,7 +95,7 @@ class GenericMaster(MasterBase):
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels) self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
self.rtio_channels.append(rtio.LogChannel()) self.rtio_channels.append(rtio.LogChannel())
self.add_rtio(self.rtio_channels) self.add_rtio(self.rtio_channels, sed_lanes=description["sed_lanes"])
if has_grabber: if has_grabber:
self.config["HAS_GRABBER"] = None self.config["HAS_GRABBER"] = None
self.add_csr_group("grabber", self.grabber_csr_group) self.add_csr_group("grabber", self.grabber_csr_group)
@ -127,7 +128,7 @@ class GenericSatellite(SatelliteBase):
self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels) self.config["RTIO_LOG_CHANNEL"] = len(self.rtio_channels)
self.rtio_channels.append(rtio.LogChannel()) self.rtio_channels.append(rtio.LogChannel())
self.add_rtio(self.rtio_channels) self.add_rtio(self.rtio_channels, sed_lanes=description["sed_lanes"])
if has_grabber: if has_grabber:
self.config["HAS_GRABBER"] = None self.config["HAS_GRABBER"] = None
self.add_csr_group("grabber", self.grabber_csr_group) self.add_csr_group("grabber", self.grabber_csr_group)

View File

@ -129,7 +129,7 @@ class _StandaloneBase(MiniSoC, AMPSoC):
} }
mem_map.update(MiniSoC.mem_map) mem_map.update(MiniSoC.mem_map)
def __init__(self, gateware_identifier_str=None, **kwargs): def __init__(self, gateware_identifier_str=None, drtio_100mhz=False, **kwargs):
MiniSoC.__init__(self, MiniSoC.__init__(self,
cpu_type="vexriscv", cpu_type="vexriscv",
cpu_bus_width=64, cpu_bus_width=64,
@ -207,7 +207,7 @@ class _MasterBase(MiniSoC, AMPSoC):
} }
mem_map.update(MiniSoC.mem_map) mem_map.update(MiniSoC.mem_map)
def __init__(self, gateware_identifier_str=None, **kwargs): def __init__(self, gateware_identifier_str=None, drtio_100mhz=False, **kwargs):
MiniSoC.__init__(self, MiniSoC.__init__(self,
cpu_type="vexriscv", cpu_type="vexriscv",
cpu_bus_width=64, cpu_bus_width=64,
@ -236,11 +236,14 @@ class _MasterBase(MiniSoC, AMPSoC):
platform.request("sfp"), platform.request("user_sma_mgt") platform.request("sfp"), platform.request("user_sma_mgt")
] ]
# 1000BASE_BX10 Ethernet compatible, 125MHz RTIO clock rtio_clk_freq = 100e6 if drtio_100mhz else 125e6
# 1000BASE_BX10 Ethernet compatible, 100/125MHz RTIO clock
self.submodules.drtio_transceiver = gtx_7series.GTX( self.submodules.drtio_transceiver = gtx_7series.GTX(
clock_pads=platform.request("si5324_clkout"), clock_pads=platform.request("si5324_clkout"),
pads=data_pads, pads=data_pads,
sys_clk_freq=self.clk_freq) sys_clk_freq=self.clk_freq,
rtio_clk_freq=rtio_clk_freq)
self.csr_devices.append("drtio_transceiver") self.csr_devices.append("drtio_transceiver")
self.submodules.rtio_tsc = rtio.TSC("async", glbl_fine_ts_width=3) self.submodules.rtio_tsc = rtio.TSC("async", glbl_fine_ts_width=3)
@ -341,7 +344,7 @@ class _SatelliteBase(BaseSoC):
} }
mem_map.update(BaseSoC.mem_map) mem_map.update(BaseSoC.mem_map)
def __init__(self, gateware_identifier_str=None, sma_as_sat=False, **kwargs): def __init__(self, gateware_identifier_str=None, sma_as_sat=False, drtio_100mhz=False, **kwargs):
BaseSoC.__init__(self, BaseSoC.__init__(self,
cpu_type="vexriscv", cpu_type="vexriscv",
cpu_bus_width=64, cpu_bus_width=64,
@ -369,11 +372,14 @@ class _SatelliteBase(BaseSoC):
if sma_as_sat: if sma_as_sat:
data_pads = data_pads[::-1] data_pads = data_pads[::-1]
# 1000BASE_BX10 Ethernet compatible, 125MHz RTIO clock rtio_clk_freq = 100e6 if drtio_100mhz else 125e6
# 1000BASE_BX10 Ethernet compatible, 100/125MHz RTIO clock
self.submodules.drtio_transceiver = gtx_7series.GTX( self.submodules.drtio_transceiver = gtx_7series.GTX(
clock_pads=platform.request("si5324_clkout"), clock_pads=platform.request("si5324_clkout"),
pads=data_pads, pads=data_pads,
sys_clk_freq=self.clk_freq) sys_clk_freq=self.clk_freq,
rtio_clk_freq=rtio_clk_freq)
self.csr_devices.append("drtio_transceiver") self.csr_devices.append("drtio_transceiver")
self.submodules.rtio_tsc = rtio.TSC("sync", glbl_fine_ts_width=3) self.submodules.rtio_tsc = rtio.TSC("sync", glbl_fine_ts_width=3)
@ -673,6 +679,8 @@ def main():
"(default: %(default)s)") "(default: %(default)s)")
parser.add_argument("--gateware-identifier-str", default=None, parser.add_argument("--gateware-identifier-str", default=None,
help="Override ROM identifier") help="Override ROM identifier")
parser.add_argument("--drtio100mhz", action="store_true", default=False,
help="DRTIO systems only - use 100MHz RTIO clock")
args = parser.parse_args() args = parser.parse_args()
variant = args.variant.lower() variant = args.variant.lower()
@ -681,7 +689,7 @@ def main():
except KeyError: except KeyError:
raise SystemExit("Invalid variant (-V/--variant)") raise SystemExit("Invalid variant (-V/--variant)")
soc = cls(gateware_identifier_str=args.gateware_identifier_str, **soc_kc705_argdict(args)) soc = cls(gateware_identifier_str=args.gateware_identifier_str, drtio_100mhz=args.drtio100mhz, **soc_kc705_argdict(args))
build_artiq_soc(soc, builder_argdict(args)) build_artiq_soc(soc, builder_argdict(args))

View File

@ -1,4 +1,3 @@
import warnings
from collections import OrderedDict from collections import OrderedDict
from inspect import isclass from inspect import isclass
@ -331,7 +330,8 @@ class HasEnvironment:
@rpc(flags={"async"}) @rpc(flags={"async"})
def set_dataset(self, key, value, def set_dataset(self, key, value,
broadcast=False, persist=False, archive=True, save=None): broadcast=False, persist=False, archive=True,
hdf5_options=None):
"""Sets the contents and handling modes of a dataset. """Sets the contents and handling modes of a dataset.
Datasets must be scalars (``bool``, ``int``, ``float`` or NumPy scalar) Datasets must be scalars (``bool``, ``int``, ``float`` or NumPy scalar)
@ -343,13 +343,16 @@ class HasEnvironment:
broadcast. broadcast.
:param archive: the data is saved into the local storage of the current :param archive: the data is saved into the local storage of the current
run (archived as a HDF5 file). run (archived as a HDF5 file).
:param save: deprecated. :param hdf5_options: dict of keyword arguments to pass to
:meth:`h5py.Group.create_dataset`. For example, pass ``{"compression": "gzip"}``
to enable transparent zlib compression of this dataset in the HDF5 archive.
See the `h5py documentation <https://docs.h5py.org/en/stable/high/group.html#h5py.Group.create_dataset>`_
for a list of valid options.
""" """
if save is not None:
warnings.warn("set_dataset save parameter is deprecated, " self.__dataset_mgr.set(
"use archive instead", FutureWarning) key, value, broadcast, persist, archive, hdf5_options
archive = save )
self.__dataset_mgr.set(key, value, broadcast, persist, archive)
@rpc(flags={"async"}) @rpc(flags={"async"})
def mutate_dataset(self, key, index, value): def mutate_dataset(self, key, index, value):

View File

@ -35,6 +35,15 @@ class DeviceDB:
return desc return desc
def make_dataset(*, persist=False, value=None, hdf5_options=None):
"PYON-serializable representation of a dataset in the DatasetDB"
return {
"persist": persist,
"value": value,
"hdf5_options": hdf5_options or {},
}
class DatasetDB(TaskObject): class DatasetDB(TaskObject):
def __init__(self, persist_file, autosave_period=30): def __init__(self, persist_file, autosave_period=30):
self.persist_file = persist_file self.persist_file = persist_file
@ -44,10 +53,23 @@ class DatasetDB(TaskObject):
file_data = pyon.load_file(self.persist_file) file_data = pyon.load_file(self.persist_file)
except FileNotFoundError: except FileNotFoundError:
file_data = dict() file_data = dict()
self.data = Notifier({k: (True, v) for k, v in file_data.items()}) self.data = Notifier(
{
k: make_dataset(
persist=True,
value=v["value"],
hdf5_options=v["hdf5_options"]
)
for k, v in file_data.items()
}
)
def save(self): def save(self):
data = {k: v[1] for k, v in self.data.raw_view.items() if v[0]} data = {
k: d
for k, d in self.data.raw_view.items()
if d["persist"]
}
pyon.store_file(self.persist_file, data) pyon.store_file(self.persist_file, data)
async def _do(self): async def _do(self):
@ -59,20 +81,23 @@ class DatasetDB(TaskObject):
self.save() self.save()
def get(self, key): def get(self, key):
return self.data.raw_view[key][1] return self.data.raw_view[key]
def update(self, mod): def update(self, mod):
process_mod(self.data, mod) process_mod(self.data, mod)
# convenience functions (update() can be used instead) # convenience functions (update() can be used instead)
def set(self, key, value, persist=None): def set(self, key, value, persist=None, hdf5_options=None):
if persist is None: if persist is None:
if key in self.data.raw_view: if key in self.data.raw_view:
persist = self.data.raw_view[key][0] persist = self.data.raw_view[key]["persist"]
else: else:
persist = False persist = False
self.data[key] = (persist, value) self.data[key] = make_dataset(
persist=persist,
value=value,
hdf5_options=hdf5_options,
)
def delete(self, key): def delete(self, key):
del self.data[key] del self.data[key]
#

View File

@ -8,9 +8,12 @@ from operator import setitem
import importlib import importlib
import logging import logging
import numpy as np
from sipyco.sync_struct import Notifier from sipyco.sync_struct import Notifier
from sipyco.pc_rpc import AutoTarget, Client, BestEffortClient from sipyco.pc_rpc import AutoTarget, Client, BestEffortClient
from artiq.master.databases import make_dataset
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -115,7 +118,8 @@ class DatasetManager:
self.ddb = ddb self.ddb = ddb
self._broadcaster.publish = ddb.update self._broadcaster.publish = ddb.update
def set(self, key, value, broadcast=False, persist=False, archive=True): def set(self, key, value, broadcast=False, persist=False, archive=True,
hdf5_options=None):
if key in self.archive: if key in self.archive:
logger.warning("Modifying dataset '%s' which is in archive, " logger.warning("Modifying dataset '%s' which is in archive, "
"archive will remain untouched", "archive will remain untouched",
@ -125,12 +129,20 @@ class DatasetManager:
broadcast = True broadcast = True
if broadcast: if broadcast:
self._broadcaster[key] = persist, value self._broadcaster[key] = make_dataset(
persist=persist,
value=value,
hdf5_options=hdf5_options,
)
elif key in self._broadcaster.raw_view: elif key in self._broadcaster.raw_view:
del self._broadcaster[key] del self._broadcaster[key]
if archive: if archive:
self.local[key] = value self.local[key] = make_dataset(
persist=persist,
value=value,
hdf5_options=hdf5_options,
)
elif key in self.local: elif key in self.local:
del self.local[key] del self.local[key]
@ -138,11 +150,11 @@ class DatasetManager:
target = self.local.get(key, None) target = self.local.get(key, None)
if key in self._broadcaster.raw_view: if key in self._broadcaster.raw_view:
if target is not None: if target is not None:
assert target is self._broadcaster.raw_view[key][1] assert target["value"] is self._broadcaster.raw_view[key]["value"]
return self._broadcaster[key][1] return self._broadcaster[key]["value"]
if target is None: if target is None:
raise KeyError("Cannot mutate nonexistent dataset '{}'".format(key)) raise KeyError("Cannot mutate nonexistent dataset '{}'".format(key))
return target return target["value"]
def mutate(self, key, index, value): def mutate(self, key, index, value):
target = self._get_mutation_target(key) target = self._get_mutation_target(key)
@ -158,15 +170,15 @@ class DatasetManager:
def get(self, key, archive=False): def get(self, key, archive=False):
if key in self.local: if key in self.local:
return self.local[key] return self.local[key]["value"]
data = self.ddb.get(key) dataset = self.ddb.get(key)
if archive: if archive:
if key in self.archive: if key in self.archive:
logger.warning("Dataset '%s' is already in archive, " logger.warning("Dataset '%s' is already in archive, "
"overwriting", key, stack_info=True) "overwriting", key, stack_info=True)
self.archive[key] = data self.archive[key] = dataset
return data return dataset["value"]
def write_hdf5(self, f): def write_hdf5(self, f):
datasets_group = f.create_group("datasets") datasets_group = f.create_group("datasets")
@ -182,7 +194,7 @@ def _write(group, k, v):
# Add context to exception message when the user writes a dataset that is # Add context to exception message when the user writes a dataset that is
# not representable in HDF5. # not representable in HDF5.
try: try:
group[k] = v group.create_dataset(k, data=v["value"], **v["hdf5_options"])
except TypeError as e: except TypeError as e:
raise TypeError("Error writing dataset '{}' of type '{}': {}".format( raise TypeError("Error writing dataset '{}' of type '{}': {}".format(
k, type(v), e)) k, type(v["value"]), e))

View File

@ -0,0 +1,30 @@
# RUN: env ARTIQ_DUMP_LLVM=%t %python -m artiq.compiler.testbench.embedding +compile %s
# RUN: OutputCheck %s --file-to-check=%t.ll
from artiq.language.core import *
from artiq.language.types import *
# Make sure `byval` and `sret` are specified both at the call site and the
# declaration. This isn't caught by the LLVM IR validator, but mismatches
# lead to miscompilations (at least in LLVM 11).
@kernel
def entrypoint():
# CHECK: call void @accept_str\({ i8\*, i32 }\* nonnull byval
accept_str("foo")
# CHECK: call void @return_str\({ i8\*, i32 }\* nonnull sret
return_str()
# CHECK: declare void @accept_str\({ i8\*, i32 }\* byval\)
@syscall
def accept_str(name: TStr) -> TNone:
pass
# CHECK: declare void @return_str\({ i8\*, i32 }\* sret\)
@syscall
def return_str() -> TStr:
pass

View File

@ -0,0 +1,111 @@
"""Test internal dataset representation (persistence, applets)"""
import unittest
import tempfile
from artiq.master.databases import DatasetDB
from sipyco import pyon
KEY1 = "key1"
KEY2 = "key2"
KEY3 = "key3"
DATA = list(range(10))
COMP = "gzip"
class TestDatasetDB(unittest.TestCase):
def setUp(self):
# empty dataset persistance file
self.persist_file = tempfile.NamedTemporaryFile(mode="w+")
print("{}", file=self.persist_file, flush=True)
self.ddb = DatasetDB(self.persist_file.name)
self.ddb.set(KEY1, DATA, persist=True)
self.ddb.set(KEY2, DATA, persist=True, hdf5_options=dict(compression=COMP))
self.ddb.set(KEY3, DATA, hdf5_options=dict(shuffle=True))
self.save_ddb_to_disk()
def save_ddb_to_disk(self):
self.ddb.save()
self.persist_file.flush()
def load_ddb_from_disk(self):
return pyon.load_file(self.persist_file.name)
def test_persist_format(self):
data = pyon.load_file(self.persist_file.name)
for key in [KEY1, KEY2]:
self.assertTrue(data[key]["persist"])
self.assertEqual(data[key]["value"], DATA)
self.assertEqual(data[KEY2]["hdf5_options"]["compression"], COMP)
self.assertEqual(data[KEY1]["hdf5_options"], dict())
def test_only_persist_marked_datasets(self):
data = self.load_ddb_from_disk()
with self.assertRaises(KeyError):
data[KEY3]
def test_memory_format(self):
ds = self.ddb.get(KEY2)
self.assertTrue(ds["persist"])
self.assertEqual(ds["value"], DATA)
self.assertEqual(ds["hdf5_options"]["compression"], COMP)
ds = self.ddb.get(KEY3)
self.assertFalse(ds["persist"])
self.assertEqual(ds["value"], DATA)
self.assertTrue(ds["hdf5_options"]["shuffle"])
def test_delete(self):
self.ddb.delete(KEY1)
self.save_ddb_to_disk()
data = self.load_ddb_from_disk()
with self.assertRaises(KeyError):
data[KEY1]
self.assertTrue(data[KEY2]["persist"])
def test_update(self):
self.assertFalse(self.ddb.get(KEY3)["persist"])
mod = {
"action": "setitem",
"path": [KEY3],
"key": "persist",
"value": True,
}
self.ddb.update(mod)
self.assertTrue(self.ddb.get(KEY3)["persist"])
def test_update_hdf5_options(self):
with self.assertRaises(KeyError):
self.ddb.get(KEY1)["hdf5_options"]["shuffle"]
mod = {
"action": "setitem",
"path": [KEY1, "hdf5_options"],
"key": "shuffle",
"value": False,
}
self.ddb.update(mod)
self.assertFalse(self.ddb.get(KEY1)["hdf5_options"]["shuffle"])
def test_reset_copies_persist(self):
self.assertTrue(self.ddb.get(KEY1)["persist"])
self.ddb.set(KEY1, DATA)
self.assertTrue(self.ddb.get(KEY1)["persist"])
self.assertFalse(self.ddb.get(KEY3)["persist"])
self.ddb.set(KEY3, DATA)
self.assertFalse(self.ddb.get(KEY3)["persist"])
self.ddb.set(KEY3, DATA, persist=True)
self.assertTrue(self.ddb.get(KEY3)["persist"])

View File

@ -3,6 +3,9 @@
import copy import copy
import unittest import unittest
import h5py
import numpy as np
from sipyco.sync_struct import process_mod from sipyco.sync_struct import process_mod
from artiq.experiment import EnvExperiment from artiq.experiment import EnvExperiment
@ -14,7 +17,7 @@ class MockDatasetDB:
self.data = dict() self.data = dict()
def get(self, key): def get(self, key):
return self.data[key][1] return self.data[key]["value"]
def update(self, mod): def update(self, mod):
# Copy mod before applying to avoid sharing references to objects # Copy mod before applying to avoid sharing references to objects
@ -82,9 +85,9 @@ class ExperimentDatasetCase(unittest.TestCase):
def test_append_broadcast(self): def test_append_broadcast(self):
self.exp.set(KEY, [], broadcast=True) self.exp.set(KEY, [], broadcast=True)
self.exp.append(KEY, 0) self.exp.append(KEY, 0)
self.assertEqual(self.dataset_db.data[KEY][1], [0]) self.assertEqual(self.dataset_db.data[KEY]["value"], [0])
self.exp.append(KEY, 1) self.exp.append(KEY, 1)
self.assertEqual(self.dataset_db.data[KEY][1], [0, 1]) self.assertEqual(self.dataset_db.data[KEY]["value"], [0, 1])
def test_append_array(self): def test_append_array(self):
for broadcast in (True, False): for broadcast in (True, False):
@ -103,3 +106,44 @@ class ExperimentDatasetCase(unittest.TestCase):
with self.assertRaises(KeyError): with self.assertRaises(KeyError):
self.exp.append(KEY, 0) self.exp.append(KEY, 0)
def test_write_hdf5_options(self):
data = np.random.randint(0, 1024, 1024)
self.exp.set(
KEY,
data,
hdf5_options=dict(
compression="gzip",
compression_opts=6,
shuffle=True,
fletcher32=True
),
)
with h5py.File("test.h5", "a", "core", backing_store=False) as f:
self.dataset_mgr.write_hdf5(f)
self.assertTrue(np.array_equal(f["datasets"][KEY][()], data))
self.assertEqual(f["datasets"][KEY].compression, "gzip")
self.assertEqual(f["datasets"][KEY].compression_opts, 6)
self.assertTrue(f["datasets"][KEY].shuffle)
self.assertTrue(f["datasets"][KEY].fletcher32)
def test_write_hdf5_no_options(self):
data = np.random.randint(0, 1024, 1024)
self.exp.set(KEY, data)
with h5py.File("test.h5", "a", "core", backing_store=False) as f:
self.dataset_mgr.write_hdf5(f)
self.assertTrue(np.array_equal(f["datasets"][KEY][()], data))
self.assertIsNone(f["datasets"][KEY].compression)
def test_write_hdf5_invalid_type(self):
class CustomType:
def __init__(self, x):
self.x = x
self.exp.set(KEY, CustomType(42))
with h5py.File("test.h5", "w", "core", backing_store=False) as f:
with self.assertRaisesRegex(TypeError, "CustomType"):
self.dataset_mgr.write_hdf5(f)

View File

@ -7,6 +7,7 @@ from time import time, sleep
from artiq.experiment import * from artiq.experiment import *
from artiq.master.scheduler import Scheduler from artiq.master.scheduler import Scheduler
from artiq.master.databases import make_dataset
class EmptyExperiment(EnvExperiment): class EmptyExperiment(EnvExperiment):
@ -291,8 +292,13 @@ class SchedulerCase(unittest.TestCase):
nonlocal termination_ok nonlocal termination_ok
self.assertEqual( self.assertEqual(
mod, mod,
{"action": "setitem", "key": "termination_ok", {
"value": (False, True), "path": []}) "action": "setitem",
"key": "termination_ok",
"value": make_dataset(value=True),
"path": []
}
)
termination_ok = True termination_ok = True
handlers = { handlers = {
"update_dataset": check_termination "update_dataset": check_termination

View File

@ -3,11 +3,11 @@
"mozilla-overlay": { "mozilla-overlay": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1636569584, "lastModified": 1637337116,
"narHash": "sha256-iDFogua24bhFJZSxG/jhZbbNxDXuKP9S/pyRIYzrRPM=", "narHash": "sha256-LKqAcdL+woWeYajs02bDQ7q8rsqgXuzhC354NoRaV80=",
"owner": "mozilla", "owner": "mozilla",
"repo": "nixpkgs-mozilla", "repo": "nixpkgs-mozilla",
"rev": "9f70f86d73fa97e043bebeb58e5676d157069cfb", "rev": "cbc7435f5b0b3d17b16fb1d20cf7b616eec5e093",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -18,16 +18,16 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1636552551, "lastModified": 1638279546,
"narHash": "sha256-k7Hq/bvUnRlAfFjPGuw3FsSqqspQdRHsCHpgadw6UkQ=", "narHash": "sha256-1KCwN7twjp1dBdp0jPgVdYFztDkCR8+roo0B34J9oBY=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "9e86f5f7a19db6da2445f07bafa6694b556f9c6d", "rev": "96b4157790fc96e70d6e6c115e3f34bba7be490f",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-21.05", "ref": "nixos-21.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@ -61,11 +61,11 @@
"src-misoc": { "src-misoc": {
"flake": false, "flake": false,
"locked": { "locked": {
"lastModified": 1636527305, "lastModified": 1638683371,
"narHash": "sha256-/2XTejqj0Bo81HaTrlTSWwInnWwsuqnq+CURXbpIrkA=", "narHash": "sha256-sm2SxHmEGfE56+V+joDHMjpOaxg8+t3EJEk1d11C1E0=",
"ref": "master", "ref": "master",
"rev": "f5203e406520874e15ab5d070058ef642fc57fd9", "rev": "71b74f87b41c56a6c6d767cdfde0356c15a379a7",
"revCount": 2417, "revCount": 2418,
"submodules": true, "submodules": true,
"type": "git", "type": "git",
"url": "https://github.com/m-labs/misoc.git" "url": "https://github.com/m-labs/misoc.git"

View File

@ -1,7 +1,7 @@
{ {
description = "A leading-edge control system for quantum information experiments"; description = "A leading-edge control system for quantum information experiments";
inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-21.05; inputs.nixpkgs.url = github:NixOS/nixpkgs/nixos-21.11;
inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; }; inputs.mozilla-overlay = { url = github:mozilla/nixpkgs-mozilla; flake = false; };
inputs.src-sipyco = { url = github:m-labs/sipyco; flake = false; }; inputs.src-sipyco = { url = github:m-labs/sipyco; flake = false; };
inputs.src-pythonparser = { url = github:m-labs/pythonparser; flake = false; }; inputs.src-pythonparser = { url = github:m-labs/pythonparser; flake = false; };
@ -270,7 +270,6 @@
ln -s $ARTIQ_PATH/firmware/Cargo.lock . ln -s $ARTIQ_PATH/firmware/Cargo.lock .
cargoSetupPostUnpackHook cargoSetupPostUnpackHook
cargoSetupPostPatchHook cargoSetupPostPatchHook
export TARGET_AR=llvm-ar
${buildCommand} ${buildCommand}
''; '';
doCheck = true; doCheck = true;
@ -384,7 +383,6 @@
packages.x86_64-linux.vivado packages.x86_64-linux.vivado
packages.x86_64-linux.openocd-bscanspi packages.x86_64-linux.openocd-bscanspi
]; ];
TARGET_AR="llvm-ar";
}; };
hydraJobs = { hydraJobs = {
@ -405,17 +403,18 @@
phases = [ "buildPhase" ]; phases = [ "buildPhase" ];
buildPhase = buildPhase =
'' ''
whoami
export HOME=`mktemp -d` export HOME=`mktemp -d`
mkdir $HOME/.ssh mkdir $HOME/.ssh
cp /opt/hydra_id_rsa $HOME/.ssh/id_rsa cp /opt/hydra_id_ed25519 $HOME/.ssh/id_ed25519
cp /opt/hydra_id_rsa.pub $HOME/.ssh/id_rsa.pub cp /opt/hydra_id_ed25519.pub $HOME/.ssh/id_ed25519.pub
echo "rpi-1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPOBQVcsvk6WgRj18v4m0zkFeKrcN9gA+r6sxQxNwFpv" > $HOME/.ssh/known_hosts echo "rpi-1 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPOBQVcsvk6WgRj18v4m0zkFeKrcN9gA+r6sxQxNwFpv" > $HOME/.ssh/known_hosts
chmod 600 $HOME/.ssh/id_rsa chmod 600 $HOME/.ssh/id_ed25519
LOCKCTL=$(mktemp -d) LOCKCTL=$(mktemp -d)
mkfifo $LOCKCTL/lockctl mkfifo $LOCKCTL/lockctl
cat $LOCKCTL/lockctl | ${pkgs.openssh}/bin/ssh \ cat $LOCKCTL/lockctl | ${pkgs.openssh}/bin/ssh \
-i $HOME/.ssh/id_rsa \ -i $HOME/.ssh/id_ed25519 \
-o UserKnownHostsFile=$HOME/.ssh/known_hosts \ -o UserKnownHostsFile=$HOME/.ssh/known_hosts \
rpi-1 \ rpi-1 \
'mkdir -p /tmp/board_lock && flock /tmp/board_lock/kc705-1 -c "echo Ok; cat"' \ 'mkdir -p /tmp/board_lock && flock /tmp/board_lock/kc705-1 -c "echo Ok; cat"' \