mirror of https://github.com/m-labs/artiq.git
Merge branch 'master' into ad9910
This commit is contained in:
commit
41b2c8d07a
|
@ -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
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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]:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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": {
|
||||||
|
|
|
@ -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_)
|
||||||
|
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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()]);
|
||||||
|
|
|
@ -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 } => {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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).
|
||||||
|
"""
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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],
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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]
|
||||||
#
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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
|
|
@ -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"])
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
22
flake.lock
22
flake.lock
|
@ -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"
|
||||||
|
|
13
flake.nix
13
flake.nix
|
@ -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"' \
|
||||||
|
|
Loading…
Reference in New Issue