forked from M-Labs/artiq
Compare commits
300 Commits
|
@ -14,7 +14,6 @@ __pycache__/
|
|||
/dist
|
||||
/*.egg-info
|
||||
/.coverage
|
||||
/artiq/test/lit/*/Output/
|
||||
/artiq/binaries
|
||||
/artiq/firmware/target/
|
||||
/misoc_*/
|
||||
|
|
|
@ -26,6 +26,7 @@ report if possible:
|
|||
* Operating System
|
||||
* ARTIQ version (with recent versions of ARTIQ, run ``artiq_client --version``)
|
||||
* Version of the gateware and runtime loaded in the core device (in the output of ``artiq_coremgmt -D .... log``)
|
||||
* If using MSYS2, output of `pacman -Q`
|
||||
* Hardware involved
|
||||
|
||||
|
||||
|
|
|
@ -8,79 +8,36 @@ ARTIQ-8 (Unreleased)
|
|||
|
||||
Highlights:
|
||||
|
||||
* New hardware support:
|
||||
- Support for Shuttler, a 16-channel 125MSPS DAC card intended for ion transport.
|
||||
Waveform generator and user API are similar to the NIST PDQ.
|
||||
* Hardware support:
|
||||
- Implemented Phaser-servo. This requires recent gateware on Phaser.
|
||||
- Almazny v1.2 with finer RF switch control.
|
||||
- Metlino and Sayma support has been dropped due to complications with synchronous RTIO clocking.
|
||||
- More user LEDs are exposed to RTIO on Kasli.
|
||||
- Implemented Phaser-MIQRO support. This requires the proprietary Phaser MIQRO gateware
|
||||
variant from QUARTIQ.
|
||||
- Implemented Phaser-MIQRO support. This requires the Phaser MIQRO gateware
|
||||
variant.
|
||||
- Sampler: fixed ADC MU to Volt conversion factor for Sampler v2.2+.
|
||||
For earlier hardware versions, specify the hardware version in the device
|
||||
database file (e.g. ``"hw_rev": "v2.1"``) to use the correct conversion factor.
|
||||
* Support for distributed DMA, where DMA is run directly on satellites for corresponding
|
||||
RTIO events, increasing bandwidth in scenarios with heavy satellite usage.
|
||||
* Support for subkernels, where kernels are run on satellite device CPUs to offload some
|
||||
of the processing and RTIO operations.
|
||||
- Almazny v1.2. It is incompatible with the legacy versions and is the default. To use legacy
|
||||
versions, specify ``almazny_hw_rev`` in the JSON description.
|
||||
- Metlino and Sayma support has been dropped due to complications with synchronous RTIO clocking.
|
||||
* CPU (on softcore platforms) and AXI bus (on Zynq) are now clocked synchronously with the RTIO
|
||||
clock, to facilitate implementation of local processing on DRTIO satellites, and to slightly
|
||||
reduce RTIO latency.
|
||||
* Support for DRTIO-over-EEM, used with Shuttler.
|
||||
* Added channel names to RTIO error messages.
|
||||
* GUI:
|
||||
- Implemented Applet Request Interfaces which allow applets to modify datasets and set the
|
||||
current values of widgets in the dashboard's experiment windows.
|
||||
- Implemented a new EntryArea widget which allows argument entry widgets to be used in applets.
|
||||
* MSYS2 packaging for Windows, which replaces Conda. Conda packages are still available to
|
||||
support legacy installations, but may be removed in a future release.
|
||||
* Added channel names to RTIO errors.
|
||||
* Full Python 3.10 support.
|
||||
* Python's built-in types (such as `float`, or `List[...]`) can now be used in type annotations on
|
||||
kernel functions.
|
||||
* Distributed DMA is now supported, allowing DMA to be run directly on satellites for corresponding
|
||||
RTIO events, increasing bandwidth in scenarios with heavy satellite usage.
|
||||
* Applet Request Interfaces have been implemented, enabling applets to directly modify datasets
|
||||
and temporarily set arguments in the dashboard.
|
||||
* EntryArea widget has been implemented, allowing argument entry widgets to be used in applets.
|
||||
* Dashboard:
|
||||
- The "Close all applets" command (shortcut: Ctrl-Alt-W) now ignores docked applets,
|
||||
making it a convenient way to clean up after exploratory work without destroying a
|
||||
carefully arranged default workspace.
|
||||
- Hotkeys now organize experiment windows in the order they were last interacted with:
|
||||
+ CTRL+SHIFT+T tiles experiment windows
|
||||
+ CTRL+SHIFT+C cascades experiment windows
|
||||
* Persistent datasets are now stored in a LMDB database for improved performance.
|
||||
* Python's built-in types (such as ``float``, or ``List[...]``) can now be used in type annotations on
|
||||
kernel functions.
|
||||
* Full Python 3.10 support.
|
||||
* MSYS2 packaging for Windows, which replaces Conda. Conda packages are still available to
|
||||
support legacy installations, but may be removed in a future release.
|
||||
|
||||
Breaking changes:
|
||||
|
||||
* ``SimpleApplet`` now calls widget constructors with an additional ``ctl`` parameter for control
|
||||
operations, which includes dataset operations. It can be ignored if not needed. For an example usage,
|
||||
refer to the ``big_number.py`` applet.
|
||||
* ``SimpleApplet`` and ``TitleApplet`` now call ``data_changed`` with additional parameters. Derived applets
|
||||
should change the function signature as below:
|
||||
|
||||
::
|
||||
|
||||
# SimpleApplet
|
||||
def data_changed(self, value, metadata, persist, mods)
|
||||
# SimpleApplet (old version)
|
||||
def data_changed(self, data, mods)
|
||||
# TitleApplet
|
||||
def data_changed(self, value, metadata, persist, mods, title)
|
||||
# TitleApplet (old version)
|
||||
def data_changed(self, data, mods, title)
|
||||
|
||||
Accesses to the data argument should be replaced as below:
|
||||
|
||||
::
|
||||
|
||||
data[key][0] ==> persist[key]
|
||||
data[key][1] ==> value[key]
|
||||
|
||||
* The ``ndecimals`` parameter in ``NumberValue`` and ``Scannable`` has been renamed to ``precision``.
|
||||
Parameters after and including ``scale`` in both constructors are now keyword-only.
|
||||
Refer to the updated ``no_hardware/arguments_demo.py`` example for current usage.
|
||||
* Almazny v1.2 is incompatible with the legacy versions and is the default.
|
||||
To use legacy versions, specify ``almazny_hw_rev`` in the JSON description.
|
||||
* kasli_generic.py has been merged into kasli.py, and the demonstration designs without JSON descriptions
|
||||
have been removed. The base classes remain present in kasli.py to support third-party flows without
|
||||
JSON descriptions.
|
||||
* Legacy PYON databases should be converted to LMDB with the script below:
|
||||
* Persistent datasets are now stored in a LMDB database for improved performance. PYON databases can
|
||||
be converted with the script below.
|
||||
|
||||
::
|
||||
|
||||
|
@ -94,7 +51,35 @@ Accesses to the data argument should be replaced as below:
|
|||
txn.put(key.encode(), pyon.encode((value, {})).encode())
|
||||
new.close()
|
||||
|
||||
* ``artiq.wavesynth`` has been removed.
|
||||
Breaking changes:
|
||||
|
||||
* ``SimpleApplet`` now calls widget constructors with an additional ``ctl`` parameter for control
|
||||
operations, which includes dataset operations. It can be ignored if not needed. For an example usage,
|
||||
refer to the ``big_number.py`` applet.
|
||||
* ``SimpleApplet`` and ``TitleApplet`` now call ``data_changed`` with additional parameters. Wrapped widgets
|
||||
should refactor the function signature as seen below:
|
||||
::
|
||||
|
||||
# SimpleApplet
|
||||
def data_changed(self, value, metadata, persist, mods)
|
||||
# SimpleApplet (old version)
|
||||
def data_changed(self, data, mods)
|
||||
# TitleApplet
|
||||
def data_changed(self, value, metadata, persist, mods, title)
|
||||
# TitleApplet (old version)
|
||||
def data_changed(self, data, mods, title)
|
||||
|
||||
Old syntax should be replaced with the form shown on the right.
|
||||
::
|
||||
|
||||
data[key][0] ==> persist[key]
|
||||
data[key][1] ==> value[key]
|
||||
data[key][2] ==> metadata[key]
|
||||
|
||||
* The ``ndecimals`` parameter in ``NumberValue`` and ``Scannable`` has been renamed to ``precision``.
|
||||
Parameters after and including ``scale`` in both constructors are now keyword-only.
|
||||
Refer to the updated ``no_hardware/arguments_demo.py`` example for current usage.
|
||||
|
||||
|
||||
ARTIQ-7
|
||||
-------
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
import os
|
||||
|
||||
def get_rev():
|
||||
return os.getenv("VERSIONEER_REV", default="unknown")
|
||||
|
||||
def get_version():
|
||||
return os.getenv("VERSIONEER_OVERRIDE", default="8.0+unknown.beta")
|
||||
|
|
|
@ -2,8 +2,6 @@
|
|||
|
||||
from PyQt5 import QtWidgets, QtCore, QtGui
|
||||
from artiq.applets.simple import SimpleApplet
|
||||
from artiq.tools import scale_from_metadata
|
||||
from artiq.gui.tools import LayoutWidget
|
||||
|
||||
|
||||
class QResponsiveLCDNumber(QtWidgets.QLCDNumber):
|
||||
|
@ -23,41 +21,29 @@ class QCancellableLineEdit(QtWidgets.QLineEdit):
|
|||
super().keyPressEvent(event)
|
||||
|
||||
|
||||
class NumberWidget(LayoutWidget):
|
||||
class NumberWidget(QtWidgets.QStackedWidget):
|
||||
def __init__(self, args, req):
|
||||
LayoutWidget.__init__(self)
|
||||
QtWidgets.QStackedWidget.__init__(self)
|
||||
self.dataset_name = args.dataset
|
||||
self.req = req
|
||||
self.metadata = dict()
|
||||
|
||||
self.number_area = QtWidgets.QStackedWidget()
|
||||
self.addWidget(self.number_area, 0, 0)
|
||||
|
||||
self.unit_area = QtWidgets.QLabel()
|
||||
self.unit_area.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignTop)
|
||||
self.addWidget(self.unit_area, 0, 1)
|
||||
|
||||
self.lcd_widget = QResponsiveLCDNumber()
|
||||
self.lcd_widget.setDigitCount(args.digit_count)
|
||||
self.lcd_widget.doubleClicked.connect(self.start_edit)
|
||||
self.number_area.addWidget(self.lcd_widget)
|
||||
self.addWidget(self.lcd_widget)
|
||||
|
||||
self.edit_widget = QCancellableLineEdit()
|
||||
self.edit_widget.setValidator(QtGui.QDoubleValidator())
|
||||
self.edit_widget.setAlignment(QtCore.Qt.AlignRight | QtCore.Qt.AlignVCenter)
|
||||
self.edit_widget.setAlignment(QtCore.Qt.AlignRight)
|
||||
self.edit_widget.editCancelled.connect(self.cancel_edit)
|
||||
self.edit_widget.returnPressed.connect(self.confirm_edit)
|
||||
self.number_area.addWidget(self.edit_widget)
|
||||
self.addWidget(self.edit_widget)
|
||||
|
||||
font = QtGui.QFont()
|
||||
font.setPointSize(60)
|
||||
self.edit_widget.setFont(font)
|
||||
|
||||
unit_font = QtGui.QFont()
|
||||
unit_font.setPointSize(20)
|
||||
self.unit_area.setFont(unit_font)
|
||||
|
||||
self.number_area.setCurrentWidget(self.lcd_widget)
|
||||
self.setCurrentWidget(self.lcd_widget)
|
||||
|
||||
def start_edit(self):
|
||||
# QLCDNumber value property contains the value of zero
|
||||
|
@ -65,32 +51,22 @@ class NumberWidget(LayoutWidget):
|
|||
self.edit_widget.setText(str(self.lcd_widget.value()))
|
||||
self.edit_widget.selectAll()
|
||||
self.edit_widget.setFocus()
|
||||
self.number_area.setCurrentWidget(self.edit_widget)
|
||||
self.setCurrentWidget(self.edit_widget)
|
||||
|
||||
def confirm_edit(self):
|
||||
scale = scale_from_metadata(self.metadata)
|
||||
val = float(self.edit_widget.text())
|
||||
val *= scale
|
||||
self.req.set_dataset(self.dataset_name, val, **self.metadata)
|
||||
self.number_area.setCurrentWidget(self.lcd_widget)
|
||||
value = float(self.edit_widget.text())
|
||||
self.req.set_dataset(self.dataset_name, value)
|
||||
self.setCurrentWidget(self.lcd_widget)
|
||||
|
||||
def cancel_edit(self):
|
||||
self.number_area.setCurrentWidget(self.lcd_widget)
|
||||
self.setCurrentWidget(self.lcd_widget)
|
||||
|
||||
def data_changed(self, value, metadata, persist, mods):
|
||||
try:
|
||||
self.metadata = metadata[self.dataset_name]
|
||||
# This applet will degenerate other scalar types to native float on edit
|
||||
# Use the dashboard ChangeEditDialog for consistent type casting
|
||||
val = float(value[self.dataset_name])
|
||||
scale = scale_from_metadata(self.metadata)
|
||||
val /= scale
|
||||
n = float(value[self.dataset_name])
|
||||
except (KeyError, ValueError, TypeError):
|
||||
val = "---"
|
||||
|
||||
unit = self.metadata.get("unit", "")
|
||||
self.unit_area.setText(unit)
|
||||
self.lcd_widget.display(val)
|
||||
n = "---"
|
||||
self.lcd_widget.display(n)
|
||||
|
||||
|
||||
def main():
|
||||
|
|
|
@ -508,9 +508,5 @@ class ExperimentsArea(QtWidgets.QMdiArea):
|
|||
self.open_experiments.append(dock)
|
||||
return dock
|
||||
|
||||
def set_argument_value(self, expurl, name, value):
|
||||
logger.warning("Unable to set argument '%s', dropping change. "
|
||||
"'set_argument_value' not supported in browser.", name)
|
||||
|
||||
def on_dock_closed(self, dock):
|
||||
self.open_experiments.remove(dock)
|
||||
|
|
|
@ -71,6 +71,7 @@ def build_artiq_soc(soc, argdict):
|
|||
if not soc.config["DRTIO_ROLE"] == "satellite":
|
||||
builder.add_software_package("runtime", os.path.join(firmware_dir, "runtime"))
|
||||
else:
|
||||
# Assume DRTIO satellite.
|
||||
builder.add_software_package("satman", os.path.join(firmware_dir, "satman"))
|
||||
try:
|
||||
builder.build()
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
from .inline import inline
|
||||
from .unroll import unroll
|
|
@ -1,79 +0,0 @@
|
|||
"""
|
||||
:func:`inline` inlines a call instruction in ARTIQ IR.
|
||||
The call instruction must have a statically known callee,
|
||||
it must be second to last in the basic block, and the basic
|
||||
block must have exactly one successor.
|
||||
"""
|
||||
|
||||
from .. import types, builtins, iodelay, ir
|
||||
|
||||
def inline(call_insn):
|
||||
assert isinstance(call_insn, ir.Call)
|
||||
assert call_insn.static_target_function is not None
|
||||
assert len(call_insn.basic_block.successors()) == 1
|
||||
assert call_insn.basic_block.index(call_insn) == \
|
||||
len(call_insn.basic_block.instructions) - 2
|
||||
|
||||
value_map = {}
|
||||
source_function = call_insn.static_target_function
|
||||
target_function = call_insn.basic_block.function
|
||||
target_predecessor = call_insn.basic_block
|
||||
target_successor = call_insn.basic_block.successors()[0]
|
||||
|
||||
if builtins.is_none(source_function.type.ret):
|
||||
target_return_phi = None
|
||||
else:
|
||||
target_return_phi = target_successor.prepend(ir.Phi(source_function.type.ret))
|
||||
|
||||
closure = target_predecessor.insert(ir.GetAttr(call_insn.target_function(), '__closure__'),
|
||||
before=call_insn)
|
||||
for actual_arg, formal_arg in zip([closure] + call_insn.arguments(),
|
||||
source_function.arguments):
|
||||
value_map[formal_arg] = actual_arg
|
||||
|
||||
for source_block in source_function.basic_blocks:
|
||||
target_block = ir.BasicBlock([], "i." + source_block.name)
|
||||
target_function.add(target_block)
|
||||
value_map[source_block] = target_block
|
||||
|
||||
def mapper(value):
|
||||
if isinstance(value, ir.Constant):
|
||||
return value
|
||||
else:
|
||||
return value_map[value]
|
||||
|
||||
for source_insn in source_function.instructions():
|
||||
target_block = value_map[source_insn.basic_block]
|
||||
if isinstance(source_insn, ir.Return):
|
||||
if target_return_phi is not None:
|
||||
target_return_phi.add_incoming(mapper(source_insn.value()), target_block)
|
||||
target_insn = ir.Branch(target_successor)
|
||||
elif isinstance(source_insn, ir.Phi):
|
||||
target_insn = ir.Phi()
|
||||
elif isinstance(source_insn, ir.Delay):
|
||||
target_insn = source_insn.copy(mapper)
|
||||
target_insn.interval = source_insn.interval.fold(call_insn.arg_exprs)
|
||||
elif isinstance(source_insn, ir.Loop):
|
||||
target_insn = source_insn.copy(mapper)
|
||||
target_insn.trip_count = source_insn.trip_count.fold(call_insn.arg_exprs)
|
||||
elif isinstance(source_insn, ir.Call):
|
||||
target_insn = source_insn.copy(mapper)
|
||||
target_insn.arg_exprs = \
|
||||
{ arg: source_insn.arg_exprs[arg].fold(call_insn.arg_exprs)
|
||||
for arg in source_insn.arg_exprs }
|
||||
else:
|
||||
target_insn = source_insn.copy(mapper)
|
||||
target_insn.name = "i." + source_insn.name
|
||||
value_map[source_insn] = target_insn
|
||||
target_block.append(target_insn)
|
||||
|
||||
for source_insn in source_function.instructions():
|
||||
if isinstance(source_insn, ir.Phi):
|
||||
target_insn = value_map[source_insn]
|
||||
for block, value in source_insn.incoming():
|
||||
target_insn.add_incoming(value_map[value], value_map[block])
|
||||
|
||||
target_predecessor.terminator().replace_with(ir.Branch(value_map[source_function.entry()]))
|
||||
if target_return_phi is not None:
|
||||
call_insn.replace_all_uses_with(target_return_phi)
|
||||
call_insn.erase()
|
|
@ -1,97 +0,0 @@
|
|||
"""
|
||||
:func:`unroll` unrolls a loop instruction in ARTIQ IR.
|
||||
The loop's trip count must be constant.
|
||||
The loop body must not have any control flow instructions
|
||||
except for one branch back to the loop head.
|
||||
The loop body must be executed if the condition to which
|
||||
the instruction refers is true.
|
||||
"""
|
||||
|
||||
from .. import types, builtins, iodelay, ir
|
||||
from ..analyses import domination
|
||||
|
||||
def _get_body_blocks(root, limit):
|
||||
postorder = []
|
||||
|
||||
visited = set()
|
||||
def visit(block):
|
||||
visited.add(block)
|
||||
for next_block in block.successors():
|
||||
if next_block not in visited and next_block is not limit:
|
||||
visit(next_block)
|
||||
postorder.append(block)
|
||||
|
||||
visit(root)
|
||||
|
||||
postorder.reverse()
|
||||
return postorder
|
||||
|
||||
def unroll(loop_insn):
|
||||
loop_head = loop_insn.basic_block
|
||||
function = loop_head.function
|
||||
assert isinstance(loop_insn, ir.Loop)
|
||||
assert len(loop_head.predecessors()) == 2
|
||||
assert len(loop_insn.if_false().predecessors()) == 1
|
||||
assert iodelay.is_const(loop_insn.trip_count)
|
||||
|
||||
trip_count = loop_insn.trip_count.fold().value
|
||||
if trip_count == 0:
|
||||
loop_insn.replace_with(ir.Branch(loop_insn.if_false()))
|
||||
return
|
||||
|
||||
source_blocks = _get_body_blocks(loop_insn.if_true(), loop_head)
|
||||
source_indvar = loop_insn.induction_variable()
|
||||
source_tail = loop_insn.if_false()
|
||||
unroll_target = loop_head
|
||||
for n in range(trip_count):
|
||||
value_map = {source_indvar: ir.Constant(n, source_indvar.type)}
|
||||
|
||||
for source_block in source_blocks:
|
||||
target_block = ir.BasicBlock([], "u{}.{}".format(n, source_block.name))
|
||||
function.add(target_block)
|
||||
value_map[source_block] = target_block
|
||||
|
||||
def mapper(value):
|
||||
if isinstance(value, ir.Constant):
|
||||
return value
|
||||
elif value in value_map:
|
||||
return value_map[value]
|
||||
else:
|
||||
return value
|
||||
|
||||
for source_block in source_blocks:
|
||||
target_block = value_map[source_block]
|
||||
for source_insn in source_block.instructions:
|
||||
if isinstance(source_insn, ir.Phi):
|
||||
target_insn = ir.Phi()
|
||||
else:
|
||||
target_insn = source_insn.copy(mapper)
|
||||
target_insn.name = "u{}.{}".format(n, source_insn.name)
|
||||
target_block.append(target_insn)
|
||||
value_map[source_insn] = target_insn
|
||||
|
||||
for source_block in source_blocks:
|
||||
for source_insn in source_block.instructions:
|
||||
if isinstance(source_insn, ir.Phi):
|
||||
target_insn = value_map[source_insn]
|
||||
for block, value in source_insn.incoming():
|
||||
target_insn.add_incoming(value_map[value], value_map[block])
|
||||
|
||||
assert isinstance(unroll_target.terminator(), (ir.Branch, ir.Loop))
|
||||
unroll_target.terminator().replace_with(ir.Branch(value_map[source_blocks[0]]))
|
||||
unroll_target = value_map[source_blocks[-1]]
|
||||
|
||||
assert isinstance(unroll_target.terminator(), ir.Branch)
|
||||
assert len(source_blocks[-1].successors()) == 1
|
||||
unroll_target.terminator().replace_with(ir.Branch(source_tail))
|
||||
|
||||
for source_block in reversed(source_blocks):
|
||||
for source_insn in reversed(source_block.instructions):
|
||||
for use in set(source_insn.uses):
|
||||
if isinstance(use, ir.Phi):
|
||||
assert use.basic_block == loop_head
|
||||
use.remove_incoming_value(source_insn)
|
||||
source_insn.erase()
|
||||
|
||||
for source_block in reversed(source_blocks):
|
||||
source_block.erase()
|
|
@ -1,3 +0,0 @@
|
|||
from .domination import DominatorTree
|
||||
from .devirtualization import Devirtualization
|
||||
from .invariant_detection import InvariantDetection
|
|
@ -1,119 +0,0 @@
|
|||
"""
|
||||
:class:`Devirtualizer` performs method resolution at
|
||||
compile time.
|
||||
|
||||
Devirtualization is implemented using a lattice
|
||||
with three states: unknown → assigned once → diverges.
|
||||
The lattice is computed individually for every
|
||||
variable in scope as well as every
|
||||
(instance type, field name) pair.
|
||||
"""
|
||||
|
||||
from pythonparser import algorithm
|
||||
from .. import asttyped, ir, types
|
||||
|
||||
def _advance(target_map, key, value):
|
||||
if key not in target_map:
|
||||
target_map[key] = value # unknown → assigned once
|
||||
else:
|
||||
target_map[key] = None # assigned once → diverges
|
||||
|
||||
class FunctionResolver(algorithm.Visitor):
|
||||
def __init__(self, variable_map):
|
||||
self.variable_map = variable_map
|
||||
|
||||
self.scope_map = dict()
|
||||
self.queue = []
|
||||
|
||||
self.in_assign = False
|
||||
self.current_scopes = []
|
||||
|
||||
def finalize(self):
|
||||
for thunk in self.queue:
|
||||
thunk()
|
||||
|
||||
def visit_scope(self, node):
|
||||
self.current_scopes.append(node)
|
||||
self.generic_visit(node)
|
||||
self.current_scopes.pop()
|
||||
|
||||
def visit_in_assign(self, node):
|
||||
self.in_assign = True
|
||||
self.visit(node)
|
||||
self.in_assign = False
|
||||
|
||||
def visit_Assign(self, node):
|
||||
self.visit(node.value)
|
||||
self.visit_in_assign(node.targets)
|
||||
|
||||
def visit_ForT(self, node):
|
||||
self.visit(node.iter)
|
||||
self.visit_in_assign(node.target)
|
||||
self.visit(node.body)
|
||||
self.visit(node.orelse)
|
||||
|
||||
def visit_withitemT(self, node):
|
||||
self.visit(node.context_expr)
|
||||
self.visit_in_assign(node.optional_vars)
|
||||
|
||||
def visit_comprehension(self, node):
|
||||
self.visit(node.iter)
|
||||
self.visit_in_assign(node.target)
|
||||
self.visit(node.ifs)
|
||||
|
||||
def visit_ModuleT(self, node):
|
||||
self.visit_scope(node)
|
||||
|
||||
def visit_FunctionDefT(self, node):
|
||||
_advance(self.scope_map, (self.current_scopes[-1], node.name), node)
|
||||
self.visit_scope(node)
|
||||
|
||||
def visit_NameT(self, node):
|
||||
if self.in_assign:
|
||||
# Just give up if we assign anything at all to a variable, and
|
||||
# assume it diverges.
|
||||
_advance(self.scope_map, (self.current_scopes[-1], node.id), None)
|
||||
else:
|
||||
# Look up the final value in scope_map and copy it into variable_map.
|
||||
keys = [(scope, node.id) for scope in reversed(self.current_scopes)]
|
||||
def thunk():
|
||||
for key in keys:
|
||||
if key in self.scope_map:
|
||||
self.variable_map[node] = self.scope_map[key]
|
||||
return
|
||||
self.queue.append(thunk)
|
||||
|
||||
class MethodResolver(algorithm.Visitor):
|
||||
def __init__(self, variable_map, method_map):
|
||||
self.variable_map = variable_map
|
||||
self.method_map = method_map
|
||||
|
||||
# embedding.Stitcher.finalize generates initialization statements
|
||||
# of form "constructor.meth = meth_body".
|
||||
def visit_Assign(self, node):
|
||||
if node.value not in self.variable_map:
|
||||
return
|
||||
|
||||
value = self.variable_map[node.value]
|
||||
for target in node.targets:
|
||||
if isinstance(target, asttyped.AttributeT):
|
||||
if types.is_constructor(target.value.type):
|
||||
instance_type = target.value.type.instance
|
||||
elif types.is_instance(target.value.type):
|
||||
instance_type = target.value.type
|
||||
else:
|
||||
continue
|
||||
_advance(self.method_map, (instance_type, target.attr), value)
|
||||
|
||||
class Devirtualization:
|
||||
def __init__(self):
|
||||
self.variable_map = dict()
|
||||
self.method_map = dict()
|
||||
|
||||
def visit(self, node):
|
||||
function_resolver = FunctionResolver(self.variable_map)
|
||||
function_resolver.visit(node)
|
||||
function_resolver.finalize()
|
||||
|
||||
method_resolver = MethodResolver(self.variable_map, self.method_map)
|
||||
method_resolver.visit(node)
|
|
@ -1,144 +0,0 @@
|
|||
"""
|
||||
:class:`DominatorTree` computes the dominance relation over
|
||||
control flow graphs.
|
||||
|
||||
See http://www.cs.rice.edu/~keith/EMBED/dom.pdf.
|
||||
"""
|
||||
|
||||
class GenericDominatorTree:
|
||||
def __init__(self):
|
||||
self._assign_names()
|
||||
self._compute()
|
||||
|
||||
def _traverse_in_postorder(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def _prev_block_names(self, block):
|
||||
raise NotImplementedError
|
||||
|
||||
def _assign_names(self):
|
||||
postorder = self._traverse_in_postorder()
|
||||
|
||||
self._start_name = len(postorder) - 1
|
||||
self._block_of_name = postorder
|
||||
self._name_of_block = {}
|
||||
for block_name, block in enumerate(postorder):
|
||||
self._name_of_block[block] = block_name
|
||||
|
||||
def _intersect(self, block_name_1, block_name_2):
|
||||
finger_1, finger_2 = block_name_1, block_name_2
|
||||
while finger_1 != finger_2:
|
||||
while finger_1 < finger_2:
|
||||
finger_1 = self._doms[finger_1]
|
||||
while finger_2 < finger_1:
|
||||
finger_2 = self._doms[finger_2]
|
||||
return finger_1
|
||||
|
||||
def _compute(self):
|
||||
self._doms = {}
|
||||
|
||||
# Start block dominates itself.
|
||||
self._doms[self._start_name] = self._start_name
|
||||
|
||||
# We don't yet know what blocks dominate all other blocks.
|
||||
for block_name in range(self._start_name):
|
||||
self._doms[block_name] = None
|
||||
|
||||
changed = True
|
||||
while changed:
|
||||
changed = False
|
||||
|
||||
# For all blocks except start block, in reverse postorder...
|
||||
for block_name in reversed(range(self._start_name)):
|
||||
# Select a new immediate dominator from the blocks we have
|
||||
# already processed, and remember all others.
|
||||
# We've already processed at least one previous block because
|
||||
# of the graph traverse order.
|
||||
new_idom, prev_block_names = None, []
|
||||
for prev_block_name in self._prev_block_names(block_name):
|
||||
if new_idom is None and self._doms[prev_block_name] is not None:
|
||||
new_idom = prev_block_name
|
||||
else:
|
||||
prev_block_names.append(prev_block_name)
|
||||
|
||||
# Find a common previous block
|
||||
for prev_block_name in prev_block_names:
|
||||
if self._doms[prev_block_name] is not None:
|
||||
new_idom = self._intersect(prev_block_name, new_idom)
|
||||
|
||||
if self._doms[block_name] != new_idom:
|
||||
self._doms[block_name] = new_idom
|
||||
changed = True
|
||||
|
||||
def immediate_dominator(self, block):
|
||||
return self._block_of_name[self._doms[self._name_of_block[block]]]
|
||||
|
||||
def dominators(self, block):
|
||||
# Blocks that are statically unreachable from entry are considered
|
||||
# dominated by every other block.
|
||||
if block not in self._name_of_block:
|
||||
yield from self._block_of_name
|
||||
return
|
||||
|
||||
block_name = self._name_of_block[block]
|
||||
yield self._block_of_name[block_name]
|
||||
while block_name != self._doms[block_name]:
|
||||
block_name = self._doms[block_name]
|
||||
yield self._block_of_name[block_name]
|
||||
|
||||
class DominatorTree(GenericDominatorTree):
|
||||
def __init__(self, function):
|
||||
self.function = function
|
||||
super().__init__()
|
||||
|
||||
def _traverse_in_postorder(self):
|
||||
postorder = []
|
||||
|
||||
visited = set()
|
||||
def visit(block):
|
||||
visited.add(block)
|
||||
for next_block in block.successors():
|
||||
if next_block not in visited:
|
||||
visit(next_block)
|
||||
postorder.append(block)
|
||||
|
||||
visit(self.function.entry())
|
||||
|
||||
return postorder
|
||||
|
||||
def _prev_block_names(self, block_name):
|
||||
for block in self._block_of_name[block_name].predecessors():
|
||||
# Only return predecessors that are statically reachable from entry.
|
||||
if block in self._name_of_block:
|
||||
yield self._name_of_block[block]
|
||||
|
||||
class PostDominatorTree(GenericDominatorTree):
|
||||
def __init__(self, function):
|
||||
self.function = function
|
||||
super().__init__()
|
||||
|
||||
def _traverse_in_postorder(self):
|
||||
postorder = []
|
||||
|
||||
visited = set()
|
||||
def visit(block):
|
||||
visited.add(block)
|
||||
for next_block in block.predecessors():
|
||||
if next_block not in visited:
|
||||
visit(next_block)
|
||||
postorder.append(block)
|
||||
|
||||
for block in self.function.basic_blocks:
|
||||
if not any(block.successors()):
|
||||
visit(block)
|
||||
|
||||
postorder.append(None) # virtual exit block
|
||||
return postorder
|
||||
|
||||
def _prev_block_names(self, block_name):
|
||||
succ_blocks = self._block_of_name[block_name].successors()
|
||||
if len(succ_blocks) > 0:
|
||||
for block in succ_blocks:
|
||||
yield self._name_of_block[block]
|
||||
else:
|
||||
yield self._start_name
|
|
@ -1,49 +0,0 @@
|
|||
"""
|
||||
:class:`InvariantDetection` determines which attributes can be safely
|
||||
marked kernel invariant.
|
||||
"""
|
||||
|
||||
from pythonparser import diagnostic
|
||||
from .. import ir, types
|
||||
|
||||
class InvariantDetection:
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
|
||||
def process(self, functions):
|
||||
self.attr_locs = dict()
|
||||
self.attr_written = set()
|
||||
|
||||
for func in functions:
|
||||
self.process_function(func)
|
||||
|
||||
for key in self.attr_locs:
|
||||
if key not in self.attr_written:
|
||||
typ, attr = key
|
||||
if attr in typ.constant_attributes:
|
||||
continue
|
||||
|
||||
diag = diagnostic.Diagnostic("note",
|
||||
"attribute '{attr}' of type '{type}' is never written to; " +
|
||||
"it could be marked as kernel invariant to potentially increase performance",
|
||||
{"attr": attr,
|
||||
"type": typ.name},
|
||||
self.attr_locs[key])
|
||||
self.engine.process(diag)
|
||||
|
||||
def process_function(self, func):
|
||||
for block in func.basic_blocks:
|
||||
for insn in block.instructions:
|
||||
if not isinstance(insn, (ir.GetAttr, ir.SetAttr)):
|
||||
continue
|
||||
if not types.is_instance(insn.object().type):
|
||||
continue
|
||||
|
||||
key = (insn.object().type, insn.attr)
|
||||
if isinstance(insn, ir.GetAttr):
|
||||
if types.is_method(insn.type):
|
||||
continue
|
||||
if key not in self.attr_locs and insn.loc is not None:
|
||||
self.attr_locs[key] = insn.loc
|
||||
elif isinstance(insn, ir.SetAttr):
|
||||
self.attr_written.add(key)
|
|
@ -1,119 +0,0 @@
|
|||
"""
|
||||
The typedtree module exports the PythonParser AST enriched with
|
||||
typing information.
|
||||
"""
|
||||
|
||||
from pythonparser import ast
|
||||
|
||||
class commontyped(ast.commonloc):
|
||||
"""A mixin for typed AST nodes."""
|
||||
|
||||
_types = ("type",)
|
||||
|
||||
def _reprfields(self):
|
||||
return self._fields + self._locs + self._types
|
||||
|
||||
class scoped(object):
|
||||
"""
|
||||
:ivar typing_env: (dict with string keys and :class:`.types.Type` values)
|
||||
map of variable names to variable types
|
||||
:ivar globals_in_scope: (set of string keys)
|
||||
set of variables resolved as globals
|
||||
"""
|
||||
|
||||
class remote(object):
|
||||
"""
|
||||
:ivar remote_fn: (bool) whether function is ran on a remote device,
|
||||
meaning arguments are received remotely and return is sent remotely
|
||||
"""
|
||||
|
||||
# Typed versions of untyped nodes
|
||||
class argT(ast.arg, commontyped):
|
||||
pass
|
||||
|
||||
class ClassDefT(ast.ClassDef):
|
||||
_types = ("constructor_type",)
|
||||
class FunctionDefT(ast.FunctionDef, scoped, remote):
|
||||
_types = ("signature_type",)
|
||||
class QuotedFunctionDefT(FunctionDefT):
|
||||
"""
|
||||
:ivar flags: (set of str) Code generation flags (see :class:`ir.Function`).
|
||||
"""
|
||||
class ModuleT(ast.Module, scoped):
|
||||
pass
|
||||
|
||||
class ExceptHandlerT(ast.ExceptHandler):
|
||||
_fields = ("filter", "name", "body") # rename ast.ExceptHandler.type to filter
|
||||
_types = ("name_type",)
|
||||
|
||||
class ForT(ast.For):
|
||||
"""
|
||||
:ivar trip_count: (:class:`iodelay.Expr`)
|
||||
:ivar trip_interval: (:class:`iodelay.Expr`)
|
||||
"""
|
||||
|
||||
class withitemT(ast.withitem):
|
||||
_types = ("enter_type", "exit_type")
|
||||
|
||||
class SliceT(ast.Slice, commontyped):
|
||||
pass
|
||||
|
||||
class AttributeT(ast.Attribute, commontyped):
|
||||
pass
|
||||
class BinOpT(ast.BinOp, commontyped):
|
||||
pass
|
||||
class BoolOpT(ast.BoolOp, commontyped):
|
||||
pass
|
||||
class CallT(ast.Call, commontyped, remote):
|
||||
"""
|
||||
:ivar iodelay: (:class:`iodelay.Expr`)
|
||||
:ivar arg_exprs: (dict of str to :class:`iodelay.Expr`)
|
||||
"""
|
||||
class CompareT(ast.Compare, commontyped):
|
||||
pass
|
||||
class DictT(ast.Dict, commontyped):
|
||||
pass
|
||||
class DictCompT(ast.DictComp, commontyped, scoped):
|
||||
pass
|
||||
class EllipsisT(ast.Ellipsis, commontyped):
|
||||
pass
|
||||
class GeneratorExpT(ast.GeneratorExp, commontyped, scoped):
|
||||
pass
|
||||
class IfExpT(ast.IfExp, commontyped):
|
||||
pass
|
||||
class LambdaT(ast.Lambda, commontyped, scoped):
|
||||
pass
|
||||
class ListT(ast.List, commontyped):
|
||||
pass
|
||||
class ListCompT(ast.ListComp, commontyped, scoped):
|
||||
pass
|
||||
class NameT(ast.Name, commontyped):
|
||||
pass
|
||||
class NameConstantT(ast.NameConstant, commontyped):
|
||||
pass
|
||||
class NumT(ast.Num, commontyped):
|
||||
pass
|
||||
class SetT(ast.Set, commontyped):
|
||||
pass
|
||||
class SetCompT(ast.SetComp, commontyped, scoped):
|
||||
pass
|
||||
class StrT(ast.Str, commontyped):
|
||||
pass
|
||||
class StarredT(ast.Starred, commontyped):
|
||||
pass
|
||||
class SubscriptT(ast.Subscript, commontyped):
|
||||
pass
|
||||
class TupleT(ast.Tuple, commontyped):
|
||||
pass
|
||||
class UnaryOpT(ast.UnaryOp, commontyped):
|
||||
pass
|
||||
class YieldT(ast.Yield, commontyped):
|
||||
pass
|
||||
class YieldFromT(ast.YieldFrom, commontyped):
|
||||
pass
|
||||
|
||||
# Novel typed nodes
|
||||
class CoerceT(ast.expr, commontyped):
|
||||
_fields = ('value',) # other_value deliberately not in _fields
|
||||
class QuoteT(ast.expr, commontyped):
|
||||
_fields = ('value',)
|
|
@ -1,355 +0,0 @@
|
|||
"""
|
||||
The :mod:`builtins` module contains the builtin Python
|
||||
and ARTIQ types, such as int or float.
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
from . import types
|
||||
|
||||
# Types
|
||||
|
||||
class TNone(types.TMono):
|
||||
def __init__(self):
|
||||
super().__init__("NoneType")
|
||||
|
||||
class TBool(types.TMono):
|
||||
def __init__(self):
|
||||
super().__init__("bool")
|
||||
|
||||
@staticmethod
|
||||
def zero():
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def one():
|
||||
return True
|
||||
|
||||
class TInt(types.TMono):
|
||||
def __init__(self, width=None):
|
||||
if width is None:
|
||||
width = types.TVar()
|
||||
super().__init__("int", {"width": width})
|
||||
|
||||
@staticmethod
|
||||
def zero():
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def one():
|
||||
return 1
|
||||
|
||||
def TInt8():
|
||||
return TInt(types.TValue(8))
|
||||
|
||||
def TInt32():
|
||||
return TInt(types.TValue(32))
|
||||
|
||||
def TInt64():
|
||||
return TInt(types.TValue(64))
|
||||
|
||||
def _int_printer(typ, printer, depth, max_depth):
|
||||
if types.is_var(typ["width"]):
|
||||
return "numpy.int?"
|
||||
else:
|
||||
return "numpy.int{}".format(types.get_value(typ.find()["width"]))
|
||||
types.TypePrinter.custom_printers["int"] = _int_printer
|
||||
|
||||
class TFloat(types.TMono):
|
||||
def __init__(self):
|
||||
super().__init__("float")
|
||||
|
||||
@staticmethod
|
||||
def zero():
|
||||
return 0.0
|
||||
|
||||
@staticmethod
|
||||
def one():
|
||||
return 1.0
|
||||
|
||||
class TStr(types.TMono):
|
||||
def __init__(self):
|
||||
super().__init__("str")
|
||||
|
||||
class TBytes(types.TMono):
|
||||
def __init__(self):
|
||||
super().__init__("bytes")
|
||||
|
||||
class TByteArray(types.TMono):
|
||||
def __init__(self):
|
||||
super().__init__("bytearray")
|
||||
|
||||
class TList(types.TMono):
|
||||
def __init__(self, elt=None):
|
||||
if elt is None:
|
||||
elt = types.TVar()
|
||||
super().__init__("list", {"elt": elt})
|
||||
|
||||
class TArray(types.TMono):
|
||||
def __init__(self, elt=None, num_dims=1):
|
||||
if elt is None:
|
||||
elt = types.TVar()
|
||||
if isinstance(num_dims, int):
|
||||
# Make TArray more convenient to instantiate from (ARTIQ) user code.
|
||||
num_dims = types.TValue(num_dims)
|
||||
# For now, enforce number of dimensions to be known, as we'd otherwise
|
||||
# need to implement custom unification logic for the type of `shape`.
|
||||
# Default to 1 to keep compatibility with old user code from before
|
||||
# multidimensional array support.
|
||||
assert isinstance(num_dims.value, int), "Number of dimensions must be resolved"
|
||||
|
||||
super().__init__("array", {"elt": elt, "num_dims": num_dims})
|
||||
self.attributes = OrderedDict([
|
||||
("buffer", types._TPointer(elt)),
|
||||
("shape", types.TTuple([TInt32()] * num_dims.value)),
|
||||
])
|
||||
|
||||
def _array_printer(typ, printer, depth, max_depth):
|
||||
return "numpy.array(elt={}, num_dims={})".format(
|
||||
printer.name(typ["elt"], depth, max_depth), typ["num_dims"].value)
|
||||
types.TypePrinter.custom_printers["array"] = _array_printer
|
||||
|
||||
class TRange(types.TMono):
|
||||
def __init__(self, elt=None):
|
||||
if elt is None:
|
||||
elt = types.TVar()
|
||||
super().__init__("range", {"elt": elt})
|
||||
self.attributes = OrderedDict([
|
||||
("start", elt),
|
||||
("stop", elt),
|
||||
("step", elt),
|
||||
])
|
||||
|
||||
class TException(types.TMono):
|
||||
# All exceptions share the same internal layout:
|
||||
# * Pointer to the unique global with the name of the exception (str)
|
||||
# (which also serves as the EHABI type_info).
|
||||
# * File, line and column where it was raised (str, int, int).
|
||||
# * Message, which can contain substitutions {0}, {1} and {2} (str).
|
||||
# * Three 64-bit integers, parameterizing the message (numpy.int64).
|
||||
# These attributes are prefixed with `#` so that users cannot access them,
|
||||
# and we don't have to do string allocation in the runtime.
|
||||
# #__name__ is now a string key in the host. TStr may not be an actual
|
||||
# CSlice in the runtime, they might be a CSlice with length = i32::MAX and
|
||||
# ptr = string key in the host.
|
||||
|
||||
# Keep this in sync with the function ARTIQIRGenerator.alloc_exn.
|
||||
attributes = OrderedDict([
|
||||
("#__name__", TInt32()),
|
||||
("#__file__", TStr()),
|
||||
("#__line__", TInt32()),
|
||||
("#__col__", TInt32()),
|
||||
("#__func__", TStr()),
|
||||
("#__message__", TStr()),
|
||||
("#__param0__", TInt64()),
|
||||
("#__param1__", TInt64()),
|
||||
("#__param2__", TInt64()),
|
||||
])
|
||||
|
||||
def __init__(self, name="Exception", id=0):
|
||||
super().__init__(name)
|
||||
self.id = id
|
||||
|
||||
def fn_bool():
|
||||
return types.TConstructor(TBool())
|
||||
|
||||
def fn_int():
|
||||
return types.TConstructor(TInt())
|
||||
|
||||
def fn_int32():
|
||||
return types.TBuiltinFunction("int32")
|
||||
|
||||
def fn_int64():
|
||||
return types.TBuiltinFunction("int64")
|
||||
|
||||
def fn_float():
|
||||
return types.TConstructor(TFloat())
|
||||
|
||||
def fn_str():
|
||||
return types.TConstructor(TStr())
|
||||
|
||||
def fn_bytes():
|
||||
return types.TConstructor(TBytes())
|
||||
|
||||
def fn_bytearray():
|
||||
return types.TConstructor(TByteArray())
|
||||
|
||||
def fn_list():
|
||||
return types.TConstructor(TList())
|
||||
|
||||
def fn_array():
|
||||
# numpy.array() is actually a "magic" macro that is expanded in-place, but
|
||||
# just as for builtin functions, we do not want to quote it, etc.
|
||||
return types.TBuiltinFunction("array")
|
||||
|
||||
def fn_Exception():
|
||||
return types.TExceptionConstructor(TException("Exception"))
|
||||
|
||||
def fn_IndexError():
|
||||
return types.TExceptionConstructor(TException("IndexError"))
|
||||
|
||||
def fn_ValueError():
|
||||
return types.TExceptionConstructor(TException("ValueError"))
|
||||
|
||||
def fn_ZeroDivisionError():
|
||||
return types.TExceptionConstructor(TException("ZeroDivisionError"))
|
||||
|
||||
def fn_RuntimeError():
|
||||
return types.TExceptionConstructor(TException("RuntimeError"))
|
||||
|
||||
def fn_range():
|
||||
return types.TBuiltinFunction("range")
|
||||
|
||||
def fn_len():
|
||||
return types.TBuiltinFunction("len")
|
||||
|
||||
def fn_round():
|
||||
return types.TBuiltinFunction("round")
|
||||
|
||||
def fn_abs():
|
||||
return types.TBuiltinFunction("abs")
|
||||
|
||||
def fn_min():
|
||||
return types.TBuiltinFunction("min")
|
||||
|
||||
def fn_max():
|
||||
return types.TBuiltinFunction("max")
|
||||
|
||||
def fn_make_array():
|
||||
return types.TBuiltinFunction("make_array")
|
||||
|
||||
def fn_print():
|
||||
return types.TBuiltinFunction("print")
|
||||
|
||||
def fn_kernel():
|
||||
return types.TBuiltinFunction("kernel")
|
||||
|
||||
def obj_parallel():
|
||||
return types.TBuiltin("parallel")
|
||||
|
||||
def obj_interleave():
|
||||
return types.TBuiltin("interleave")
|
||||
|
||||
def obj_sequential():
|
||||
return types.TBuiltin("sequential")
|
||||
|
||||
def fn_delay():
|
||||
return types.TBuiltinFunction("delay")
|
||||
|
||||
def fn_now_mu():
|
||||
return types.TBuiltinFunction("now_mu")
|
||||
|
||||
def fn_delay_mu():
|
||||
return types.TBuiltinFunction("delay_mu")
|
||||
|
||||
def fn_at_mu():
|
||||
return types.TBuiltinFunction("at_mu")
|
||||
|
||||
def fn_rtio_log():
|
||||
return types.TBuiltinFunction("rtio_log")
|
||||
|
||||
def fn_subkernel_await():
|
||||
return types.TBuiltinFunction("subkernel_await")
|
||||
|
||||
def fn_subkernel_preload():
|
||||
return types.TBuiltinFunction("subkernel_preload")
|
||||
|
||||
# Accessors
|
||||
|
||||
def is_none(typ):
|
||||
return types.is_mono(typ, "NoneType")
|
||||
|
||||
def is_bool(typ):
|
||||
return types.is_mono(typ, "bool")
|
||||
|
||||
def is_int(typ, width=None):
|
||||
if width is not None:
|
||||
return types.is_mono(typ, "int", width=width)
|
||||
else:
|
||||
return types.is_mono(typ, "int")
|
||||
|
||||
def is_int32(typ):
|
||||
return is_int(typ, types.TValue(32))
|
||||
|
||||
def is_int64(typ):
|
||||
return is_int(typ, types.TValue(64))
|
||||
|
||||
def get_int_width(typ):
|
||||
if is_int(typ):
|
||||
return types.get_value(typ.find()["width"])
|
||||
|
||||
def is_float(typ):
|
||||
return types.is_mono(typ, "float")
|
||||
|
||||
def is_str(typ):
|
||||
return types.is_mono(typ, "str")
|
||||
|
||||
def is_bytes(typ):
|
||||
return types.is_mono(typ, "bytes")
|
||||
|
||||
def is_bytearray(typ):
|
||||
return types.is_mono(typ, "bytearray")
|
||||
|
||||
def is_numeric(typ):
|
||||
typ = typ.find()
|
||||
return isinstance(typ, types.TMono) and \
|
||||
typ.name in ('int', 'float')
|
||||
|
||||
def is_list(typ, elt=None):
|
||||
if elt is not None:
|
||||
return types.is_mono(typ, "list", elt=elt)
|
||||
else:
|
||||
return types.is_mono(typ, "list")
|
||||
|
||||
def is_array(typ, elt=None):
|
||||
if elt is not None:
|
||||
return types.is_mono(typ, "array", elt=elt)
|
||||
else:
|
||||
return types.is_mono(typ, "array")
|
||||
|
||||
def is_listish(typ, elt=None):
|
||||
if is_list(typ, elt) or is_array(typ, elt):
|
||||
return True
|
||||
elif elt is None:
|
||||
return is_str(typ) or is_bytes(typ) or is_bytearray(typ)
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_range(typ, elt=None):
|
||||
if elt is not None:
|
||||
return types.is_mono(typ, "range", {"elt": elt})
|
||||
else:
|
||||
return types.is_mono(typ, "range")
|
||||
|
||||
def is_exception(typ, name=None):
|
||||
if name is None:
|
||||
return isinstance(typ.find(), TException)
|
||||
else:
|
||||
return isinstance(typ.find(), TException) and \
|
||||
typ.name == name
|
||||
|
||||
def is_iterable(typ):
|
||||
return is_listish(typ) or is_range(typ)
|
||||
|
||||
def get_iterable_elt(typ):
|
||||
# TODO: Arrays count as listish, but this returns the innermost element type for
|
||||
# n-dimensional arrays, rather than the n-1 dimensional result of iterating over
|
||||
# the first axis, which makes the name a bit misleading.
|
||||
if is_str(typ) or is_bytes(typ) or is_bytearray(typ):
|
||||
return TInt8()
|
||||
elif types._is_pointer(typ) or is_iterable(typ):
|
||||
return typ.find()["elt"].find()
|
||||
else:
|
||||
assert False
|
||||
|
||||
def is_collection(typ):
|
||||
typ = typ.find()
|
||||
return isinstance(typ, types.TTuple) or \
|
||||
types.is_mono(typ, "list")
|
||||
|
||||
def is_allocated(typ):
|
||||
return not (is_none(typ) or is_bool(typ) or is_int(typ) or
|
||||
is_float(typ) or is_range(typ) or
|
||||
types._is_pointer(typ) or types.is_function(typ) or
|
||||
types.is_external_function(typ) or types.is_rpc(typ) or
|
||||
types.is_subkernel(typ) or types.is_method(typ) or
|
||||
types.is_tuple(typ) or types.is_value(typ))
|
File diff suppressed because it is too large
Load Diff
|
@ -1,58 +0,0 @@
|
|||
import sys
|
||||
import builtins
|
||||
import linecache
|
||||
import tokenize
|
||||
import logging
|
||||
import importlib.machinery as im
|
||||
|
||||
from artiq.experiment import kernel, portable
|
||||
|
||||
|
||||
__all__ = ["install_hook"]
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
cache = dict()
|
||||
im_exec_module = None
|
||||
linecache_getlines = None
|
||||
|
||||
|
||||
def hook_exec_module(self, module):
|
||||
im_exec_module(self, module)
|
||||
if (hasattr(module, "__file__")
|
||||
# Heuristic to determine if the module may contain ARTIQ kernels.
|
||||
# This breaks if kernel is not imported the usual way.
|
||||
and ((getattr(module, "kernel", None) is kernel)
|
||||
or (getattr(module, "portable", None) is portable))):
|
||||
fn = module.__file__
|
||||
try:
|
||||
with tokenize.open(fn) as fp:
|
||||
lines = fp.readlines()
|
||||
if lines and not lines[-1].endswith("\n"):
|
||||
lines[-1] += "\n"
|
||||
cache[fn] = lines
|
||||
except:
|
||||
logger.warning("failed to add '%s' to cache", fn, exc_info=True)
|
||||
else:
|
||||
logger.debug("added '%s' to cache", fn)
|
||||
|
||||
|
||||
def hook_getlines(filename, module_globals=None):
|
||||
if filename in cache:
|
||||
return cache[filename]
|
||||
else:
|
||||
return linecache_getlines(filename, module_globals)
|
||||
|
||||
|
||||
def install_hook():
|
||||
global im_exec_module, linecache_getlines
|
||||
|
||||
im_exec_module = im.SourceFileLoader.exec_module
|
||||
im.SourceFileLoader.exec_module = hook_exec_module
|
||||
|
||||
linecache_getlines = linecache.getlines
|
||||
linecache.getlines = hook_getlines
|
||||
|
||||
logger.debug("hook installed")
|
|
@ -1,249 +0,0 @@
|
|||
"""
|
||||
The :mod:`iodelay` module contains the classes describing
|
||||
the statically inferred RTIO delay arising from executing
|
||||
a function.
|
||||
"""
|
||||
|
||||
from functools import reduce
|
||||
|
||||
class Expr:
|
||||
def __add__(lhs, rhs):
|
||||
assert isinstance(rhs, Expr)
|
||||
return Add(lhs, rhs)
|
||||
__iadd__ = __add__
|
||||
|
||||
def __sub__(lhs, rhs):
|
||||
assert isinstance(rhs, Expr)
|
||||
return Sub(lhs, rhs)
|
||||
__isub__ = __sub__
|
||||
|
||||
def __mul__(lhs, rhs):
|
||||
assert isinstance(rhs, Expr)
|
||||
return Mul(lhs, rhs)
|
||||
__imul__ = __mul__
|
||||
|
||||
def __truediv__(lhs, rhs):
|
||||
assert isinstance(rhs, Expr)
|
||||
return TrueDiv(lhs, rhs)
|
||||
__itruediv__ = __truediv__
|
||||
|
||||
def __floordiv__(lhs, rhs):
|
||||
assert isinstance(rhs, Expr)
|
||||
return FloorDiv(lhs, rhs)
|
||||
__ifloordiv__ = __floordiv__
|
||||
|
||||
def __ne__(lhs, rhs):
|
||||
return not (lhs == rhs)
|
||||
|
||||
def free_vars(self):
|
||||
return set()
|
||||
|
||||
def fold(self, vars=None):
|
||||
return self
|
||||
|
||||
class Const(Expr):
|
||||
_priority = 1
|
||||
|
||||
def __init__(self, value):
|
||||
assert isinstance(value, (int, float))
|
||||
self.value = value
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
def __eq__(lhs, rhs):
|
||||
return rhs.__class__ == lhs.__class__ and lhs.value == rhs.value
|
||||
|
||||
def eval(self, env):
|
||||
return self.value
|
||||
|
||||
class Var(Expr):
|
||||
_priority = 1
|
||||
|
||||
def __init__(self, name):
|
||||
assert isinstance(name, str)
|
||||
self.name = name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __eq__(lhs, rhs):
|
||||
return rhs.__class__ == lhs.__class__ and lhs.name == rhs.name
|
||||
|
||||
def free_vars(self):
|
||||
return {self.name}
|
||||
|
||||
def fold(self, vars=None):
|
||||
if vars is not None and self.name in vars:
|
||||
return vars[self.name]
|
||||
else:
|
||||
return self
|
||||
|
||||
class Conv(Expr):
|
||||
_priority = 1
|
||||
|
||||
def __init__(self, operand, ref_period):
|
||||
assert isinstance(operand, Expr)
|
||||
assert isinstance(ref_period, float)
|
||||
self.operand, self.ref_period = operand, ref_period
|
||||
|
||||
def __eq__(lhs, rhs):
|
||||
return rhs.__class__ == lhs.__class__ and \
|
||||
lhs.ref_period == rhs.ref_period and \
|
||||
lhs.operand == rhs.operand
|
||||
|
||||
def free_vars(self):
|
||||
return self.operand.free_vars()
|
||||
|
||||
class MUToS(Conv):
|
||||
def __str__(self):
|
||||
return "mu->s({})".format(self.operand)
|
||||
|
||||
def eval(self, env):
|
||||
return self.operand.eval(env) * self.ref_period
|
||||
|
||||
def fold(self, vars=None):
|
||||
operand = self.operand.fold(vars)
|
||||
if isinstance(operand, Const):
|
||||
return Const(operand.value * self.ref_period)
|
||||
else:
|
||||
return MUToS(operand, ref_period=self.ref_period)
|
||||
|
||||
class SToMU(Conv):
|
||||
def __str__(self):
|
||||
return "s->mu({})".format(self.operand)
|
||||
|
||||
def eval(self, env):
|
||||
return int(self.operand.eval(env) / self.ref_period)
|
||||
|
||||
def fold(self, vars=None):
|
||||
operand = self.operand.fold(vars)
|
||||
if isinstance(operand, Const):
|
||||
return Const(int(operand.value / self.ref_period))
|
||||
else:
|
||||
return SToMU(operand, ref_period=self.ref_period)
|
||||
|
||||
class BinOp(Expr):
|
||||
def __init__(self, lhs, rhs):
|
||||
self.lhs, self.rhs = lhs, rhs
|
||||
|
||||
def __str__(self):
|
||||
lhs = "({})".format(self.lhs) if self.lhs._priority > self._priority else str(self.lhs)
|
||||
rhs = "({})".format(self.rhs) if self.rhs._priority > self._priority else str(self.rhs)
|
||||
return "{} {} {}".format(lhs, self._symbol, rhs)
|
||||
|
||||
def __eq__(lhs, rhs):
|
||||
return rhs.__class__ == lhs.__class__ and lhs.lhs == rhs.lhs and lhs.rhs == rhs.rhs
|
||||
|
||||
def eval(self, env):
|
||||
return self.__class__._op(self.lhs.eval(env), self.rhs.eval(env))
|
||||
|
||||
def free_vars(self):
|
||||
return self.lhs.free_vars() | self.rhs.free_vars()
|
||||
|
||||
def _fold_binop(self, lhs, rhs):
|
||||
if isinstance(lhs, Const) and lhs.__class__ == rhs.__class__:
|
||||
return Const(self.__class__._op(lhs.value, rhs.value))
|
||||
elif isinstance(lhs, (MUToS, SToMU)) and lhs.__class__ == rhs.__class__:
|
||||
return lhs.__class__(self.__class__(lhs.operand, rhs.operand),
|
||||
ref_period=lhs.ref_period).fold()
|
||||
else:
|
||||
return self.__class__(lhs, rhs)
|
||||
|
||||
def fold(self, vars=None):
|
||||
return self._fold_binop(self.lhs.fold(vars), self.rhs.fold(vars))
|
||||
|
||||
class BinOpFixpoint(BinOp):
|
||||
def _fold_binop(self, lhs, rhs):
|
||||
if isinstance(lhs, Const) and lhs.value == self._fixpoint:
|
||||
return rhs
|
||||
elif isinstance(rhs, Const) and rhs.value == self._fixpoint:
|
||||
return lhs
|
||||
else:
|
||||
return super()._fold_binop(lhs, rhs)
|
||||
|
||||
class Add(BinOpFixpoint):
|
||||
_priority = 2
|
||||
_symbol = "+"
|
||||
_op = lambda a, b: a + b
|
||||
_fixpoint = 0
|
||||
|
||||
class Mul(BinOpFixpoint):
|
||||
_priority = 1
|
||||
_symbol = "*"
|
||||
_op = lambda a, b: a * b
|
||||
_fixpoint = 1
|
||||
|
||||
class Sub(BinOp):
|
||||
_priority = 2
|
||||
_symbol = "-"
|
||||
_op = lambda a, b: a - b
|
||||
|
||||
def _fold_binop(self, lhs, rhs):
|
||||
if isinstance(rhs, Const) and rhs.value == 0:
|
||||
return lhs
|
||||
else:
|
||||
return super()._fold_binop(lhs, rhs)
|
||||
|
||||
class Div(BinOp):
|
||||
def _fold_binop(self, lhs, rhs):
|
||||
if isinstance(rhs, Const) and rhs.value == 1:
|
||||
return lhs
|
||||
else:
|
||||
return super()._fold_binop(lhs, rhs)
|
||||
|
||||
class TrueDiv(Div):
|
||||
_priority = 1
|
||||
_symbol = "/"
|
||||
_op = lambda a, b: a / b if b != 0 else 0
|
||||
|
||||
class FloorDiv(Div):
|
||||
_priority = 1
|
||||
_symbol = "//"
|
||||
_op = lambda a, b: a // b if b != 0 else 0
|
||||
|
||||
class Max(Expr):
|
||||
_priority = 1
|
||||
|
||||
def __init__(self, operands):
|
||||
assert isinstance(operands, list)
|
||||
assert all([isinstance(operand, Expr) for operand in operands])
|
||||
assert operands != []
|
||||
self.operands = operands
|
||||
|
||||
def __str__(self):
|
||||
return "max({})".format(", ".join([str(operand) for operand in self.operands]))
|
||||
|
||||
def __eq__(lhs, rhs):
|
||||
return rhs.__class__ == lhs.__class__ and lhs.operands == rhs.operands
|
||||
|
||||
def free_vars(self):
|
||||
return reduce(lambda a, b: a | b, [operand.free_vars() for operand in self.operands])
|
||||
|
||||
def eval(self, env):
|
||||
return max([operand.eval() for operand in self.operands])
|
||||
|
||||
def fold(self, vars=None):
|
||||
consts, exprs = [], []
|
||||
for operand in self.operands:
|
||||
operand = operand.fold(vars)
|
||||
if isinstance(operand, Const):
|
||||
consts.append(operand.value)
|
||||
elif operand not in exprs:
|
||||
exprs.append(operand)
|
||||
if len(consts) > 0:
|
||||
exprs.append(Const(max(consts)))
|
||||
if len(exprs) == 1:
|
||||
return exprs[0]
|
||||
else:
|
||||
return Max(exprs)
|
||||
|
||||
def is_const(expr, value=None):
|
||||
expr = expr.fold()
|
||||
if value is None:
|
||||
return isinstance(expr, Const)
|
||||
else:
|
||||
return isinstance(expr, Const) and expr.value == value
|
||||
|
||||
def is_zero(expr):
|
||||
return is_const(expr, 0)
|
1585
artiq/compiler/ir.py
1585
artiq/compiler/ir.py
File diff suppressed because it is too large
Load Diff
|
@ -1,70 +0,0 @@
|
|||
/* Force ld to make the ELF header as loadable. */
|
||||
PHDRS
|
||||
{
|
||||
headers PT_LOAD FILEHDR PHDRS ;
|
||||
text PT_LOAD ;
|
||||
data PT_LOAD ;
|
||||
dynamic PT_DYNAMIC ;
|
||||
eh_frame PT_GNU_EH_FRAME ;
|
||||
}
|
||||
|
||||
SECTIONS
|
||||
{
|
||||
/* Push back .text section enough so that ld.lld not complain */
|
||||
. = SIZEOF_HEADERS;
|
||||
|
||||
.text :
|
||||
{
|
||||
*(.text .text.*)
|
||||
} : text
|
||||
|
||||
.rodata :
|
||||
{
|
||||
*(.rodata .rodata.*)
|
||||
}
|
||||
|
||||
.eh_frame :
|
||||
{
|
||||
KEEP(*(.eh_frame))
|
||||
} : text
|
||||
|
||||
.eh_frame_hdr :
|
||||
{
|
||||
KEEP(*(.eh_frame_hdr))
|
||||
} : text : eh_frame
|
||||
|
||||
.got :
|
||||
{
|
||||
*(.got)
|
||||
} : text
|
||||
|
||||
.got.plt :
|
||||
{
|
||||
*(.got.plt)
|
||||
} : text
|
||||
|
||||
.data :
|
||||
{
|
||||
*(.data .data.*)
|
||||
} : data
|
||||
|
||||
.dynamic :
|
||||
{
|
||||
*(.dynamic)
|
||||
} : data : dynamic
|
||||
|
||||
.bss (NOLOAD) : ALIGN(4)
|
||||
{
|
||||
__bss_start = .;
|
||||
*(.sbss .sbss.* .bss .bss.*);
|
||||
. = ALIGN(4);
|
||||
_end = .;
|
||||
}
|
||||
|
||||
/* Kernel stack grows downward from end of memory, so put guard page after
|
||||
* all the program contents. Note: This requires all loaded sections (at
|
||||
* least those accessed) to be explicitly listed in the above!
|
||||
*/
|
||||
. = ALIGN(0x1000);
|
||||
_sstack_guard = .;
|
||||
}
|
|
@ -1,132 +0,0 @@
|
|||
r"""
|
||||
The :mod:`math_fns` module lists math-related functions from NumPy recognized
|
||||
by the ARTIQ compiler so host function objects can be :func:`match`\ ed to
|
||||
the compiler type metadata describing their core device analogue.
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
import numpy
|
||||
from . import builtins, types
|
||||
|
||||
# Some special mathematical functions are exposed via their scipy.special
|
||||
# equivalents. Since the rest of the ARTIQ core does not depend on SciPy,
|
||||
# gracefully handle it not being present, making the functions simply not
|
||||
# available.
|
||||
try:
|
||||
import scipy.special as scipy_special
|
||||
except ImportError:
|
||||
scipy_special = None
|
||||
|
||||
#: float -> float numpy.* math functions for which llvm.* intrinsics exist.
|
||||
unary_fp_intrinsics = [(name, "llvm." + name + ".f64") for name in [
|
||||
"sin",
|
||||
"cos",
|
||||
"exp",
|
||||
"exp2",
|
||||
"log",
|
||||
"log10",
|
||||
"log2",
|
||||
"fabs",
|
||||
"floor",
|
||||
"ceil",
|
||||
"trunc",
|
||||
"sqrt",
|
||||
]] + [
|
||||
# numpy.rint() seems to (NumPy 1.19.0, Python 3.8.5, Linux x86_64)
|
||||
# implement round-to-even, but unfortunately, rust-lang/libm only
|
||||
# provides round(), which always rounds away from zero.
|
||||
#
|
||||
# As there is no equivalent of the latter in NumPy (nor any other
|
||||
# basic rounding function), expose round() as numpy.rint anyway,
|
||||
# even if the rounding modes don't match up, so there is some way
|
||||
# to do rounding on the core device. (numpy.round() has entirely
|
||||
# different semantics; it rounds to a configurable number of
|
||||
# decimals.)
|
||||
("rint", "llvm.round.f64"),
|
||||
]
|
||||
|
||||
#: float -> float numpy.* math functions lowered to runtime calls.
|
||||
unary_fp_runtime_calls = [
|
||||
("tan", "tan"),
|
||||
("arcsin", "asin"),
|
||||
("arccos", "acos"),
|
||||
("arctan", "atan"),
|
||||
("sinh", "sinh"),
|
||||
("cosh", "cosh"),
|
||||
("tanh", "tanh"),
|
||||
("arcsinh", "asinh"),
|
||||
("arccosh", "acosh"),
|
||||
("arctanh", "atanh"),
|
||||
("expm1", "expm1"),
|
||||
("cbrt", "cbrt"),
|
||||
]
|
||||
|
||||
#: float -> float numpy.* math functions lowered to runtime calls.
|
||||
unary_fp_runtime_calls = [
|
||||
("tan", "tan"),
|
||||
("arcsin", "asin"),
|
||||
("arccos", "acos"),
|
||||
("arctan", "atan"),
|
||||
("sinh", "sinh"),
|
||||
("cosh", "cosh"),
|
||||
("tanh", "tanh"),
|
||||
("arcsinh", "asinh"),
|
||||
("arccosh", "acosh"),
|
||||
("arctanh", "atanh"),
|
||||
("expm1", "expm1"),
|
||||
("cbrt", "cbrt"),
|
||||
]
|
||||
|
||||
scipy_special_unary_runtime_calls = [
|
||||
("erf", "erf"),
|
||||
("erfc", "erfc"),
|
||||
("gamma", "tgamma"),
|
||||
("gammaln", "lgamma"),
|
||||
("j0", "j0"),
|
||||
("j1", "j1"),
|
||||
("y0", "y0"),
|
||||
("y1", "y1"),
|
||||
]
|
||||
# Not mapped: jv/yv, libm only supports integer orders.
|
||||
|
||||
#: (float, float) -> float numpy.* math functions lowered to runtime calls.
|
||||
binary_fp_runtime_calls = [
|
||||
("arctan2", "atan2"),
|
||||
("copysign", "copysign"),
|
||||
("fmax", "fmax"),
|
||||
("fmin", "fmin"),
|
||||
# ("ldexp", "ldexp"), # One argument is an int; would need a bit more plumbing.
|
||||
("hypot", "hypot"),
|
||||
("nextafter", "nextafter"),
|
||||
]
|
||||
|
||||
#: Array handling builtins (special treatment due to allocations).
|
||||
numpy_builtins = ["transpose"]
|
||||
|
||||
|
||||
def fp_runtime_type(name, arity):
|
||||
args = [("arg{}".format(i), builtins.TFloat()) for i in range(arity)]
|
||||
return types.TExternalFunction(
|
||||
OrderedDict(args),
|
||||
builtins.TFloat(),
|
||||
name,
|
||||
# errno isn't observable from ARTIQ Python.
|
||||
flags={"nounwind", "nowrite"},
|
||||
broadcast_across_arrays=True)
|
||||
|
||||
|
||||
math_fn_map = {
|
||||
getattr(numpy, symbol): fp_runtime_type(mangle, arity=1)
|
||||
for symbol, mangle in (unary_fp_intrinsics + unary_fp_runtime_calls)
|
||||
}
|
||||
for symbol, mangle in binary_fp_runtime_calls:
|
||||
math_fn_map[getattr(numpy, symbol)] = fp_runtime_type(mangle, arity=2)
|
||||
for name in numpy_builtins:
|
||||
math_fn_map[getattr(numpy, name)] = types.TBuiltinFunction("numpy." + name)
|
||||
if scipy_special is not None:
|
||||
for symbol, mangle in scipy_special_unary_runtime_calls:
|
||||
math_fn_map[getattr(scipy_special, symbol)] = fp_runtime_type(mangle, arity=1)
|
||||
|
||||
|
||||
def match(obj):
|
||||
return math_fn_map.get(obj, None)
|
|
@ -1,101 +0,0 @@
|
|||
"""
|
||||
The :class:`Module` class encapsulates a single Python module,
|
||||
which corresponds to a single ARTIQ translation unit (one LLVM
|
||||
bitcode file and one object file, unless LTO is used).
|
||||
A :class:`Module` can be created from a typed AST.
|
||||
|
||||
The :class:`Source` class parses a single source file or
|
||||
string and infers types for it using a trivial :module:`prelude`.
|
||||
"""
|
||||
|
||||
import os
|
||||
from pythonparser import source, diagnostic, parse_buffer
|
||||
from . import prelude, types, transforms, analyses, validators, embedding
|
||||
|
||||
class Source:
|
||||
def __init__(self, source_buffer, engine=None):
|
||||
if engine is None:
|
||||
self.engine = diagnostic.Engine(all_errors_are_fatal=True)
|
||||
else:
|
||||
self.engine = engine
|
||||
self.embedding_map = embedding.EmbeddingMap()
|
||||
self.name, _ = os.path.splitext(os.path.basename(source_buffer.name))
|
||||
|
||||
asttyped_rewriter = transforms.ASTTypedRewriter(engine=engine,
|
||||
prelude=prelude.globals())
|
||||
inferencer = transforms.Inferencer(engine=engine)
|
||||
|
||||
self.parsetree, self.comments = parse_buffer(source_buffer, engine=engine)
|
||||
self.typedtree = asttyped_rewriter.visit(self.parsetree)
|
||||
self.globals = asttyped_rewriter.globals
|
||||
inferencer.visit(self.typedtree)
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, source_string, name="input.py", first_line=1, engine=None):
|
||||
return cls(source.Buffer(source_string + "\n", name, first_line), engine=engine)
|
||||
|
||||
@classmethod
|
||||
def from_filename(cls, filename, engine=None):
|
||||
with open(filename) as f:
|
||||
return cls(source.Buffer(f.read(), filename, 1), engine=engine)
|
||||
|
||||
class Module:
|
||||
def __init__(self, src, ref_period=1e-6, attribute_writeback=True, remarks=False):
|
||||
self.attribute_writeback = attribute_writeback
|
||||
self.engine = src.engine
|
||||
self.embedding_map = src.embedding_map
|
||||
self.name = src.name
|
||||
self.globals = src.globals
|
||||
|
||||
int_monomorphizer = transforms.IntMonomorphizer(engine=self.engine)
|
||||
cast_monomorphizer = transforms.CastMonomorphizer(engine=self.engine)
|
||||
inferencer = transforms.Inferencer(engine=self.engine)
|
||||
monomorphism_validator = validators.MonomorphismValidator(engine=self.engine)
|
||||
escape_validator = validators.EscapeValidator(engine=self.engine)
|
||||
iodelay_estimator = transforms.IODelayEstimator(engine=self.engine,
|
||||
ref_period=ref_period)
|
||||
constness_validator = validators.ConstnessValidator(engine=self.engine)
|
||||
artiq_ir_generator = transforms.ARTIQIRGenerator(engine=self.engine,
|
||||
module_name=src.name,
|
||||
ref_period=ref_period,
|
||||
embedding_map=self.embedding_map)
|
||||
dead_code_eliminator = transforms.DeadCodeEliminator(engine=self.engine)
|
||||
local_access_validator = validators.LocalAccessValidator(engine=self.engine)
|
||||
local_demoter = transforms.LocalDemoter()
|
||||
constant_hoister = transforms.ConstantHoister()
|
||||
devirtualization = analyses.Devirtualization()
|
||||
interleaver = transforms.Interleaver(engine=self.engine)
|
||||
invariant_detection = analyses.InvariantDetection(engine=self.engine)
|
||||
|
||||
int_monomorphizer.visit(src.typedtree)
|
||||
cast_monomorphizer.visit(src.typedtree)
|
||||
inferencer.visit(src.typedtree)
|
||||
monomorphism_validator.visit(src.typedtree)
|
||||
escape_validator.visit(src.typedtree)
|
||||
iodelay_estimator.visit_fixpoint(src.typedtree)
|
||||
constness_validator.visit(src.typedtree)
|
||||
devirtualization.visit(src.typedtree)
|
||||
self.artiq_ir = artiq_ir_generator.visit(src.typedtree)
|
||||
artiq_ir_generator.annotate_calls(devirtualization)
|
||||
dead_code_eliminator.process(self.artiq_ir)
|
||||
interleaver.process(self.artiq_ir)
|
||||
local_access_validator.process(self.artiq_ir)
|
||||
local_demoter.process(self.artiq_ir)
|
||||
constant_hoister.process(self.artiq_ir)
|
||||
if remarks:
|
||||
invariant_detection.process(self.artiq_ir)
|
||||
# for subkernels: main kernel inferencer output, to be passed to further compilations
|
||||
self.subkernel_arg_types = inferencer.subkernel_arg_types
|
||||
|
||||
def build_llvm_ir(self, target):
|
||||
"""Compile the module to LLVM IR for the specified target."""
|
||||
llvm_ir_generator = transforms.LLVMIRGenerator(
|
||||
engine=self.engine, module_name=self.name, target=target,
|
||||
embedding_map=self.embedding_map)
|
||||
return llvm_ir_generator.process(self.artiq_ir,
|
||||
attribute_writeback=self.attribute_writeback)
|
||||
|
||||
def __repr__(self):
|
||||
printer = types.TypePrinter()
|
||||
globals = ["%s: %s" % (var, printer.name(self.globals[var])) for var in self.globals]
|
||||
return "<artiq.compiler.Module %s {\n %s\n}>" % (repr(self.name), ",\n ".join(globals))
|
|
@ -1,62 +0,0 @@
|
|||
"""
|
||||
The :mod:`prelude` module contains the initial global environment
|
||||
in which ARTIQ kernels are evaluated.
|
||||
"""
|
||||
|
||||
from . import builtins
|
||||
|
||||
def globals():
|
||||
return {
|
||||
# Value constructors
|
||||
"bool": builtins.fn_bool(),
|
||||
"int": builtins.fn_int(),
|
||||
"float": builtins.fn_float(),
|
||||
"str": builtins.fn_str(),
|
||||
"bytes": builtins.fn_bytes(),
|
||||
"bytearray": builtins.fn_bytearray(),
|
||||
"list": builtins.fn_list(),
|
||||
"array": builtins.fn_array(),
|
||||
"range": builtins.fn_range(),
|
||||
"int32": builtins.fn_int32(),
|
||||
"int64": builtins.fn_int64(),
|
||||
|
||||
# Exception constructors
|
||||
"Exception": builtins.fn_Exception(),
|
||||
"IndexError": builtins.fn_IndexError(),
|
||||
"ValueError": builtins.fn_ValueError(),
|
||||
"ZeroDivisionError": builtins.fn_ZeroDivisionError(),
|
||||
"RuntimeError": builtins.fn_RuntimeError(),
|
||||
|
||||
# Built-in Python functions
|
||||
"len": builtins.fn_len(),
|
||||
"round": builtins.fn_round(),
|
||||
"abs": builtins.fn_abs(),
|
||||
"min": builtins.fn_min(),
|
||||
"max": builtins.fn_max(),
|
||||
"print": builtins.fn_print(),
|
||||
|
||||
# ARTIQ decorators
|
||||
"kernel": builtins.fn_kernel(),
|
||||
"subkernel": builtins.fn_kernel(),
|
||||
"portable": builtins.fn_kernel(),
|
||||
"rpc": builtins.fn_kernel(),
|
||||
|
||||
# ARTIQ context managers
|
||||
"parallel": builtins.obj_parallel(),
|
||||
"interleave": builtins.obj_interleave(),
|
||||
"sequential": builtins.obj_sequential(),
|
||||
|
||||
# ARTIQ time management functions
|
||||
"delay": builtins.fn_delay(),
|
||||
"now_mu": builtins.fn_now_mu(),
|
||||
"delay_mu": builtins.fn_delay_mu(),
|
||||
"at_mu": builtins.fn_at_mu(),
|
||||
|
||||
# ARTIQ utility functions
|
||||
"rtio_log": builtins.fn_rtio_log(),
|
||||
"core_log": builtins.fn_print(),
|
||||
|
||||
# ARTIQ subkernel utility functions
|
||||
"subkernel_await": builtins.fn_subkernel_await(),
|
||||
"subkernel_preload": builtins.fn_subkernel_preload(),
|
||||
}
|
|
@ -1,309 +0,0 @@
|
|||
import os, sys, tempfile, subprocess, io
|
||||
from artiq.compiler import types, ir
|
||||
from llvmlite import ir as ll, binding as llvm
|
||||
|
||||
llvm.initialize()
|
||||
llvm.initialize_all_targets()
|
||||
llvm.initialize_all_asmprinters()
|
||||
|
||||
class RunTool:
|
||||
def __init__(self, pattern, **tempdata):
|
||||
self._pattern = pattern
|
||||
self._tempdata = tempdata
|
||||
self._tempnames = {}
|
||||
self._tempfiles = {}
|
||||
|
||||
def __enter__(self):
|
||||
for key, data in self._tempdata.items():
|
||||
if data is None:
|
||||
fd, filename = tempfile.mkstemp()
|
||||
os.close(fd)
|
||||
self._tempnames[key] = filename
|
||||
else:
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
f.write(data)
|
||||
self._tempnames[key] = f.name
|
||||
|
||||
cmdline = []
|
||||
for argument in self._pattern:
|
||||
cmdline.append(argument.format(**self._tempnames))
|
||||
|
||||
# https://bugs.python.org/issue17023
|
||||
windows = os.name == "nt"
|
||||
process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
universal_newlines=True, shell=windows)
|
||||
stdout, stderr = process.communicate()
|
||||
if process.returncode != 0:
|
||||
raise Exception("{} invocation failed: {}".
|
||||
format(cmdline[0], stderr))
|
||||
|
||||
self._tempfiles["__stdout__"] = io.StringIO(stdout)
|
||||
for key in self._tempdata:
|
||||
if self._tempdata[key] is None:
|
||||
self._tempfiles[key] = open(self._tempnames[key], "rb")
|
||||
return self._tempfiles
|
||||
|
||||
def __exit__(self, exc_typ, exc_value, exc_trace):
|
||||
for file in self._tempfiles.values():
|
||||
file.close()
|
||||
for filename in self._tempnames.values():
|
||||
os.unlink(filename)
|
||||
|
||||
def _dump(target, kind, suffix, content):
|
||||
if target is not None:
|
||||
print("====== {} DUMP ======".format(kind.upper()), file=sys.stderr)
|
||||
content_value = content()
|
||||
if isinstance(content_value, str):
|
||||
content_value = bytes(content_value, 'utf-8')
|
||||
if target == "":
|
||||
file = tempfile.NamedTemporaryFile(suffix=suffix, delete=False)
|
||||
else:
|
||||
file = open(target + suffix, "wb")
|
||||
file.write(content_value)
|
||||
file.close()
|
||||
print("{} dumped as {}".format(kind, file.name), file=sys.stderr)
|
||||
|
||||
class Target:
|
||||
"""
|
||||
A description of the target environment where the binaries
|
||||
generated by the ARTIQ compiler will be deployed.
|
||||
|
||||
:var triple: (string)
|
||||
LLVM target triple, e.g. ``"riscv32"``
|
||||
:var data_layout: (string)
|
||||
LLVM target data layout, e.g. ``"E-m:e-p:32:32-i64:32-f64:32-v64:32-v128:32-a:0:32-n32"``
|
||||
:var features: (list of string)
|
||||
LLVM target CPU features, e.g. ``["mul", "div", "ffl1"]``
|
||||
:var additional_linker_options: (list of string)
|
||||
Linker options for the target in addition to the target-independent ones, e.g. ``["--target2=rel"]``
|
||||
:var print_function: (string)
|
||||
Name of a formatted print functions (with the signature of ``printf``)
|
||||
provided by the target, e.g. ``"printf"``.
|
||||
:var now_pinning: (boolean)
|
||||
Whether the target implements the now-pinning RTIO optimization.
|
||||
"""
|
||||
triple = "unknown"
|
||||
data_layout = ""
|
||||
features = []
|
||||
additional_linker_options = []
|
||||
print_function = "printf"
|
||||
now_pinning = True
|
||||
|
||||
tool_ld = "ld.lld"
|
||||
tool_strip = "llvm-strip"
|
||||
tool_symbolizer = "llvm-symbolizer"
|
||||
tool_cxxfilt = "llvm-cxxfilt"
|
||||
|
||||
def __init__(self, subkernel_id=None):
|
||||
self.llcontext = ll.Context()
|
||||
self.subkernel_id = subkernel_id
|
||||
|
||||
def target_machine(self):
|
||||
lltarget = llvm.Target.from_triple(self.triple)
|
||||
llmachine = lltarget.create_target_machine(
|
||||
features=",".join(["+{}".format(f) for f in self.features]),
|
||||
reloc="pic", codemodel="default",
|
||||
abiname="ilp32d" if isinstance(self, RV32GTarget) else "")
|
||||
llmachine.set_asm_verbosity(True)
|
||||
return llmachine
|
||||
|
||||
def optimize(self, llmodule):
|
||||
llpassmgr = llvm.create_module_pass_manager()
|
||||
|
||||
# Register our alias analysis passes.
|
||||
llpassmgr.add_basic_alias_analysis_pass()
|
||||
llpassmgr.add_type_based_alias_analysis_pass()
|
||||
|
||||
# Start by cleaning up after our codegen and exposing as much
|
||||
# information to LLVM as possible.
|
||||
llpassmgr.add_constant_merge_pass()
|
||||
llpassmgr.add_cfg_simplification_pass()
|
||||
llpassmgr.add_instruction_combining_pass()
|
||||
llpassmgr.add_sroa_pass()
|
||||
llpassmgr.add_dead_code_elimination_pass()
|
||||
llpassmgr.add_function_attrs_pass()
|
||||
llpassmgr.add_global_optimizer_pass()
|
||||
|
||||
# Now, actually optimize the code.
|
||||
llpassmgr.add_function_inlining_pass(275)
|
||||
llpassmgr.add_ipsccp_pass()
|
||||
llpassmgr.add_instruction_combining_pass()
|
||||
llpassmgr.add_gvn_pass()
|
||||
llpassmgr.add_cfg_simplification_pass()
|
||||
llpassmgr.add_licm_pass()
|
||||
|
||||
# Clean up after optimizing.
|
||||
llpassmgr.add_dead_arg_elimination_pass()
|
||||
llpassmgr.add_global_dce_pass()
|
||||
|
||||
llpassmgr.run(llmodule)
|
||||
|
||||
def compile(self, module):
|
||||
"""Compile the module to a relocatable object for this target."""
|
||||
|
||||
if os.getenv("ARTIQ_DUMP_SIG"):
|
||||
print("====== MODULE_SIGNATURE DUMP ======", file=sys.stderr)
|
||||
print(module, file=sys.stderr)
|
||||
|
||||
if os.getenv("ARTIQ_IR_NO_LOC") is not None:
|
||||
ir.BasicBlock._dump_loc = False
|
||||
|
||||
type_printer = types.TypePrinter()
|
||||
suffix = "_subkernel_{}".format(self.subkernel_id) if self.subkernel_id is not None else ""
|
||||
_dump(os.getenv("ARTIQ_DUMP_IR"), "ARTIQ IR", suffix + ".txt",
|
||||
lambda: "\n".join(fn.as_entity(type_printer) for fn in module.artiq_ir))
|
||||
|
||||
llmod = module.build_llvm_ir(self)
|
||||
|
||||
try:
|
||||
llparsedmod = llvm.parse_assembly(str(llmod))
|
||||
llparsedmod.verify()
|
||||
except RuntimeError:
|
||||
_dump("", "LLVM IR (broken)", ".ll", lambda: str(llmod))
|
||||
raise
|
||||
|
||||
_dump(os.getenv("ARTIQ_DUMP_UNOPT_LLVM"), "LLVM IR (generated)", suffix + "_unopt.ll",
|
||||
lambda: str(llparsedmod))
|
||||
|
||||
self.optimize(llparsedmod)
|
||||
|
||||
_dump(os.getenv("ARTIQ_DUMP_LLVM"), "LLVM IR (optimized)", suffix + ".ll",
|
||||
lambda: str(llparsedmod))
|
||||
|
||||
return llparsedmod
|
||||
|
||||
def assemble(self, llmodule):
|
||||
llmachine = self.target_machine()
|
||||
|
||||
_dump(os.getenv("ARTIQ_DUMP_ASM"), "Assembly", ".s",
|
||||
lambda: llmachine.emit_assembly(llmodule))
|
||||
|
||||
_dump(os.getenv("ARTIQ_DUMP_OBJ"), "Object file", ".o",
|
||||
lambda: llmachine.emit_object(llmodule))
|
||||
|
||||
return llmachine.emit_object(llmodule)
|
||||
|
||||
def link(self, objects):
|
||||
"""Link the relocatable objects into a shared library for this target."""
|
||||
with RunTool([self.tool_ld, "-shared", "--eh-frame-hdr"] +
|
||||
self.additional_linker_options +
|
||||
["-T" + os.path.join(os.path.dirname(__file__), "kernel.ld")] +
|
||||
["{{obj{}}}".format(index) for index in range(len(objects))] +
|
||||
["-x"] +
|
||||
["-o", "{output}"],
|
||||
output=None,
|
||||
**{"obj{}".format(index): obj for index, obj in enumerate(objects)}) \
|
||||
as results:
|
||||
library = results["output"].read()
|
||||
|
||||
_dump(os.getenv("ARTIQ_DUMP_ELF"), "Shared library", ".elf",
|
||||
lambda: library)
|
||||
|
||||
return library
|
||||
|
||||
def compile_and_link(self, modules):
|
||||
return self.link([self.assemble(self.compile(module)) for module in modules])
|
||||
|
||||
def strip(self, library):
|
||||
with RunTool([self.tool_strip, "--strip-debug", "{library}", "-o", "{output}"],
|
||||
library=library, output=None) \
|
||||
as results:
|
||||
return results["output"].read()
|
||||
|
||||
def symbolize(self, library, addresses):
|
||||
if addresses == []:
|
||||
return []
|
||||
|
||||
# We got a list of return addresses, i.e. addresses of instructions
|
||||
# just after the call. Offset them back to get an address somewhere
|
||||
# inside the call instruction (or its delay slot), since that's what
|
||||
# the backtrace entry should point at.
|
||||
last_inlined = None
|
||||
offset_addresses = [hex(addr - 1) for addr in addresses]
|
||||
with RunTool([self.tool_symbolizer, "--addresses", "--functions", "--inlines",
|
||||
"--demangle", "--output-style=GNU", "--exe={library}"] + offset_addresses,
|
||||
library=library) \
|
||||
as results:
|
||||
lines = iter(results["__stdout__"].read().rstrip().split("\n"))
|
||||
backtrace = []
|
||||
while True:
|
||||
try:
|
||||
address_or_function = next(lines)
|
||||
except StopIteration:
|
||||
break
|
||||
if address_or_function[:2] == "0x":
|
||||
address = int(address_or_function[2:], 16) + 1 # remove offset
|
||||
function = next(lines)
|
||||
inlined = False
|
||||
else:
|
||||
address = backtrace[-1][4] # inlined
|
||||
function = address_or_function
|
||||
inlined = True
|
||||
location = next(lines)
|
||||
|
||||
filename, line = location.rsplit(":", 1)
|
||||
if filename == "??" or filename == "<synthesized>":
|
||||
continue
|
||||
if line == "?":
|
||||
line = -1
|
||||
else:
|
||||
line = int(line)
|
||||
# can't get column out of addr2line D:
|
||||
if inlined:
|
||||
last_inlined.append((filename, line, -1, function, address))
|
||||
else:
|
||||
last_inlined = []
|
||||
backtrace.append((filename, line, -1, function, address,
|
||||
last_inlined))
|
||||
return backtrace
|
||||
|
||||
def demangle(self, names):
|
||||
if not any(names):
|
||||
return names
|
||||
with RunTool([self.tool_cxxfilt] + names) as results:
|
||||
return results["__stdout__"].read().rstrip().split("\n")
|
||||
|
||||
class NativeTarget(Target):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.triple = llvm.get_default_triple()
|
||||
self.data_layout = str(llvm.targets.Target.from_default_triple().create_target_machine().target_data)
|
||||
|
||||
class RV32IMATarget(Target):
|
||||
triple = "riscv32-unknown-linux"
|
||||
data_layout = "e-m:e-p:32:32-i64:64-n32-S128"
|
||||
features = ["m", "a"]
|
||||
additional_linker_options = ["-m", "elf32lriscv"]
|
||||
print_function = "core_log"
|
||||
now_pinning = True
|
||||
|
||||
tool_ld = "ld.lld"
|
||||
tool_strip = "llvm-strip"
|
||||
tool_symbolizer = "llvm-symbolizer"
|
||||
tool_cxxfilt = "llvm-cxxfilt"
|
||||
|
||||
class RV32GTarget(Target):
|
||||
triple = "riscv32-unknown-linux"
|
||||
data_layout = "e-m:e-p:32:32-i64:64-n32-S128"
|
||||
features = ["m", "a", "f", "d"]
|
||||
additional_linker_options = ["-m", "elf32lriscv"]
|
||||
print_function = "core_log"
|
||||
now_pinning = True
|
||||
|
||||
tool_ld = "ld.lld"
|
||||
tool_strip = "llvm-strip"
|
||||
tool_symbolizer = "llvm-symbolizer"
|
||||
tool_cxxfilt = "llvm-cxxfilt"
|
||||
|
||||
class CortexA9Target(Target):
|
||||
triple = "armv7-unknown-linux-gnueabihf"
|
||||
data_layout = "e-m:e-p:32:32-i64:64-v128:64:128-a:0:32-n32-S64"
|
||||
features = ["dsp", "fp16", "neon", "vfp3"]
|
||||
additional_linker_options = ["-m", "armelf_linux_eabi", "--target2=rel"]
|
||||
print_function = "core_log"
|
||||
now_pinning = False
|
||||
|
||||
tool_ld = "ld.lld"
|
||||
tool_strip = "llvm-strip"
|
||||
tool_symbolizer = "llvm-symbolizer"
|
||||
tool_cxxfilt = "llvm-cxxfilt"
|
|
@ -1,21 +0,0 @@
|
|||
import time, cProfile as profile, pstats
|
||||
|
||||
def benchmark(f, name):
|
||||
profiler = profile.Profile()
|
||||
profiler.enable()
|
||||
|
||||
start = time.perf_counter()
|
||||
end = 0
|
||||
runs = 0
|
||||
while end - start < 5 or runs < 10:
|
||||
f()
|
||||
runs += 1
|
||||
end = time.perf_counter()
|
||||
|
||||
profiler.create_stats()
|
||||
|
||||
print("{} {} runs: {:.2f}s, {:.2f}ms/run".format(
|
||||
runs, name, end - start, (end - start) / runs * 1000))
|
||||
|
||||
stats = pstats.Stats(profiler)
|
||||
stats.strip_dirs().sort_stats('time').print_stats(10)
|
|
@ -1,46 +0,0 @@
|
|||
import sys, os, tokenize
|
||||
|
||||
from artiq.master.databases import DeviceDB
|
||||
from artiq.master.worker_db import DeviceManager
|
||||
|
||||
import artiq.coredevice.core
|
||||
from artiq.coredevice.core import Core, CompileError
|
||||
|
||||
def _render_diagnostic(diagnostic, colored):
|
||||
return "\n".join(diagnostic.render(only_line=True))
|
||||
|
||||
artiq.coredevice.core._render_diagnostic = _render_diagnostic
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "+diag":
|
||||
del sys.argv[1]
|
||||
diag = True
|
||||
else:
|
||||
diag = False
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "+compile":
|
||||
del sys.argv[1]
|
||||
compile_only = True
|
||||
else:
|
||||
compile_only = False
|
||||
|
||||
ddb_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py")
|
||||
dmgr = DeviceManager(DeviceDB(ddb_path))
|
||||
|
||||
with tokenize.open(sys.argv[1]) as f:
|
||||
testcase_code = compile(f.read(), f.name, "exec")
|
||||
testcase_vars = {'__name__': 'testbench', 'dmgr': dmgr}
|
||||
exec(testcase_code, testcase_vars)
|
||||
|
||||
try:
|
||||
core = dmgr.get("core")
|
||||
if compile_only:
|
||||
core.compile(testcase_vars["entrypoint"], (), {})
|
||||
else:
|
||||
core.run(testcase_vars["entrypoint"], (), {})
|
||||
except CompileError as error:
|
||||
if not diag:
|
||||
exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,103 +0,0 @@
|
|||
import sys, fileinput, os
|
||||
from pythonparser import source, diagnostic, algorithm, parse_buffer
|
||||
from .. import prelude, types
|
||||
from ..transforms import ASTTypedRewriter, Inferencer, IntMonomorphizer, CastMonomorphizer
|
||||
from ..transforms import IODelayEstimator
|
||||
from ..validators import ConstnessValidator
|
||||
|
||||
class Printer(algorithm.Visitor):
|
||||
"""
|
||||
:class:`Printer` prints ``:`` and the node type after every typed node,
|
||||
and ``->`` and the node type before the colon in a function definition.
|
||||
|
||||
In almost all cases (except function definition) this does not result
|
||||
in valid Python syntax.
|
||||
|
||||
:ivar rewriter: (:class:`pythonparser.source.Rewriter`) rewriter instance
|
||||
"""
|
||||
|
||||
def __init__(self, buf):
|
||||
self.rewriter = source.Rewriter(buf)
|
||||
self.type_printer = types.TypePrinter()
|
||||
|
||||
def rewrite(self):
|
||||
return self.rewriter.rewrite()
|
||||
|
||||
def visit_FunctionDefT(self, node):
|
||||
super().generic_visit(node)
|
||||
|
||||
self.rewriter.insert_before(node.colon_loc,
|
||||
"->{}".format(self.type_printer.name(node.return_type)))
|
||||
|
||||
def visit_ExceptHandlerT(self, node):
|
||||
super().generic_visit(node)
|
||||
|
||||
if node.name_loc:
|
||||
self.rewriter.insert_after(node.name_loc,
|
||||
":{}".format(self.type_printer.name(node.name_type)))
|
||||
|
||||
def visit_ForT(self, node):
|
||||
super().generic_visit(node)
|
||||
|
||||
if node.trip_count is not None and node.trip_interval is not None:
|
||||
self.rewriter.insert_after(node.keyword_loc,
|
||||
"[{} x {} mu]".format(node.trip_count.fold(),
|
||||
node.trip_interval.fold()))
|
||||
|
||||
def generic_visit(self, node):
|
||||
super().generic_visit(node)
|
||||
|
||||
if hasattr(node, "type"):
|
||||
self.rewriter.insert_after(node.loc,
|
||||
":{}".format(self.type_printer.name(node.type)))
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "+mono":
|
||||
del sys.argv[1]
|
||||
monomorphize = True
|
||||
else:
|
||||
monomorphize = False
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "+iodelay":
|
||||
del sys.argv[1]
|
||||
iodelay = True
|
||||
else:
|
||||
iodelay = False
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "+diag":
|
||||
del sys.argv[1]
|
||||
def process_diagnostic(diag):
|
||||
print("\n".join(diag.render(only_line=True)))
|
||||
if diag.level == "fatal":
|
||||
exit()
|
||||
else:
|
||||
def process_diagnostic(diag):
|
||||
print("\n".join(diag.render()))
|
||||
if diag.level in ("fatal", "error"):
|
||||
exit(1)
|
||||
|
||||
engine = diagnostic.Engine()
|
||||
engine.process = process_diagnostic
|
||||
|
||||
buf = source.Buffer("".join(fileinput.input()).expandtabs(),
|
||||
os.path.basename(fileinput.filename()))
|
||||
parsed, comments = parse_buffer(buf, engine=engine)
|
||||
typed = ASTTypedRewriter(engine=engine, prelude=prelude.globals()).visit(parsed)
|
||||
Inferencer(engine=engine).visit(typed)
|
||||
ConstnessValidator(engine=engine).visit(typed)
|
||||
if monomorphize:
|
||||
CastMonomorphizer(engine=engine).visit(typed)
|
||||
IntMonomorphizer(engine=engine).visit(typed)
|
||||
Inferencer(engine=engine).visit(typed)
|
||||
if iodelay:
|
||||
IODelayEstimator(engine=engine, ref_period=1e6).visit_fixpoint(typed)
|
||||
|
||||
printer = Printer(buf)
|
||||
printer.visit(typed)
|
||||
for comment in comments:
|
||||
if comment.text.find("CHECK") >= 0:
|
||||
printer.rewriter.remove(comment.loc)
|
||||
print(printer.rewrite().source)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,23 +0,0 @@
|
|||
import sys, os, fileinput
|
||||
from pythonparser import diagnostic
|
||||
from .. import ir
|
||||
from ..module import Module, Source
|
||||
|
||||
def main():
|
||||
if os.getenv("ARTIQ_IR_NO_LOC") is not None:
|
||||
ir.BasicBlock._dump_loc = False
|
||||
|
||||
def process_diagnostic(diag):
|
||||
print("\n".join(diag.render()))
|
||||
if diag.level in ("fatal", "error"):
|
||||
exit(1)
|
||||
|
||||
engine = diagnostic.Engine()
|
||||
engine.process = process_diagnostic
|
||||
|
||||
mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine))
|
||||
for fn in mod.artiq_ir:
|
||||
print(fn)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,35 +0,0 @@
|
|||
import os, sys, fileinput, ctypes
|
||||
from pythonparser import diagnostic
|
||||
from llvmlite import binding as llvm
|
||||
from ..module import Module, Source
|
||||
from ..targets import NativeTarget
|
||||
|
||||
def main():
|
||||
libartiq_support = os.getenv("LIBARTIQ_SUPPORT")
|
||||
if libartiq_support is not None:
|
||||
llvm.load_library_permanently(libartiq_support)
|
||||
|
||||
def process_diagnostic(diag):
|
||||
print("\n".join(diag.render()))
|
||||
if diag.level in ("fatal", "error"):
|
||||
exit(1)
|
||||
|
||||
engine = diagnostic.Engine()
|
||||
engine.process = process_diagnostic
|
||||
|
||||
source = "".join(fileinput.input())
|
||||
source = source.replace("#ARTIQ#", "")
|
||||
mod = Module(Source.from_string(source.expandtabs(), engine=engine))
|
||||
|
||||
target = NativeTarget()
|
||||
llmod = mod.build_llvm_ir(target)
|
||||
llparsedmod = llvm.parse_assembly(str(llmod))
|
||||
llparsedmod.verify()
|
||||
|
||||
llmachine = llvm.Target.from_triple(target.triple).create_target_machine()
|
||||
lljit = llvm.create_mcjit_compiler(llparsedmod, llmachine)
|
||||
llmain = lljit.get_function_address(llmod.name + ".__modinit__")
|
||||
ctypes.CFUNCTYPE(None)(llmain)()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,31 +0,0 @@
|
|||
import sys, fileinput
|
||||
from pythonparser import diagnostic
|
||||
from llvmlite import ir as ll
|
||||
from ..module import Module, Source
|
||||
from ..targets import NativeTarget
|
||||
|
||||
def main():
|
||||
def process_diagnostic(diag):
|
||||
print("\n".join(diag.render()))
|
||||
if diag.level in ("fatal", "error"):
|
||||
exit(1)
|
||||
|
||||
engine = diagnostic.Engine()
|
||||
engine.process = process_diagnostic
|
||||
|
||||
mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine))
|
||||
|
||||
target = NativeTarget()
|
||||
llmod = mod.build_llvm_ir(target=target)
|
||||
|
||||
# Add main so that the result can be executed with lli
|
||||
llmain = ll.Function(llmod, ll.FunctionType(ll.VoidType(), []), "main")
|
||||
llbuilder = ll.IRBuilder(llmain.append_basic_block("entry"))
|
||||
llbuilder.call(llmod.get_global(llmod.name + ".__modinit__"), [
|
||||
ll.Constant(ll.IntType(8).as_pointer(), None)])
|
||||
llbuilder.ret_void()
|
||||
|
||||
print(llmod)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,37 +0,0 @@
|
|||
import sys, os
|
||||
from pythonparser import diagnostic
|
||||
from ..module import Module, Source
|
||||
from ..targets import RV32GTarget
|
||||
from . import benchmark
|
||||
|
||||
def main():
|
||||
if not len(sys.argv) == 2:
|
||||
print("Expected exactly one module filename", file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
def process_diagnostic(diag):
|
||||
print("\n".join(diag.render()), file=sys.stderr)
|
||||
if diag.level in ("fatal", "error"):
|
||||
exit(1)
|
||||
|
||||
engine = diagnostic.Engine()
|
||||
engine.process = process_diagnostic
|
||||
|
||||
# Make sure everything's valid
|
||||
filename = sys.argv[1]
|
||||
with open(filename) as f:
|
||||
code = f.read()
|
||||
source = Source.from_string(code, filename, engine=engine)
|
||||
module = Module(source)
|
||||
|
||||
benchmark(lambda: Source.from_string(code, filename),
|
||||
"ARTIQ parsing and inference")
|
||||
|
||||
benchmark(lambda: Module(source),
|
||||
"ARTIQ transforms and validators")
|
||||
|
||||
benchmark(lambda: RV32GTarget().compile_and_link([module]),
|
||||
"LLVM optimization and linking")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,75 +0,0 @@
|
|||
import sys, os, tokenize
|
||||
from pythonparser import diagnostic
|
||||
from ...language.environment import ProcessArgumentManager
|
||||
from ...master.databases import DeviceDB, DatasetDB
|
||||
from ...master.worker_db import DeviceManager, DatasetManager
|
||||
from ..module import Module
|
||||
from ..embedding import Stitcher
|
||||
from ..targets import RV32GTarget
|
||||
from . import benchmark
|
||||
|
||||
|
||||
def main():
|
||||
if not len(sys.argv) == 2:
|
||||
print("Expected exactly one module filename", file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
def process_diagnostic(diag):
|
||||
print("\n".join(diag.render()), file=sys.stderr)
|
||||
if diag.level in ("fatal", "error"):
|
||||
exit(1)
|
||||
|
||||
engine = diagnostic.Engine()
|
||||
engine.process = process_diagnostic
|
||||
|
||||
with tokenize.open(sys.argv[1]) as f:
|
||||
testcase_code = compile(f.read(), f.name, "exec")
|
||||
testcase_vars = {'__name__': 'testbench'}
|
||||
exec(testcase_code, testcase_vars)
|
||||
|
||||
device_db_path = os.path.join(os.path.dirname(sys.argv[1]), "device_db.py")
|
||||
device_mgr = DeviceManager(DeviceDB(device_db_path))
|
||||
|
||||
dataset_db_path = os.path.join(os.path.dirname(sys.argv[1]), "dataset_db.mdb")
|
||||
dataset_db = DatasetDB(dataset_db_path)
|
||||
dataset_mgr = DatasetManager()
|
||||
|
||||
argument_mgr = ProcessArgumentManager({})
|
||||
|
||||
def embed():
|
||||
experiment = testcase_vars["Benchmark"]((device_mgr, dataset_mgr, argument_mgr))
|
||||
|
||||
stitcher = Stitcher(core=experiment.core, dmgr=device_mgr)
|
||||
stitcher.stitch_call(experiment.run, (), {})
|
||||
stitcher.finalize()
|
||||
return stitcher
|
||||
|
||||
stitcher = embed()
|
||||
module = Module(stitcher)
|
||||
target = RV32GTarget()
|
||||
llvm_ir = target.compile(module)
|
||||
elf_obj = target.assemble(llvm_ir)
|
||||
elf_shlib = target.link([elf_obj])
|
||||
|
||||
benchmark(lambda: embed(),
|
||||
"ARTIQ embedding")
|
||||
|
||||
benchmark(lambda: Module(stitcher),
|
||||
"ARTIQ transforms and validators")
|
||||
|
||||
benchmark(lambda: target.compile(module),
|
||||
"LLVM optimizations")
|
||||
|
||||
benchmark(lambda: target.assemble(llvm_ir),
|
||||
"LLVM machine code emission")
|
||||
|
||||
benchmark(lambda: target.link([elf_obj]),
|
||||
"Linking")
|
||||
|
||||
benchmark(lambda: target.strip(elf_shlib),
|
||||
"Stripping debug information")
|
||||
|
||||
dataset_db.close_db()
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,30 +0,0 @@
|
|||
import sys, os
|
||||
from pythonparser import diagnostic
|
||||
from ..module import Module, Source
|
||||
from ..targets import RV32GTarget
|
||||
|
||||
def main():
|
||||
if not len(sys.argv) > 1:
|
||||
print("Expected at least one module filename", file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
def process_diagnostic(diag):
|
||||
print("\n".join(diag.render()), file=sys.stderr)
|
||||
if diag.level in ("fatal", "error"):
|
||||
exit(1)
|
||||
|
||||
engine = diagnostic.Engine()
|
||||
engine.process = process_diagnostic
|
||||
|
||||
modules = []
|
||||
for filename in sys.argv[1:]:
|
||||
modules.append(Module(Source.from_filename(filename, engine=engine)))
|
||||
|
||||
llobj = RV32GTarget().compile_and_link(modules)
|
||||
|
||||
basename, ext = os.path.splitext(sys.argv[-1])
|
||||
with open(basename + ".so", "wb") as f:
|
||||
f.write(llobj)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,44 +0,0 @@
|
|||
import sys, fileinput
|
||||
from pythonparser import diagnostic
|
||||
from ..module import Module, Source
|
||||
from .. import types, iodelay
|
||||
|
||||
def main():
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "+diag":
|
||||
del sys.argv[1]
|
||||
diag = True
|
||||
def process_diagnostic(diag):
|
||||
print("\n".join(diag.render(only_line=True)))
|
||||
if diag.level == "fatal":
|
||||
exit()
|
||||
else:
|
||||
diag = False
|
||||
def process_diagnostic(diag):
|
||||
print("\n".join(diag.render(colored=False)))
|
||||
if diag.level in ("fatal", "error"):
|
||||
exit(1)
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] == "+delay":
|
||||
del sys.argv[1]
|
||||
force_delays = True
|
||||
else:
|
||||
force_delays = False
|
||||
|
||||
engine = diagnostic.Engine()
|
||||
engine.process = process_diagnostic
|
||||
|
||||
try:
|
||||
mod = Module(Source.from_string("".join(fileinput.input()).expandtabs(), engine=engine))
|
||||
|
||||
if force_delays:
|
||||
for var in mod.globals:
|
||||
typ = mod.globals[var].find()
|
||||
if types.is_function(typ) and types.is_indeterminate_delay(typ.delay):
|
||||
process_diagnostic(typ.delay.find().cause)
|
||||
|
||||
print(repr(mod))
|
||||
except:
|
||||
if not diag: raise
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
|
@ -1,12 +0,0 @@
|
|||
from .asttyped_rewriter import ASTTypedRewriter
|
||||
from .inferencer import Inferencer
|
||||
from .int_monomorphizer import IntMonomorphizer
|
||||
from .cast_monomorphizer import CastMonomorphizer
|
||||
from .iodelay_estimator import IODelayEstimator
|
||||
from .artiq_ir_generator import ARTIQIRGenerator
|
||||
from .dead_code_eliminator import DeadCodeEliminator
|
||||
from .local_demoter import LocalDemoter
|
||||
from .constant_hoister import ConstantHoister
|
||||
from .interleaver import Interleaver
|
||||
from .typedtree_printer import TypedtreePrinter
|
||||
from .llvm_ir_generator import LLVMIRGenerator
|
File diff suppressed because it is too large
Load Diff
|
@ -1,527 +0,0 @@
|
|||
"""
|
||||
:class:`ASTTypedRewriter` rewrites a parsetree (:mod:`pythonparser.ast`)
|
||||
to a typedtree (:mod:`..asttyped`).
|
||||
"""
|
||||
|
||||
from collections import OrderedDict
|
||||
from pythonparser import ast, algorithm, diagnostic
|
||||
from .. import asttyped, types, builtins
|
||||
|
||||
# This visitor will be called for every node with a scope,
|
||||
# i.e.: class, function, comprehension, lambda
|
||||
class LocalExtractor(algorithm.Visitor):
|
||||
def __init__(self, env_stack, engine):
|
||||
super().__init__()
|
||||
self.env_stack = env_stack
|
||||
self.engine = engine
|
||||
|
||||
self.in_root = False
|
||||
self.in_assign = False
|
||||
self.typing_env = OrderedDict()
|
||||
|
||||
# which names are global have to be recorded in the current scope
|
||||
self.global_ = set()
|
||||
|
||||
# which names are nonlocal only affects whether the current scope
|
||||
# gets a new binding or not, so we throw this away
|
||||
self.nonlocal_ = set()
|
||||
|
||||
# parameters can't be declared as global or nonlocal
|
||||
self.params = set()
|
||||
|
||||
def visit_in_assign(self, node, in_assign):
|
||||
try:
|
||||
old_in_assign, self.in_assign = self.in_assign, in_assign
|
||||
return self.visit(node)
|
||||
finally:
|
||||
self.in_assign = old_in_assign
|
||||
|
||||
def visit_Assign(self, node):
|
||||
self.visit(node.value)
|
||||
self.visit_in_assign(node.targets, in_assign=True)
|
||||
|
||||
def visit_For(self, node):
|
||||
self.visit(node.iter)
|
||||
self.visit_in_assign(node.target, in_assign=True)
|
||||
self.visit(node.body)
|
||||
self.visit(node.orelse)
|
||||
|
||||
def visit_withitem(self, node):
|
||||
self.visit(node.context_expr)
|
||||
self.visit_in_assign(node.optional_vars, in_assign=True)
|
||||
|
||||
def visit_comprehension(self, node):
|
||||
self.visit(node.iter)
|
||||
self.visit_in_assign(node.target, in_assign=True)
|
||||
self.visit(node.ifs)
|
||||
|
||||
def visit_generator(self, node):
|
||||
if self.in_root:
|
||||
return
|
||||
self.in_root = True
|
||||
self.visit(list(reversed(node.generators)))
|
||||
self.visit(node.elt)
|
||||
|
||||
visit_ListComp = visit_generator
|
||||
visit_SetComp = visit_generator
|
||||
visit_GeneratorExp = visit_generator
|
||||
|
||||
def visit_DictComp(self, node):
|
||||
if self.in_root:
|
||||
return
|
||||
self.in_root = True
|
||||
self.visit(list(reversed(node.generators)))
|
||||
self.visit(node.key)
|
||||
self.visit(node.value)
|
||||
|
||||
def visit_root(self, node):
|
||||
if self.in_root:
|
||||
return
|
||||
self.in_root = True
|
||||
self.generic_visit(node)
|
||||
|
||||
visit_Module = visit_root # don't look at inner scopes
|
||||
visit_ClassDef = visit_root
|
||||
visit_Lambda = visit_root
|
||||
|
||||
def visit_FunctionDef(self, node):
|
||||
if self.in_root:
|
||||
self._assignable(node.name)
|
||||
self.visit_root(node)
|
||||
|
||||
def _assignable(self, name):
|
||||
assert name is not None
|
||||
if name not in self.typing_env and name not in self.nonlocal_:
|
||||
self.typing_env[name] = types.TVar()
|
||||
|
||||
def visit_arg(self, node):
|
||||
if node.arg in self.params:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"duplicate parameter '{name}'", {"name": node.arg},
|
||||
node.loc)
|
||||
self.engine.process(diag)
|
||||
return
|
||||
self._assignable(node.arg)
|
||||
self.params.add(node.arg)
|
||||
|
||||
def visit_Name(self, node):
|
||||
if self.in_assign:
|
||||
# code like:
|
||||
# x = 1
|
||||
# def f():
|
||||
# x = 1
|
||||
# creates a new binding for x in f's scope
|
||||
self._assignable(node.id)
|
||||
|
||||
def visit_Attribute(self, node):
|
||||
self.visit_in_assign(node.value, in_assign=False)
|
||||
|
||||
def visit_Subscript(self, node):
|
||||
self.visit_in_assign(node.value, in_assign=False)
|
||||
self.visit_in_assign(node.slice, in_assign=False)
|
||||
|
||||
def _check_not_in(self, name, names, curkind, newkind, loc):
|
||||
if name in names:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"name '{name}' cannot be {curkind} and {newkind} simultaneously",
|
||||
{"name": name, "curkind": curkind, "newkind": newkind}, loc)
|
||||
self.engine.process(diag)
|
||||
return True
|
||||
return False
|
||||
|
||||
def visit_Global(self, node):
|
||||
for name, loc in zip(node.names, node.name_locs):
|
||||
if self._check_not_in(name, self.nonlocal_, "nonlocal", "global", loc) or \
|
||||
self._check_not_in(name, self.params, "a parameter", "global", loc):
|
||||
continue
|
||||
|
||||
self.global_.add(name)
|
||||
if len(self.env_stack) == 1:
|
||||
self._assignable(name) # already in global scope
|
||||
else:
|
||||
if name not in self.env_stack[1]:
|
||||
self.env_stack[1][name] = types.TVar()
|
||||
self.typing_env[name] = self.env_stack[1][name]
|
||||
|
||||
def visit_Nonlocal(self, node):
|
||||
for name, loc in zip(node.names, node.name_locs):
|
||||
if self._check_not_in(name, self.global_, "global", "nonlocal", loc) or \
|
||||
self._check_not_in(name, self.params, "a parameter", "nonlocal", loc):
|
||||
continue
|
||||
|
||||
# nonlocal does not search prelude and global scopes
|
||||
found = False
|
||||
for outer_env in reversed(self.env_stack[2:]):
|
||||
if name in outer_env:
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"cannot declare name '{name}' as nonlocal: it is not bound in any outer scope",
|
||||
{"name": name},
|
||||
loc, [node.keyword_loc])
|
||||
self.engine.process(diag)
|
||||
continue
|
||||
|
||||
self.nonlocal_.add(name)
|
||||
|
||||
def visit_ExceptHandler(self, node):
|
||||
self.visit(node.type)
|
||||
if node.name is not None:
|
||||
self._assignable(node.name)
|
||||
for stmt in node.body:
|
||||
self.visit(stmt)
|
||||
|
||||
|
||||
class ASTTypedRewriter(algorithm.Transformer):
|
||||
"""
|
||||
:class:`ASTTypedRewriter` converts an untyped AST to a typed AST
|
||||
where all type fields of non-literals are filled with fresh type variables,
|
||||
and type fields of literals are filled with corresponding types.
|
||||
|
||||
:class:`ASTTypedRewriter` also discovers the scope of variable bindings
|
||||
via :class:`LocalExtractor`.
|
||||
"""
|
||||
|
||||
def __init__(self, engine, prelude):
|
||||
self.engine = engine
|
||||
self.globals = None
|
||||
self.env_stack = [prelude]
|
||||
self.in_class = None
|
||||
|
||||
def _try_find_name(self, name):
|
||||
if self.in_class is not None:
|
||||
typ = self.in_class.constructor_type.attributes.get(name)
|
||||
if typ is not None:
|
||||
return typ
|
||||
|
||||
for typing_env in reversed(self.env_stack):
|
||||
if name in typing_env:
|
||||
return typing_env[name]
|
||||
|
||||
def _find_name(self, name, loc):
|
||||
typ = self._try_find_name(name)
|
||||
if typ is not None:
|
||||
return typ
|
||||
|
||||
diag = diagnostic.Diagnostic("fatal",
|
||||
"undefined variable '{name}'", {"name":name}, loc)
|
||||
self.engine.process(diag)
|
||||
|
||||
# Visitors that replace node with a typed node
|
||||
#
|
||||
def visit_Module(self, node):
|
||||
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
|
||||
extractor.visit(node)
|
||||
|
||||
node = asttyped.ModuleT(
|
||||
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
|
||||
body=node.body, loc=node.loc)
|
||||
self.globals = node.typing_env
|
||||
|
||||
try:
|
||||
self.env_stack.append(node.typing_env)
|
||||
return self.generic_visit(node)
|
||||
finally:
|
||||
self.env_stack.pop()
|
||||
|
||||
def visit_FunctionDef(self, node):
|
||||
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
|
||||
extractor.visit(node)
|
||||
|
||||
signature_type = self._find_name(node.name, node.name_loc)
|
||||
|
||||
node = asttyped.FunctionDefT(
|
||||
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
|
||||
signature_type=signature_type, return_type=types.TVar(),
|
||||
name=node.name, args=node.args, returns=node.returns,
|
||||
body=node.body, decorator_list=node.decorator_list,
|
||||
keyword_loc=node.keyword_loc, name_loc=node.name_loc,
|
||||
arrow_loc=node.arrow_loc, colon_loc=node.colon_loc, at_locs=node.at_locs,
|
||||
loc=node.loc, remote_fn=False)
|
||||
|
||||
try:
|
||||
self.env_stack.append(node.typing_env)
|
||||
return self.generic_visit(node)
|
||||
finally:
|
||||
self.env_stack.pop()
|
||||
|
||||
def visit_ClassDef(self, node):
|
||||
if any(node.bases) or any(node.keywords) or \
|
||||
node.starargs is not None or node.kwargs is not None:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"inheritance is not supported", {},
|
||||
node.lparen_loc.join(node.rparen_loc))
|
||||
self.engine.process(diag)
|
||||
|
||||
for child in node.body:
|
||||
if isinstance(child, (ast.Assign, ast.FunctionDef, ast.Pass)):
|
||||
continue
|
||||
|
||||
diag = diagnostic.Diagnostic("fatal",
|
||||
"class body must contain only assignments and function definitions", {},
|
||||
child.loc)
|
||||
self.engine.process(diag)
|
||||
|
||||
if node.name in self.env_stack[-1]:
|
||||
diag = diagnostic.Diagnostic("fatal",
|
||||
"variable '{name}' is already defined", {"name":node.name}, node.name_loc)
|
||||
self.engine.process(diag)
|
||||
|
||||
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
|
||||
extractor.visit(node)
|
||||
|
||||
# Now we create two types.
|
||||
# The first type is the type of instances created by the constructor.
|
||||
# Its attributes are those of the class environment, but wrapped
|
||||
# appropriately so that they are linked to the class from which they
|
||||
# originate.
|
||||
instance_type = types.TInstance(node.name, OrderedDict())
|
||||
|
||||
# The second type is the type of the constructor itself (in other words,
|
||||
# the class object): it is simply a singleton type that has the class
|
||||
# environment as attributes.
|
||||
constructor_type = types.TConstructor(instance_type)
|
||||
constructor_type.attributes = extractor.typing_env
|
||||
instance_type.constructor = constructor_type
|
||||
|
||||
self.env_stack[-1][node.name] = constructor_type
|
||||
|
||||
node = asttyped.ClassDefT(
|
||||
constructor_type=constructor_type,
|
||||
name=node.name,
|
||||
bases=self.visit(node.bases), keywords=self.visit(node.keywords),
|
||||
starargs=self.visit(node.starargs), kwargs=self.visit(node.kwargs),
|
||||
body=node.body,
|
||||
decorator_list=self.visit(node.decorator_list),
|
||||
keyword_loc=node.keyword_loc, name_loc=node.name_loc,
|
||||
lparen_loc=node.lparen_loc, star_loc=node.star_loc,
|
||||
dstar_loc=node.dstar_loc, rparen_loc=node.rparen_loc,
|
||||
colon_loc=node.colon_loc, at_locs=node.at_locs,
|
||||
loc=node.loc)
|
||||
|
||||
try:
|
||||
old_in_class, self.in_class = self.in_class, node
|
||||
return self.generic_visit(node)
|
||||
finally:
|
||||
self.in_class = old_in_class
|
||||
|
||||
def visit_arg(self, node):
|
||||
if node.annotation is not None:
|
||||
diag = diagnostic.Diagnostic("fatal",
|
||||
"type annotations are not supported here", {},
|
||||
node.annotation.loc)
|
||||
self.engine.process(diag)
|
||||
|
||||
return asttyped.argT(type=self._find_name(node.arg, node.loc),
|
||||
arg=node.arg, annotation=None,
|
||||
arg_loc=node.arg_loc, colon_loc=node.colon_loc, loc=node.loc)
|
||||
|
||||
def visit_Num(self, node):
|
||||
if isinstance(node.n, int):
|
||||
typ = builtins.TInt()
|
||||
elif isinstance(node.n, float):
|
||||
typ = builtins.TFloat()
|
||||
else:
|
||||
diag = diagnostic.Diagnostic("fatal",
|
||||
"numeric type {type} is not supported", {"type": node.n.__class__.__name__},
|
||||
node.loc)
|
||||
self.engine.process(diag)
|
||||
return asttyped.NumT(type=typ,
|
||||
n=node.n, loc=node.loc)
|
||||
|
||||
def visit_Str(self, node):
|
||||
if isinstance(node.s, str):
|
||||
typ = builtins.TStr()
|
||||
elif isinstance(node.s, bytes):
|
||||
typ = builtins.TBytes()
|
||||
else:
|
||||
assert False
|
||||
return asttyped.StrT(type=typ, s=node.s,
|
||||
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
|
||||
|
||||
def visit_Name(self, node):
|
||||
return asttyped.NameT(type=self._find_name(node.id, node.loc),
|
||||
id=node.id, ctx=node.ctx, loc=node.loc)
|
||||
|
||||
def visit_NameConstant(self, node):
|
||||
if node.value is True or node.value is False:
|
||||
typ = builtins.TBool()
|
||||
elif node.value is None:
|
||||
typ = builtins.TNone()
|
||||
return asttyped.NameConstantT(type=typ, value=node.value, loc=node.loc)
|
||||
|
||||
def visit_Tuple(self, node):
|
||||
node = self.generic_visit(node)
|
||||
return asttyped.TupleT(type=types.TTuple([x.type for x in node.elts]),
|
||||
elts=node.elts, ctx=node.ctx, loc=node.loc)
|
||||
|
||||
def visit_List(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.ListT(type=builtins.TList(),
|
||||
elts=node.elts, ctx=node.ctx,
|
||||
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
|
||||
return self.visit(node)
|
||||
|
||||
def visit_Attribute(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.AttributeT(type=types.TVar(),
|
||||
value=node.value, attr=node.attr, ctx=node.ctx,
|
||||
dot_loc=node.dot_loc, attr_loc=node.attr_loc, loc=node.loc)
|
||||
return self.visit(node)
|
||||
|
||||
def visit_Slice(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.SliceT(type=types.TVar(),
|
||||
lower=node.lower, upper=node.upper, step=node.step,
|
||||
bound_colon_loc=node.bound_colon_loc,
|
||||
step_colon_loc=node.step_colon_loc,
|
||||
loc=node.loc)
|
||||
return self.visit(node)
|
||||
|
||||
def visit_Subscript(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.SubscriptT(type=types.TVar(),
|
||||
value=node.value, slice=node.slice, ctx=node.ctx,
|
||||
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
|
||||
return self.visit(node)
|
||||
|
||||
def visit_BoolOp(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.BoolOpT(type=types.TVar(),
|
||||
op=node.op, values=node.values,
|
||||
op_locs=node.op_locs, loc=node.loc)
|
||||
return self.visit(node)
|
||||
|
||||
def visit_UnaryOp(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.UnaryOpT(type=types.TVar(),
|
||||
op=node.op, operand=node.operand,
|
||||
loc=node.loc)
|
||||
return self.visit(node)
|
||||
|
||||
def visit_BinOp(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.BinOpT(type=types.TVar(),
|
||||
left=node.left, op=node.op, right=node.right,
|
||||
loc=node.loc)
|
||||
return self.visit(node)
|
||||
|
||||
def visit_Compare(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.CompareT(type=types.TVar(),
|
||||
left=node.left, ops=node.ops, comparators=node.comparators,
|
||||
loc=node.loc)
|
||||
return self.visit(node)
|
||||
|
||||
def visit_IfExp(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.IfExpT(type=types.TVar(),
|
||||
test=node.test, body=node.body, orelse=node.orelse,
|
||||
if_loc=node.if_loc, else_loc=node.else_loc, loc=node.loc)
|
||||
return self.visit(node)
|
||||
|
||||
def visit_ListComp(self, node):
|
||||
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
|
||||
extractor.visit(node)
|
||||
|
||||
node = asttyped.ListCompT(
|
||||
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
|
||||
type=types.TVar(),
|
||||
elt=node.elt, generators=node.generators,
|
||||
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
|
||||
|
||||
try:
|
||||
self.env_stack.append(node.typing_env)
|
||||
return self.generic_visit(node)
|
||||
finally:
|
||||
self.env_stack.pop()
|
||||
|
||||
def visit_Call(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.CallT(type=types.TVar(), iodelay=None, arg_exprs={},
|
||||
remote_fn=False, func=node.func,
|
||||
args=node.args, keywords=node.keywords,
|
||||
starargs=node.starargs, kwargs=node.kwargs,
|
||||
star_loc=node.star_loc, dstar_loc=node.dstar_loc,
|
||||
begin_loc=node.begin_loc, end_loc=node.end_loc, loc=node.loc)
|
||||
return node
|
||||
|
||||
def visit_Lambda(self, node):
|
||||
extractor = LocalExtractor(env_stack=self.env_stack, engine=self.engine)
|
||||
extractor.visit(node)
|
||||
|
||||
node = asttyped.LambdaT(
|
||||
typing_env=extractor.typing_env, globals_in_scope=extractor.global_,
|
||||
type=types.TVar(),
|
||||
args=node.args, body=node.body,
|
||||
lambda_loc=node.lambda_loc, colon_loc=node.colon_loc, loc=node.loc)
|
||||
|
||||
try:
|
||||
self.env_stack.append(node.typing_env)
|
||||
return self.generic_visit(node)
|
||||
finally:
|
||||
self.env_stack.pop()
|
||||
|
||||
def visit_ExceptHandler(self, node):
|
||||
node = self.generic_visit(node)
|
||||
if node.name is not None:
|
||||
name_type = self._find_name(node.name, node.name_loc)
|
||||
else:
|
||||
name_type = types.TVar()
|
||||
node = asttyped.ExceptHandlerT(
|
||||
name_type=name_type,
|
||||
filter=node.type, name=node.name, body=node.body,
|
||||
except_loc=node.except_loc, as_loc=node.as_loc, name_loc=node.name_loc,
|
||||
colon_loc=node.colon_loc, loc=node.loc)
|
||||
return node
|
||||
|
||||
def visit_Raise(self, node):
|
||||
node = self.generic_visit(node)
|
||||
if node.cause:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"'raise from' syntax is not supported", {},
|
||||
node.from_loc)
|
||||
self.engine.process(diag)
|
||||
return node
|
||||
|
||||
def visit_For(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.ForT(
|
||||
target=node.target, iter=node.iter, body=node.body, orelse=node.orelse,
|
||||
trip_count=None, trip_interval=None,
|
||||
keyword_loc=node.keyword_loc, in_loc=node.in_loc, for_colon_loc=node.for_colon_loc,
|
||||
else_loc=node.else_loc, else_colon_loc=node.else_colon_loc, loc=node.loc)
|
||||
return node
|
||||
|
||||
def visit_withitem(self, node):
|
||||
node = self.generic_visit(node)
|
||||
node = asttyped.withitemT(
|
||||
context_expr=node.context_expr, optional_vars=node.optional_vars,
|
||||
enter_type=types.TVar(), exit_type=types.TVar(),
|
||||
as_loc=node.as_loc, loc=node.loc)
|
||||
return node
|
||||
|
||||
# Unsupported visitors
|
||||
#
|
||||
def visit_unsupported(self, node):
|
||||
diag = diagnostic.Diagnostic("fatal",
|
||||
"this syntax is not supported", {},
|
||||
node.loc)
|
||||
self.engine.process(diag)
|
||||
|
||||
# expr
|
||||
visit_Dict = visit_unsupported
|
||||
visit_DictComp = visit_unsupported
|
||||
visit_Ellipsis = visit_unsupported
|
||||
visit_GeneratorExp = visit_unsupported
|
||||
# visit_Set = visit_unsupported
|
||||
visit_SetComp = visit_unsupported
|
||||
visit_Starred = visit_unsupported
|
||||
visit_Yield = visit_unsupported
|
||||
visit_YieldFrom = visit_unsupported
|
||||
|
||||
# stmt
|
||||
visit_Delete = visit_unsupported
|
||||
visit_Import = visit_unsupported
|
||||
visit_ImportFrom = visit_unsupported
|
|
@ -1,47 +0,0 @@
|
|||
"""
|
||||
:class:`CastMonomorphizer` uses explicit casts to monomorphize
|
||||
expressions of undetermined integer type to either 32 or 64 bits.
|
||||
"""
|
||||
|
||||
from pythonparser import algorithm, diagnostic
|
||||
from .. import types, builtins, asttyped
|
||||
|
||||
class CastMonomorphizer(algorithm.Visitor):
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
|
||||
def visit_CallT(self, node):
|
||||
if (types.is_builtin(node.func.type, "int") or
|
||||
types.is_builtin(node.func.type, "int32") or
|
||||
types.is_builtin(node.func.type, "int64")):
|
||||
typ = node.type.find()
|
||||
if (not types.is_var(typ["width"]) and
|
||||
len(node.args) == 1 and
|
||||
builtins.is_int(node.args[0].type) and
|
||||
types.is_var(node.args[0].type.find()["width"])):
|
||||
if isinstance(node.args[0], asttyped.BinOpT):
|
||||
# Binary operations are a bit special: they can widen, and so their
|
||||
# return type is indeterminate until both argument types are fully known.
|
||||
# In case we first monomorphize the return type, and then some argument type,
|
||||
# and argument type is wider than return type, we'll introduce a conflict.
|
||||
return
|
||||
|
||||
node.args[0].type.unify(typ)
|
||||
|
||||
if types.is_builtin(node.func.type, "int") or \
|
||||
types.is_builtin(node.func.type, "round"):
|
||||
typ = node.type.find()
|
||||
if types.is_var(typ["width"]):
|
||||
typ["width"].unify(types.TValue(32))
|
||||
|
||||
self.generic_visit(node)
|
||||
|
||||
def visit_CoerceT(self, node):
|
||||
if isinstance(node.value, asttyped.NumT) and \
|
||||
builtins.is_int(node.type) and \
|
||||
builtins.is_int(node.value.type) and \
|
||||
not types.is_var(node.type["width"]) and \
|
||||
types.is_var(node.value.type["width"]):
|
||||
node.value.type.unify(node.type)
|
||||
|
||||
self.generic_visit(node)
|
|
@ -1,43 +0,0 @@
|
|||
"""
|
||||
:class:`ConstantHoister` is a code motion transform:
|
||||
it moves any invariant loads to the earliest point where
|
||||
they may be executed.
|
||||
"""
|
||||
|
||||
from .. import types, ir
|
||||
|
||||
class ConstantHoister:
|
||||
def process(self, functions):
|
||||
for func in functions:
|
||||
self.process_function(func)
|
||||
|
||||
def process_function(self, func):
|
||||
entry = func.entry()
|
||||
worklist = set(func.instructions())
|
||||
moved = set()
|
||||
while len(worklist) > 0:
|
||||
insn = worklist.pop()
|
||||
|
||||
if (isinstance(insn, ir.GetAttr) and insn not in moved and
|
||||
types.is_instance(insn.object().type) and
|
||||
insn.attr in insn.object().type.constant_attributes):
|
||||
has_variant_operands = False
|
||||
index_in_entry = 0
|
||||
for operand in insn.operands:
|
||||
if isinstance(operand, ir.Argument):
|
||||
pass
|
||||
elif isinstance(operand, ir.Instruction) and operand.basic_block == entry:
|
||||
index_in_entry = entry.index(operand) + 1
|
||||
else:
|
||||
has_variant_operands = True
|
||||
break
|
||||
|
||||
if has_variant_operands:
|
||||
continue
|
||||
|
||||
insn.remove_from_parent()
|
||||
entry.instructions.insert(index_in_entry, insn)
|
||||
moved.add(insn)
|
||||
|
||||
for use in insn.uses:
|
||||
worklist.add(use)
|
|
@ -1,83 +0,0 @@
|
|||
"""
|
||||
:class:`DeadCodeEliminator` is a dead code elimination transform:
|
||||
it only basic blocks with no predecessors as well as unused
|
||||
instructions without side effects.
|
||||
"""
|
||||
|
||||
from .. import ir
|
||||
|
||||
class DeadCodeEliminator:
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
|
||||
def process(self, functions):
|
||||
for func in functions:
|
||||
self.process_function(func)
|
||||
|
||||
def process_function(self, func):
|
||||
# defer removing those blocks, so our use checks will ignore deleted blocks
|
||||
preserve = [func.entry()]
|
||||
work_list = [func.entry()]
|
||||
while any(work_list):
|
||||
block = work_list.pop()
|
||||
for succ in block.successors():
|
||||
if succ not in preserve:
|
||||
preserve.append(succ)
|
||||
work_list.append(succ)
|
||||
|
||||
to_be_removed = []
|
||||
for block in func.basic_blocks:
|
||||
if block not in preserve:
|
||||
block.is_removed = True
|
||||
to_be_removed.append(block)
|
||||
for insn in block.instructions:
|
||||
insn.is_removed = True
|
||||
|
||||
for block in to_be_removed:
|
||||
self.remove_block(block)
|
||||
|
||||
modified = True
|
||||
while modified:
|
||||
modified = False
|
||||
for insn in func.instructions():
|
||||
# Note that GetLocal is treated as an impure operation:
|
||||
# the local access validator has to observe it to emit
|
||||
# a diagnostic for reads of uninitialized locals, and
|
||||
# it also has to run after the interleaver, but interleaver
|
||||
# doesn't like to work with IR before DCE.
|
||||
if isinstance(insn, (ir.Phi, ir.Alloc, ir.GetAttr, ir.GetElem, ir.Coerce,
|
||||
ir.Arith, ir.Compare, ir.Select, ir.Quote, ir.Closure,
|
||||
ir.Offset)) \
|
||||
and not any(insn.uses):
|
||||
insn.erase()
|
||||
modified = True
|
||||
|
||||
def remove_block(self, block):
|
||||
# block.uses are updated while iterating
|
||||
for use in set(block.uses):
|
||||
if use.is_removed:
|
||||
continue
|
||||
if isinstance(use, ir.Phi):
|
||||
use.remove_incoming_block(block)
|
||||
if not any(use.operands):
|
||||
self.remove_instruction(use)
|
||||
elif isinstance(use, ir.SetLocal):
|
||||
# setlocal %env, %block is only used for lowering finally
|
||||
use.erase()
|
||||
else:
|
||||
assert False
|
||||
|
||||
block.erase()
|
||||
|
||||
def remove_instruction(self, insn):
|
||||
for use in set(insn.uses):
|
||||
if use.is_removed:
|
||||
continue
|
||||
if isinstance(use, ir.Phi):
|
||||
use.remove_incoming_value(insn)
|
||||
if not any(use.operands):
|
||||
self.remove_instruction(use)
|
||||
else:
|
||||
assert False
|
||||
|
||||
insn.erase()
|
File diff suppressed because it is too large
Load Diff
|
@ -1,28 +0,0 @@
|
|||
"""
|
||||
:class:`IntMonomorphizer` collapses the integer literals of undetermined
|
||||
width to 32 bits, assuming they fit into 32 bits, or 64 bits if they
|
||||
do not.
|
||||
"""
|
||||
|
||||
from pythonparser import algorithm, diagnostic
|
||||
from .. import types, builtins, asttyped
|
||||
|
||||
class IntMonomorphizer(algorithm.Visitor):
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
|
||||
def visit_NumT(self, node):
|
||||
if builtins.is_int(node.type):
|
||||
if types.is_var(node.type["width"]):
|
||||
if -2**31 < node.n < 2**31-1:
|
||||
width = 32
|
||||
elif -2**63 < node.n < 2**63-1:
|
||||
width = 64
|
||||
else:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"integer literal out of range for a signed 64-bit value", {},
|
||||
node.loc)
|
||||
self.engine.process(diag)
|
||||
return
|
||||
|
||||
node.type["width"].unify(types.TValue(width))
|
|
@ -1,185 +0,0 @@
|
|||
"""
|
||||
:class:`Interleaver` reorders requests to the RTIO core so that
|
||||
the timestamp would always monotonically nondecrease.
|
||||
"""
|
||||
|
||||
from pythonparser import diagnostic
|
||||
|
||||
from .. import types, builtins, ir, iodelay
|
||||
from ..analyses import domination
|
||||
from ..algorithms import inline, unroll
|
||||
|
||||
def delay_free_subgraph(root, limit):
|
||||
visited = set()
|
||||
queue = root.successors()
|
||||
while len(queue) > 0:
|
||||
block = queue.pop()
|
||||
visited.add(block)
|
||||
|
||||
if block is limit:
|
||||
continue
|
||||
|
||||
if isinstance(block.terminator(), ir.Delay):
|
||||
return False
|
||||
|
||||
for successor in block.successors():
|
||||
if successor not in visited:
|
||||
queue.append(successor)
|
||||
|
||||
return True
|
||||
|
||||
def iodelay_of_block(block):
|
||||
terminator = block.terminator()
|
||||
if isinstance(terminator, ir.Delay):
|
||||
# We should be able to fold everything without free variables.
|
||||
folded_expr = terminator.interval.fold()
|
||||
assert iodelay.is_const(folded_expr)
|
||||
return folded_expr.value
|
||||
else:
|
||||
return 0
|
||||
|
||||
def is_pure_delay(insn):
|
||||
return isinstance(insn, ir.Builtin) and insn.op in ("delay", "delay_mu")
|
||||
|
||||
def is_impure_delay_block(block):
|
||||
terminator = block.terminator()
|
||||
return isinstance(terminator, ir.Delay) and \
|
||||
not is_pure_delay(terminator.decomposition())
|
||||
|
||||
class Interleaver:
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
|
||||
def process(self, functions):
|
||||
for func in functions:
|
||||
self.process_function(func)
|
||||
|
||||
def process_function(self, func):
|
||||
for insn in func.instructions():
|
||||
if isinstance(insn, ir.Delay):
|
||||
if any(insn.interval.free_vars()):
|
||||
# If a function has free variables in delay expressions,
|
||||
# that means its IO delay depends on arguments.
|
||||
# Do not change such functions in any way so that it will
|
||||
# be successfully inlined and then removed by DCE.
|
||||
return
|
||||
|
||||
postdom_tree = None
|
||||
for insn in func.instructions():
|
||||
if not isinstance(insn, ir.Interleave):
|
||||
continue
|
||||
|
||||
# Lazily compute dominators.
|
||||
if postdom_tree is None:
|
||||
postdom_tree = domination.PostDominatorTree(func)
|
||||
|
||||
target_block = insn.basic_block
|
||||
target_time = 0
|
||||
source_blocks = insn.basic_block.successors()
|
||||
source_times = [0 for _ in source_blocks]
|
||||
|
||||
if len(source_blocks) == 1:
|
||||
# Immediate dominator for a interleave instruction with one successor
|
||||
# is the first instruction in the body of the statement which created
|
||||
# it, but below we expect that it would be the first instruction after
|
||||
# the statement itself.
|
||||
insn.replace_with(ir.Branch(source_blocks[0]))
|
||||
continue
|
||||
|
||||
interleave_until = postdom_tree.immediate_dominator(insn.basic_block)
|
||||
assert interleave_until is not None # no nonlocal flow in `with interleave`
|
||||
assert interleave_until not in source_blocks
|
||||
|
||||
while len(source_blocks) > 0:
|
||||
def time_after_block(pair):
|
||||
index, block = pair
|
||||
return source_times[index] + iodelay_of_block(block)
|
||||
|
||||
# Always prefer impure blocks (with calls) to pure blocks, because
|
||||
# impure blocks may expand with smaller delays appearing, and in
|
||||
# case of a tie, if a pure block is preferred, this would violate
|
||||
# the timeline monotonicity.
|
||||
available_source_blocks = list(filter(is_impure_delay_block, source_blocks))
|
||||
if not any(available_source_blocks):
|
||||
available_source_blocks = source_blocks
|
||||
|
||||
index, source_block = min(enumerate(available_source_blocks), key=time_after_block)
|
||||
source_block_delay = iodelay_of_block(source_block)
|
||||
|
||||
new_target_time = source_times[index] + source_block_delay
|
||||
target_time_delta = new_target_time - target_time
|
||||
assert target_time_delta >= 0
|
||||
|
||||
target_terminator = target_block.terminator()
|
||||
if isinstance(target_terminator, ir.Interleave):
|
||||
target_terminator.replace_with(ir.Branch(source_block))
|
||||
elif isinstance(target_terminator, (ir.Delay, ir.Branch)):
|
||||
target_terminator.set_target(source_block)
|
||||
else:
|
||||
assert False
|
||||
|
||||
source_terminator = source_block.terminator()
|
||||
if isinstance(source_terminator, ir.Interleave):
|
||||
source_terminator.replace_with(ir.Branch(source_terminator.target()))
|
||||
elif isinstance(source_terminator, ir.Branch):
|
||||
pass
|
||||
elif isinstance(source_terminator, ir.BranchIf):
|
||||
# Skip a delay-free loop/conditional
|
||||
source_block = postdom_tree.immediate_dominator(source_block)
|
||||
assert (source_block is not None)
|
||||
elif isinstance(source_terminator, ir.Return):
|
||||
break
|
||||
elif isinstance(source_terminator, ir.Delay):
|
||||
old_decomp = source_terminator.decomposition()
|
||||
if is_pure_delay(old_decomp):
|
||||
if target_time_delta > 0:
|
||||
new_decomp_expr = ir.Constant(int(target_time_delta), builtins.TInt64())
|
||||
new_decomp = ir.Builtin("delay_mu", [new_decomp_expr], builtins.TNone())
|
||||
new_decomp.loc = old_decomp.loc
|
||||
|
||||
source_terminator.basic_block.insert(new_decomp, before=source_terminator)
|
||||
source_terminator.interval = iodelay.Const(target_time_delta)
|
||||
source_terminator.set_decomposition(new_decomp)
|
||||
else:
|
||||
source_terminator.replace_with(ir.Branch(source_terminator.target()))
|
||||
old_decomp.erase()
|
||||
else: # It's a call.
|
||||
need_to_inline = len(source_blocks) > 1
|
||||
if need_to_inline:
|
||||
if old_decomp.static_target_function is None:
|
||||
diag = diagnostic.Diagnostic("fatal",
|
||||
"it is not possible to interleave this function call within "
|
||||
"a 'with interleave:' statement because the compiler could not "
|
||||
"prove that the same function would always be called", {},
|
||||
old_decomp.loc)
|
||||
self.engine.process(diag)
|
||||
|
||||
inline(old_decomp)
|
||||
postdom_tree = domination.PostDominatorTree(func)
|
||||
continue
|
||||
elif target_time_delta > 0:
|
||||
source_terminator.interval = iodelay.Const(target_time_delta)
|
||||
else:
|
||||
source_terminator.replace_with(ir.Branch(source_terminator.target()))
|
||||
elif isinstance(source_terminator, ir.Loop):
|
||||
unroll(source_terminator)
|
||||
|
||||
postdom_tree = domination.PostDominatorTree(func)
|
||||
continue
|
||||
else:
|
||||
assert False
|
||||
|
||||
target_block = source_block
|
||||
target_time = new_target_time
|
||||
|
||||
new_source_block = postdom_tree.immediate_dominator(source_block)
|
||||
assert (new_source_block is not None)
|
||||
assert delay_free_subgraph(source_block, new_source_block)
|
||||
|
||||
if new_source_block == interleave_until:
|
||||
# We're finished with this branch.
|
||||
del source_blocks[index]
|
||||
del source_times[index]
|
||||
else:
|
||||
source_blocks[index] = new_source_block
|
||||
source_times[index] = new_target_time
|
|
@ -1,334 +0,0 @@
|
|||
"""
|
||||
:class:`IODelayEstimator` calculates the amount of time
|
||||
elapsed from the point of view of the RTIO core for
|
||||
every function.
|
||||
"""
|
||||
|
||||
from pythonparser import ast, algorithm, diagnostic
|
||||
from .. import types, iodelay, builtins, asttyped
|
||||
|
||||
class _UnknownDelay(Exception):
|
||||
pass
|
||||
|
||||
class _IndeterminateDelay(Exception):
|
||||
def __init__(self, cause):
|
||||
self.cause = cause
|
||||
|
||||
class IODelayEstimator(algorithm.Visitor):
|
||||
def __init__(self, engine, ref_period):
|
||||
self.engine = engine
|
||||
self.ref_period = ref_period
|
||||
self.changed = False
|
||||
self.current_delay = iodelay.Const(0)
|
||||
self.current_args = None
|
||||
self.current_goto = None
|
||||
self.current_return = None
|
||||
|
||||
def evaluate(self, node, abort, context):
|
||||
if isinstance(node, asttyped.NumT):
|
||||
return iodelay.Const(node.n)
|
||||
elif isinstance(node, asttyped.CoerceT):
|
||||
return self.evaluate(node.value, abort, context)
|
||||
elif isinstance(node, asttyped.NameT):
|
||||
if self.current_args is None:
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"this variable is not an argument", {},
|
||||
node.loc)
|
||||
abort([note])
|
||||
elif node.id in [arg.arg for arg in self.current_args.args]:
|
||||
return iodelay.Var(node.id)
|
||||
else:
|
||||
notes = [
|
||||
diagnostic.Diagnostic("note",
|
||||
"this variable is not an argument of the innermost function", {},
|
||||
node.loc),
|
||||
diagnostic.Diagnostic("note",
|
||||
"only these arguments are in scope of analysis", {},
|
||||
self.current_args.loc)
|
||||
]
|
||||
abort(notes)
|
||||
elif isinstance(node, asttyped.BinOpT):
|
||||
lhs = self.evaluate(node.left, abort, context)
|
||||
rhs = self.evaluate(node.right, abort, context)
|
||||
if isinstance(node.op, ast.Add):
|
||||
return lhs + rhs
|
||||
elif isinstance(node.op, ast.Sub):
|
||||
return lhs - rhs
|
||||
elif isinstance(node.op, ast.Mult):
|
||||
return lhs * rhs
|
||||
elif isinstance(node.op, ast.Div):
|
||||
return lhs / rhs
|
||||
elif isinstance(node.op, ast.FloorDiv):
|
||||
return lhs // rhs
|
||||
else:
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"this operator is not supported {context}",
|
||||
{"context": context},
|
||||
node.op.loc)
|
||||
abort([note])
|
||||
else:
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"this expression is not supported {context}",
|
||||
{"context": context},
|
||||
node.loc)
|
||||
abort([note])
|
||||
|
||||
def abort(self, message, loc, notes=[]):
|
||||
diag = diagnostic.Diagnostic("error", message, {}, loc, notes=notes)
|
||||
raise _IndeterminateDelay(diag)
|
||||
|
||||
def visit_fixpoint(self, node):
|
||||
while True:
|
||||
self.changed = False
|
||||
self.visit(node)
|
||||
if not self.changed:
|
||||
return
|
||||
|
||||
def visit_ModuleT(self, node):
|
||||
try:
|
||||
for stmt in node.body:
|
||||
try:
|
||||
self.visit(stmt)
|
||||
except _UnknownDelay:
|
||||
pass # more luck next time?
|
||||
except _IndeterminateDelay:
|
||||
pass # we don't care; module-level code is never interleaved
|
||||
|
||||
def visit_function(self, args, body, typ, loc):
|
||||
old_args, self.current_args = self.current_args, args
|
||||
old_return, self.current_return = self.current_return, None
|
||||
old_delay, self.current_delay = self.current_delay, iodelay.Const(0)
|
||||
try:
|
||||
self.visit(body)
|
||||
if not iodelay.is_zero(self.current_delay) and self.current_return is not None:
|
||||
self.abort("only return statement at the end of the function "
|
||||
"can be interleaved", self.current_return.loc)
|
||||
|
||||
delay = types.TFixedDelay(self.current_delay.fold())
|
||||
except _IndeterminateDelay as error:
|
||||
delay = types.TIndeterminateDelay(error.cause)
|
||||
self.current_delay = old_delay
|
||||
self.current_return = old_return
|
||||
self.current_args = old_args
|
||||
|
||||
if types.is_indeterminate_delay(delay) and types.is_indeterminate_delay(typ.delay):
|
||||
# Both delays indeterminate; no point in unifying since that will
|
||||
# replace the lazy and more specific error with an eager and more generic
|
||||
# error (unification error of delay(?) with delay(?), which is useless).
|
||||
return
|
||||
|
||||
try:
|
||||
old_delay = typ.delay.find()
|
||||
typ.delay.unify(delay)
|
||||
if typ.delay.find() != old_delay:
|
||||
self.changed = True
|
||||
except types.UnificationError as e:
|
||||
printer = types.TypePrinter()
|
||||
diag = diagnostic.Diagnostic("fatal",
|
||||
"delay {delaya} was inferred for this function, but its delay is already "
|
||||
"constrained externally to {delayb}",
|
||||
{"delaya": printer.name(delay), "delayb": printer.name(typ.delay)},
|
||||
loc)
|
||||
self.engine.process(diag)
|
||||
|
||||
def visit_FunctionDefT(self, node):
|
||||
self.visit(node.args.defaults)
|
||||
self.visit(node.args.kw_defaults)
|
||||
|
||||
# We can only handle return in tail position.
|
||||
if isinstance(node.body[-1], ast.Return):
|
||||
body = node.body[:-1]
|
||||
else:
|
||||
body = node.body
|
||||
self.visit_function(node.args, body, node.signature_type.find(), node.loc)
|
||||
|
||||
visit_QuotedFunctionDefT = visit_FunctionDefT
|
||||
|
||||
def visit_LambdaT(self, node):
|
||||
self.visit_function(node.args, node.body, node.type.find(), node.loc)
|
||||
|
||||
def get_iterable_length(self, node, context):
|
||||
def abort(notes):
|
||||
self.abort("for statement cannot be interleaved because "
|
||||
"iteration count is indeterminate",
|
||||
node.loc, notes)
|
||||
|
||||
def evaluate(node):
|
||||
return self.evaluate(node, abort, context)
|
||||
|
||||
if isinstance(node, asttyped.CallT) and types.is_builtin(node.func.type, "range"):
|
||||
range_min, range_max, range_step = iodelay.Const(0), None, iodelay.Const(1)
|
||||
if len(node.args) == 3:
|
||||
range_min, range_max, range_step = map(evaluate, node.args)
|
||||
elif len(node.args) == 2:
|
||||
range_min, range_max = map(evaluate, node.args)
|
||||
elif len(node.args) == 1:
|
||||
range_max, = map(evaluate, node.args)
|
||||
return (range_max - range_min) // range_step
|
||||
else:
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"this value is not a constant range literal", {},
|
||||
node.loc)
|
||||
abort([note])
|
||||
|
||||
def visit_ForT(self, node):
|
||||
self.visit(node.iter)
|
||||
|
||||
old_goto, self.current_goto = self.current_goto, None
|
||||
old_delay, self.current_delay = self.current_delay, iodelay.Const(0)
|
||||
self.visit(node.body)
|
||||
if iodelay.is_zero(self.current_delay):
|
||||
self.current_delay = old_delay
|
||||
else:
|
||||
if self.current_goto is not None:
|
||||
self.abort("loop iteration count is indeterminate because of control flow",
|
||||
self.current_goto.loc)
|
||||
|
||||
context = "in an iterable used in a for loop that is being interleaved"
|
||||
node.trip_count = self.get_iterable_length(node.iter, context).fold()
|
||||
node.trip_interval = self.current_delay.fold()
|
||||
self.current_delay = old_delay + node.trip_interval * node.trip_count
|
||||
self.current_goto = old_goto
|
||||
|
||||
self.visit(node.orelse)
|
||||
|
||||
def visit_goto(self, node):
|
||||
self.current_goto = node
|
||||
|
||||
visit_Break = visit_goto
|
||||
visit_Continue = visit_goto
|
||||
|
||||
def visit_control_flow(self, kind, node):
|
||||
old_delay, self.current_delay = self.current_delay, iodelay.Const(0)
|
||||
self.generic_visit(node)
|
||||
if not iodelay.is_zero(self.current_delay):
|
||||
self.abort("{} cannot be interleaved".format(kind), node.loc)
|
||||
self.current_delay = old_delay
|
||||
|
||||
visit_If = lambda self, node: self.visit_control_flow("if statement", node)
|
||||
visit_IfExpT = lambda self, node: self.visit_control_flow("if expression", node)
|
||||
visit_Try = lambda self, node: self.visit_control_flow("try statement", node)
|
||||
|
||||
def visit_While(self, node):
|
||||
old_goto, self.current_goto = self.current_goto, None
|
||||
self.visit_control_flow("while statement", node)
|
||||
self.current_goto = old_goto
|
||||
|
||||
def visit_Return(self, node):
|
||||
self.current_return = node
|
||||
|
||||
def visit_With(self, node):
|
||||
self.visit(node.items)
|
||||
|
||||
context_expr = node.items[0].context_expr
|
||||
if len(node.items) == 1 and types.is_builtin(context_expr.type, "interleave"):
|
||||
try:
|
||||
delays = []
|
||||
for stmt in node.body:
|
||||
old_delay, self.current_delay = self.current_delay, iodelay.Const(0)
|
||||
self.visit(stmt)
|
||||
delays.append(self.current_delay)
|
||||
self.current_delay = old_delay
|
||||
|
||||
if any(delays):
|
||||
self.current_delay += iodelay.Max(delays)
|
||||
except _IndeterminateDelay as error:
|
||||
# Interleave failures inside `with` statements are hard failures,
|
||||
# since there's no chance that the code will never actually execute
|
||||
# inside a `with` statement after all.
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"while interleaving this 'with interleave:' statement", {},
|
||||
node.loc)
|
||||
error.cause.notes += [note]
|
||||
self.engine.process(error.cause)
|
||||
|
||||
flow_stmt = None
|
||||
if self.current_goto is not None:
|
||||
flow_stmt = self.current_goto
|
||||
elif self.current_return is not None:
|
||||
flow_stmt = self.current_return
|
||||
|
||||
if flow_stmt is not None:
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"this '{kind}' statement transfers control out of "
|
||||
"the 'with interleave:' statement",
|
||||
{"kind": flow_stmt.keyword_loc.source()},
|
||||
flow_stmt.loc)
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"cannot interleave this 'with interleave:' statement", {},
|
||||
node.keyword_loc.join(node.colon_loc), notes=[note])
|
||||
self.engine.process(diag)
|
||||
|
||||
elif len(node.items) == 1 and types.is_builtin(context_expr.type, "sequential"):
|
||||
self.visit(node.body)
|
||||
else:
|
||||
self.abort("with statement cannot be interleaved", node.loc)
|
||||
|
||||
def visit_CallT(self, node):
|
||||
typ = node.func.type.find()
|
||||
def abort(notes):
|
||||
self.abort("call cannot be interleaved because "
|
||||
"an argument cannot be statically evaluated",
|
||||
node.loc, notes)
|
||||
|
||||
if types.is_builtin(typ, "delay"):
|
||||
value = self.evaluate(node.args[0], abort=abort,
|
||||
context="as an argument for delay()")
|
||||
call_delay = iodelay.SToMU(value, ref_period=self.ref_period)
|
||||
elif types.is_builtin(typ, "delay_mu"):
|
||||
value = self.evaluate(node.args[0], abort=abort,
|
||||
context="as an argument for delay_mu()")
|
||||
call_delay = value
|
||||
elif not types.is_builtin(typ):
|
||||
if types.is_function(typ) or types.is_rpc(typ) or types.is_subkernel(typ):
|
||||
offset = 0
|
||||
elif types.is_method(typ):
|
||||
offset = 1
|
||||
typ = types.get_method_function(typ)
|
||||
else:
|
||||
assert False
|
||||
|
||||
if types.is_rpc(typ) or types.is_subkernel(typ):
|
||||
call_delay = iodelay.Const(0)
|
||||
else:
|
||||
delay = typ.find().delay.find()
|
||||
if types.is_var(delay):
|
||||
raise _UnknownDelay()
|
||||
elif delay.is_indeterminate():
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"function called here", {},
|
||||
node.loc)
|
||||
cause = delay.cause
|
||||
cause = diagnostic.Diagnostic(cause.level, cause.reason, cause.arguments,
|
||||
cause.location, cause.highlights,
|
||||
cause.notes + [note])
|
||||
raise _IndeterminateDelay(cause)
|
||||
elif delay.is_fixed():
|
||||
args = {}
|
||||
for kw_node in node.keywords:
|
||||
args[kw_node.arg] = kw_node.value
|
||||
for arg_name, arg_node in zip(list(typ.args)[offset:], node.args):
|
||||
args[arg_name] = arg_node
|
||||
|
||||
free_vars = delay.duration.free_vars()
|
||||
try:
|
||||
node.arg_exprs = {
|
||||
arg: self.evaluate(args[arg], abort=abort,
|
||||
context="in the expression for argument '{}' "
|
||||
"that affects I/O delay".format(arg))
|
||||
for arg in free_vars
|
||||
}
|
||||
call_delay = delay.duration.fold(node.arg_exprs)
|
||||
except KeyError as e:
|
||||
if getattr(node, "remote_fn", False):
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"function called here", {},
|
||||
node.loc)
|
||||
self.abort("due to arguments passed remotely", node.loc, note)
|
||||
else:
|
||||
assert False
|
||||
else:
|
||||
call_delay = iodelay.Const(0)
|
||||
|
||||
self.current_delay += call_delay
|
||||
node.iodelay = call_delay
|
File diff suppressed because it is too large
Load Diff
|
@ -1,51 +0,0 @@
|
|||
"""
|
||||
:class:`LocalDemoter` is a constant propagation transform:
|
||||
it replaces reads of any local variable with only one write
|
||||
in a function without closures with the value that was written.
|
||||
|
||||
:class:`LocalAccessValidator` must be run before this transform
|
||||
to ensure that the transformation it performs is sound.
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
from .. import ir
|
||||
|
||||
class LocalDemoter:
|
||||
def process(self, functions):
|
||||
for func in functions:
|
||||
self.process_function(func)
|
||||
|
||||
def process_function(self, func):
|
||||
env_safe = {}
|
||||
env_gets = defaultdict(lambda: set())
|
||||
env_sets = defaultdict(lambda: set())
|
||||
|
||||
for insn in func.instructions():
|
||||
if isinstance(insn, (ir.GetLocal, ir.SetLocal)):
|
||||
if "$" in insn.var_name:
|
||||
continue
|
||||
|
||||
env = insn.environment()
|
||||
|
||||
if env not in env_safe:
|
||||
for use in env.uses:
|
||||
if not isinstance(use, (ir.GetLocal, ir.SetLocal)):
|
||||
env_safe[env] = False
|
||||
break
|
||||
else:
|
||||
env_safe[env] = True
|
||||
|
||||
if not env_safe[env]:
|
||||
continue
|
||||
|
||||
if isinstance(insn, ir.SetLocal):
|
||||
env_sets[(env, insn.var_name)].add(insn)
|
||||
else:
|
||||
env_gets[(env, insn.var_name)].add(insn)
|
||||
|
||||
for (env, var_name) in env_sets:
|
||||
if len(env_sets[(env, var_name)]) == 1:
|
||||
set_insn = next(iter(env_sets[(env, var_name)]))
|
||||
for get_insn in env_gets[(env, var_name)]:
|
||||
get_insn.replace_all_uses_with(set_insn.value())
|
||||
get_insn.erase()
|
|
@ -1,89 +0,0 @@
|
|||
"""
|
||||
:class:`TypedtreePrinter` prints a human-readable representation of typedtrees.
|
||||
"""
|
||||
|
||||
from pythonparser import algorithm, ast
|
||||
from .. import types, asttyped
|
||||
|
||||
class TypedtreePrinter(algorithm.Visitor):
|
||||
def __init__(self):
|
||||
self.str = None
|
||||
self.level = None
|
||||
self.last_nl = None
|
||||
self.type_printer = None
|
||||
|
||||
def print(self, node):
|
||||
try:
|
||||
self.str = ""
|
||||
self.level = 0
|
||||
self.last_nl = 0
|
||||
self.type_printer = types.TypePrinter()
|
||||
self.visit(node)
|
||||
self._nl()
|
||||
return self.str
|
||||
finally:
|
||||
self.str = None
|
||||
self.level = None
|
||||
self.last_nl = 0
|
||||
self.type_printer = None
|
||||
|
||||
def _nl(self):
|
||||
# self.str += "·"
|
||||
if len(self.str) != self.last_nl:
|
||||
self.str += "\n" + (" " * self.level)
|
||||
self.last_nl = len(self.str)
|
||||
|
||||
def _indent(self):
|
||||
self.level += 1
|
||||
self._nl()
|
||||
|
||||
def _dedent(self):
|
||||
self._nl()
|
||||
self.level -= 1
|
||||
self.str = self.str[:-2]
|
||||
self.last_nl -= 2
|
||||
|
||||
def visit(self, obj):
|
||||
if isinstance(obj, ast.AST):
|
||||
attrs = set(obj._fields) - {'ctx'}
|
||||
if isinstance(obj, asttyped.commontyped):
|
||||
attrs.update(set(obj._types))
|
||||
|
||||
for attr in set(attrs):
|
||||
if not getattr(obj, attr):
|
||||
attrs.remove(attr) # omit falsey stuff
|
||||
|
||||
self.str += obj.__class__.__name__ + "("
|
||||
if len(attrs) > 1:
|
||||
self._indent()
|
||||
|
||||
for attr in attrs:
|
||||
if len(attrs) > 1:
|
||||
self._nl()
|
||||
self.str += attr + "="
|
||||
self.visit(getattr(obj, attr))
|
||||
if len(attrs) > 1:
|
||||
self._nl()
|
||||
|
||||
if len(attrs) > 1:
|
||||
self._dedent()
|
||||
self.str += ")"
|
||||
elif isinstance(obj, types.Type):
|
||||
self.str += self.type_printer.name(obj, max_depth=0)
|
||||
elif isinstance(obj, list):
|
||||
self.str += "["
|
||||
if len(obj) > 1:
|
||||
self._indent()
|
||||
|
||||
for elem in obj:
|
||||
if len(obj) > 1:
|
||||
self._nl()
|
||||
self.visit(elem)
|
||||
if len(obj) > 1:
|
||||
self._nl()
|
||||
|
||||
if len(obj) > 1:
|
||||
self._dedent()
|
||||
self.str += "]"
|
||||
else:
|
||||
self.str += repr(obj)
|
|
@ -1,890 +0,0 @@
|
|||
"""
|
||||
The :mod:`types` module contains the classes describing the types
|
||||
in :mod:`asttyped`.
|
||||
"""
|
||||
|
||||
import builtins
|
||||
import string
|
||||
from collections import OrderedDict
|
||||
from . import iodelay
|
||||
|
||||
|
||||
class UnificationError(Exception):
|
||||
def __init__(self, typea, typeb):
|
||||
self.typea, self.typeb = typea, typeb
|
||||
|
||||
|
||||
def genalnum():
|
||||
ident = ["a"]
|
||||
while True:
|
||||
yield "".join(ident)
|
||||
pos = len(ident) - 1
|
||||
while pos >= 0:
|
||||
cur_n = string.ascii_lowercase.index(ident[pos])
|
||||
if cur_n < 25:
|
||||
ident[pos] = string.ascii_lowercase[cur_n + 1]
|
||||
break
|
||||
else:
|
||||
ident[pos] = "a"
|
||||
pos -= 1
|
||||
if pos < 0:
|
||||
ident = ["a"] + ident
|
||||
|
||||
def _freeze(dict_):
|
||||
return tuple((key, dict_[key]) for key in dict_)
|
||||
|
||||
def _map_find(elts):
|
||||
if isinstance(elts, list):
|
||||
return [x.find() for x in elts]
|
||||
elif isinstance(elts, dict):
|
||||
return {k: elts[k].find() for k in elts}
|
||||
else:
|
||||
assert False
|
||||
|
||||
|
||||
class Type(object):
|
||||
def __str__(self):
|
||||
return TypePrinter().name(self)
|
||||
|
||||
class TVar(Type):
|
||||
"""
|
||||
A type variable.
|
||||
|
||||
In effect, the classic union-find data structure is intrusively
|
||||
folded into this class.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.parent = self
|
||||
self.rank = 0
|
||||
|
||||
def find(self):
|
||||
parent = self.parent
|
||||
if parent is self:
|
||||
return self
|
||||
else:
|
||||
# The recursive find() invocation is turned into a loop
|
||||
# because paths resulting from unification of large arrays
|
||||
# can easily cause a stack overflow.
|
||||
root = self
|
||||
while parent.__class__ == TVar and root is not parent:
|
||||
_, parent = root, root.parent = parent, parent.parent
|
||||
return root.parent
|
||||
|
||||
def unify(self, other):
|
||||
if other is self:
|
||||
return
|
||||
x = other.find()
|
||||
y = self.find()
|
||||
if x is y:
|
||||
return
|
||||
if y.__class__ == TVar:
|
||||
if x.__class__ == TVar:
|
||||
if x.rank < y.rank:
|
||||
x, y = y, x
|
||||
y.parent = x
|
||||
if x.rank == y.rank:
|
||||
x.rank += 1
|
||||
else:
|
||||
y.parent = x
|
||||
else:
|
||||
y.unify(x)
|
||||
|
||||
def fold(self, accum, fn):
|
||||
if self.parent is self:
|
||||
return fn(accum, self)
|
||||
else:
|
||||
return self.find().fold(accum, fn)
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
if self.parent is self:
|
||||
return "<artiq.compiler.types.TVar %d>" % id(self)
|
||||
else:
|
||||
return repr(self.find())
|
||||
|
||||
# __eq__ and __hash__ are not overridden and default to
|
||||
# comparison by identity. Use .find() explicitly before
|
||||
# any lookups or comparisons.
|
||||
|
||||
class TMono(Type):
|
||||
"""
|
||||
A monomorphic type, possibly parametric.
|
||||
|
||||
:class:`TMono` is supposed to be subclassed by builtin types,
|
||||
unlike all other :class:`Type` descendants. Similarly,
|
||||
instances of :class:`TMono` should never be allocated directly,
|
||||
as that will break the type-sniffing code in :mod:`builtins`.
|
||||
"""
|
||||
|
||||
attributes = OrderedDict()
|
||||
|
||||
def __init__(self, name, params={}):
|
||||
assert isinstance(params, (dict, OrderedDict))
|
||||
self.name, self.params = name, OrderedDict(sorted(params.items()))
|
||||
|
||||
def find(self):
|
||||
return self
|
||||
|
||||
def unify(self, other):
|
||||
if other is self:
|
||||
return
|
||||
if isinstance(other, TMono) and self.name == other.name:
|
||||
assert self.params.keys() == other.params.keys()
|
||||
for param in self.params:
|
||||
self.params[param].unify(other.params[param])
|
||||
elif isinstance(other, TVar):
|
||||
other.unify(self)
|
||||
else:
|
||||
raise UnificationError(self, other)
|
||||
|
||||
def fold(self, accum, fn):
|
||||
for param in self.params:
|
||||
accum = self.params[param].fold(accum, fn)
|
||||
return fn(accum, self)
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
return "artiq.compiler.types.TMono(%s, %s)" % (repr(self.name), repr(self.params))
|
||||
|
||||
def __getitem__(self, param):
|
||||
return self.params[param]
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, TMono) and \
|
||||
self.name == other.name and \
|
||||
_map_find(self.params) == _map_find(other.params)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((self.name, _freeze(self.params)))
|
||||
|
||||
class TTuple(Type):
|
||||
"""
|
||||
A tuple type.
|
||||
|
||||
:ivar elts: (list of :class:`Type`) elements
|
||||
"""
|
||||
|
||||
attributes = OrderedDict()
|
||||
|
||||
def __init__(self, elts=[]):
|
||||
self.elts = elts
|
||||
|
||||
def find(self):
|
||||
return self
|
||||
|
||||
def unify(self, other):
|
||||
if other is self:
|
||||
return
|
||||
if isinstance(other, TTuple) and len(self.elts) == len(other.elts):
|
||||
for selfelt, otherelt in zip(self.elts, other.elts):
|
||||
selfelt.unify(otherelt)
|
||||
elif isinstance(other, TVar):
|
||||
other.unify(self)
|
||||
else:
|
||||
raise UnificationError(self, other)
|
||||
|
||||
def fold(self, accum, fn):
|
||||
for elt in self.elts:
|
||||
accum = elt.fold(accum, fn)
|
||||
return fn(accum, self)
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
return "artiq.compiler.types.TTuple(%s)" % repr(self.elts)
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, TTuple) and \
|
||||
_map_find(self.elts) == _map_find(other.elts)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(tuple(self.elts))
|
||||
|
||||
class _TPointer(TMono):
|
||||
def __init__(self, elt=None):
|
||||
if elt is None:
|
||||
elt = TMono("int", {"width": 8}) # i8*
|
||||
super().__init__("pointer", params={"elt": elt})
|
||||
|
||||
class TFunction(Type):
|
||||
"""
|
||||
A function type.
|
||||
|
||||
:ivar args: (:class:`collections.OrderedDict` of string to :class:`Type`)
|
||||
mandatory arguments
|
||||
:ivar optargs: (:class:`collections.OrderedDict` of string to :class:`Type`)
|
||||
optional arguments
|
||||
:ivar ret: (:class:`Type`)
|
||||
return type
|
||||
:ivar delay: (:class:`Type`)
|
||||
RTIO delay
|
||||
"""
|
||||
|
||||
attributes = OrderedDict([
|
||||
('__closure__', _TPointer()),
|
||||
('__code__', _TPointer()),
|
||||
])
|
||||
|
||||
def __init__(self, args, optargs, ret):
|
||||
assert isinstance(args, OrderedDict)
|
||||
assert isinstance(optargs, OrderedDict)
|
||||
assert isinstance(ret, Type)
|
||||
self.args, self.optargs, self.ret = args, optargs, ret
|
||||
self.delay = TVar()
|
||||
|
||||
def arity(self):
|
||||
return len(self.args) + len(self.optargs)
|
||||
|
||||
def arg_names(self):
|
||||
return list(self.args.keys()) + list(self.optargs.keys())
|
||||
|
||||
def find(self):
|
||||
return self
|
||||
|
||||
def unify(self, other):
|
||||
if other is self:
|
||||
return
|
||||
if isinstance(other, TFunction) and \
|
||||
self.args.keys() == other.args.keys() and \
|
||||
self.optargs.keys() == other.optargs.keys():
|
||||
for selfarg, otherarg in zip(list(self.args.values()) + list(self.optargs.values()),
|
||||
list(other.args.values()) + list(other.optargs.values())):
|
||||
selfarg.unify(otherarg)
|
||||
self.ret.unify(other.ret)
|
||||
self.delay.unify(other.delay)
|
||||
elif isinstance(other, TVar):
|
||||
other.unify(self)
|
||||
else:
|
||||
raise UnificationError(self, other)
|
||||
|
||||
def fold(self, accum, fn):
|
||||
for arg in self.args:
|
||||
accum = self.args[arg].fold(accum, fn)
|
||||
for optarg in self.optargs:
|
||||
accum = self.optargs[optarg].fold(accum, fn)
|
||||
accum = self.ret.fold(accum, fn)
|
||||
return fn(accum, self)
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
return "artiq.compiler.types.TFunction({}, {}, {})".format(
|
||||
repr(self.args), repr(self.optargs), repr(self.ret))
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, TFunction) and \
|
||||
_map_find(self.args) == _map_find(other.args) and \
|
||||
_map_find(self.optargs) == _map_find(other.optargs)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash((_freeze(self.args), _freeze(self.optargs), self.ret))
|
||||
|
||||
class TExternalFunction(TFunction):
|
||||
"""
|
||||
A type of an externally-provided function.
|
||||
|
||||
This can be any function following the C ABI, such as provided by the
|
||||
C/Rust runtime, or a compiler backend intrinsic. The mangled name to link
|
||||
against is encoded as part of the type.
|
||||
|
||||
:ivar name: (str) external symbol name.
|
||||
This will be the symbol linked against (following any extra C name
|
||||
mangling rules).
|
||||
:ivar flags: (set of str) function flags.
|
||||
Flag ``nounwind`` means the function never raises an exception.
|
||||
Flag ``nowrite`` means the function never accesses any memory
|
||||
that the ARTIQ Python code can observe.
|
||||
:ivar broadcast_across_arrays: (bool)
|
||||
If True, the function is transparently applied element-wise when called
|
||||
with TArray arguments.
|
||||
"""
|
||||
|
||||
attributes = OrderedDict()
|
||||
|
||||
def __init__(self, args, ret, name, flags=set(), broadcast_across_arrays=False):
|
||||
assert isinstance(flags, set)
|
||||
for flag in flags:
|
||||
assert flag in {'nounwind', 'nowrite'}
|
||||
super().__init__(args, OrderedDict(), ret)
|
||||
self.name = name
|
||||
self.delay = TFixedDelay(iodelay.Const(0))
|
||||
self.flags = flags
|
||||
self.broadcast_across_arrays = broadcast_across_arrays
|
||||
|
||||
def unify(self, other):
|
||||
if other is self:
|
||||
return
|
||||
if isinstance(other, TExternalFunction) and \
|
||||
self.name == other.name:
|
||||
super().unify(other)
|
||||
elif isinstance(other, TVar):
|
||||
other.unify(self)
|
||||
else:
|
||||
raise UnificationError(self, other)
|
||||
|
||||
class TRPC(Type):
|
||||
"""
|
||||
A type of a remote call.
|
||||
|
||||
:ivar ret: (:class:`Type`)
|
||||
return type
|
||||
:ivar service: (int) RPC service number
|
||||
:ivar is_async: (bool) whether the RPC blocks until return
|
||||
"""
|
||||
|
||||
attributes = OrderedDict()
|
||||
|
||||
def __init__(self, ret, service, is_async=False):
|
||||
assert isinstance(ret, Type)
|
||||
self.ret, self.service, self.is_async = ret, service, is_async
|
||||
|
||||
def find(self):
|
||||
return self
|
||||
|
||||
def unify(self, other):
|
||||
if other is self:
|
||||
return
|
||||
if isinstance(other, TRPC) and \
|
||||
self.service == other.service and \
|
||||
self.is_async == other.is_async:
|
||||
self.ret.unify(other.ret)
|
||||
elif isinstance(other, TVar):
|
||||
other.unify(self)
|
||||
else:
|
||||
raise UnificationError(self, other)
|
||||
|
||||
def fold(self, accum, fn):
|
||||
accum = self.ret.fold(accum, fn)
|
||||
return fn(accum, self)
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
return "artiq.compiler.types.TRPC({})".format(repr(self.ret))
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, TRPC) and \
|
||||
self.service == other.service and \
|
||||
self.is_async == other.is_async
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.service)
|
||||
|
||||
class TSubkernel(TFunction):
|
||||
"""
|
||||
A kernel to be run on a satellite.
|
||||
|
||||
:ivar args: (:class:`collections.OrderedDict` of string to :class:`Type`)
|
||||
function arguments
|
||||
:ivar ret: (:class:`Type`)
|
||||
return type
|
||||
:ivar sid: (int) subkernel ID number
|
||||
:ivar destination: (int) satellite destination number
|
||||
"""
|
||||
|
||||
attributes = OrderedDict()
|
||||
|
||||
def __init__(self, args, optargs, ret, sid, destination):
|
||||
assert isinstance(ret, Type)
|
||||
super().__init__(args, optargs, ret)
|
||||
self.sid, self.destination = sid, destination
|
||||
self.delay = TFixedDelay(iodelay.Const(0))
|
||||
|
||||
def unify(self, other):
|
||||
if other is self:
|
||||
return
|
||||
if isinstance(other, TSubkernel) and \
|
||||
self.sid == other.sid and \
|
||||
self.destination == other.destination:
|
||||
self.ret.unify(other.ret)
|
||||
elif isinstance(other, TVar):
|
||||
other.unify(self)
|
||||
else:
|
||||
raise UnificationError(self, other)
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
return "artiq.compiler.types.TSubkernel({})".format(repr(self.ret))
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, TSubkernel) and \
|
||||
self.sid == other.sid
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.sid)
|
||||
|
||||
class TBuiltin(Type):
|
||||
"""
|
||||
An instance of builtin type. Every instance of a builtin
|
||||
type is treated specially according to its name.
|
||||
"""
|
||||
|
||||
def __init__(self, name):
|
||||
assert isinstance(name, str)
|
||||
self.name = name
|
||||
self.attributes = OrderedDict()
|
||||
|
||||
def find(self):
|
||||
return self
|
||||
|
||||
def unify(self, other):
|
||||
if other is self:
|
||||
return
|
||||
if self != other:
|
||||
raise UnificationError(self, other)
|
||||
|
||||
def fold(self, accum, fn):
|
||||
return fn(accum, self)
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
return "artiq.compiler.types.{}({})".format(type(self).__name__, repr(self.name))
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, TBuiltin) and \
|
||||
self.name == other.name
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
|
||||
class TBuiltinFunction(TBuiltin):
|
||||
"""
|
||||
A type of a builtin function.
|
||||
|
||||
Builtin functions are treated specially throughout all stages of the
|
||||
compilation process according to their name (e.g. calls may not actually
|
||||
lower to a function call). See :class:`TExternalFunction` for externally
|
||||
defined functions that are otherwise regular.
|
||||
"""
|
||||
|
||||
class TConstructor(TBuiltin):
|
||||
"""
|
||||
A type of a constructor of a class, e.g. ``list``.
|
||||
Note that this is not the same as the type of an instance of
|
||||
the class, which is ``TMono("list", ...)`` (or a descendant).
|
||||
|
||||
:ivar instance: (:class:`Type`)
|
||||
the type of the instance created by this constructor
|
||||
"""
|
||||
|
||||
def __init__(self, instance):
|
||||
assert isinstance(instance, TMono)
|
||||
super().__init__(instance.name)
|
||||
self.instance = instance
|
||||
|
||||
class TExceptionConstructor(TConstructor):
|
||||
"""
|
||||
A type of a constructor of an exception, e.g. ``Exception``.
|
||||
Note that this is not the same as the type of an instance of
|
||||
the class, which is ``TMono("Exception", ...)``.
|
||||
"""
|
||||
|
||||
class TInstance(TMono):
|
||||
"""
|
||||
A type of an instance of a user-defined class.
|
||||
|
||||
:ivar constructor: (:class:`TConstructor`)
|
||||
the type of the constructor with which this instance
|
||||
was created
|
||||
"""
|
||||
|
||||
def __init__(self, name, attributes):
|
||||
assert isinstance(attributes, OrderedDict)
|
||||
super().__init__(name)
|
||||
self.attributes = attributes
|
||||
self.constant_attributes = set()
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
return "artiq.compiler.types.TInstance({}, {})".format(
|
||||
repr(self.name), repr(self.attributes))
|
||||
|
||||
class TModule(TMono):
|
||||
"""
|
||||
A type of a module.
|
||||
"""
|
||||
|
||||
def __init__(self, name, attributes):
|
||||
assert isinstance(attributes, OrderedDict)
|
||||
super().__init__(name)
|
||||
self.attributes = attributes
|
||||
self.constant_attributes = set()
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
return "artiq.compiler.types.TModule({}, {})".format(
|
||||
repr(self.name), repr(self.attributes))
|
||||
|
||||
class TMethod(TMono):
|
||||
"""
|
||||
A type of a method.
|
||||
"""
|
||||
|
||||
def __init__(self, self_type, function_type):
|
||||
super().__init__("method", {"self": self_type, "fn": function_type})
|
||||
self.attributes = OrderedDict([
|
||||
("__func__", function_type),
|
||||
("__self__", self_type),
|
||||
])
|
||||
|
||||
class TValue(Type):
|
||||
"""
|
||||
A type-level value (such as the integer denoting width of
|
||||
a generic integer type.
|
||||
"""
|
||||
|
||||
def __init__(self, value):
|
||||
self.value = value
|
||||
|
||||
def find(self):
|
||||
return self
|
||||
|
||||
def unify(self, other):
|
||||
if other is self:
|
||||
return
|
||||
if isinstance(other, TVar):
|
||||
other.unify(self)
|
||||
elif self != other:
|
||||
raise UnificationError(self, other)
|
||||
|
||||
def fold(self, accum, fn):
|
||||
return fn(accum, self)
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
return "artiq.compiler.types.TValue(%s)" % repr(self.value)
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, TValue) and \
|
||||
self.value == other.value
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.value)
|
||||
|
||||
class TDelay(Type):
|
||||
"""
|
||||
The type-level representation of IO delay.
|
||||
"""
|
||||
|
||||
def __init__(self, duration, cause):
|
||||
# Avoid pulling in too many dependencies with `artiq.language`.
|
||||
from pythonparser import diagnostic
|
||||
assert duration is None or isinstance(duration, iodelay.Expr)
|
||||
assert cause is None or isinstance(cause, diagnostic.Diagnostic)
|
||||
assert (not (duration and cause)) and (duration or cause)
|
||||
self.duration, self.cause = duration, cause
|
||||
|
||||
def is_fixed(self):
|
||||
return self.duration is not None
|
||||
|
||||
def is_indeterminate(self):
|
||||
return self.cause is not None
|
||||
|
||||
def find(self):
|
||||
return self
|
||||
|
||||
def unify(self, other):
|
||||
other = other.find()
|
||||
|
||||
if isinstance(other, TVar):
|
||||
other.unify(self)
|
||||
elif self.is_fixed() and other.is_fixed() and \
|
||||
self.duration.fold() == other.duration.fold():
|
||||
pass
|
||||
elif self is not other:
|
||||
raise UnificationError(self, other)
|
||||
|
||||
def fold(self, accum, fn):
|
||||
# delay types do not participate in folding
|
||||
pass
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, TDelay) and \
|
||||
(self.duration == other.duration and \
|
||||
self.cause == other.cause)
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __repr__(self):
|
||||
if getattr(builtins, "__in_sphinx__", False):
|
||||
return str(self)
|
||||
if self.duration is None:
|
||||
return "<{}.TIndeterminateDelay>".format(__name__)
|
||||
elif self.cause is None:
|
||||
return "{}.TFixedDelay({})".format(__name__, self.duration)
|
||||
else:
|
||||
assert False
|
||||
|
||||
def TIndeterminateDelay(cause):
|
||||
return TDelay(None, cause)
|
||||
|
||||
def TFixedDelay(duration):
|
||||
return TDelay(duration, None)
|
||||
|
||||
|
||||
def is_var(typ):
|
||||
return isinstance(typ.find(), TVar)
|
||||
|
||||
def is_mono(typ, name=None, **params):
|
||||
typ = typ.find()
|
||||
|
||||
if not isinstance(typ, TMono):
|
||||
return False
|
||||
|
||||
if name is not None and typ.name != name:
|
||||
return False
|
||||
|
||||
for param in params:
|
||||
if param not in typ.params:
|
||||
return False
|
||||
if typ.params[param].find() != params[param].find():
|
||||
return False
|
||||
return True
|
||||
|
||||
def is_polymorphic(typ):
|
||||
return typ.fold(False, lambda accum, typ: accum or is_var(typ))
|
||||
|
||||
def is_tuple(typ, elts=None):
|
||||
typ = typ.find()
|
||||
if elts:
|
||||
return isinstance(typ, TTuple) and \
|
||||
elts == typ.elts
|
||||
else:
|
||||
return isinstance(typ, TTuple)
|
||||
|
||||
def _is_pointer(typ):
|
||||
return isinstance(typ.find(), _TPointer)
|
||||
|
||||
def is_function(typ):
|
||||
return isinstance(typ.find(), TFunction)
|
||||
|
||||
def is_rpc(typ):
|
||||
return isinstance(typ.find(), TRPC)
|
||||
|
||||
def is_subkernel(typ):
|
||||
return isinstance(typ.find(), TSubkernel)
|
||||
|
||||
def is_external_function(typ, name=None):
|
||||
typ = typ.find()
|
||||
if name is None:
|
||||
return isinstance(typ, TExternalFunction)
|
||||
else:
|
||||
return isinstance(typ, TExternalFunction) and \
|
||||
typ.name == name
|
||||
|
||||
def is_builtin(typ, name=None):
|
||||
typ = typ.find()
|
||||
if name is None:
|
||||
return isinstance(typ, TBuiltin)
|
||||
else:
|
||||
return isinstance(typ, TBuiltin) and \
|
||||
typ.name == name
|
||||
|
||||
def is_builtin_function(typ, name=None):
|
||||
typ = typ.find()
|
||||
if name is None:
|
||||
return isinstance(typ, TBuiltinFunction)
|
||||
else:
|
||||
return isinstance(typ, TBuiltinFunction) and \
|
||||
typ.name == name
|
||||
|
||||
def is_broadcast_across_arrays(typ):
|
||||
# For now, broadcasting is only exposed to predefined external functions, and
|
||||
# statically selected. Might be extended to user-defined functions if the design
|
||||
# pans out.
|
||||
typ = typ.find()
|
||||
if not isinstance(typ, TExternalFunction):
|
||||
return False
|
||||
return typ.broadcast_across_arrays
|
||||
|
||||
def is_constructor(typ, name=None):
|
||||
typ = typ.find()
|
||||
if name is not None:
|
||||
return isinstance(typ, TConstructor) and \
|
||||
typ.name == name
|
||||
else:
|
||||
return isinstance(typ, TConstructor)
|
||||
|
||||
def is_exn_constructor(typ, name=None):
|
||||
typ = typ.find()
|
||||
if name is not None:
|
||||
return isinstance(typ, TExceptionConstructor) and \
|
||||
typ.name == name
|
||||
else:
|
||||
return isinstance(typ, TExceptionConstructor)
|
||||
|
||||
def is_instance(typ, name=None):
|
||||
typ = typ.find()
|
||||
if name is not None:
|
||||
return isinstance(typ, TInstance) and \
|
||||
typ.name == name
|
||||
else:
|
||||
return isinstance(typ, TInstance)
|
||||
|
||||
def is_module(typ, name=None):
|
||||
typ = typ.find()
|
||||
if name is not None:
|
||||
return isinstance(typ, TModule) and \
|
||||
typ.name == name
|
||||
else:
|
||||
return isinstance(typ, TModule)
|
||||
|
||||
def is_method(typ):
|
||||
return isinstance(typ.find(), TMethod)
|
||||
|
||||
def get_method_self(typ):
|
||||
if is_method(typ):
|
||||
return typ.find().params["self"].find()
|
||||
|
||||
def get_method_function(typ):
|
||||
if is_method(typ):
|
||||
return typ.find().params["fn"].find()
|
||||
|
||||
def is_value(typ):
|
||||
return isinstance(typ.find(), TValue)
|
||||
|
||||
def get_value(typ):
|
||||
typ = typ.find()
|
||||
if isinstance(typ, TVar):
|
||||
return None
|
||||
elif isinstance(typ, TValue):
|
||||
return typ.value
|
||||
else:
|
||||
assert False
|
||||
|
||||
def is_delay(typ):
|
||||
return isinstance(typ.find(), TDelay)
|
||||
|
||||
def is_fixed_delay(typ):
|
||||
return is_delay(typ) and typ.find().is_fixed()
|
||||
|
||||
def is_indeterminate_delay(typ):
|
||||
return is_delay(typ) and typ.find().is_indeterminate()
|
||||
|
||||
|
||||
class TypePrinter(object):
|
||||
"""
|
||||
A class that prints types using Python-like syntax and gives
|
||||
type variables sequential alphabetic names.
|
||||
"""
|
||||
|
||||
custom_printers = {}
|
||||
|
||||
def __init__(self):
|
||||
self.gen = genalnum()
|
||||
self.map = {}
|
||||
self.recurse_guard = set()
|
||||
|
||||
def name(self, typ, depth=0, max_depth=1):
|
||||
typ = typ.find()
|
||||
if isinstance(typ, TVar):
|
||||
if typ not in self.map:
|
||||
self.map[typ] = "'%s" % next(self.gen)
|
||||
return self.map[typ]
|
||||
elif isinstance(typ, TInstance):
|
||||
if typ in self.recurse_guard or depth >= max_depth:
|
||||
return "<instance {}>".format(typ.name)
|
||||
elif len(typ.attributes) > 0:
|
||||
self.recurse_guard.add(typ)
|
||||
attrs = ",\n\t\t".join(["{}: {}".format(attr, self.name(typ.attributes[attr],
|
||||
depth + 1))
|
||||
for attr in typ.attributes])
|
||||
return "<instance {} {{\n\t\t{}\n\t}}>".format(typ.name, attrs)
|
||||
else:
|
||||
self.recurse_guard.add(typ)
|
||||
return "<instance {} {{}}>".format(typ.name)
|
||||
elif isinstance(typ, TMono):
|
||||
if typ.name in self.custom_printers:
|
||||
return self.custom_printers[typ.name](typ, self, depth + 1, max_depth)
|
||||
elif typ.params == {}:
|
||||
return typ.name
|
||||
else:
|
||||
return "%s(%s)" % (typ.name, ", ".join(
|
||||
["%s=%s" % (k, self.name(typ.params[k], depth + 1)) for k in typ.params]))
|
||||
elif isinstance(typ, _TPointer):
|
||||
return "{}*".format(self.name(typ["elt"], depth + 1))
|
||||
elif isinstance(typ, TTuple):
|
||||
if len(typ.elts) == 1:
|
||||
return "(%s,)" % self.name(typ.elts[0], depth + 1)
|
||||
else:
|
||||
return "(%s)" % ", ".join([self.name(typ, depth + 1) for typ in typ.elts])
|
||||
elif isinstance(typ, (TFunction, TExternalFunction)):
|
||||
args = []
|
||||
args += [ "%s:%s" % (arg, self.name(typ.args[arg], depth + 1))
|
||||
for arg in typ.args]
|
||||
args += ["?%s:%s" % (arg, self.name(typ.optargs[arg], depth + 1))
|
||||
for arg in typ.optargs]
|
||||
signature = "(%s)->%s" % (", ".join(args), self.name(typ.ret, depth + 1))
|
||||
|
||||
delay = typ.delay.find()
|
||||
if isinstance(delay, TVar):
|
||||
signature += " delay({})".format(self.name(delay, depth + 1))
|
||||
elif not (delay.is_fixed() and iodelay.is_zero(delay.duration)):
|
||||
signature += " " + self.name(delay, depth + 1)
|
||||
|
||||
if isinstance(typ, TExternalFunction):
|
||||
return "[ffi {}]{}".format(repr(typ.name), signature)
|
||||
elif isinstance(typ, TFunction):
|
||||
return signature
|
||||
elif isinstance(typ, TRPC):
|
||||
return "[rpc{} #{}](...)->{}".format(typ.service,
|
||||
" async" if typ.is_async else "",
|
||||
self.name(typ.ret, depth + 1))
|
||||
elif isinstance(typ, TSubkernel):
|
||||
return "<subkernel{} dest#{}>->{}".format(typ.sid,
|
||||
typ.destination,
|
||||
self.name(typ.ret, depth + 1))
|
||||
elif isinstance(typ, TBuiltinFunction):
|
||||
return "<function {}>".format(typ.name)
|
||||
elif isinstance(typ, (TConstructor, TExceptionConstructor)):
|
||||
if typ in self.recurse_guard or depth >= max_depth:
|
||||
return "<constructor {}>".format(typ.name)
|
||||
elif len(typ.attributes) > 0:
|
||||
self.recurse_guard.add(typ)
|
||||
attrs = ", ".join(["{}: {}".format(attr, self.name(typ.attributes[attr],
|
||||
depth + 1))
|
||||
for attr in typ.attributes])
|
||||
return "<constructor {} {{{}}}>".format(typ.name, attrs)
|
||||
else:
|
||||
self.recurse_guard.add(typ)
|
||||
return "<constructor {} {{}}>".format(typ.name)
|
||||
elif isinstance(typ, TBuiltin):
|
||||
return "<builtin {}>".format(typ.name)
|
||||
elif isinstance(typ, TValue):
|
||||
return repr(typ.value)
|
||||
elif isinstance(typ, TDelay):
|
||||
if typ.is_fixed():
|
||||
return "delay({} mu)".format(typ.duration)
|
||||
elif typ.is_indeterminate():
|
||||
return "delay(?)"
|
||||
else:
|
||||
assert False
|
||||
else:
|
||||
assert False
|
|
@ -1,4 +0,0 @@
|
|||
from .monomorphism import MonomorphismValidator
|
||||
from .escape import EscapeValidator
|
||||
from .local_access import LocalAccessValidator
|
||||
from .constness import ConstnessValidator
|
|
@ -1,58 +0,0 @@
|
|||
"""
|
||||
:class:`ConstnessValidator` checks that no attribute marked
|
||||
as constant is ever set.
|
||||
"""
|
||||
|
||||
from pythonparser import algorithm, diagnostic
|
||||
from .. import types, builtins
|
||||
|
||||
class ConstnessValidator(algorithm.Visitor):
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
self.in_assign = False
|
||||
|
||||
def visit_Assign(self, node):
|
||||
self.visit(node.value)
|
||||
self.in_assign = True
|
||||
self.visit(node.targets)
|
||||
self.in_assign = False
|
||||
|
||||
def visit_AugAssign(self, node):
|
||||
self.visit(node.value)
|
||||
self.in_assign = True
|
||||
self.visit(node.target)
|
||||
self.in_assign = False
|
||||
|
||||
def visit_SubscriptT(self, node):
|
||||
old_in_assign, self.in_assign = self.in_assign, False
|
||||
self.visit(node.value)
|
||||
self.visit(node.slice)
|
||||
self.in_assign = old_in_assign
|
||||
|
||||
if self.in_assign and builtins.is_bytes(node.value.type):
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"type {typ} is not mutable",
|
||||
{"typ": "bytes"},
|
||||
node.loc)
|
||||
self.engine.process(diag)
|
||||
|
||||
def visit_AttributeT(self, node):
|
||||
old_in_assign, self.in_assign = self.in_assign, False
|
||||
self.visit(node.value)
|
||||
self.in_assign = old_in_assign
|
||||
|
||||
if self.in_assign:
|
||||
typ = node.value.type.find()
|
||||
if types.is_instance(typ) and node.attr in typ.constant_attributes:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"cannot assign to constant attribute '{attr}' of class '{class}'",
|
||||
{"attr": node.attr, "class": typ.name},
|
||||
node.loc)
|
||||
self.engine.process(diag)
|
||||
return
|
||||
if builtins.is_array(typ):
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"array attributes cannot be assigned to",
|
||||
{}, node.loc)
|
||||
self.engine.process(diag)
|
||||
return
|
|
@ -1,372 +0,0 @@
|
|||
"""
|
||||
:class:`EscapeValidator` verifies that no mutable data escapes
|
||||
the region of its allocation.
|
||||
"""
|
||||
|
||||
import functools
|
||||
from pythonparser import algorithm, diagnostic
|
||||
from .. import asttyped, types, builtins
|
||||
|
||||
def has_region(typ):
|
||||
return typ.fold(False, lambda accum, typ: accum or builtins.is_allocated(typ))
|
||||
|
||||
class Global:
|
||||
def __repr__(self):
|
||||
return "Global()"
|
||||
|
||||
class Argument:
|
||||
def __init__(self, loc):
|
||||
self.loc = loc
|
||||
|
||||
def __repr__(self):
|
||||
return "Argument()"
|
||||
|
||||
class Region:
|
||||
"""
|
||||
A last-in-first-out allocation region. Tied to lexical scoping
|
||||
and is internally represented simply by a source range.
|
||||
|
||||
:ivar range: (:class:`pythonparser.source.Range` or None)
|
||||
"""
|
||||
|
||||
def __init__(self, source_range=None):
|
||||
self.range = source_range
|
||||
|
||||
def present(self):
|
||||
return bool(self.range)
|
||||
|
||||
def includes(self, other):
|
||||
assert self.range
|
||||
assert self.range.source_buffer == other.range.source_buffer
|
||||
|
||||
return self.range.begin_pos <= other.range.begin_pos and \
|
||||
self.range.end_pos >= other.range.end_pos
|
||||
|
||||
def intersects(self, other):
|
||||
assert self.range
|
||||
assert self.range.source_buffer == other.range.source_buffer
|
||||
|
||||
return (self.range.begin_pos <= other.range.begin_pos <= self.range.end_pos and \
|
||||
other.range.end_pos > self.range.end_pos) or \
|
||||
(other.range.begin_pos <= self.range.begin_pos <= other.range.end_pos and \
|
||||
self.range.end_pos > other.range.end_pos)
|
||||
|
||||
def outlives(lhs, rhs):
|
||||
if not isinstance(lhs, Region): # lhs lives nonlexically
|
||||
return True
|
||||
elif not isinstance(rhs, Region): # rhs lives nonlexically, lhs does not
|
||||
return False
|
||||
else:
|
||||
assert not lhs.intersects(rhs)
|
||||
return lhs.includes(rhs)
|
||||
|
||||
def __repr__(self):
|
||||
return "Region({})".format(repr(self.range))
|
||||
|
||||
class RegionOf(algorithm.Visitor):
|
||||
"""
|
||||
Visit an expression and return the region that must be alive for the
|
||||
expression to execute.
|
||||
|
||||
For expressions involving multiple regions, the shortest-lived one is
|
||||
returned.
|
||||
"""
|
||||
|
||||
def __init__(self, env_stack, youngest_region):
|
||||
self.env_stack, self.youngest_region = env_stack, youngest_region
|
||||
|
||||
# Liveness determined by assignments
|
||||
def visit_NameT(self, node):
|
||||
# First, look at stack regions
|
||||
for region in reversed(self.env_stack[1:]):
|
||||
if node.id in region:
|
||||
return region[node.id]
|
||||
|
||||
# Then, look at the global region of this module
|
||||
if node.id in self.env_stack[0]:
|
||||
return Global()
|
||||
|
||||
assert False
|
||||
|
||||
# Value lives as long as the current scope, if it's mutable,
|
||||
# or else forever
|
||||
def visit_sometimes_allocating(self, node):
|
||||
if has_region(node.type):
|
||||
return self.youngest_region
|
||||
else:
|
||||
return Global()
|
||||
|
||||
visit_BinOpT = visit_sometimes_allocating
|
||||
|
||||
def visit_CallT(self, node):
|
||||
if types.is_external_function(node.func.type, "cache_get"):
|
||||
# The cache is borrow checked dynamically
|
||||
return Global()
|
||||
|
||||
if (types.is_builtin_function(node.func.type, "array")
|
||||
or types.is_builtin_function(node.func.type, "make_array")
|
||||
or types.is_builtin_function(node.func.type, "numpy.transpose")):
|
||||
# While lifetime tracking across function calls in general is currently
|
||||
# broken (see below), these special builtins that allocate an array on
|
||||
# the stack of the caller _always_ allocate regardless of the parameters,
|
||||
# and we can thus handle them without running into the precision issue
|
||||
# mentioned in commit ae999db.
|
||||
return self.visit_allocating(node)
|
||||
|
||||
# FIXME: Return statement missing here, but see m-labs/artiq#1497 and
|
||||
# commit ae999db.
|
||||
self.visit_sometimes_allocating(node)
|
||||
|
||||
# Value lives as long as the object/container, if it's mutable,
|
||||
# or else forever
|
||||
def visit_accessor(self, node):
|
||||
if has_region(node.type):
|
||||
return self.visit(node.value)
|
||||
else:
|
||||
return Global()
|
||||
|
||||
visit_AttributeT = visit_accessor
|
||||
visit_SubscriptT = visit_accessor
|
||||
|
||||
# Value lives as long as the shortest living operand
|
||||
def visit_selecting(self, nodes):
|
||||
regions = [self.visit(node) for node in nodes]
|
||||
regions = list(filter(lambda x: x, regions))
|
||||
if any(regions):
|
||||
regions.sort(key=functools.cmp_to_key(Region.outlives), reverse=True)
|
||||
return regions[0]
|
||||
else:
|
||||
return Global()
|
||||
|
||||
def visit_BoolOpT(self, node):
|
||||
return self.visit_selecting(node.values)
|
||||
|
||||
def visit_IfExpT(self, node):
|
||||
return self.visit_selecting([node.body, node.orelse])
|
||||
|
||||
def visit_TupleT(self, node):
|
||||
return self.visit_selecting(node.elts)
|
||||
|
||||
# Value lives as long as the current scope
|
||||
def visit_allocating(self, node):
|
||||
return self.youngest_region
|
||||
|
||||
visit_DictT = visit_allocating
|
||||
visit_DictCompT = visit_allocating
|
||||
visit_GeneratorExpT = visit_allocating
|
||||
visit_LambdaT = visit_allocating
|
||||
visit_ListT = visit_allocating
|
||||
visit_ListCompT = visit_allocating
|
||||
visit_SetT = visit_allocating
|
||||
visit_SetCompT = visit_allocating
|
||||
|
||||
# Value lives forever
|
||||
def visit_immutable(self, node):
|
||||
assert not has_region(node.type)
|
||||
return Global()
|
||||
|
||||
visit_NameConstantT = visit_immutable
|
||||
visit_NumT = visit_immutable
|
||||
visit_EllipsisT = visit_immutable
|
||||
visit_UnaryOpT = visit_sometimes_allocating # possibly array op
|
||||
visit_CompareT = visit_immutable
|
||||
|
||||
# Value lives forever
|
||||
def visit_global(self, node):
|
||||
return Global()
|
||||
|
||||
visit_StrT = visit_global
|
||||
visit_QuoteT = visit_global
|
||||
|
||||
# Not implemented
|
||||
def visit_unimplemented(self, node):
|
||||
assert False
|
||||
|
||||
visit_StarredT = visit_unimplemented
|
||||
visit_YieldT = visit_unimplemented
|
||||
visit_YieldFromT = visit_unimplemented
|
||||
|
||||
|
||||
class AssignedNamesOf(algorithm.Visitor):
|
||||
"""
|
||||
Visit an expression and return the list of names that appear
|
||||
on the lhs of assignment, directly or through an accessor.
|
||||
"""
|
||||
|
||||
def visit_name(self, node):
|
||||
return [node]
|
||||
|
||||
visit_NameT = visit_name
|
||||
visit_QuoteT = visit_name
|
||||
|
||||
def visit_accessor(self, node):
|
||||
return self.visit(node.value)
|
||||
|
||||
visit_AttributeT = visit_accessor
|
||||
visit_SubscriptT = visit_accessor
|
||||
|
||||
def visit_sequence(self, node):
|
||||
return functools.reduce(list.__add__, map(self.visit, node.elts))
|
||||
|
||||
visit_TupleT = visit_sequence
|
||||
visit_ListT = visit_sequence
|
||||
|
||||
def visit_StarredT(self, node):
|
||||
assert False
|
||||
|
||||
|
||||
class EscapeValidator(algorithm.Visitor):
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
self.youngest_region = Global()
|
||||
self.env_stack = []
|
||||
self.youngest_env = None
|
||||
|
||||
def _region_of(self, expr):
|
||||
return RegionOf(self.env_stack, self.youngest_region).visit(expr)
|
||||
|
||||
def _names_of(self, expr):
|
||||
return AssignedNamesOf().visit(expr)
|
||||
|
||||
def _diagnostics_for(self, region, loc, descr="the value of the expression"):
|
||||
if isinstance(region, Region):
|
||||
return [
|
||||
diagnostic.Diagnostic("note",
|
||||
"{descr} is alive from this point...", {"descr": descr},
|
||||
region.range.begin()),
|
||||
diagnostic.Diagnostic("note",
|
||||
"... to this point", {},
|
||||
region.range.end())
|
||||
]
|
||||
elif isinstance(region, Global):
|
||||
return [
|
||||
diagnostic.Diagnostic("note",
|
||||
"{descr} is alive forever", {"descr": descr},
|
||||
loc)
|
||||
]
|
||||
elif isinstance(region, Argument):
|
||||
return [
|
||||
diagnostic.Diagnostic("note",
|
||||
"{descr} is still alive after this function returns", {"descr": descr},
|
||||
loc),
|
||||
diagnostic.Diagnostic("note",
|
||||
"{descr} is introduced here as a formal argument", {"descr": descr},
|
||||
region.loc)
|
||||
]
|
||||
else:
|
||||
assert False
|
||||
|
||||
def visit_in_region(self, node, region, typing_env, args=[]):
|
||||
try:
|
||||
old_youngest_region = self.youngest_region
|
||||
self.youngest_region = region
|
||||
|
||||
old_youngest_env = self.youngest_env
|
||||
self.youngest_env = {}
|
||||
|
||||
for name in typing_env:
|
||||
if has_region(typing_env[name]):
|
||||
if name in args:
|
||||
self.youngest_env[name] = args[name]
|
||||
else:
|
||||
self.youngest_env[name] = Region(None) # not yet known
|
||||
else:
|
||||
self.youngest_env[name] = Global()
|
||||
self.env_stack.append(self.youngest_env)
|
||||
|
||||
self.generic_visit(node)
|
||||
finally:
|
||||
self.env_stack.pop()
|
||||
self.youngest_env = old_youngest_env
|
||||
self.youngest_region = old_youngest_region
|
||||
|
||||
def visit_ModuleT(self, node):
|
||||
self.visit_in_region(node, None, node.typing_env)
|
||||
|
||||
def visit_FunctionDefT(self, node):
|
||||
self.youngest_env[node.name] = self.youngest_region
|
||||
self.visit_in_region(node, Region(node.loc), node.typing_env,
|
||||
args={ arg.arg: Argument(arg.loc) for arg in node.args.args })
|
||||
|
||||
visit_QuotedFunctionDefT = visit_FunctionDefT
|
||||
|
||||
def visit_ClassDefT(self, node):
|
||||
self.youngest_env[node.name] = self.youngest_region
|
||||
self.visit_in_region(node, Region(node.loc), node.constructor_type.attributes)
|
||||
|
||||
# Only three ways for a pointer to escape:
|
||||
# * Assigning or op-assigning it (we ensure an outlives relationship)
|
||||
# * Returning it (we only allow returning values that live forever)
|
||||
# * Raising it (we forbid allocating exceptions that refer to mutable data)¹
|
||||
#
|
||||
# Literals doesn't count: a constructed object is always
|
||||
# outlived by all its constituents.
|
||||
# Closures don't count: see above.
|
||||
# Calling functions doesn't count: arguments never outlive
|
||||
# the function body.
|
||||
#
|
||||
# ¹Strings are currently never allocated with a limited lifetime,
|
||||
# and exceptions can only refer to strings, so we don't actually check
|
||||
# this property. But we will need to, if string operations are ever added.
|
||||
|
||||
def visit_assignment(self, target, value):
|
||||
value_region = self._region_of(value)
|
||||
|
||||
# If we assign to an attribute of a quoted value, there will be no names
|
||||
# in the assignment lhs.
|
||||
target_names = self._names_of(target) or []
|
||||
|
||||
# Adopt the value region for any variables declared on the lhs.
|
||||
for name in target_names:
|
||||
region = self._region_of(name)
|
||||
if isinstance(region, Region) and not region.present():
|
||||
# Find the name's environment to overwrite the region.
|
||||
for env in self.env_stack[::-1]:
|
||||
if name.id in env:
|
||||
env[name.id] = value_region
|
||||
break
|
||||
|
||||
# The assigned value should outlive the assignee
|
||||
target_regions = [self._region_of(name) for name in target_names]
|
||||
for target_region in target_regions:
|
||||
if not Region.outlives(value_region, target_region):
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"the assigned value does not outlive the assignment target", {},
|
||||
value.loc, [target.loc],
|
||||
notes=self._diagnostics_for(target_region, target.loc,
|
||||
"the assignment target") +
|
||||
self._diagnostics_for(value_region, value.loc,
|
||||
"the assigned value"))
|
||||
self.engine.process(diag)
|
||||
|
||||
def visit_Assign(self, node):
|
||||
for target in node.targets:
|
||||
self.visit_assignment(target, node.value)
|
||||
|
||||
def visit_AugAssign(self, node):
|
||||
if builtins.is_list(node.target.type):
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"try using `{lhs} = {lhs} {op} {rhs}` instead",
|
||||
{"lhs": node.target.loc.source(),
|
||||
"rhs": node.value.loc.source(),
|
||||
"op": node.op.loc.source()[:-1]},
|
||||
node.loc)
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"lists cannot be mutated in-place", {},
|
||||
node.op.loc, [node.target.loc],
|
||||
notes=[note])
|
||||
self.engine.process(diag)
|
||||
|
||||
self.visit_assignment(node.target, node.value)
|
||||
|
||||
def visit_Return(self, node):
|
||||
region = self._region_of(node.value)
|
||||
if isinstance(region, Region):
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"this expression has type {type}",
|
||||
{"type": types.TypePrinter().name(node.value.type)},
|
||||
node.value.loc)
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"cannot return an allocated value that does not live forever", {},
|
||||
node.value.loc, notes=self._diagnostics_for(region, node.value.loc) + [note])
|
||||
self.engine.process(diag)
|
|
@ -1,187 +0,0 @@
|
|||
"""
|
||||
:class:`LocalAccessValidator` verifies that local variables
|
||||
are not accessed before being used.
|
||||
"""
|
||||
|
||||
from functools import reduce
|
||||
from pythonparser import diagnostic
|
||||
from .. import ir, analyses
|
||||
|
||||
def is_special_variable(name):
|
||||
return "$" in name
|
||||
|
||||
class LocalAccessValidator:
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
|
||||
def process(self, functions):
|
||||
for func in functions:
|
||||
self.process_function(func)
|
||||
|
||||
def process_function(self, func):
|
||||
# Find all environments and closures allocated in this func.
|
||||
environments, closures = [], []
|
||||
for insn in func.instructions():
|
||||
if isinstance(insn, ir.Alloc) and ir.is_environment(insn.type):
|
||||
environments.append(insn)
|
||||
elif isinstance(insn, ir.Closure):
|
||||
closures.append(insn)
|
||||
|
||||
# Compute initial state of interesting environments.
|
||||
# Environments consisting only of internal variables (containing a ".")
|
||||
# are ignored.
|
||||
initial_state = {}
|
||||
for env in environments:
|
||||
env_state = {var: False for var in env.type.params if "." not in var}
|
||||
if any(env_state):
|
||||
initial_state[env] = env_state
|
||||
|
||||
# Traverse the acyclic graph made of basic blocks and forward edges only,
|
||||
# while updating the environment state.
|
||||
domtree = analyses.DominatorTree(func)
|
||||
state = {}
|
||||
def traverse(block):
|
||||
# Have we computed the state of this block already?
|
||||
if block in state:
|
||||
return state[block]
|
||||
|
||||
# No! Which forward edges lead to this block?
|
||||
# If we dominate a predecessor, it's a back edge instead.
|
||||
forward_edge_preds = [pred for pred in block.predecessors()
|
||||
if block not in domtree.dominators(pred)]
|
||||
|
||||
# Figure out what the state is before the leader
|
||||
# instruction of this block.
|
||||
pred_states = [traverse(pred) for pred in forward_edge_preds]
|
||||
block_state = {}
|
||||
if len(pred_states) > 1:
|
||||
for env in initial_state:
|
||||
# The variable has to be initialized in all predecessors
|
||||
# in order to be initialized in this block.
|
||||
def merge_state(a, b):
|
||||
return {var: a[var] and b[var] for var in a}
|
||||
block_state[env] = reduce(merge_state,
|
||||
[state[env] for state in pred_states])
|
||||
elif len(pred_states) == 1:
|
||||
# The state is the same as at the terminator of predecessor.
|
||||
# We'll mutate it, so copy.
|
||||
pred_state = pred_states[0]
|
||||
for env in initial_state:
|
||||
env_state = pred_state[env]
|
||||
block_state[env] = {var: env_state[var] for var in env_state}
|
||||
else:
|
||||
# This is the entry block.
|
||||
for env in initial_state:
|
||||
env_state = initial_state[env]
|
||||
block_state[env] = {var: env_state[var] for var in env_state}
|
||||
|
||||
# Update the state based on block contents, while validating
|
||||
# that no access to uninitialized variables will be done.
|
||||
for insn in block.instructions:
|
||||
def pred_at_fault(env, var_name):
|
||||
# Find out where the uninitialized state comes from.
|
||||
for pred, pred_state in zip(forward_edge_preds, pred_states):
|
||||
if not pred_state[env][var_name]:
|
||||
return pred
|
||||
|
||||
# It's the entry block and it was never initialized.
|
||||
return None
|
||||
|
||||
set_local_in_this_frame = False
|
||||
if (isinstance(insn, (ir.SetLocal, ir.GetLocal)) and
|
||||
not is_special_variable(insn.var_name)):
|
||||
env, var_name = insn.environment(), insn.var_name
|
||||
|
||||
# Make sure that the variable is defined in the scope of this function.
|
||||
if env in block_state and var_name in block_state[env]:
|
||||
if isinstance(insn, ir.SetLocal):
|
||||
# We've just initialized it.
|
||||
block_state[env][var_name] = True
|
||||
set_local_in_this_frame = True
|
||||
else: # isinstance(insn, ir.GetLocal)
|
||||
if not block_state[env][var_name]:
|
||||
# Oops, accessing it uninitialized.
|
||||
self._uninitialized_access(insn, var_name,
|
||||
pred_at_fault(env, var_name))
|
||||
|
||||
closures_to_check = []
|
||||
|
||||
if (isinstance(insn, (ir.SetLocal, ir.SetAttr, ir.SetElem)) and
|
||||
not set_local_in_this_frame):
|
||||
# Closures may escape via these mechanisms and be invoked elsewhere.
|
||||
if isinstance(insn.value(), ir.Closure):
|
||||
closures_to_check.append(insn.value())
|
||||
|
||||
if isinstance(insn, (ir.Call, ir.Invoke)):
|
||||
# We can't always trace the flow of closures from point of
|
||||
# definition to point of call; however, we know that, by transitiveness
|
||||
# of this analysis, only closures defined in this function can contain
|
||||
# uninitialized variables.
|
||||
#
|
||||
# Thus, enumerate the closures, and check all of them during any operation
|
||||
# that may eventually result in the closure being called.
|
||||
closures_to_check = closures
|
||||
|
||||
for closure in closures_to_check:
|
||||
env = closure.environment()
|
||||
# Make sure this environment has any interesting variables.
|
||||
if env in block_state:
|
||||
for var_name in block_state[env]:
|
||||
if not block_state[env][var_name] and not is_special_variable(var_name):
|
||||
# A closure would capture this variable while it is not always
|
||||
# initialized. Note that this check is transitive.
|
||||
self._uninitialized_access(closure, var_name,
|
||||
pred_at_fault(env, var_name))
|
||||
|
||||
# Save the state.
|
||||
state[block] = block_state
|
||||
|
||||
return block_state
|
||||
|
||||
for block in func.basic_blocks:
|
||||
traverse(block)
|
||||
|
||||
def _uninitialized_access(self, insn, var_name, pred_at_fault):
|
||||
if pred_at_fault is not None:
|
||||
visited = set()
|
||||
possible_preds = [pred_at_fault]
|
||||
|
||||
uninitialized_loc = None
|
||||
while uninitialized_loc is None:
|
||||
possible_pred = possible_preds.pop(0)
|
||||
visited.add(possible_pred)
|
||||
|
||||
for pred_insn in reversed(possible_pred.instructions):
|
||||
if pred_insn.loc is not None:
|
||||
uninitialized_loc = pred_insn.loc.begin()
|
||||
break
|
||||
|
||||
for block in possible_pred.predecessors():
|
||||
if block not in visited:
|
||||
possible_preds.append(block)
|
||||
|
||||
assert uninitialized_loc is not None
|
||||
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"variable is not initialized when control flows from this point", {},
|
||||
uninitialized_loc)
|
||||
else:
|
||||
note = None
|
||||
|
||||
if note is not None:
|
||||
notes = [note]
|
||||
else:
|
||||
notes = []
|
||||
|
||||
if isinstance(insn, ir.Closure):
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"variable '{name}' can be captured in a closure uninitialized here",
|
||||
{"name": var_name},
|
||||
insn.loc, notes=notes)
|
||||
else:
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"variable '{name}' is not always initialized here",
|
||||
{"name": var_name},
|
||||
insn.loc, notes=notes)
|
||||
|
||||
self.engine.process(diag)
|
|
@ -1,41 +0,0 @@
|
|||
"""
|
||||
:class:`MonomorphismValidator` verifies that all type variables have been
|
||||
elided, which is necessary for code generation.
|
||||
"""
|
||||
|
||||
from pythonparser import algorithm, diagnostic
|
||||
from .. import asttyped, types, builtins
|
||||
|
||||
class MonomorphismValidator(algorithm.Visitor):
|
||||
def __init__(self, engine):
|
||||
self.engine = engine
|
||||
|
||||
def visit_FunctionDefT(self, node):
|
||||
super().generic_visit(node)
|
||||
|
||||
return_type = node.signature_type.find().ret
|
||||
if types.is_polymorphic(return_type):
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"the function has return type {type}",
|
||||
{"type": types.TypePrinter().name(return_type)},
|
||||
node.name_loc)
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"the return type of this function cannot be fully inferred", {},
|
||||
node.name_loc, notes=[note])
|
||||
self.engine.process(diag)
|
||||
|
||||
visit_QuotedFunctionDefT = visit_FunctionDefT
|
||||
|
||||
def generic_visit(self, node):
|
||||
super().generic_visit(node)
|
||||
|
||||
if isinstance(node, asttyped.commontyped):
|
||||
if types.is_polymorphic(node.type):
|
||||
note = diagnostic.Diagnostic("note",
|
||||
"the expression has type {type}",
|
||||
{"type": types.TypePrinter().name(node.type)},
|
||||
node.loc)
|
||||
diag = diagnostic.Diagnostic("error",
|
||||
"the type of this expression cannot be fully inferred", {},
|
||||
node.loc, notes=[note])
|
||||
self.engine.process(diag)
|
|
@ -8,17 +8,19 @@ time is an error.
|
|||
# Designed from the data sheets and somewhat after the linux kernel
|
||||
# iio driver.
|
||||
|
||||
from numpy import int32
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import (kernel, portable, delay_mu, delay, now_mu,
|
||||
at_mu)
|
||||
from artiq.language.core import *
|
||||
from artiq.language.units import ns, us
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.ttl import TTLOut
|
||||
from artiq.coredevice.spi2 import *
|
||||
|
||||
SPI_AD53XX_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
|
||||
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
|
||||
0*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE |
|
||||
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
|
||||
|
||||
SPI_AD53XX_CONFIG = (0*SPI_OFFLINE | 1*SPI_END |
|
||||
0*SPI_INPUT | 0*SPI_CS_POLARITY |
|
||||
0*SPI_CLK_POLARITY | 1*SPI_CLK_PHASE |
|
||||
0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
|
||||
|
||||
AD53XX_CMD_DATA = 3 << 22
|
||||
AD53XX_CMD_OFFSET = 2 << 22
|
||||
|
@ -52,7 +54,7 @@ AD53XX_READ_AB3 = 0x109 << 7
|
|||
|
||||
|
||||
@portable
|
||||
def ad53xx_cmd_write_ch(channel, value, op):
|
||||
def ad53xx_cmd_write_ch(channel: int32, value: int32, op: int32) -> int32:
|
||||
"""Returns the word that must be written to the DAC to set a DAC
|
||||
channel register to a given value.
|
||||
|
||||
|
@ -67,7 +69,7 @@ def ad53xx_cmd_write_ch(channel, value, op):
|
|||
|
||||
|
||||
@portable
|
||||
def ad53xx_cmd_read_ch(channel, op):
|
||||
def ad53xx_cmd_read_ch(channel: int32, op: int32) -> int32:
|
||||
"""Returns the word that must be written to the DAC to read a given
|
||||
DAC channel register.
|
||||
|
||||
|
@ -82,7 +84,7 @@ def ad53xx_cmd_read_ch(channel, op):
|
|||
|
||||
# maintain function definition for backward compatibility
|
||||
@portable
|
||||
def voltage_to_mu(voltage, offset_dacs=0x2000, vref=5.):
|
||||
def voltage_to_mu(voltage: float, offset_dacs: int32 = 0x2000, vref: float = 5.) -> int32:
|
||||
"""Returns the 16-bit DAC register value required to produce a given output
|
||||
voltage, assuming offset and gain errors have been trimmed out.
|
||||
|
||||
|
@ -100,22 +102,24 @@ def voltage_to_mu(voltage, offset_dacs=0x2000, vref=5.):
|
|||
:param vref: DAC reference voltage (default: 5.)
|
||||
:return: The 16-bit DAC register value
|
||||
"""
|
||||
code = int(round((1 << 16) * (voltage / (4. * vref)) + offset_dacs * 0x4))
|
||||
code = round(float(1 << 16) * (voltage / (4. * vref))) + offset_dacs * 0x4
|
||||
if code < 0x0 or code > 0xffff:
|
||||
raise ValueError("Invalid DAC voltage!")
|
||||
return code
|
||||
|
||||
|
||||
@nac3
|
||||
class _DummyTTL:
|
||||
@portable
|
||||
@kernel
|
||||
def on(self):
|
||||
pass
|
||||
|
||||
@portable
|
||||
@kernel
|
||||
def off(self):
|
||||
pass
|
||||
|
||||
|
||||
@nac3
|
||||
class AD53xx:
|
||||
"""Analog devices AD53[67][0123] family of multi-channel Digital to Analog
|
||||
Converters.
|
||||
|
@ -137,8 +141,15 @@ class AD53xx:
|
|||
experiments. (default: 8192)
|
||||
:param core_device: Core device name (default: "core")
|
||||
"""
|
||||
kernel_invariants = {"bus", "ldac", "clr", "chip_select", "div_write",
|
||||
"div_read", "vref", "core"}
|
||||
core: KernelInvariant[Core]
|
||||
bus: KernelInvariant[SPIMaster]
|
||||
ldac: KernelInvariant[TTLOut]
|
||||
clr: KernelInvariant[TTLOut]
|
||||
chip_select: KernelInvariant[int32]
|
||||
div_write: KernelInvariant[int32]
|
||||
div_read: KernelInvariant[int32]
|
||||
vref: KernelInvariant[float]
|
||||
offset_dacs: Kernel[int32]
|
||||
|
||||
def __init__(self, dmgr, spi_device, ldac_device=None, clr_device=None,
|
||||
chip_select=1, div_write=4, div_read=16, vref=5.,
|
||||
|
@ -161,7 +172,7 @@ class AD53xx:
|
|||
self.core = dmgr.get(core)
|
||||
|
||||
@kernel
|
||||
def init(self, blind=False):
|
||||
def init(self, blind: bool = False):
|
||||
"""Configures the SPI bus, drives LDAC and CLR high, programmes
|
||||
the offset DACs, and enables overtemperature shutdown.
|
||||
|
||||
|
@ -177,22 +188,22 @@ class AD53xx:
|
|||
self.chip_select)
|
||||
self.write_offset_dacs_mu(self.offset_dacs)
|
||||
if not blind:
|
||||
ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL)
|
||||
ctrl = self.read_reg(0, AD53XX_READ_CONTROL)
|
||||
if ctrl == 0xffff:
|
||||
raise ValueError("DAC not found")
|
||||
if ctrl & 0b10000:
|
||||
if (ctrl & 0b10000) != 0:
|
||||
raise ValueError("DAC over temperature")
|
||||
delay(25*us)
|
||||
self.core.delay(25.*us)
|
||||
self.bus.write( # enable power and overtemperature shutdown
|
||||
(AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_CONTROL | 0b0010) << 8)
|
||||
if not blind:
|
||||
ctrl = self.read_reg(channel=0, op=AD53XX_READ_CONTROL)
|
||||
ctrl = self.read_reg(0, AD53XX_READ_CONTROL)
|
||||
if (ctrl & 0b10111) != 0b00010:
|
||||
raise ValueError("DAC CONTROL readback mismatch")
|
||||
delay(15*us)
|
||||
self.core.delay(15.*us)
|
||||
|
||||
@kernel
|
||||
def read_reg(self, channel=0, op=AD53XX_READ_X1A):
|
||||
def read_reg(self, channel: int32 = 0, op: int32 = AD53XX_READ_X1A) -> int32:
|
||||
"""Read a DAC register.
|
||||
|
||||
This method advances the timeline by the duration of two SPI transfers
|
||||
|
@ -205,17 +216,16 @@ class AD53xx:
|
|||
:return: The 16 bit register value
|
||||
"""
|
||||
self.bus.write(ad53xx_cmd_read_ch(channel, op) << 8)
|
||||
self.bus.set_config_mu(SPI_AD53XX_CONFIG | spi.SPI_INPUT, 24,
|
||||
self.bus.set_config_mu(SPI_AD53XX_CONFIG | SPI_INPUT, 24,
|
||||
self.div_read, self.chip_select)
|
||||
delay(270*ns) # t_21 min sync high in readback
|
||||
self.core.delay(270.*ns) # t_21 min sync high in readback
|
||||
self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_NOP) << 8)
|
||||
self.bus.set_config_mu(SPI_AD53XX_CONFIG, 24, self.div_write,
|
||||
self.chip_select)
|
||||
# FIXME: the int32 should not be needed to resolve unification
|
||||
return self.bus.read() & int32(0xffff)
|
||||
return self.bus.read() & 0xffff
|
||||
|
||||
@kernel
|
||||
def write_offset_dacs_mu(self, value):
|
||||
def write_offset_dacs_mu(self, value: int32):
|
||||
"""Program the OFS0 and OFS1 offset DAC registers.
|
||||
|
||||
Writes to the offset DACs take effect immediately without requiring
|
||||
|
@ -230,10 +240,10 @@ class AD53xx:
|
|||
self.bus.write((AD53XX_CMD_SPECIAL | AD53XX_SPECIAL_OFS1 | value) << 8)
|
||||
|
||||
@kernel
|
||||
def write_gain_mu(self, channel, gain=0xffff):
|
||||
def write_gain_mu(self, channel: int32, gain: int32 = 0xffff):
|
||||
"""Program the gain register for a DAC channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
This method advances the timeline by the duration of one SPI transfer.
|
||||
|
||||
:param gain: 16-bit gain register value (default: 0xffff)
|
||||
|
@ -242,10 +252,10 @@ class AD53xx:
|
|||
ad53xx_cmd_write_ch(channel, gain, AD53XX_CMD_GAIN) << 8)
|
||||
|
||||
@kernel
|
||||
def write_offset_mu(self, channel, offset=0x8000):
|
||||
def write_offset_mu(self, channel: int32, offset: int32 = 0x8000):
|
||||
"""Program the offset register for a DAC channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
This method advances the timeline by the duration of one SPI transfer.
|
||||
|
||||
:param offset: 16-bit offset register value (default: 0x8000)
|
||||
|
@ -254,11 +264,11 @@ class AD53xx:
|
|||
ad53xx_cmd_write_ch(channel, offset, AD53XX_CMD_OFFSET) << 8)
|
||||
|
||||
@kernel
|
||||
def write_offset(self, channel, voltage):
|
||||
def write_offset(self, channel: int32, voltage: float):
|
||||
"""Program the DAC offset voltage for a channel.
|
||||
|
||||
An offset of +V can be used to trim out a DAC offset error of -V.
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
This method advances the timeline by the duration of one SPI transfer.
|
||||
|
||||
:param voltage: the offset voltage
|
||||
|
@ -267,20 +277,20 @@ class AD53xx:
|
|||
self.vref))
|
||||
|
||||
@kernel
|
||||
def write_dac_mu(self, channel, value):
|
||||
def write_dac_mu(self, channel: int32, value: int32):
|
||||
"""Program the DAC input register for a channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
This method advances the timeline by the duration of one SPI transfer.
|
||||
"""
|
||||
self.bus.write(
|
||||
ad53xx_cmd_write_ch(channel, value, AD53XX_CMD_DATA) << 8)
|
||||
|
||||
@kernel
|
||||
def write_dac(self, channel, voltage):
|
||||
def write_dac(self, channel: int32, voltage: float):
|
||||
"""Program the DAC output voltage for a channel.
|
||||
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth:`load`).
|
||||
The DAC output is not updated until LDAC is pulsed (see :meth load:).
|
||||
This method advances the timeline by the duration of one SPI transfer.
|
||||
"""
|
||||
self.write_dac_mu(channel, voltage_to_mu(voltage, self.offset_dacs,
|
||||
|
@ -299,11 +309,11 @@ class AD53xx:
|
|||
This method advances the timeline by two RTIO clock periods.
|
||||
"""
|
||||
self.ldac.off()
|
||||
delay_mu(2*self.bus.ref_period_mu) # t13 = 10ns ldac pulse width low
|
||||
delay_mu(int64(2)*self.bus.ref_period_mu) # t13 = 10ns ldac pulse width low
|
||||
self.ldac.on()
|
||||
|
||||
@kernel
|
||||
def set_dac_mu(self, values, channels=list(range(40))):
|
||||
def set_dac_mu(self, values: list[int32], channels: Option[list[int32]] = none):
|
||||
"""Program multiple DAC channels and pulse LDAC to update the DAC
|
||||
outputs.
|
||||
|
||||
|
@ -313,7 +323,7 @@ class AD53xx:
|
|||
|
||||
If no LDAC device was defined, the LDAC pulse is skipped.
|
||||
|
||||
See :meth:`load`.
|
||||
See :meth load:.
|
||||
|
||||
:param values: list of DAC values to program
|
||||
:param channels: list of DAC channels to program. If not specified,
|
||||
|
@ -322,17 +332,18 @@ class AD53xx:
|
|||
t0 = now_mu()
|
||||
|
||||
# t10: max busy period after writing to DAC registers
|
||||
t_10 = self.core.seconds_to_mu(1500*ns)
|
||||
t_10 = self.core.seconds_to_mu(1500.*ns)
|
||||
# compensate all delays that will be applied
|
||||
delay_mu(-t_10-len(values)*self.bus.xfer_duration_mu)
|
||||
delay_mu(-t_10-int64(len(values))*self.bus.xfer_duration_mu)
|
||||
channels_list = channels.unwrap() if channels.is_some() else [i for i in range(40)]
|
||||
for i in range(len(values)):
|
||||
self.write_dac_mu(channels[i], values[i])
|
||||
self.write_dac_mu(channels_list[i], values[i])
|
||||
delay_mu(t_10)
|
||||
self.load()
|
||||
at_mu(t0)
|
||||
|
||||
@kernel
|
||||
def set_dac(self, voltages, channels=list(range(40))):
|
||||
def set_dac(self, voltages: list[float], channels: Option[list[int32]] = none):
|
||||
"""Program multiple DAC channels and pulse LDAC to update the DAC
|
||||
outputs.
|
||||
|
||||
|
@ -351,11 +362,11 @@ class AD53xx:
|
|||
self.set_dac_mu(values, channels)
|
||||
|
||||
@kernel
|
||||
def calibrate(self, channel, vzs, vfs):
|
||||
""" Two-point calibration of a DAC channel.
|
||||
def calibrate(self, channel: int32, vzs: float, vfs: float):
|
||||
"""Two-point calibration of a DAC channel.
|
||||
|
||||
Programs the offset and gain register to trim out DAC errors. Does not
|
||||
take effect until LDAC is pulsed (see :meth:`load`).
|
||||
take effect until LDAC is pulsed (see :meth load:).
|
||||
|
||||
Calibration consists of measuring the DAC output voltage for a channel
|
||||
with the DAC set to zero-scale (0x0000) and full-scale (0xffff).
|
||||
|
@ -379,7 +390,7 @@ class AD53xx:
|
|||
self.write_gain_mu(channel, 0xffff-gain_err)
|
||||
|
||||
@portable
|
||||
def voltage_to_mu(self, voltage):
|
||||
def voltage_to_mu(self, voltage: float) -> int32:
|
||||
"""Returns the 16-bit DAC register value required to produce a given
|
||||
output voltage, assuming offset and gain errors have been trimmed out.
|
||||
|
||||
|
|
|
@ -1,17 +1,13 @@
|
|||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import (
|
||||
kernel, delay, portable, delay_mu, now_mu, at_mu)
|
||||
from artiq.language.core import *
|
||||
from artiq.language.units import us, ms
|
||||
from artiq.language.types import TBool, TInt32, TInt64, TFloat, TList, TTuple
|
||||
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from artiq.coredevice import urukul
|
||||
from artiq.coredevice.urukul import DEFAULT_PROFILE
|
||||
from artiq.coredevice.spi2 import *
|
||||
from artiq.coredevice.urukul import *
|
||||
from artiq.coredevice.ttl import TTLOut
|
||||
from artiq.coredevice.kasli_i2c import KasliEEPROM # NAC3TODO
|
||||
|
||||
# Work around ARTIQ-Python import machinery
|
||||
urukul_sta_pll_lock = urukul.urukul_sta_pll_lock
|
||||
urukul_sta_smp_err = urukul.urukul_sta_smp_err
|
||||
|
||||
__all__ = [
|
||||
"AD9910",
|
||||
|
@ -64,8 +60,12 @@ RAM_MODE_CONT_RAMPUP = 4
|
|||
# Default profile for RAM mode
|
||||
_DEFAULT_PROFILE_RAM = 0
|
||||
|
||||
|
||||
@nac3
|
||||
class SyncDataUser:
|
||||
core: KernelInvariant[Core]
|
||||
sync_delay_seed: Kernel[int32]
|
||||
io_update_delay: Kernel[int32]
|
||||
|
||||
def __init__(self, core, sync_delay_seed, io_update_delay):
|
||||
self.core = core
|
||||
self.sync_delay_seed = sync_delay_seed
|
||||
|
@ -76,7 +76,14 @@ class SyncDataUser:
|
|||
pass
|
||||
|
||||
|
||||
@nac3
|
||||
class SyncDataEeprom:
|
||||
core: KernelInvariant[Core]
|
||||
eeprom_device: KernelInvariant[KasliEEPROM] # NAC3TODO support generic EEPROM driver
|
||||
eeprom_offset: KernelInvariant[int32]
|
||||
sync_delay_seed: Kernel[int32]
|
||||
io_update_delay: Kernel[int32]
|
||||
|
||||
def __init__(self, dmgr, core, eeprom_str):
|
||||
self.core = core
|
||||
|
||||
|
@ -102,6 +109,7 @@ class SyncDataEeprom:
|
|||
self.io_update_delay = int32(io_update_delay)
|
||||
|
||||
|
||||
@nac3
|
||||
class AD9910:
|
||||
"""
|
||||
AD9910 DDS channel on Urukul.
|
||||
|
@ -119,7 +127,7 @@ class AD9910:
|
|||
f_ref/clk_div*pll_n where f_ref is the reference frequency and
|
||||
clk_div is the reference clock divider (both set in the parent
|
||||
Urukul CPLD instance).
|
||||
:param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1).
|
||||
:param pll_en: PLL enable bit, set to False to bypass PLL (default: True).
|
||||
Note that when bypassing the PLL the red front panel LED may remain on.
|
||||
:param pll_cp: DDS PLL charge pump setting.
|
||||
:param pll_vco: DDS PLL VCO range selection.
|
||||
|
@ -138,9 +146,24 @@ class AD9910:
|
|||
to the same string value.
|
||||
"""
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
cpld: KernelInvariant[CPLD]
|
||||
bus: KernelInvariant[SPIMaster]
|
||||
chip_select: KernelInvariant[int32]
|
||||
pll_en: KernelInvariant[bool]
|
||||
pll_n: KernelInvariant[int32]
|
||||
pll_vco: KernelInvariant[int32]
|
||||
pll_cp: KernelInvariant[int32]
|
||||
ftw_per_hz: KernelInvariant[float]
|
||||
sysclk_per_mu: KernelInvariant[int32]
|
||||
sysclk: KernelInvariant[float]
|
||||
sw: KernelInvariant[Option[TTLOut]]
|
||||
sync_data: KernelInvariant[SyncDataUser]
|
||||
phase_mode: Kernel[int32]
|
||||
|
||||
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
|
||||
pll_n=40, pll_cp=7, pll_vco=5, sync_delay_seed=-1,
|
||||
io_update_delay=0, pll_en=1):
|
||||
io_update_delay=0, pll_en=True):
|
||||
self.kernel_invariants = {"cpld", "core", "bus", "chip_select",
|
||||
"pll_en", "pll_n", "pll_vco", "pll_cp",
|
||||
"ftw_per_hz", "sysclk_per_mu", "sysclk",
|
||||
|
@ -151,8 +174,9 @@ class AD9910:
|
|||
assert 3 <= chip_select <= 7
|
||||
self.chip_select = chip_select
|
||||
if sw_device:
|
||||
self.sw = dmgr.get(sw_device)
|
||||
self.kernel_invariants.add("sw")
|
||||
self.sw = Some(dmgr.get(sw_device))
|
||||
else:
|
||||
self.sw = none
|
||||
clk = self.cpld.refclk / [4, 1, 2, 4][self.cpld.clk_div]
|
||||
self.pll_en = pll_en
|
||||
self.pll_n = pll_n
|
||||
|
@ -174,6 +198,7 @@ class AD9910:
|
|||
self.sysclk_per_mu = int(round(sysclk * self.core.ref_period))
|
||||
self.sysclk = sysclk
|
||||
|
||||
# NAC3TODO
|
||||
if isinstance(sync_delay_seed, str) or isinstance(io_update_delay,
|
||||
str):
|
||||
if sync_delay_seed != io_update_delay:
|
||||
|
@ -187,7 +212,7 @@ class AD9910:
|
|||
self.phase_mode = PHASE_MODE_CONTINUOUS
|
||||
|
||||
@kernel
|
||||
def set_phase_mode(self, phase_mode: TInt32):
|
||||
def set_phase_mode(self, phase_mode: int32):
|
||||
r"""Set the default phase mode.
|
||||
|
||||
for future calls to :meth:`set` and
|
||||
|
@ -232,103 +257,103 @@ class AD9910:
|
|||
self.phase_mode = phase_mode
|
||||
|
||||
@kernel
|
||||
def write16(self, addr: TInt32, data: TInt32):
|
||||
def write16(self, addr: int32, data: int32):
|
||||
"""Write to 16 bit register.
|
||||
|
||||
:param addr: Register address
|
||||
:param data: Data to be written
|
||||
"""
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 24,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write((addr << 24) | ((data & 0xffff) << 8))
|
||||
|
||||
@kernel
|
||||
def write32(self, addr: TInt32, data: TInt32):
|
||||
def write32(self, addr: int32, data: int32):
|
||||
"""Write to 32 bit register.
|
||||
|
||||
:param addr: Register address
|
||||
:param data: Data to be written
|
||||
"""
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG, 8,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write(addr << 24)
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write(data)
|
||||
|
||||
@kernel
|
||||
def read16(self, addr: TInt32) -> TInt32:
|
||||
def read16(self, addr: int32) -> int32:
|
||||
"""Read from 16 bit register.
|
||||
|
||||
:param addr: Register address
|
||||
"""
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG, 8,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write((addr | 0x80) << 24)
|
||||
self.bus.set_config_mu(
|
||||
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||
16, urukul.SPIT_DDS_RD, self.chip_select)
|
||||
SPI_CONFIG | SPI_END | SPI_INPUT,
|
||||
16, SPIT_DDS_RD, self.chip_select)
|
||||
self.bus.write(0)
|
||||
return self.bus.read()
|
||||
|
||||
@kernel
|
||||
def read32(self, addr: TInt32) -> TInt32:
|
||||
def read32(self, addr: int32) -> int32:
|
||||
"""Read from 32 bit register.
|
||||
|
||||
:param addr: Register address
|
||||
"""
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG, 8,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write((addr | 0x80) << 24)
|
||||
self.bus.set_config_mu(
|
||||
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||
32, urukul.SPIT_DDS_RD, self.chip_select)
|
||||
SPI_CONFIG | SPI_END | SPI_INPUT,
|
||||
32, SPIT_DDS_RD, self.chip_select)
|
||||
self.bus.write(0)
|
||||
return self.bus.read()
|
||||
|
||||
@kernel
|
||||
def read64(self, addr: TInt32) -> TInt64:
|
||||
def read64(self, addr: int32) -> int64:
|
||||
"""Read from 64 bit register.
|
||||
|
||||
:param addr: Register address
|
||||
:return: 64 bit integer register value
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
urukul.SPI_CONFIG, 8,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
SPI_CONFIG, 8,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write((addr | 0x80) << 24)
|
||||
self.bus.set_config_mu(
|
||||
urukul.SPI_CONFIG | spi.SPI_INPUT, 32,
|
||||
urukul.SPIT_DDS_RD, self.chip_select)
|
||||
SPI_CONFIG | SPI_INPUT, 32,
|
||||
SPIT_DDS_RD, self.chip_select)
|
||||
self.bus.write(0)
|
||||
self.bus.set_config_mu(
|
||||
urukul.SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 32,
|
||||
urukul.SPIT_DDS_RD, self.chip_select)
|
||||
SPI_CONFIG | SPI_END | SPI_INPUT, 32,
|
||||
SPIT_DDS_RD, self.chip_select)
|
||||
self.bus.write(0)
|
||||
hi = self.bus.read()
|
||||
lo = self.bus.read()
|
||||
return (int64(hi) << 32) | lo
|
||||
return (int64(hi) << 32) | int64(lo)
|
||||
|
||||
@kernel
|
||||
def write64(self, addr: TInt32, data_high: TInt32, data_low: TInt32):
|
||||
def write64(self, addr: int32, data_high: int32, data_low: int32):
|
||||
"""Write to 64 bit register.
|
||||
|
||||
:param addr: Register address
|
||||
:param data_high: High (MSB) 32 bits of the data
|
||||
:param data_low: Low (LSB) 32 data bits
|
||||
"""
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 8,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG, 8,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write(addr << 24)
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 32,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG, 32,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write(data_high)
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write(data_low)
|
||||
|
||||
@kernel
|
||||
def write_ram(self, data: TList(TInt32)):
|
||||
def write_ram(self, data: list[int32]):
|
||||
"""Write data to RAM.
|
||||
|
||||
The profile to write to and the step, start, and end address
|
||||
|
@ -337,19 +362,19 @@ class AD9910:
|
|||
|
||||
:param data: Data to be written to RAM.
|
||||
"""
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR,
|
||||
self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_DDS_WR,
|
||||
self.chip_select)
|
||||
self.bus.write(_AD9910_REG_RAM << 24)
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 32,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG, 32,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
for i in range(len(data) - 1):
|
||||
self.bus.write(data[i])
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write(data[len(data) - 1])
|
||||
|
||||
@kernel
|
||||
def read_ram(self, data: TList(TInt32)):
|
||||
def read_ram(self, data: list[int32]):
|
||||
"""Read data from RAM.
|
||||
|
||||
The profile to read from and the step, start, and end address
|
||||
|
@ -358,38 +383,38 @@ class AD9910:
|
|||
|
||||
:param data: List to be filled with data read from RAM.
|
||||
"""
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 8, urukul.SPIT_DDS_WR,
|
||||
self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_DDS_WR,
|
||||
self.chip_select)
|
||||
self.bus.write((_AD9910_REG_RAM | 0x80) << 24)
|
||||
n = len(data) - 1
|
||||
if n > 0:
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_INPUT, 32,
|
||||
urukul.SPIT_DDS_RD, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_INPUT, 32,
|
||||
SPIT_DDS_RD, self.chip_select)
|
||||
preload = min(n, 8)
|
||||
for i in range(n):
|
||||
self.bus.write(0)
|
||||
if i >= preload:
|
||||
data[i - preload] = self.bus.read()
|
||||
self.bus.set_config_mu(
|
||||
urukul.SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 32,
|
||||
urukul.SPIT_DDS_RD, self.chip_select)
|
||||
SPI_CONFIG | SPI_INPUT | SPI_END, 32,
|
||||
SPIT_DDS_RD, self.chip_select)
|
||||
self.bus.write(0)
|
||||
for i in range(preload + 1):
|
||||
data[(n - preload) + i] = self.bus.read()
|
||||
|
||||
@kernel
|
||||
def set_cfr1(self,
|
||||
power_down: TInt32 = 0b0000,
|
||||
phase_autoclear: TInt32 = 0,
|
||||
drg_load_lrr: TInt32 = 0,
|
||||
drg_autoclear: TInt32 = 0,
|
||||
phase_clear: 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):
|
||||
power_down: int32 = 0b0000,
|
||||
phase_autoclear: int32 = 0,
|
||||
drg_load_lrr: int32 = 0,
|
||||
drg_autoclear: int32 = 0,
|
||||
phase_clear: int32 = 0,
|
||||
internal_profile: int32 = 0,
|
||||
ram_destination: int32 = 0,
|
||||
ram_enable: int32 = 0,
|
||||
manual_osk_external: int32 = 0,
|
||||
osk_enable: int32 = 0,
|
||||
select_auto_osk: int32 = 0):
|
||||
"""Set CFR1. See the AD9910 datasheet for parameter meanings.
|
||||
|
||||
This method does not pulse IO_UPDATE.
|
||||
|
@ -424,11 +449,11 @@ class AD9910:
|
|||
|
||||
@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):
|
||||
asf_profile_enable: int32 = 1,
|
||||
drg_enable: int32 = 0,
|
||||
effective_ftw: int32 = 1,
|
||||
sync_validation_disable: int32 = 0,
|
||||
matched_latency_enable: int32 = 0):
|
||||
"""Set CFR2. See the AD9910 datasheet for parameter meanings.
|
||||
|
||||
This method does not pulse IO_UPDATE.
|
||||
|
@ -452,7 +477,7 @@ class AD9910:
|
|||
(sync_validation_disable << 5))
|
||||
|
||||
@kernel
|
||||
def init(self, blind: TBool = False):
|
||||
def init(self, blind: bool = False):
|
||||
"""Initialize and configure the DDS.
|
||||
|
||||
Sets up SPI mode, confirms chip presence, powers down unused blocks,
|
||||
|
@ -465,66 +490,66 @@ class AD9910:
|
|||
if self.sync_data.sync_delay_seed >= 0 and not self.cpld.sync_div:
|
||||
raise ValueError("parent cpld does not drive SYNC")
|
||||
if self.sync_data.sync_delay_seed >= 0:
|
||||
if self.sysclk_per_mu != self.sysclk * self.core.ref_period:
|
||||
if float(self.sysclk_per_mu) != self.sysclk * self.core.ref_period:
|
||||
raise ValueError("incorrect clock ratio for synchronization")
|
||||
delay(50 * ms) # slack
|
||||
self.core.delay(50. * ms) # slack
|
||||
|
||||
# Set SPI mode
|
||||
self.set_cfr1()
|
||||
self.cpld.io_update.pulse(1 * us)
|
||||
delay(1 * ms)
|
||||
self.cpld.io_update.pulse(1. * us)
|
||||
self.core.delay(1. * ms)
|
||||
if not blind:
|
||||
# Use the AUX DAC setting to identify and confirm presence
|
||||
aux_dac = self.read32(_AD9910_REG_AUX_DAC)
|
||||
if aux_dac & 0xff != 0x7f:
|
||||
raise ValueError("Urukul AD9910 AUX_DAC mismatch")
|
||||
delay(50 * us) # slack
|
||||
self.core.delay(50. * us) # slack
|
||||
# Configure PLL settings and bring up PLL
|
||||
# enable amplitude scale from profiles
|
||||
# read effective FTW
|
||||
# sync timing validation disable (enabled later)
|
||||
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) |
|
||||
(self.pll_cp << 19) | (self.pll_en << 8) |
|
||||
(self.pll_n << 1))
|
||||
(self.pll_cp << 19) | (int32(self.pll_en) << 8) |
|
||||
(int32(self.pll_n) << 1))
|
||||
self.write32(_AD9910_REG_CFR3, cfr3 | 0x400) # PFD reset
|
||||
self.cpld.io_update.pulse(1 * us)
|
||||
self.cpld.io_update.pulse(1. * us)
|
||||
if self.pll_en:
|
||||
self.write32(_AD9910_REG_CFR3, cfr3)
|
||||
self.cpld.io_update.pulse(1 * us)
|
||||
self.cpld.io_update.pulse(1. * us)
|
||||
if blind:
|
||||
delay(100 * ms)
|
||||
self.core.delay(100. * ms)
|
||||
else:
|
||||
# Wait for PLL lock, up to 100 ms
|
||||
for i in range(100):
|
||||
sta = self.cpld.sta_read()
|
||||
lock = urukul_sta_pll_lock(sta)
|
||||
delay(1 * ms)
|
||||
if lock & (1 << self.chip_select - 4):
|
||||
self.core.delay(1. * ms)
|
||||
if lock & (1 << self.chip_select - 4) != 0:
|
||||
break
|
||||
if i >= 100 - 1:
|
||||
raise ValueError("PLL lock timeout")
|
||||
delay(10 * us) # slack
|
||||
self.core.delay(10. * us) # slack
|
||||
if self.sync_data.sync_delay_seed >= 0 and not blind:
|
||||
self.tune_sync_delay(self.sync_data.sync_delay_seed)
|
||||
delay(1 * ms)
|
||||
self.core.delay(1. * ms)
|
||||
|
||||
@kernel
|
||||
def power_down(self, bits: TInt32 = 0b1111):
|
||||
def power_down(self, bits: int32 = 0b1111):
|
||||
"""Power down DDS.
|
||||
|
||||
:param bits: Power down bits, see datasheet
|
||||
"""
|
||||
self.set_cfr1(power_down=bits)
|
||||
self.cpld.io_update.pulse(1 * us)
|
||||
self.cpld.io_update.pulse(1. * us)
|
||||
|
||||
@kernel
|
||||
def set_mu(self, ftw: TInt32 = 0, pow_: TInt32 = 0, asf: TInt32 = 0x3fff,
|
||||
phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
|
||||
ref_time_mu: TInt64 = int64(-1),
|
||||
profile: TInt32 = DEFAULT_PROFILE,
|
||||
ram_destination: TInt32 = -1) -> TInt32:
|
||||
def set_mu(self, ftw: int32 = 0, pow_: int32 = 0, asf: int32 = 0x3fff,
|
||||
phase_mode: int32 = _PHASE_MODE_DEFAULT,
|
||||
ref_time_mu: int64 = int64(-1),
|
||||
profile: int32 = DEFAULT_PROFILE,
|
||||
ram_destination: int32 = -1) -> int32:
|
||||
"""Set DDS data in machine units.
|
||||
|
||||
This uses machine units (FTW, POW, ASF). The frequency tuning word
|
||||
|
@ -559,15 +584,15 @@ class AD9910:
|
|||
phase_mode = self.phase_mode
|
||||
# Align to coarse RTIO which aligns SYNC_CLK. I.e. clear fine TSC
|
||||
# This will not cause a collision or sequence error.
|
||||
at_mu(now_mu() & ~7)
|
||||
at_mu(now_mu() & ~int64(7))
|
||||
if phase_mode != PHASE_MODE_CONTINUOUS:
|
||||
# Auto-clear phase accumulator on IO_UPDATE.
|
||||
# This is active already for the next IO_UPDATE
|
||||
self.set_cfr1(phase_autoclear=1)
|
||||
if phase_mode == PHASE_MODE_TRACKING and ref_time_mu < 0:
|
||||
if phase_mode == PHASE_MODE_TRACKING and ref_time_mu < int64(0):
|
||||
# set default fiducial time stamp
|
||||
ref_time_mu = 0
|
||||
if ref_time_mu >= 0:
|
||||
ref_time_mu = int64(0)
|
||||
if ref_time_mu >= int64(0):
|
||||
# 32 LSB are sufficient.
|
||||
# Also no need to use IO_UPDATE time as this
|
||||
# is equivalent to an output pipeline latency.
|
||||
|
@ -585,16 +610,16 @@ class AD9910:
|
|||
if not ram_destination == RAM_DEST_POW:
|
||||
self.set_pow(pow_)
|
||||
delay_mu(int64(self.sync_data.io_update_delay))
|
||||
self.cpld.io_update.pulse_mu(8) # assumes 8 mu > t_SYN_CCLK
|
||||
at_mu(now_mu() & ~7) # clear fine TSC again
|
||||
self.cpld.io_update.pulse_mu(int64(8)) # assumes 8 mu > t_SYN_CCLK
|
||||
at_mu(now_mu() & ~int64(7)) # clear fine TSC again
|
||||
if phase_mode != PHASE_MODE_CONTINUOUS:
|
||||
self.set_cfr1()
|
||||
# future IO_UPDATE will activate
|
||||
return pow_
|
||||
|
||||
@kernel
|
||||
def get_mu(self, profile: TInt32 = DEFAULT_PROFILE
|
||||
) -> TTuple([TInt32, TInt32, TInt32]):
|
||||
def get_mu(self, profile: int32 = DEFAULT_PROFILE
|
||||
) -> tuple[int32, int32, int32]:
|
||||
"""Get the frequency tuning word, phase offset word,
|
||||
and amplitude scale factor.
|
||||
|
||||
|
@ -608,15 +633,15 @@ class AD9910:
|
|||
data = int64(self.read64(_AD9910_REG_PROFILE0 + profile))
|
||||
# Extract and return fields
|
||||
ftw = int32(data)
|
||||
pow_ = int32((data >> 32) & 0xffff)
|
||||
asf = int32((data >> 48) & 0x3fff)
|
||||
pow_ = int32(data >> 32) & 0xffff
|
||||
asf = int32(data >> 48) & 0x3fff
|
||||
return ftw, pow_, asf
|
||||
|
||||
@kernel
|
||||
def set_profile_ram(self, start: TInt32, end: TInt32, step: TInt32 = 1,
|
||||
profile: TInt32 = _DEFAULT_PROFILE_RAM,
|
||||
nodwell_high: TInt32 = 0, zero_crossing: TInt32 = 0,
|
||||
mode: TInt32 = 1):
|
||||
def set_profile_ram(self, start: int32, end: int32, step: int32 = 1,
|
||||
profile: int32 = _DEFAULT_PROFILE_RAM,
|
||||
nodwell_high: int32 = 0, zero_crossing: int32 = 0,
|
||||
mode: int32 = 1):
|
||||
"""Set the RAM profile settings.
|
||||
|
||||
:param start: Profile start address in RAM.
|
||||
|
@ -640,7 +665,7 @@ class AD9910:
|
|||
self.write64(_AD9910_REG_PROFILE0 + profile, hi, lo)
|
||||
|
||||
@kernel
|
||||
def set_ftw(self, ftw: TInt32):
|
||||
def set_ftw(self, ftw: int32):
|
||||
"""Set the value stored to the AD9910's frequency tuning word (FTW)
|
||||
register.
|
||||
|
||||
|
@ -649,7 +674,7 @@ class AD9910:
|
|||
self.write32(_AD9910_REG_FTW, ftw)
|
||||
|
||||
@kernel
|
||||
def set_asf(self, asf: TInt32):
|
||||
def set_asf(self, asf: int32):
|
||||
"""Set the value stored to the AD9910's amplitude scale factor (ASF)
|
||||
register.
|
||||
|
||||
|
@ -658,7 +683,7 @@ class AD9910:
|
|||
self.write32(_AD9910_REG_ASF, asf << 2)
|
||||
|
||||
@kernel
|
||||
def set_pow(self, pow_: TInt32):
|
||||
def set_pow(self, pow_: int32):
|
||||
"""Set the value stored to the AD9910's phase offset word (POW)
|
||||
register.
|
||||
|
||||
|
@ -667,7 +692,7 @@ class AD9910:
|
|||
self.write16(_AD9910_REG_POW, pow_)
|
||||
|
||||
@kernel
|
||||
def get_ftw(self) -> TInt32:
|
||||
def get_ftw(self) -> int32:
|
||||
"""Get the value stored to the AD9910's frequency tuning word (FTW)
|
||||
register.
|
||||
|
||||
|
@ -676,7 +701,7 @@ class AD9910:
|
|||
return self.read32(_AD9910_REG_FTW)
|
||||
|
||||
@kernel
|
||||
def get_asf(self) -> TInt32:
|
||||
def get_asf(self) -> int32:
|
||||
"""Get the value stored to the AD9910's amplitude scale factor (ASF)
|
||||
register.
|
||||
|
||||
|
@ -685,7 +710,7 @@ class AD9910:
|
|||
return self.read32(_AD9910_REG_ASF) >> 2
|
||||
|
||||
@kernel
|
||||
def get_pow(self) -> TInt32:
|
||||
def get_pow(self) -> int32:
|
||||
"""Get the value stored to the AD9910's phase offset word (POW)
|
||||
register.
|
||||
|
||||
|
@ -693,49 +718,49 @@ class AD9910:
|
|||
"""
|
||||
return self.read16(_AD9910_REG_POW)
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def frequency_to_ftw(self, frequency: TFloat) -> TInt32:
|
||||
@portable
|
||||
def frequency_to_ftw(self, frequency: float) -> int32:
|
||||
"""Return the 32-bit frequency tuning word corresponding to the given
|
||||
frequency.
|
||||
"""
|
||||
return int32(round(self.ftw_per_hz * frequency))
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def ftw_to_frequency(self, ftw: TInt32) -> TFloat:
|
||||
@portable
|
||||
def ftw_to_frequency(self, ftw: int32) -> float:
|
||||
"""Return the frequency corresponding to the given frequency tuning
|
||||
word.
|
||||
"""
|
||||
return ftw / self.ftw_per_hz
|
||||
return float(ftw) / self.ftw_per_hz
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def turns_to_pow(self, turns: TFloat) -> TInt32:
|
||||
@portable
|
||||
def turns_to_pow(self, turns: float) -> int32:
|
||||
"""Return the 16-bit phase offset word corresponding to the given phase
|
||||
in turns."""
|
||||
return int32(round(turns * 0x10000)) & int32(0xffff)
|
||||
return round(turns * float(0x10000)) & 0xffff
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def pow_to_turns(self, pow_: TInt32) -> TFloat:
|
||||
@portable
|
||||
def pow_to_turns(self, pow_: int32) -> float:
|
||||
"""Return the phase in turns corresponding to a given phase offset
|
||||
word."""
|
||||
return pow_ / 0x10000
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def amplitude_to_asf(self, amplitude: TFloat) -> TInt32:
|
||||
@portable
|
||||
def amplitude_to_asf(self, amplitude: float) -> int32:
|
||||
"""Return 14-bit amplitude scale factor corresponding to given
|
||||
fractional amplitude."""
|
||||
code = int32(round(amplitude * 0x3fff))
|
||||
code = round(amplitude * float(0x3fff))
|
||||
if code < 0 or code > 0x3fff:
|
||||
raise ValueError("Invalid AD9910 fractional amplitude!")
|
||||
return code
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def asf_to_amplitude(self, asf: TInt32) -> TFloat:
|
||||
@portable
|
||||
def asf_to_amplitude(self, asf: int32) -> float:
|
||||
"""Return amplitude as a fraction of full scale corresponding to given
|
||||
amplitude scale factor."""
|
||||
return asf / float(0x3fff)
|
||||
return float(asf) / float(0x3fff)
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def frequency_to_ram(self, frequency: TList(TFloat), ram: TList(TInt32)):
|
||||
@portable
|
||||
def frequency_to_ram(self, frequency: list[float], ram: list[int32]):
|
||||
"""Convert frequency values to RAM profile data.
|
||||
|
||||
To be used with :const:`RAM_DEST_FTW`.
|
||||
|
@ -747,8 +772,8 @@ class AD9910:
|
|||
for i in range(len(ram)):
|
||||
ram[i] = self.frequency_to_ftw(frequency[i])
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def turns_to_ram(self, turns: TList(TFloat), ram: TList(TInt32)):
|
||||
@portable
|
||||
def turns_to_ram(self, turns: list[float], ram: list[int32]):
|
||||
"""Convert phase values to RAM profile data.
|
||||
|
||||
To be used with :const:`RAM_DEST_POW`.
|
||||
|
@ -760,8 +785,8 @@ class AD9910:
|
|||
for i in range(len(ram)):
|
||||
ram[i] = self.turns_to_pow(turns[i]) << 16
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def amplitude_to_ram(self, amplitude: TList(TFloat), ram: TList(TInt32)):
|
||||
@portable
|
||||
def amplitude_to_ram(self, amplitude: list[float], ram: list[int32]):
|
||||
"""Convert amplitude values to RAM profile data.
|
||||
|
||||
To be used with :const:`RAM_DEST_ASF`.
|
||||
|
@ -773,9 +798,9 @@ class AD9910:
|
|||
for i in range(len(ram)):
|
||||
ram[i] = self.amplitude_to_asf(amplitude[i]) << 18
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def turns_amplitude_to_ram(self, turns: TList(TFloat),
|
||||
amplitude: TList(TFloat), ram: TList(TInt32)):
|
||||
@portable
|
||||
def turns_amplitude_to_ram(self, turns: list[float],
|
||||
amplitude: list[float], ram: list[int32]):
|
||||
"""Convert phase and amplitude values to RAM profile data.
|
||||
|
||||
To be used with :const:`RAM_DEST_POWASF`.
|
||||
|
@ -790,7 +815,7 @@ class AD9910:
|
|||
self.amplitude_to_asf(amplitude[i]) << 2)
|
||||
|
||||
@kernel
|
||||
def set_frequency(self, frequency: TFloat):
|
||||
def set_frequency(self, frequency: float):
|
||||
"""Set the value stored to the AD9910's frequency tuning word (FTW)
|
||||
register.
|
||||
|
||||
|
@ -799,7 +824,7 @@ class AD9910:
|
|||
self.set_ftw(self.frequency_to_ftw(frequency))
|
||||
|
||||
@kernel
|
||||
def set_amplitude(self, amplitude: TFloat):
|
||||
def set_amplitude(self, amplitude: float):
|
||||
"""Set the value stored to the AD9910's amplitude scale factor (ASF)
|
||||
register.
|
||||
|
||||
|
@ -808,7 +833,7 @@ class AD9910:
|
|||
self.set_asf(self.amplitude_to_asf(amplitude))
|
||||
|
||||
@kernel
|
||||
def set_phase(self, turns: TFloat):
|
||||
def set_phase(self, turns: float):
|
||||
"""Set the value stored to the AD9910's phase offset word (POW)
|
||||
register.
|
||||
|
||||
|
@ -817,7 +842,7 @@ class AD9910:
|
|||
self.set_pow(self.turns_to_pow(turns))
|
||||
|
||||
@kernel
|
||||
def get_frequency(self) -> TFloat:
|
||||
def get_frequency(self) -> float:
|
||||
"""Get the value stored to the AD9910's frequency tuning word (FTW)
|
||||
register.
|
||||
|
||||
|
@ -826,7 +851,7 @@ class AD9910:
|
|||
return self.ftw_to_frequency(self.get_ftw())
|
||||
|
||||
@kernel
|
||||
def get_amplitude(self) -> TFloat:
|
||||
def get_amplitude(self) -> float:
|
||||
"""Get the value stored to the AD9910's amplitude scale factor (ASF)
|
||||
register.
|
||||
|
||||
|
@ -835,7 +860,7 @@ class AD9910:
|
|||
return self.asf_to_amplitude(self.get_asf())
|
||||
|
||||
@kernel
|
||||
def get_phase(self) -> TFloat:
|
||||
def get_phase(self) -> float:
|
||||
"""Get the value stored to the AD9910's phase offset word (POW)
|
||||
register.
|
||||
|
||||
|
@ -844,10 +869,10 @@ class AD9910:
|
|||
return self.pow_to_turns(self.get_pow())
|
||||
|
||||
@kernel
|
||||
def set(self, frequency: TFloat = 0.0, phase: TFloat = 0.0,
|
||||
amplitude: TFloat = 1.0, phase_mode: TInt32 = _PHASE_MODE_DEFAULT,
|
||||
ref_time_mu: TInt64 = int64(-1), profile: TInt32 = DEFAULT_PROFILE,
|
||||
ram_destination: TInt32 = -1) -> TFloat:
|
||||
def set(self, frequency: float = 0.0, phase: float = 0.0,
|
||||
amplitude: float = 1.0, phase_mode: int32 = _PHASE_MODE_DEFAULT,
|
||||
ref_time_mu: int64 = int64(-1), profile: int32 = DEFAULT_PROFILE,
|
||||
ram_destination: int32 = -1) -> float:
|
||||
"""Set DDS data in SI units.
|
||||
|
||||
.. seealso:: :meth:`set_mu`
|
||||
|
@ -867,8 +892,8 @@ class AD9910:
|
|||
profile, ram_destination))
|
||||
|
||||
@kernel
|
||||
def get(self, profile: TInt32 = DEFAULT_PROFILE
|
||||
) -> TTuple([TFloat, TFloat, TFloat]):
|
||||
def get(self, profile: int32 = DEFAULT_PROFILE
|
||||
) -> tuple[float, float, float]:
|
||||
"""Get the frequency, phase, and amplitude.
|
||||
|
||||
.. seealso:: :meth:`get_mu`
|
||||
|
@ -884,7 +909,7 @@ class AD9910:
|
|||
self.asf_to_amplitude(asf))
|
||||
|
||||
@kernel
|
||||
def set_att_mu(self, att: TInt32):
|
||||
def set_att_mu(self, att: int32):
|
||||
"""Set digital step attenuator in machine units.
|
||||
|
||||
This method will write the attenuator settings of all four channels.
|
||||
|
@ -896,7 +921,7 @@ class AD9910:
|
|||
self.cpld.set_att_mu(self.chip_select - 4, att)
|
||||
|
||||
@kernel
|
||||
def set_att(self, att: TFloat):
|
||||
def set_att(self, att: float):
|
||||
"""Set digital step attenuator in SI units.
|
||||
|
||||
This method will write the attenuator settings of all four channels.
|
||||
|
@ -908,7 +933,7 @@ class AD9910:
|
|||
self.cpld.set_att(self.chip_select - 4, att)
|
||||
|
||||
@kernel
|
||||
def get_att_mu(self) -> TInt32:
|
||||
def get_att_mu(self) -> int32:
|
||||
"""Get digital step attenuator value in machine units.
|
||||
|
||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu`
|
||||
|
@ -918,7 +943,7 @@ class AD9910:
|
|||
return self.cpld.get_channel_att_mu(self.chip_select - 4)
|
||||
|
||||
@kernel
|
||||
def get_att(self) -> TFloat:
|
||||
def get_att(self) -> float:
|
||||
"""Get digital step attenuator value in SI units.
|
||||
|
||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att`
|
||||
|
@ -928,7 +953,7 @@ class AD9910:
|
|||
return self.cpld.get_channel_att(self.chip_select - 4)
|
||||
|
||||
@kernel
|
||||
def cfg_sw(self, state: TBool):
|
||||
def cfg_sw(self, state: bool):
|
||||
"""Set CPLD CFG RF switch state. The RF switch is controlled by the
|
||||
logical or of the CPLD configuration shift register
|
||||
RF switch bit and the SW TTL line (if used).
|
||||
|
@ -939,9 +964,9 @@ class AD9910:
|
|||
|
||||
@kernel
|
||||
def set_sync(self,
|
||||
in_delay: TInt32,
|
||||
window: TInt32,
|
||||
en_sync_gen: TInt32 = 0):
|
||||
in_delay: int32,
|
||||
window: int32,
|
||||
en_sync_gen: int32 = 0):
|
||||
"""Set the relevant parameters in the multi device synchronization
|
||||
register. See the AD9910 datasheet for details. The SYNC clock
|
||||
generator preset value is set to zero, and the SYNC_OUT generator is
|
||||
|
@ -974,14 +999,14 @@ class AD9910:
|
|||
Also modifies CFR2.
|
||||
"""
|
||||
self.set_cfr2(sync_validation_disable=1) # clear SMP_ERR
|
||||
self.cpld.io_update.pulse(1 * us)
|
||||
delay(10 * us) # slack
|
||||
self.cpld.io_update.pulse(1. * us)
|
||||
self.core.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
|
||||
def tune_sync_delay(self,
|
||||
search_seed: TInt32 = 15) -> TTuple([TInt32, TInt32]):
|
||||
search_seed: int32 = 15) -> tuple[int32, int32]:
|
||||
"""Find a stable SYNC_IN delay.
|
||||
|
||||
This method first locates a valid SYNC_IN delay at zero validation
|
||||
|
@ -1008,7 +1033,7 @@ class AD9910:
|
|||
next_seed = -1
|
||||
for in_delay in range(search_span - 2 * window):
|
||||
# alternate search direction around search_seed
|
||||
if in_delay & 1:
|
||||
if in_delay & 1 != 0:
|
||||
in_delay = -in_delay
|
||||
in_delay = search_seed + (in_delay >> 1)
|
||||
if in_delay < 0 or in_delay > 31:
|
||||
|
@ -1016,9 +1041,9 @@ class AD9910:
|
|||
self.set_sync(in_delay, window)
|
||||
self.clear_smp_err()
|
||||
# integrate SMP_ERR statistics for a few hundred cycles
|
||||
delay(100 * us)
|
||||
self.core.delay(100. * us)
|
||||
err = urukul_sta_smp_err(self.cpld.sta_read())
|
||||
delay(100 * us) # slack
|
||||
self.core.delay(100. * us) # slack
|
||||
if not (err >> (self.chip_select - 4)) & 1:
|
||||
next_seed = in_delay
|
||||
break
|
||||
|
@ -1030,15 +1055,15 @@ class AD9910:
|
|||
window = max(min_window, window - 1 - margin)
|
||||
self.set_sync(search_seed, window)
|
||||
self.clear_smp_err()
|
||||
delay(100 * us) # slack
|
||||
self.core.delay(100. * us) # slack
|
||||
return search_seed, window
|
||||
else:
|
||||
break
|
||||
raise ValueError("no valid window/delay")
|
||||
|
||||
@kernel
|
||||
def measure_io_update_alignment(self, delay_start: TInt64,
|
||||
delay_stop: TInt64) -> TInt32:
|
||||
def measure_io_update_alignment(self, delay_start: int64,
|
||||
delay_stop: int64) -> int32:
|
||||
"""Use the digital ramp generator to locate the alignment between
|
||||
IO_UPDATE and SYNC_CLK.
|
||||
|
||||
|
@ -1062,25 +1087,25 @@ class AD9910:
|
|||
# dFTW = 1, (work around negative slope)
|
||||
self.write64(_AD9910_REG_RAMP_STEP, -1, 0)
|
||||
# delay io_update after RTIO edge
|
||||
t = now_mu() + 8 & ~7
|
||||
t = now_mu() + int64(8) & ~int64(7)
|
||||
at_mu(t + delay_start)
|
||||
# assumes a maximum t_SYNC_CLK period
|
||||
self.cpld.io_update.pulse_mu(16 - delay_start) # realign
|
||||
self.cpld.io_update.pulse_mu(int64(16) - delay_start) # realign
|
||||
# disable DRG autoclear and LRR on io_update
|
||||
self.set_cfr1()
|
||||
# stop DRG
|
||||
self.write64(_AD9910_REG_RAMP_STEP, 0, 0)
|
||||
at_mu(t + 0x1000 + delay_stop)
|
||||
self.cpld.io_update.pulse_mu(16 - delay_stop) # realign
|
||||
at_mu(t + int64(0x1000) + delay_stop)
|
||||
self.cpld.io_update.pulse_mu(int64(16) - delay_stop) # realign
|
||||
ftw = self.read32(_AD9910_REG_FTW) # read out effective FTW
|
||||
delay(100 * us) # slack
|
||||
self.core.delay(100. * us) # slack
|
||||
# disable DRG
|
||||
self.set_cfr2(drg_enable=0)
|
||||
self.cpld.io_update.pulse_mu(8)
|
||||
self.cpld.io_update.pulse_mu(int64(8))
|
||||
return ftw & 1
|
||||
|
||||
@kernel
|
||||
def tune_io_update_delay(self) -> TInt32:
|
||||
def tune_io_update_delay(self) -> int32:
|
||||
"""Find a stable IO_UPDATE delay alignment.
|
||||
|
||||
Scan through increasing IO_UPDATE delays until a delay is found that
|
||||
|
@ -1102,14 +1127,14 @@ class AD9910:
|
|||
t = 0
|
||||
# check whether the sync edge is strictly between i, i+2
|
||||
for j in range(repeat):
|
||||
t += self.measure_io_update_alignment(i, i + 2)
|
||||
t += self.measure_io_update_alignment(int64(i), int64(i + 2))
|
||||
if t != 0: # no certain edge
|
||||
continue
|
||||
# check left/right half: i,i+1 and i+1,i+2
|
||||
t1 = [0, 0]
|
||||
for j in range(repeat):
|
||||
t1[0] += self.measure_io_update_alignment(i, i + 1)
|
||||
t1[1] += self.measure_io_update_alignment(i + 1, i + 2)
|
||||
t1[0] += self.measure_io_update_alignment(int64(i), int64(i + 1))
|
||||
t1[1] += self.measure_io_update_alignment(int64(i + 1), int64(i + 2))
|
||||
if ((t1[0] == 0 and t1[1] == 0) or
|
||||
(t1[0] == repeat and t1[1] == repeat)):
|
||||
# edge is not close to i + 1, can't interpret result
|
||||
|
|
|
@ -1,14 +1,16 @@
|
|||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.types import TInt32, TInt64, TFloat, TTuple, TBool
|
||||
from artiq.language.core import kernel, delay, portable
|
||||
from artiq.language.core import *
|
||||
from artiq.language.units import ms, us, ns
|
||||
from artiq.coredevice.ad9912_reg import *
|
||||
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from artiq.coredevice import urukul
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.spi2 import *
|
||||
from artiq.coredevice.urukul import *
|
||||
from artiq.coredevice.ttl import TTLOut
|
||||
|
||||
|
||||
@nac3
|
||||
class AD9912:
|
||||
"""
|
||||
AD9912 DDS channel on Urukul
|
||||
|
@ -25,22 +27,30 @@ class AD9912:
|
|||
f_ref/clk_div*pll_n where f_ref is the reference frequency and clk_div
|
||||
is the reference clock divider (both set in the parent Urukul CPLD
|
||||
instance).
|
||||
:param pll_en: PLL enable bit, set to 0 to bypass PLL (default: 1).
|
||||
:param pll_en: PLL enable bit, set to False to bypass PLL (default: True).
|
||||
Note that when bypassing the PLL the red front panel LED may remain on.
|
||||
"""
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
cpld: KernelInvariant[CPLD]
|
||||
bus: KernelInvariant[SPIMaster]
|
||||
chip_select: KernelInvariant[int32]
|
||||
pll_n: KernelInvariant[int32]
|
||||
pll_en: KernelInvariant[bool]
|
||||
ftw_per_hz: KernelInvariant[float]
|
||||
sw: KernelInvariant[Option[TTLOut]]
|
||||
|
||||
def __init__(self, dmgr, chip_select, cpld_device, sw_device=None,
|
||||
pll_n=10, pll_en=1):
|
||||
self.kernel_invariants = {"cpld", "core", "bus", "chip_select",
|
||||
"pll_n", "pll_en", "ftw_per_hz"}
|
||||
pll_n=10, pll_en=True):
|
||||
self.cpld = dmgr.get(cpld_device)
|
||||
self.core = self.cpld.core
|
||||
self.bus = self.cpld.bus
|
||||
assert 4 <= chip_select <= 7
|
||||
self.chip_select = chip_select
|
||||
if sw_device:
|
||||
self.sw = dmgr.get(sw_device)
|
||||
self.kernel_invariants.add("sw")
|
||||
self.sw = Some(dmgr.get(sw_device))
|
||||
else:
|
||||
self.sw = none
|
||||
self.pll_en = pll_en
|
||||
self.pll_n = pll_n
|
||||
if pll_en:
|
||||
|
@ -48,10 +58,10 @@ class AD9912:
|
|||
else:
|
||||
sysclk = self.cpld.refclk
|
||||
assert sysclk <= 1e9
|
||||
self.ftw_per_hz = 1 / sysclk * (int64(1) << 48)
|
||||
self.ftw_per_hz = 1 / sysclk * (1 << 48)
|
||||
|
||||
@kernel
|
||||
def write(self, addr: TInt32, data: TInt32, length: TInt32):
|
||||
def write(self, addr: int32, data: int32, length: int32):
|
||||
"""Variable length write to a register.
|
||||
Up to 4 bytes.
|
||||
|
||||
|
@ -61,15 +71,15 @@ class AD9912:
|
|||
"""
|
||||
assert length > 0
|
||||
assert length <= 4
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG, 16,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write((addr | ((length - 1) << 13)) << 16)
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, length * 8,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, length * 8,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write(data << (32 - length * 8))
|
||||
|
||||
@kernel
|
||||
def read(self, addr: TInt32, length: TInt32) -> TInt32:
|
||||
def read(self, addr: int32, length: int32) -> int32:
|
||||
"""Variable length read from a register.
|
||||
Up to 4 bytes.
|
||||
|
||||
|
@ -79,12 +89,12 @@ class AD9912:
|
|||
"""
|
||||
assert length > 0
|
||||
assert length <= 4
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG, 16,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write((addr | ((length - 1) << 13) | 0x8000) << 16)
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END
|
||||
| spi.SPI_INPUT, length * 8,
|
||||
urukul.SPIT_DDS_RD, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END
|
||||
| SPI_INPUT, length * 8,
|
||||
SPIT_DDS_RD, self.chip_select)
|
||||
self.bus.write(0)
|
||||
data = self.bus.read()
|
||||
if length < 4:
|
||||
|
@ -100,27 +110,27 @@ class AD9912:
|
|||
IO_UPDATE signal multiple times.
|
||||
"""
|
||||
# SPI mode
|
||||
self.write(AD9912_SER_CONF, 0x99, length=1)
|
||||
self.cpld.io_update.pulse(2 * us)
|
||||
self.write(AD9912_SER_CONF, 0x99, 1)
|
||||
self.cpld.io_update.pulse(2. * us)
|
||||
# Verify chip ID and presence
|
||||
prodid = self.read(AD9912_PRODIDH, length=2)
|
||||
prodid = self.read(AD9912_PRODIDH, 2)
|
||||
if (prodid != 0x1982) and (prodid != 0x1902):
|
||||
raise ValueError("Urukul AD9912 product id mismatch")
|
||||
delay(50 * us)
|
||||
self.core.delay(50. * us)
|
||||
# HSTL power down, CMOS power down
|
||||
pwrcntrl1 = 0x80 | ((~self.pll_en & 1) << 4)
|
||||
self.write(AD9912_PWRCNTRL1, pwrcntrl1, length=1)
|
||||
self.cpld.io_update.pulse(2 * us)
|
||||
pwrcntrl1 = 0x80 | (int32(not self.pll_en) << 4)
|
||||
self.write(AD9912_PWRCNTRL1, pwrcntrl1, 1)
|
||||
self.cpld.io_update.pulse(2. * us)
|
||||
if self.pll_en:
|
||||
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, length=1)
|
||||
self.cpld.io_update.pulse(2 * us)
|
||||
self.write(AD9912_N_DIV, self.pll_n // 2 - 2, 1)
|
||||
self.cpld.io_update.pulse(2. * us)
|
||||
# I_cp = 375 µA, VCO high range
|
||||
self.write(AD9912_PLLCFG, 0b00000101, length=1)
|
||||
self.cpld.io_update.pulse(2 * us)
|
||||
delay(1 * ms)
|
||||
self.write(AD9912_PLLCFG, 0b00000101, 1)
|
||||
self.cpld.io_update.pulse(2. * us)
|
||||
self.core.delay(1. * ms)
|
||||
|
||||
@kernel
|
||||
def set_att_mu(self, att: TInt32):
|
||||
def set_att_mu(self, att: int32):
|
||||
"""Set digital step attenuator in machine units.
|
||||
|
||||
This method will write the attenuator settings of all four channels.
|
||||
|
@ -132,7 +142,7 @@ class AD9912:
|
|||
self.cpld.set_att_mu(self.chip_select - 4, att)
|
||||
|
||||
@kernel
|
||||
def set_att(self, att: TFloat):
|
||||
def set_att(self, att: float):
|
||||
"""Set digital step attenuator in SI units.
|
||||
|
||||
This method will write the attenuator settings of all four channels.
|
||||
|
@ -144,7 +154,7 @@ class AD9912:
|
|||
self.cpld.set_att(self.chip_select - 4, att)
|
||||
|
||||
@kernel
|
||||
def get_att_mu(self) -> TInt32:
|
||||
def get_att_mu(self) -> int32:
|
||||
"""Get digital step attenuator value in machine units.
|
||||
|
||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att_mu`
|
||||
|
@ -154,7 +164,7 @@ class AD9912:
|
|||
return self.cpld.get_channel_att_mu(self.chip_select - 4)
|
||||
|
||||
@kernel
|
||||
def get_att(self) -> TFloat:
|
||||
def get_att(self) -> float:
|
||||
"""Get digital step attenuator value in SI units.
|
||||
|
||||
.. seealso:: :meth:`artiq.coredevice.urukul.CPLD.get_channel_att`
|
||||
|
@ -164,7 +174,7 @@ class AD9912:
|
|||
return self.cpld.get_channel_att(self.chip_select - 4)
|
||||
|
||||
@kernel
|
||||
def set_mu(self, ftw: TInt64, pow_: TInt32 = 0):
|
||||
def set_mu(self, ftw: int64, pow_: int32 = 0):
|
||||
"""Set profile 0 data in machine units.
|
||||
|
||||
After the SPI transfer, the shared IO update pin is pulsed to
|
||||
|
@ -174,19 +184,19 @@ class AD9912:
|
|||
:param pow_: Phase tuning word: 16 bit unsigned.
|
||||
"""
|
||||
# streaming transfer of FTW and POW
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 16,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG, 16,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write((AD9912_POW1 << 16) | (3 << 29))
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG, 32,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG, 32,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write((pow_ << 16) | (int32(ftw >> 32) & 0xffff))
|
||||
self.bus.set_config_mu(urukul.SPI_CONFIG | spi.SPI_END, 32,
|
||||
urukul.SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
|
||||
SPIT_DDS_WR, self.chip_select)
|
||||
self.bus.write(int32(ftw))
|
||||
self.cpld.io_update.pulse(10 * ns)
|
||||
self.cpld.io_update.pulse(10. * ns)
|
||||
|
||||
@kernel
|
||||
def get_mu(self) -> TTuple([TInt64, TInt32]):
|
||||
def get_mu(self) -> tuple[int64, int32]:
|
||||
"""Get the frequency tuning word and phase offset word.
|
||||
|
||||
.. seealso:: :meth:`get`
|
||||
|
@ -203,30 +213,30 @@ class AD9912:
|
|||
pow_ = (high >> 16) & 0x3fff
|
||||
return ftw, pow_
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def frequency_to_ftw(self, frequency: TFloat) -> TInt64:
|
||||
@portable
|
||||
def frequency_to_ftw(self, frequency: float) -> int64:
|
||||
"""Returns the 48-bit frequency tuning word corresponding to the given
|
||||
frequency.
|
||||
"""
|
||||
return int64(round(self.ftw_per_hz * frequency)) & (
|
||||
(int64(1) << 48) - 1)
|
||||
return round64(self.ftw_per_hz * frequency) & (
|
||||
(int64(1) << 48) - int64(1))
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def ftw_to_frequency(self, ftw: TInt64) -> TFloat:
|
||||
@portable
|
||||
def ftw_to_frequency(self, ftw: int64) -> float:
|
||||
"""Returns the frequency corresponding to the given
|
||||
frequency tuning word.
|
||||
"""
|
||||
return ftw / self.ftw_per_hz
|
||||
return float(ftw) / self.ftw_per_hz
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def turns_to_pow(self, phase: TFloat) -> TInt32:
|
||||
@portable
|
||||
def turns_to_pow(self, phase: float) -> int32:
|
||||
"""Returns the 16-bit phase offset word corresponding to the given
|
||||
phase.
|
||||
"""
|
||||
return int32(round((1 << 14) * phase)) & 0xffff
|
||||
return int32(round(float(1 << 14) * phase)) & 0xffff
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def pow_to_turns(self, pow_: TInt32) -> TFloat:
|
||||
@portable
|
||||
def pow_to_turns(self, pow_: int32) -> float:
|
||||
"""Return the phase in turns corresponding to a given phase offset
|
||||
word.
|
||||
|
||||
|
@ -236,7 +246,7 @@ class AD9912:
|
|||
return pow_ / (1 << 14)
|
||||
|
||||
@kernel
|
||||
def set(self, frequency: TFloat, phase: TFloat = 0.0):
|
||||
def set(self, frequency: float, phase: float = 0.0):
|
||||
"""Set profile 0 data in SI units.
|
||||
|
||||
.. seealso:: :meth:`set_mu`
|
||||
|
@ -248,7 +258,7 @@ class AD9912:
|
|||
self.turns_to_pow(phase))
|
||||
|
||||
@kernel
|
||||
def get(self) -> TTuple([TFloat, TFloat]):
|
||||
def get(self) -> tuple[float, float]:
|
||||
"""Get the frequency and phase.
|
||||
|
||||
.. seealso:: :meth:`get_mu`
|
||||
|
@ -262,7 +272,7 @@ class AD9912:
|
|||
return self.ftw_to_frequency(ftw), self.pow_to_turns(pow_)
|
||||
|
||||
@kernel
|
||||
def cfg_sw(self, state: TBool):
|
||||
def cfg_sw(self, state: bool):
|
||||
"""Set CPLD CFG RF switch state. The RF switch is controlled by the
|
||||
logical or of the CPLD configuration shift register
|
||||
RF switch bit and the SW TTL line (if used).
|
||||
|
|
|
@ -1,78 +1,78 @@
|
|||
# auto-generated, do not edit
|
||||
from numpy import int32
|
||||
from artiq.language.core import portable
|
||||
from artiq.language.types import TInt32
|
||||
|
||||
AD9912_SER_CONF = 0x000
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_SDOACTIVE_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_SDOACTIVE_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 0
|
||||
|
||||
@portable
|
||||
def AD9912_SDOACTIVE_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_SDOACTIVE_GET(x: int32) -> int32:
|
||||
return (x >> 0) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_LSBFIRST_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_LSBFIRST_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 1
|
||||
|
||||
@portable
|
||||
def AD9912_LSBFIRST_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_LSBFIRST_GET(x: int32) -> int32:
|
||||
return (x >> 1) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_SOFTRESET_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_SOFTRESET_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 2
|
||||
|
||||
@portable
|
||||
def AD9912_SOFTRESET_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_SOFTRESET_GET(x: int32) -> int32:
|
||||
return (x >> 2) & 0x1
|
||||
|
||||
# default: 0x01, access: R/W
|
||||
@portable
|
||||
def AD9912_LONGINSN_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_LONGINSN_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 3
|
||||
|
||||
@portable
|
||||
def AD9912_LONGINSN_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_LONGINSN_GET(x: int32) -> int32:
|
||||
return (x >> 3) & 0x1
|
||||
|
||||
# default: 0x01, access: R/W
|
||||
@portable
|
||||
def AD9912_LONGINSN_M_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_LONGINSN_M_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 4
|
||||
|
||||
@portable
|
||||
def AD9912_LONGINSN_M_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_LONGINSN_M_GET(x: int32) -> int32:
|
||||
return (x >> 4) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_SOFTRESET_M_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_SOFTRESET_M_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 5
|
||||
|
||||
@portable
|
||||
def AD9912_SOFTRESET_M_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_SOFTRESET_M_GET(x: int32) -> int32:
|
||||
return (x >> 5) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_LSBFIRST_M_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_LSBFIRST_M_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 6
|
||||
|
||||
@portable
|
||||
def AD9912_LSBFIRST_M_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_LSBFIRST_M_GET(x: int32) -> int32:
|
||||
return (x >> 6) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_SDOACTIVE_M_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_SDOACTIVE_M_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 7
|
||||
|
||||
@portable
|
||||
def AD9912_SDOACTIVE_M_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_SDOACTIVE_M_GET(x: int32) -> int32:
|
||||
return (x >> 7) & 0x1
|
||||
|
||||
|
||||
|
@ -83,118 +83,118 @@ AD9912_PRODIDH = 0x003
|
|||
AD9912_SER_OPT1 = 0x004
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_READ_BUF_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_READ_BUF_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 0
|
||||
|
||||
@portable
|
||||
def AD9912_READ_BUF_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_READ_BUF_GET(x: int32) -> int32:
|
||||
return (x >> 0) & 0x1
|
||||
|
||||
|
||||
AD9912_SER_OPT2 = 0x005
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_RED_UPDATE_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_RED_UPDATE_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 0
|
||||
|
||||
@portable
|
||||
def AD9912_RED_UPDATE_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_RED_UPDATE_GET(x: int32) -> int32:
|
||||
return (x >> 0) & 0x1
|
||||
|
||||
|
||||
AD9912_PWRCNTRL1 = 0x010
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_PD_DIGITAL_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_PD_DIGITAL_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 0
|
||||
|
||||
@portable
|
||||
def AD9912_PD_DIGITAL_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_PD_DIGITAL_GET(x: int32) -> int32:
|
||||
return (x >> 0) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_PD_FULL_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_PD_FULL_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 1
|
||||
|
||||
@portable
|
||||
def AD9912_PD_FULL_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_PD_FULL_GET(x: int32) -> int32:
|
||||
return (x >> 1) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_PD_SYSCLK_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_PD_SYSCLK_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 4
|
||||
|
||||
@portable
|
||||
def AD9912_PD_SYSCLK_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_PD_SYSCLK_GET(x: int32) -> int32:
|
||||
return (x >> 4) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_EN_DOUBLER_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_EN_DOUBLER_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 5
|
||||
|
||||
@portable
|
||||
def AD9912_EN_DOUBLER_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_EN_DOUBLER_GET(x: int32) -> int32:
|
||||
return (x >> 5) & 0x1
|
||||
|
||||
# default: 0x01, access: R/W
|
||||
@portable
|
||||
def AD9912_EN_CMOS_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_EN_CMOS_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 6
|
||||
|
||||
@portable
|
||||
def AD9912_EN_CMOS_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_EN_CMOS_GET(x: int32) -> int32:
|
||||
return (x >> 6) & 0x1
|
||||
|
||||
# default: 0x01, access: R/W
|
||||
@portable
|
||||
def AD9912_PD_HSTL_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_PD_HSTL_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 7
|
||||
|
||||
@portable
|
||||
def AD9912_PD_HSTL_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_PD_HSTL_GET(x: int32) -> int32:
|
||||
return (x >> 7) & 0x1
|
||||
|
||||
|
||||
AD9912_PWRCNTRL2 = 0x012
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_DDS_RESET_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_DDS_RESET_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 0
|
||||
|
||||
@portable
|
||||
def AD9912_DDS_RESET_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_DDS_RESET_GET(x: int32) -> int32:
|
||||
return (x >> 0) & 0x1
|
||||
|
||||
|
||||
AD9912_PWRCNTRL3 = 0x013
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_S_DIV_RESET_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_S_DIV_RESET_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 1
|
||||
|
||||
@portable
|
||||
def AD9912_S_DIV_RESET_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_S_DIV_RESET_GET(x: int32) -> int32:
|
||||
return (x >> 1) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_S_DIV2_RESET_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_S_DIV2_RESET_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 3
|
||||
|
||||
@portable
|
||||
def AD9912_S_DIV2_RESET_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_S_DIV2_RESET_GET(x: int32) -> int32:
|
||||
return (x >> 3) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_PD_FUND_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_PD_FUND_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 7
|
||||
|
||||
@portable
|
||||
def AD9912_PD_FUND_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_PD_FUND_GET(x: int32) -> int32:
|
||||
return (x >> 7) & 0x1
|
||||
|
||||
|
||||
|
@ -203,38 +203,38 @@ AD9912_N_DIV = 0x020
|
|||
AD9912_PLLCFG = 0x022
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_PLL_ICP_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_PLL_ICP_SET(x: int32) -> int32:
|
||||
return (x & 0x3) << 0
|
||||
|
||||
@portable
|
||||
def AD9912_PLL_ICP_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_PLL_ICP_GET(x: int32) -> int32:
|
||||
return (x >> 0) & 0x3
|
||||
|
||||
# default: 0x01, access: R/W
|
||||
@portable
|
||||
def AD9912_VCO_RANGE_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_VCO_RANGE_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 2
|
||||
|
||||
@portable
|
||||
def AD9912_VCO_RANGE_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_VCO_RANGE_GET(x: int32) -> int32:
|
||||
return (x >> 2) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_PLL_REF2X_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_PLL_REF2X_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 3
|
||||
|
||||
@portable
|
||||
def AD9912_PLL_REF2X_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_PLL_REF2X_GET(x: int32) -> int32:
|
||||
return (x >> 3) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_VCO_AUTO_RANGE_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_VCO_AUTO_RANGE_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 7
|
||||
|
||||
@portable
|
||||
def AD9912_VCO_AUTO_RANGE_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_VCO_AUTO_RANGE_GET(x: int32) -> int32:
|
||||
return (x >> 7) & 0x1
|
||||
|
||||
|
||||
|
@ -245,20 +245,20 @@ AD9912_S_DIVH = 0x105
|
|||
AD9912_S_DIV_CFG = 0x106
|
||||
# default: 0x01, access: R/W
|
||||
@portable
|
||||
def AD9912_S_DIV2_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_S_DIV2_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 0
|
||||
|
||||
@portable
|
||||
def AD9912_S_DIV2_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_S_DIV2_GET(x: int32) -> int32:
|
||||
return (x >> 0) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_S_DIV_FALL_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_S_DIV_FALL_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 7
|
||||
|
||||
@portable
|
||||
def AD9912_S_DIV_FALL_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_S_DIV_FALL_GET(x: int32) -> int32:
|
||||
return (x >> 7) & 0x1
|
||||
|
||||
|
||||
|
@ -281,31 +281,31 @@ AD9912_POW1 = 0x1ad
|
|||
AD9912_HSTL = 0x200
|
||||
# default: 0x01, access: R/W
|
||||
@portable
|
||||
def AD9912_HSTL_CFG_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSTL_CFG_SET(x: int32) -> int32:
|
||||
return (x & 0x3) << 0
|
||||
|
||||
@portable
|
||||
def AD9912_HSTL_CFG_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSTL_CFG_GET(x: int32) -> int32:
|
||||
return (x >> 0) & 0x3
|
||||
|
||||
# default: 0x01, access: R/W
|
||||
@portable
|
||||
def AD9912_HSTL_OPOL_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSTL_OPOL_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 4
|
||||
|
||||
@portable
|
||||
def AD9912_HSTL_OPOL_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSTL_OPOL_GET(x: int32) -> int32:
|
||||
return (x >> 4) & 0x1
|
||||
|
||||
|
||||
AD9912_CMOS = 0x201
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_CMOS_MUX_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_CMOS_MUX_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 0
|
||||
|
||||
@portable
|
||||
def AD9912_CMOS_MUX_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_CMOS_MUX_GET(x: int32) -> int32:
|
||||
return (x >> 0) & 0x1
|
||||
|
||||
|
||||
|
@ -316,29 +316,29 @@ AD9912_FSC1 = 0x40c
|
|||
AD9912_HSR_A_CFG = 0x500
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_HSR_A_HARMONIC_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_A_HARMONIC_SET(x: int32) -> int32:
|
||||
return (x & 0xf) << 0
|
||||
|
||||
@portable
|
||||
def AD9912_HSR_A_HARMONIC_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_A_HARMONIC_GET(x: int32) -> int32:
|
||||
return (x >> 0) & 0xf
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_HSR_A_MAG2X_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_A_MAG2X_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 6
|
||||
|
||||
@portable
|
||||
def AD9912_HSR_A_MAG2X_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_A_MAG2X_GET(x: int32) -> int32:
|
||||
return (x >> 6) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_HSR_A_EN_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_A_EN_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 7
|
||||
|
||||
@portable
|
||||
def AD9912_HSR_A_EN_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_A_EN_GET(x: int32) -> int32:
|
||||
return (x >> 7) & 0x1
|
||||
|
||||
|
||||
|
@ -351,29 +351,29 @@ AD9912_HSR_A_POW1 = 0x504
|
|||
AD9912_HSR_B_CFG = 0x505
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_HSR_B_HARMONIC_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_B_HARMONIC_SET(x: int32) -> int32:
|
||||
return (x & 0xf) << 0
|
||||
|
||||
@portable
|
||||
def AD9912_HSR_B_HARMONIC_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_B_HARMONIC_GET(x: int32) -> int32:
|
||||
return (x >> 0) & 0xf
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_HSR_B_MAG2X_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_B_MAG2X_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 6
|
||||
|
||||
@portable
|
||||
def AD9912_HSR_B_MAG2X_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_B_MAG2X_GET(x: int32) -> int32:
|
||||
return (x >> 6) & 0x1
|
||||
|
||||
# default: 0x00, access: R/W
|
||||
@portable
|
||||
def AD9912_HSR_B_EN_SET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_B_EN_SET(x: int32) -> int32:
|
||||
return (x & 0x1) << 7
|
||||
|
||||
@portable
|
||||
def AD9912_HSR_B_EN_GET(x: TInt32) -> TInt32:
|
||||
def AD9912_HSR_B_EN_GET(x: int32) -> int32:
|
||||
return (x >> 7) & 0x1
|
||||
|
||||
|
||||
|
|
|
@ -2,13 +2,12 @@
|
|||
Driver for the AD9914 DDS (with parallel bus) on RTIO.
|
||||
"""
|
||||
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import *
|
||||
from artiq.language.types import *
|
||||
from artiq.language.units import *
|
||||
from artiq.coredevice.rtio import rtio_output
|
||||
|
||||
from numpy import int32, int64
|
||||
from artiq.coredevice.core import Core
|
||||
|
||||
|
||||
__all__ = [
|
||||
|
@ -43,6 +42,7 @@ AD9914_FUD = 0x80
|
|||
AD9914_GPIO = 0x81
|
||||
|
||||
|
||||
@nac3
|
||||
class AD9914:
|
||||
"""Driver for one AD9914 DDS channel.
|
||||
|
||||
|
@ -57,10 +57,21 @@ class AD9914:
|
|||
:param channel: channel number (on the bus) of the DDS device to control.
|
||||
"""
|
||||
|
||||
kernel_invariants = {"core", "sysclk", "bus_channel", "channel",
|
||||
"rtio_period_mu", "sysclk_per_mu", "write_duration_mu",
|
||||
"dac_cal_duration_mu", "init_duration_mu", "init_sync_duration_mu",
|
||||
"set_duration_mu", "set_x_duration_mu", "exit_x_duration_mu"}
|
||||
core: KernelInvariant[Core]
|
||||
sysclk: KernelInvariant[float]
|
||||
bus_channel: KernelInvariant[int32]
|
||||
channel: KernelInvariant[int32]
|
||||
phase_mode: Kernel[int32]
|
||||
rtio_period_mu: KernelInvariant[int64]
|
||||
sysclk_per_mu: KernelInvariant[int64]
|
||||
write_duration_mu: KernelInvariant[int64]
|
||||
dac_cal_duration_mu: KernelInvariant[int64]
|
||||
init_duration_mu: KernelInvariant[int64]
|
||||
init_sync_duration_mu: KernelInvariant[int64]
|
||||
set_duration_mu: KernelInvariant[int64]
|
||||
set_x_duration_mu: KernelInvariant[int64]
|
||||
exit_x_duration_mu: KernelInvariant[int64]
|
||||
|
||||
|
||||
def __init__(self, dmgr, sysclk, bus_channel, channel, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -70,7 +81,7 @@ class AD9914:
|
|||
self.phase_mode = PHASE_MODE_CONTINUOUS
|
||||
|
||||
self.rtio_period_mu = int64(8)
|
||||
self.sysclk_per_mu = int32(self.sysclk * self.core.ref_period)
|
||||
self.sysclk_per_mu = int64(self.sysclk * self.core.ref_period)
|
||||
|
||||
self.write_duration_mu = 5 * self.rtio_period_mu
|
||||
self.dac_cal_duration_mu = 147000 * self.rtio_period_mu
|
||||
|
@ -88,7 +99,7 @@ class AD9914:
|
|||
return []
|
||||
|
||||
@kernel
|
||||
def write(self, addr, data):
|
||||
def write(self, addr: int32, data: int32):
|
||||
rtio_output((self.bus_channel << 8) | addr, data)
|
||||
delay_mu(self.write_duration_mu)
|
||||
|
||||
|
@ -120,7 +131,7 @@ class AD9914:
|
|||
self.write(AD9914_FUD, 0)
|
||||
|
||||
@kernel
|
||||
def init_sync(self, sync_delay):
|
||||
def init_sync(self, sync_delay: int32):
|
||||
"""Resets and initializes the DDS channel as well as configures
|
||||
the AD9914 DDS for synchronisation. The synchronisation procedure
|
||||
follows the steps outlined in the AN-1254 application note.
|
||||
|
@ -164,7 +175,7 @@ class AD9914:
|
|||
self.write(AD9914_FUD, 0)
|
||||
|
||||
@kernel
|
||||
def set_phase_mode(self, phase_mode):
|
||||
def set_phase_mode(self, phase_mode: int32):
|
||||
"""Sets the phase mode of the DDS channel. Supported phase modes are:
|
||||
|
||||
* :const:`PHASE_MODE_CONTINUOUS`: the phase accumulator is unchanged when
|
||||
|
@ -188,8 +199,8 @@ class AD9914:
|
|||
self.phase_mode = phase_mode
|
||||
|
||||
@kernel
|
||||
def set_mu(self, ftw, pow=0, phase_mode=_PHASE_MODE_DEFAULT,
|
||||
asf=0x0fff, ref_time_mu=-1):
|
||||
def set_mu(self, ftw: int32, pow: int32 = 0, phase_mode: int32 = _PHASE_MODE_DEFAULT,
|
||||
asf: int32 = 0x0fff, ref_time_mu: int64 = int64(-1)) -> int32:
|
||||
"""Sets the DDS channel to the specified frequency and phase.
|
||||
|
||||
This uses machine units (FTW and POW). The frequency tuning word width
|
||||
|
@ -212,7 +223,7 @@ class AD9914:
|
|||
"""
|
||||
if phase_mode == _PHASE_MODE_DEFAULT:
|
||||
phase_mode = self.phase_mode
|
||||
if ref_time_mu < 0:
|
||||
if ref_time_mu < int64(0):
|
||||
ref_time_mu = now_mu()
|
||||
delay_mu(-self.set_duration_mu)
|
||||
|
||||
|
@ -231,60 +242,60 @@ class AD9914:
|
|||
# Clear phase accumulator on FUD
|
||||
# Enable autoclear phase accumulator and enables OSK.
|
||||
self.write(AD9914_REG_CFR1L, 0x2108)
|
||||
fud_time = now_mu() + 2 * self.write_duration_mu
|
||||
pow -= int32((ref_time_mu - fud_time) * self.sysclk_per_mu * ftw >> (32 - 16))
|
||||
fud_time = now_mu() + int64(2) * self.write_duration_mu
|
||||
pow -= int32((ref_time_mu - fud_time) * self.sysclk_per_mu * int64(ftw) >> int64(32 - 16))
|
||||
if phase_mode == PHASE_MODE_TRACKING:
|
||||
pow += int32(ref_time_mu * self.sysclk_per_mu * ftw >> (32 - 16))
|
||||
pow += int32(ref_time_mu * self.sysclk_per_mu * int64(ftw) >> int64(32 - 16))
|
||||
|
||||
self.write(AD9914_REG_POW, pow)
|
||||
self.write(AD9914_REG_ASF, asf)
|
||||
self.write(AD9914_FUD, 0)
|
||||
return pow
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def frequency_to_ftw(self, frequency):
|
||||
@portable
|
||||
def frequency_to_ftw(self, frequency: float) -> int32:
|
||||
"""Returns the 32-bit frequency tuning word corresponding to the given
|
||||
frequency.
|
||||
"""
|
||||
return int32(round(float(int64(2)**32*frequency/self.sysclk)))
|
||||
return round(float(int64(2)**int64(32))*frequency/self.sysclk)
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def ftw_to_frequency(self, ftw):
|
||||
@portable
|
||||
def ftw_to_frequency(self, ftw: int32) -> float:
|
||||
"""Returns the frequency corresponding to the given frequency tuning
|
||||
word.
|
||||
"""
|
||||
return ftw*self.sysclk/int64(2)**32
|
||||
return float(ftw)*self.sysclk/float(int64(2)**int64(32))
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def turns_to_pow(self, turns):
|
||||
@portable
|
||||
def turns_to_pow(self, turns: float) -> int32:
|
||||
"""Returns the 16-bit phase offset word corresponding to the given
|
||||
phase in turns."""
|
||||
return round(float(turns*2**16)) & 0xffff
|
||||
return round(float(turns*float(2**16))) & 0xffff
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def pow_to_turns(self, pow):
|
||||
@portable
|
||||
def pow_to_turns(self, pow: int32) -> float:
|
||||
"""Returns the phase in turns corresponding to the given phase offset
|
||||
word."""
|
||||
return pow/2**16
|
||||
return float(pow)/float(2**16)
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def amplitude_to_asf(self, amplitude):
|
||||
@portable
|
||||
def amplitude_to_asf(self, amplitude: float) -> int32:
|
||||
"""Returns 12-bit amplitude scale factor corresponding to given
|
||||
amplitude."""
|
||||
code = round(float(amplitude * 0x0fff))
|
||||
code = round(float(amplitude * float(0x0fff)))
|
||||
if code < 0 or code > 0xfff:
|
||||
raise ValueError("Invalid AD9914 amplitude!")
|
||||
return code
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def asf_to_amplitude(self, asf):
|
||||
@portable
|
||||
def asf_to_amplitude(self, asf: int32) -> float:
|
||||
"""Returns the amplitude corresponding to the given amplitude scale
|
||||
factor."""
|
||||
return asf/0x0fff
|
||||
|
||||
@kernel
|
||||
def set(self, frequency, phase=0.0, phase_mode=_PHASE_MODE_DEFAULT,
|
||||
amplitude=1.0):
|
||||
def set(self, frequency: float, phase: float = 0.0, phase_mode: int32 = _PHASE_MODE_DEFAULT,
|
||||
amplitude: float = 1.0) -> float:
|
||||
"""Like :meth:`set_mu`, but uses Hz and turns."""
|
||||
return self.pow_to_turns(
|
||||
self.set_mu(self.frequency_to_ftw(frequency),
|
||||
|
@ -293,7 +304,7 @@ class AD9914:
|
|||
|
||||
# Extended-resolution functions
|
||||
@kernel
|
||||
def set_x_mu(self, xftw, amplitude=0x0fff):
|
||||
def set_x_mu(self, xftw: int64, amplitude: int32 = 0x0fff):
|
||||
"""Set the DDS frequency and amplitude with an extended-resolution
|
||||
(63-bit) frequency tuning word.
|
||||
|
||||
|
@ -307,10 +318,10 @@ class AD9914:
|
|||
|
||||
self.write(AD9914_GPIO, (1 << self.channel) << 1)
|
||||
|
||||
self.write(AD9914_REG_DRGAL, xftw & 0xffff)
|
||||
self.write(AD9914_REG_DRGAH, (xftw >> 16) & 0x7fff)
|
||||
self.write(AD9914_REG_DRGFL, (xftw >> 31) & 0xffff)
|
||||
self.write(AD9914_REG_DRGFH, (xftw >> 47) & 0xffff)
|
||||
self.write(AD9914_REG_DRGAL, int32(xftw) & 0xffff)
|
||||
self.write(AD9914_REG_DRGAH, int32(xftw >> int64(16)) & 0x7fff)
|
||||
self.write(AD9914_REG_DRGFL, int32(xftw >> int64(31)) & 0xffff)
|
||||
self.write(AD9914_REG_DRGFH, int32(xftw >> int64(47)) & 0xffff)
|
||||
self.write(AD9914_REG_ASF, amplitude)
|
||||
|
||||
self.write(AD9914_FUD, 0)
|
||||
|
@ -323,23 +334,23 @@ class AD9914:
|
|||
self.write(AD9914_REG_DRGAL, 0)
|
||||
self.write(AD9914_REG_DRGAH, 0)
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def frequency_to_xftw(self, frequency):
|
||||
@portable
|
||||
def frequency_to_xftw(self, frequency: float) -> int64:
|
||||
"""Returns the 63-bit frequency tuning word corresponding to the given
|
||||
frequency (extended resolution mode).
|
||||
"""
|
||||
return int64(round(2.0*float(int64(2)**62)*frequency/self.sysclk)) & (
|
||||
(int64(1) << 63) - 1)
|
||||
return round64(2.0*float(int64(2)**int64(62))*frequency/self.sysclk) & (
|
||||
(int64(1) << int64(63)) - int64(1))
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def xftw_to_frequency(self, xftw):
|
||||
@portable
|
||||
def xftw_to_frequency(self, xftw: int64) -> float:
|
||||
"""Returns the frequency corresponding to the given frequency tuning
|
||||
word (extended resolution mode).
|
||||
"""
|
||||
return xftw*self.sysclk/(2.0*float(int64(2)**62))
|
||||
return float(xftw)*self.sysclk/(2.0*float(int64(2)**int64(62)))
|
||||
|
||||
@kernel
|
||||
def set_x(self, frequency, amplitude=1.0):
|
||||
def set_x(self, frequency: float, amplitude: float = 1.0):
|
||||
"""Like :meth:`set_x_mu`, but uses Hz and turns.
|
||||
|
||||
Note that the precision of ``float`` is less than the precision
|
||||
|
|
|
@ -8,25 +8,27 @@ on Mirny-style prefixed SPI buses.
|
|||
# https://www.analog.com/media/en/technical-documentation/data-sheets/ADF5355.pdf
|
||||
# https://www.analog.com/media/en/technical-documentation/user-guides/EV-ADF5355SD1Z-UG-1087.pdf
|
||||
|
||||
from numpy import int32, int64
|
||||
from math import floor, ceil
|
||||
|
||||
from artiq.language.core import kernel, portable, delay
|
||||
from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable, round64
|
||||
from artiq.language.units import us, GHz, MHz
|
||||
from artiq.language.types import TInt32, TInt64
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.mirny import Mirny
|
||||
from artiq.coredevice.ttl import TTLOut
|
||||
from artiq.coredevice.spi2 import *
|
||||
from artiq.coredevice.adf5356_reg import *
|
||||
|
||||
from numpy import int32, int64, floor, ceil
|
||||
|
||||
|
||||
SPI_CONFIG = (
|
||||
0 * spi.SPI_OFFLINE
|
||||
| 0 * spi.SPI_END
|
||||
| 0 * spi.SPI_INPUT
|
||||
| 1 * spi.SPI_CS_POLARITY
|
||||
| 0 * spi.SPI_CLK_POLARITY
|
||||
| 0 * spi.SPI_CLK_PHASE
|
||||
| 0 * spi.SPI_LSB_FIRST
|
||||
| 0 * spi.SPI_HALF_DUPLEX
|
||||
0 * SPI_OFFLINE
|
||||
| 0 * SPI_END
|
||||
| 0 * SPI_INPUT
|
||||
| 1 * SPI_CS_POLARITY
|
||||
| 0 * SPI_CLK_POLARITY
|
||||
| 0 * SPI_CLK_PHASE
|
||||
| 0 * SPI_LSB_FIRST
|
||||
| 0 * SPI_HALF_DUPLEX
|
||||
)
|
||||
|
||||
|
||||
|
@ -38,6 +40,7 @@ ADF5356_MAX_MODULUS2 = int32(1 << 28) # FIXME: ADF5356 has 28 bits MOD2
|
|||
ADF5356_MAX_R_CNT = int32(1023)
|
||||
|
||||
|
||||
@nac3
|
||||
class ADF5356:
|
||||
"""Analog Devices AD[45]35[56] family of GHz PLLs.
|
||||
|
||||
|
@ -49,7 +52,14 @@ class ADF5356:
|
|||
:param core_device: Core device name (default: "core")
|
||||
"""
|
||||
|
||||
kernel_invariants = {"cpld", "sw", "channel", "core", "sysclk"}
|
||||
core: KernelInvariant[Core]
|
||||
cpld: KernelInvariant[Mirny]
|
||||
channel: KernelInvariant[int32]
|
||||
sw: KernelInvariant[TTLOut]
|
||||
sysclk: KernelInvariant[float]
|
||||
regs: Kernel[list[int32]]
|
||||
ref_doubler: Kernel[bool]
|
||||
ref_divider: Kernel[bool]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -78,7 +88,7 @@ class ADF5356:
|
|||
return []
|
||||
|
||||
@kernel
|
||||
def init(self, blind=False):
|
||||
def init(self, blind: bool = False):
|
||||
"""
|
||||
Initialize and configure the PLL.
|
||||
|
||||
|
@ -88,18 +98,18 @@ class ADF5356:
|
|||
# MUXOUT = VDD
|
||||
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 1)
|
||||
self.sync()
|
||||
delay(1000 * us)
|
||||
self.core.delay(1000. * us)
|
||||
if not self.read_muxout():
|
||||
raise ValueError("MUXOUT not high")
|
||||
delay(800 * us)
|
||||
self.core.delay(800. * us)
|
||||
|
||||
# MUXOUT = DGND
|
||||
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 2)
|
||||
self.sync()
|
||||
delay(1000 * us)
|
||||
self.core.delay(1000. * us)
|
||||
if self.read_muxout():
|
||||
raise ValueError("MUXOUT not low")
|
||||
delay(800 * us)
|
||||
self.core.delay(800. * us)
|
||||
|
||||
# MUXOUT = digital lock-detect
|
||||
self.regs[4] = ADF5356_REG4_MUXOUT_UPDATE(self.regs[4], 6)
|
||||
|
@ -107,7 +117,7 @@ class ADF5356:
|
|||
self.sync()
|
||||
|
||||
@kernel
|
||||
def set_att(self, att):
|
||||
def set_att(self, att: float):
|
||||
"""Set digital step attenuator in SI units.
|
||||
|
||||
This method will write the attenuator settings of the channel.
|
||||
|
@ -119,7 +129,7 @@ class ADF5356:
|
|||
self.cpld.set_att(self.channel, att)
|
||||
|
||||
@kernel
|
||||
def set_att_mu(self, att):
|
||||
def set_att_mu(self, att: int32):
|
||||
"""Set digital step attenuator in machine units.
|
||||
|
||||
:param att: Attenuation setting, 8 bit digital.
|
||||
|
@ -127,11 +137,11 @@ class ADF5356:
|
|||
self.cpld.set_att_mu(self.channel, att)
|
||||
|
||||
@kernel
|
||||
def write(self, data):
|
||||
def write(self, data: int32):
|
||||
self.cpld.write_ext(self.channel | 4, 32, data)
|
||||
|
||||
@kernel
|
||||
def read_muxout(self):
|
||||
def read_muxout(self) -> bool:
|
||||
"""
|
||||
Read the state of the MUXOUT line.
|
||||
|
||||
|
@ -140,7 +150,7 @@ class ADF5356:
|
|||
return bool(self.cpld.read_reg(0) & (1 << (self.channel + 8)))
|
||||
|
||||
@kernel
|
||||
def set_output_power_mu(self, n):
|
||||
def set_output_power_mu(self, n: int32):
|
||||
"""
|
||||
Set the power level at output A of the PLL chip in machine units.
|
||||
|
||||
|
@ -148,13 +158,13 @@ class ADF5356:
|
|||
|
||||
:param n: output power setting, 0, 1, 2, or 3 (see ADF5356 datasheet, fig. 44).
|
||||
"""
|
||||
if n not in [0, 1, 2, 3]:
|
||||
if not 0 <= n <= 3:
|
||||
raise ValueError("invalid power setting")
|
||||
self.regs[6] = ADF5356_REG6_RF_OUTPUT_A_POWER_UPDATE(self.regs[6], n)
|
||||
self.sync()
|
||||
|
||||
@portable
|
||||
def output_power_mu(self):
|
||||
def output_power_mu(self) -> int32:
|
||||
"""
|
||||
Return the power level at output A of the PLL chip in machine units.
|
||||
"""
|
||||
|
@ -177,13 +187,13 @@ class ADF5356:
|
|||
self.sync()
|
||||
|
||||
@kernel
|
||||
def set_frequency(self, f):
|
||||
def set_frequency(self, f: float):
|
||||
"""
|
||||
Output given frequency on output A.
|
||||
|
||||
:param f: 53.125 MHz <= f <= 6800 MHz
|
||||
"""
|
||||
freq = int64(round(f))
|
||||
freq = round64(f)
|
||||
|
||||
if freq > ADF5356_MAX_VCO_FREQ:
|
||||
raise ValueError("Requested too high frequency")
|
||||
|
@ -209,7 +219,7 @@ class ADF5356:
|
|||
n_min, n_max = 75, 65535
|
||||
|
||||
# adjust reference divider to be able to match n_min constraint
|
||||
while n_min * f_pfd > freq:
|
||||
while int64(n_min) * f_pfd > freq:
|
||||
r = ADF5356_REG4_R_COUNTER_GET(self.regs[4])
|
||||
self.regs[4] = ADF5356_REG4_R_COUNTER_UPDATE(self.regs[4], r + 1)
|
||||
f_pfd = self.f_pfd()
|
||||
|
@ -237,10 +247,10 @@ class ADF5356:
|
|||
|
||||
self.regs[6] = ADF5356_REG6_RF_DIVIDER_SELECT_UPDATE(self.regs[6], rf_div_sel)
|
||||
self.regs[6] = ADF5356_REG6_CP_BLEED_CURRENT_UPDATE(
|
||||
self.regs[6], int32(floor(24 * f_pfd / (61.44 * MHz)))
|
||||
self.regs[6], floor(24. * float(f_pfd) / (61.44 * MHz))
|
||||
)
|
||||
self.regs[9] = ADF5356_REG9_VCO_BAND_DIVISION_UPDATE(
|
||||
self.regs[9], int32(ceil(f_pfd / 160e3))
|
||||
self.regs[9], ceil(float(f_pfd) / 160e3)
|
||||
)
|
||||
|
||||
# commit
|
||||
|
@ -252,12 +262,12 @@ class ADF5356:
|
|||
Write all registers to the device. Attempts to lock the PLL.
|
||||
"""
|
||||
f_pfd = self.f_pfd()
|
||||
delay(200 * us) # Slack
|
||||
self.core.delay(200. * us) # Slack
|
||||
|
||||
if f_pfd <= 75.0 * MHz:
|
||||
if f_pfd <= round64(75.0 * MHz):
|
||||
for i in range(13, 0, -1):
|
||||
self.write(self.regs[i])
|
||||
delay(200 * us)
|
||||
self.core.delay(200. * us)
|
||||
self.write(self.regs[0] | ADF5356_REG0_AUTOCAL(1))
|
||||
else:
|
||||
# AUTOCAL AT HALF PFD FREQUENCY
|
||||
|
@ -266,7 +276,7 @@ class ADF5356:
|
|||
n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb) = calculate_pll(
|
||||
self.f_vco(), f_pfd >> 1
|
||||
)
|
||||
delay(200 * us) # Slack
|
||||
self.core.delay(200. * us) # Slack
|
||||
|
||||
self.write(
|
||||
13
|
||||
|
@ -289,7 +299,7 @@ class ADF5356:
|
|||
)
|
||||
self.write(1 | ADF5356_REG1_MAIN_FRAC_VALUE(frac1))
|
||||
|
||||
delay(200 * us)
|
||||
self.core.delay(200. * us)
|
||||
self.write(ADF5356_REG0_INT_VALUE(n) | ADF5356_REG0_AUTOCAL(1))
|
||||
|
||||
# RELOCK AT WANTED PFD FREQUENCY
|
||||
|
@ -301,7 +311,7 @@ class ADF5356:
|
|||
self.write(self.regs[0] & ~ADF5356_REG0_AUTOCAL(1))
|
||||
|
||||
@portable
|
||||
def f_pfd(self) -> TInt64:
|
||||
def f_pfd(self) -> int64:
|
||||
"""
|
||||
Return the PFD frequency for the cached set of registers.
|
||||
"""
|
||||
|
@ -311,35 +321,35 @@ class ADF5356:
|
|||
return self._compute_pfd_frequency(r, d, t)
|
||||
|
||||
@portable
|
||||
def f_vco(self) -> TInt64:
|
||||
def f_vco(self) -> int64:
|
||||
"""
|
||||
Return the VCO frequency for the cached set of registers.
|
||||
"""
|
||||
return int64(
|
||||
self.f_pfd()
|
||||
return round64(
|
||||
float(self.f_pfd())
|
||||
* (
|
||||
self.pll_n()
|
||||
+ (self.pll_frac1() + self.pll_frac2() / self.pll_mod2())
|
||||
/ ADF5356_MODULUS1
|
||||
)
|
||||
float(self.pll_n())
|
||||
+ (float(self.pll_frac1() + self.pll_frac2()) / float(self.pll_mod2()))
|
||||
/ float(ADF5356_MODULUS1)
|
||||
)
|
||||
)
|
||||
|
||||
@portable
|
||||
def pll_n(self) -> TInt32:
|
||||
def pll_n(self) -> int32:
|
||||
"""
|
||||
Return the PLL integer value (INT) for the cached set of registers.
|
||||
"""
|
||||
return ADF5356_REG0_INT_VALUE_GET(self.regs[0])
|
||||
|
||||
@portable
|
||||
def pll_frac1(self) -> TInt32:
|
||||
def pll_frac1(self) -> int32:
|
||||
"""
|
||||
Return the main fractional value (FRAC1) for the cached set of registers.
|
||||
"""
|
||||
return ADF5356_REG1_MAIN_FRAC_VALUE_GET(self.regs[1])
|
||||
|
||||
@portable
|
||||
def pll_frac2(self) -> TInt32:
|
||||
def pll_frac2(self) -> int32:
|
||||
"""
|
||||
Return the auxiliary fractional value (FRAC2) for the cached set of registers.
|
||||
"""
|
||||
|
@ -348,7 +358,7 @@ class ADF5356:
|
|||
) | ADF5356_REG2_AUX_FRAC_LSB_VALUE_GET(self.regs[2])
|
||||
|
||||
@portable
|
||||
def pll_mod2(self) -> TInt32:
|
||||
def pll_mod2(self) -> int32:
|
||||
"""
|
||||
Return the auxiliary modulus value (MOD2) for the cached set of registers.
|
||||
"""
|
||||
|
@ -357,14 +367,14 @@ class ADF5356:
|
|||
) | ADF5356_REG2_AUX_MOD_LSB_VALUE_GET(self.regs[2])
|
||||
|
||||
@portable
|
||||
def ref_counter(self) -> TInt32:
|
||||
def ref_counter(self) -> int32:
|
||||
"""
|
||||
Return the reference counter value (R) for the cached set of registers.
|
||||
"""
|
||||
return ADF5356_REG4_R_COUNTER_GET(self.regs[4])
|
||||
|
||||
@portable
|
||||
def output_divider(self) -> TInt32:
|
||||
def output_divider(self) -> int32:
|
||||
"""
|
||||
Return the value of the output A divider.
|
||||
"""
|
||||
|
@ -414,7 +424,7 @@ class ADF5356:
|
|||
|
||||
# single-ended reference mode is recommended
|
||||
# for references up to 250 MHz, even if the signal is differential
|
||||
if self.sysclk <= 250 * MHz:
|
||||
if self.sysclk <= 250.*MHz:
|
||||
self.regs[4] |= ADF5356_REG4_REF_MODE(0)
|
||||
else:
|
||||
self.regs[4] |= ADF5356_REG4_REF_MODE(1)
|
||||
|
@ -456,7 +466,7 @@ class ADF5356:
|
|||
|
||||
# charge pump bleed current
|
||||
self.regs[6] |= ADF5356_REG6_CP_BLEED_CURRENT(
|
||||
int32(floor(24 * self.f_pfd() / (61.44 * MHz)))
|
||||
floor(24. * float(self.f_pfd()) / (61.44 * MHz))
|
||||
)
|
||||
|
||||
# direct feedback from VCO to N counter
|
||||
|
@ -500,7 +510,7 @@ class ADF5356:
|
|||
)
|
||||
|
||||
self.regs[9] |= ADF5356_REG9_VCO_BAND_DIVISION(
|
||||
int32(ceil(self.f_pfd() / 160e3))
|
||||
ceil(float(self.f_pfd()) / 160e3)
|
||||
)
|
||||
|
||||
# REG10
|
||||
|
@ -529,39 +539,39 @@ class ADF5356:
|
|||
self.regs[12] = int32(0x15FC)
|
||||
|
||||
@portable
|
||||
def _compute_pfd_frequency(self, r, d, t) -> TInt64:
|
||||
def _compute_pfd_frequency(self, r: int32, d: int32, t: int32) -> int64:
|
||||
"""
|
||||
Calculate the PFD frequency from the given reference path parameters
|
||||
"""
|
||||
return int64(self.sysclk * ((1 + d) / (r * (1 + t))))
|
||||
return round64(self.sysclk * (float(1 + d) / float(r * (1 + t))))
|
||||
|
||||
@portable
|
||||
def _compute_reference_counter(self) -> TInt32:
|
||||
def _compute_reference_counter(self) -> int32:
|
||||
"""
|
||||
Determine the reference counter R that maximizes the PFD frequency
|
||||
"""
|
||||
d = ADF5356_REG4_R_DOUBLER_GET(self.regs[4])
|
||||
t = ADF5356_REG4_R_DIVIDER_GET(self.regs[4])
|
||||
r = 1
|
||||
while self._compute_pfd_frequency(r, d, t) > ADF5356_MAX_FREQ_PFD:
|
||||
while self._compute_pfd_frequency(r, d, t) > int64(ADF5356_MAX_FREQ_PFD):
|
||||
r += 1
|
||||
return int32(r)
|
||||
return r
|
||||
|
||||
|
||||
@portable
|
||||
def gcd(a, b):
|
||||
while b:
|
||||
def gcd(a: int64, b: int64) -> int64:
|
||||
while b != int64(0):
|
||||
a, b = b, a % b
|
||||
return a
|
||||
|
||||
|
||||
@portable
|
||||
def split_msb_lsb_28b(v):
|
||||
return int32((v >> 14) & 0x3FFF), int32(v & 0x3FFF)
|
||||
def split_msb_lsb_28b(v: int32) -> tuple[int32, int32]:
|
||||
return (v >> 14) & 0x3FFF, v & 0x3FFF
|
||||
|
||||
|
||||
@portable
|
||||
def calculate_pll(f_vco: TInt64, f_pfd: TInt64):
|
||||
def calculate_pll(f_vco: int64, f_pfd: int64) -> tuple[int32, int32, tuple[int32, int32], tuple[int32, int32]]:
|
||||
"""
|
||||
Calculate fractional-N PLL parameters such that
|
||||
|
||||
|
@ -574,20 +584,18 @@ def calculate_pll(f_vco: TInt64, f_pfd: TInt64):
|
|||
:param f_pfd: PFD frequency
|
||||
:return: ``(n, frac1, (frac2_msb, frac2_lsb), (mod2_msb, mod2_lsb))``
|
||||
"""
|
||||
f_pfd = int64(f_pfd)
|
||||
f_vco = int64(f_vco)
|
||||
|
||||
# integral part
|
||||
n, r = int32(f_vco // f_pfd), f_vco % f_pfd
|
||||
|
||||
# main fractional part
|
||||
r *= ADF5356_MODULUS1
|
||||
r *= int64(ADF5356_MODULUS1)
|
||||
frac1, frac2 = int32(r // f_pfd), r % f_pfd
|
||||
|
||||
# auxiliary fractional part
|
||||
mod2 = f_pfd
|
||||
|
||||
while mod2 > ADF5356_MAX_MODULUS2:
|
||||
while mod2 > int64(ADF5356_MAX_MODULUS2):
|
||||
mod2 >>= 1
|
||||
frac2 >>= 1
|
||||
|
||||
|
@ -595,4 +603,4 @@ def calculate_pll(f_vco: TInt64, f_pfd: TInt64):
|
|||
mod2 //= gcd_div
|
||||
frac2 //= gcd_div
|
||||
|
||||
return n, frac1, split_msb_lsb_28b(frac2), split_msb_lsb_28b(mod2)
|
||||
return n, frac1, split_msb_lsb_28b(int32(frac2)), split_msb_lsb_28b(int32(mod2))
|
||||
|
|
|
@ -1,642 +1,643 @@
|
|||
# auto-generated, do not edit
|
||||
from artiq.language.core import portable
|
||||
from artiq.language.types import TInt32
|
||||
from numpy import int32
|
||||
|
||||
from artiq.language.core import portable
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG0_AUTOCAL_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG0_AUTOCAL_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 21) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG0_AUTOCAL(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG0_AUTOCAL(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 21)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG0_AUTOCAL_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG0_AUTOCAL_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 21)) | ((x & 0x1) << 21))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG0_INT_VALUE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG0_INT_VALUE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 4) & 0xffff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG0_INT_VALUE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG0_INT_VALUE(x: int32) -> int32:
|
||||
return int32((x & 0xffff) << 4)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG0_INT_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG0_INT_VALUE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0xffff << 4)) | ((x & 0xffff) << 4))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG0_PRESCALER_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG0_PRESCALER_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 20) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG0_PRESCALER(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG0_PRESCALER(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 20)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG0_PRESCALER_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG0_PRESCALER_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 20)) | ((x & 0x1) << 20))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG1_MAIN_FRAC_VALUE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG1_MAIN_FRAC_VALUE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 4) & 0xffffff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG1_MAIN_FRAC_VALUE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG1_MAIN_FRAC_VALUE(x: int32) -> int32:
|
||||
return int32((x & 0xffffff) << 4)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG1_MAIN_FRAC_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG1_MAIN_FRAC_VALUE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0xffffff << 4)) | ((x & 0xffffff) << 4))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG2_AUX_FRAC_LSB_VALUE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG2_AUX_FRAC_LSB_VALUE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 18) & 0x3fff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG2_AUX_FRAC_LSB_VALUE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG2_AUX_FRAC_LSB_VALUE(x: int32) -> int32:
|
||||
return int32((x & 0x3fff) << 18)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG2_AUX_FRAC_LSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG2_AUX_FRAC_LSB_VALUE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x3fff << 18)) | ((x & 0x3fff) << 18))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG2_AUX_MOD_LSB_VALUE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG2_AUX_MOD_LSB_VALUE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 4) & 0x3fff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG2_AUX_MOD_LSB_VALUE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG2_AUX_MOD_LSB_VALUE(x: int32) -> int32:
|
||||
return int32((x & 0x3fff) << 4)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG2_AUX_MOD_LSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG2_AUX_MOD_LSB_VALUE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x3fff << 4)) | ((x & 0x3fff) << 4))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_PHASE_ADJUST_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_PHASE_ADJUST_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 28) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_PHASE_ADJUST(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_PHASE_ADJUST(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 28)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_PHASE_ADJUST_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_PHASE_ADJUST_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 28)) | ((x & 0x1) << 28))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_PHASE_RESYNC_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_PHASE_RESYNC_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 29) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_PHASE_RESYNC(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_PHASE_RESYNC(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 29)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_PHASE_RESYNC_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_PHASE_RESYNC_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 29)) | ((x & 0x1) << 29))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_PHASE_VALUE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_PHASE_VALUE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 4) & 0xffffff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_PHASE_VALUE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_PHASE_VALUE(x: int32) -> int32:
|
||||
return int32((x & 0xffffff) << 4)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_PHASE_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_PHASE_VALUE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0xffffff << 4)) | ((x & 0xffffff) << 4))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_SD_LOAD_RESET_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_SD_LOAD_RESET_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 30) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_SD_LOAD_RESET(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_SD_LOAD_RESET(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 30)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG3_SD_LOAD_RESET_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG3_SD_LOAD_RESET_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 30)) | ((x & 0x1) << 30))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_COUNTER_RESET_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_COUNTER_RESET_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 4) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_COUNTER_RESET(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_COUNTER_RESET(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 4)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_COUNTER_RESET_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_COUNTER_RESET_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_CP_THREE_STATE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_CP_THREE_STATE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 5) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_CP_THREE_STATE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_CP_THREE_STATE(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 5)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_CP_THREE_STATE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_CP_THREE_STATE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 5)) | ((x & 0x1) << 5))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_CURRENT_SETTING_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_CURRENT_SETTING_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 10) & 0xf)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_CURRENT_SETTING(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_CURRENT_SETTING(x: int32) -> int32:
|
||||
return int32((x & 0xf) << 10)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_CURRENT_SETTING_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_CURRENT_SETTING_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0xf << 10)) | ((x & 0xf) << 10))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_DOUBLE_BUFF_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_DOUBLE_BUFF_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 14) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_DOUBLE_BUFF(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_DOUBLE_BUFF(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 14)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_DOUBLE_BUFF_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_DOUBLE_BUFF_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 14)) | ((x & 0x1) << 14))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_MUX_LOGIC_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_MUX_LOGIC_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 8) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_MUX_LOGIC(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_MUX_LOGIC(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 8)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_MUX_LOGIC_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_MUX_LOGIC_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 8)) | ((x & 0x1) << 8))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_MUXOUT_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_MUXOUT_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 27) & 0x7)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_MUXOUT(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_MUXOUT(x: int32) -> int32:
|
||||
return int32((x & 0x7) << 27)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_MUXOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_MUXOUT_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x7 << 27)) | ((x & 0x7) << 27))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_PD_POLARITY_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_PD_POLARITY_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 7) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_PD_POLARITY(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_PD_POLARITY(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 7)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_PD_POLARITY_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_PD_POLARITY_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 7)) | ((x & 0x1) << 7))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_POWER_DOWN_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_POWER_DOWN_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 6) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_POWER_DOWN(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_POWER_DOWN(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 6)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_POWER_DOWN_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_POWER_DOWN_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 6)) | ((x & 0x1) << 6))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_R_COUNTER_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_R_COUNTER_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 15) & 0x3ff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_R_COUNTER(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_R_COUNTER(x: int32) -> int32:
|
||||
return int32((x & 0x3ff) << 15)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_R_COUNTER_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_R_COUNTER_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x3ff << 15)) | ((x & 0x3ff) << 15))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_R_DIVIDER_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_R_DIVIDER_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 25) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_R_DIVIDER(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_R_DIVIDER(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 25)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_R_DIVIDER_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_R_DIVIDER_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 25)) | ((x & 0x1) << 25))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_R_DOUBLER_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_R_DOUBLER_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 26) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_R_DOUBLER(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_R_DOUBLER(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 26)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_R_DOUBLER_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_R_DOUBLER_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 26)) | ((x & 0x1) << 26))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_REF_MODE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_REF_MODE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 9) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_REF_MODE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_REF_MODE(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 9)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG4_REF_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG4_REF_MODE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 9)) | ((x & 0x1) << 9))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_BLEED_POLARITY_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_BLEED_POLARITY_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 31) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_BLEED_POLARITY(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_BLEED_POLARITY(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 31)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_BLEED_POLARITY_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_BLEED_POLARITY_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 31)) | ((x & 0x1) << 31))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_CP_BLEED_CURRENT_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_CP_BLEED_CURRENT_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 13) & 0xff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_CP_BLEED_CURRENT(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_CP_BLEED_CURRENT(x: int32) -> int32:
|
||||
return int32((x & 0xff) << 13)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_CP_BLEED_CURRENT_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_CP_BLEED_CURRENT_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0xff << 13)) | ((x & 0xff) << 13))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_FB_SELECT_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_FB_SELECT_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 24) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_FB_SELECT(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_FB_SELECT(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 24)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_FB_SELECT_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_FB_SELECT_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 24)) | ((x & 0x1) << 24))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_GATE_BLEED_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_GATE_BLEED_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 30) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_GATE_BLEED(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_GATE_BLEED(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 30)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_GATE_BLEED_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_GATE_BLEED_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 30)) | ((x & 0x1) << 30))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_MUTE_TILL_LD_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_MUTE_TILL_LD_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 11) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_MUTE_TILL_LD(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_MUTE_TILL_LD(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 11)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_MUTE_TILL_LD_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_MUTE_TILL_LD_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 11)) | ((x & 0x1) << 11))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_NEGATIVE_BLEED_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_NEGATIVE_BLEED_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 29) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_NEGATIVE_BLEED(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_NEGATIVE_BLEED(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 29)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_NEGATIVE_BLEED_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_NEGATIVE_BLEED_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 29)) | ((x & 0x1) << 29))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_DIVIDER_SELECT_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_DIVIDER_SELECT_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 21) & 0x7)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_DIVIDER_SELECT(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_DIVIDER_SELECT(x: int32) -> int32:
|
||||
return int32((x & 0x7) << 21)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_DIVIDER_SELECT_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_DIVIDER_SELECT_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x7 << 21)) | ((x & 0x7) << 21))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_OUTPUT_A_ENABLE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_OUTPUT_A_ENABLE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 6) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_OUTPUT_A_ENABLE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_OUTPUT_A_ENABLE(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 6)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_OUTPUT_A_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_OUTPUT_A_ENABLE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 6)) | ((x & 0x1) << 6))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_OUTPUT_A_POWER_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_OUTPUT_A_POWER_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 4) & 0x3)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_OUTPUT_A_POWER(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_OUTPUT_A_POWER(x: int32) -> int32:
|
||||
return int32((x & 0x3) << 4)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_OUTPUT_A_POWER_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_OUTPUT_A_POWER_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x3 << 4)) | ((x & 0x3) << 4))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_OUTPUT_B_ENABLE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_OUTPUT_B_ENABLE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 10) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_OUTPUT_B_ENABLE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_OUTPUT_B_ENABLE(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 10)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG6_RF_OUTPUT_B_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG6_RF_OUTPUT_B_ENABLE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 10)) | ((x & 0x1) << 10))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_FRAC_N_LD_PRECISION_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_FRAC_N_LD_PRECISION_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 5) & 0x3)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_FRAC_N_LD_PRECISION(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_FRAC_N_LD_PRECISION(x: int32) -> int32:
|
||||
return int32((x & 0x3) << 5)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_FRAC_N_LD_PRECISION_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_FRAC_N_LD_PRECISION_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x3 << 5)) | ((x & 0x3) << 5))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LD_CYCLE_COUNT_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LD_CYCLE_COUNT_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 8) & 0x3)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LD_CYCLE_COUNT(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LD_CYCLE_COUNT(x: int32) -> int32:
|
||||
return int32((x & 0x3) << 8)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LD_CYCLE_COUNT_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LD_CYCLE_COUNT_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x3 << 8)) | ((x & 0x3) << 8))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LD_MODE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LD_MODE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 4) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LD_MODE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LD_MODE(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 4)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LD_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LD_MODE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LE_SEL_SYNC_EDGE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LE_SEL_SYNC_EDGE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 27) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LE_SEL_SYNC_EDGE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LE_SEL_SYNC_EDGE(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 27)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LE_SEL_SYNC_EDGE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LE_SEL_SYNC_EDGE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 27)) | ((x & 0x1) << 27))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LE_SYNC_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LE_SYNC_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 25) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LE_SYNC(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LE_SYNC(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 25)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LE_SYNC_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LE_SYNC_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 25)) | ((x & 0x1) << 25))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LOL_MODE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LOL_MODE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 7) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LOL_MODE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LOL_MODE(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 7)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG7_LOL_MODE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG7_LOL_MODE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 7)) | ((x & 0x1) << 7))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_AUTOCAL_TIMEOUT_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_AUTOCAL_TIMEOUT_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 9) & 0x1f)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_AUTOCAL_TIMEOUT(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_AUTOCAL_TIMEOUT(x: int32) -> int32:
|
||||
return int32((x & 0x1f) << 9)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_AUTOCAL_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_AUTOCAL_TIMEOUT_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1f << 9)) | ((x & 0x1f) << 9))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 4) & 0x1f)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_SYNTH_LOCK_TIMEOUT(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_SYNTH_LOCK_TIMEOUT(x: int32) -> int32:
|
||||
return int32((x & 0x1f) << 4)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_SYNTH_LOCK_TIMEOUT_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1f << 4)) | ((x & 0x1f) << 4))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_TIMEOUT_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_TIMEOUT_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 14) & 0x3ff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_TIMEOUT(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_TIMEOUT(x: int32) -> int32:
|
||||
return int32((x & 0x3ff) << 14)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_TIMEOUT_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_TIMEOUT_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x3ff << 14)) | ((x & 0x3ff) << 14))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_VCO_BAND_DIVISION_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_VCO_BAND_DIVISION_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 24) & 0xff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_VCO_BAND_DIVISION(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_VCO_BAND_DIVISION(x: int32) -> int32:
|
||||
return int32((x & 0xff) << 24)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG9_VCO_BAND_DIVISION_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG9_VCO_BAND_DIVISION_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0xff << 24)) | ((x & 0xff) << 24))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG10_ADC_CLK_DIV_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG10_ADC_CLK_DIV_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 6) & 0xff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG10_ADC_CLK_DIV(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG10_ADC_CLK_DIV(x: int32) -> int32:
|
||||
return int32((x & 0xff) << 6)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG10_ADC_CLK_DIV_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG10_ADC_CLK_DIV_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0xff << 6)) | ((x & 0xff) << 6))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG10_ADC_CONV_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG10_ADC_CONV_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 5) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG10_ADC_CONV(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG10_ADC_CONV(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 5)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG10_ADC_CONV_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG10_ADC_CONV_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 5)) | ((x & 0x1) << 5))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG10_ADC_ENABLE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG10_ADC_ENABLE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 4) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG10_ADC_ENABLE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG10_ADC_ENABLE(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 4)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG10_ADC_ENABLE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG10_ADC_ENABLE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 4)) | ((x & 0x1) << 4))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG11_VCO_BAND_HOLD_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG11_VCO_BAND_HOLD_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 24) & 0x1)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG11_VCO_BAND_HOLD(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG11_VCO_BAND_HOLD(x: int32) -> int32:
|
||||
return int32((x & 0x1) << 24)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG11_VCO_BAND_HOLD_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG11_VCO_BAND_HOLD_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x1 << 24)) | ((x & 0x1) << 24))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 12) & 0xfffff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE(x: int32) -> int32:
|
||||
return int32((x & 0xfffff) << 12)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG12_PHASE_RESYNC_CLK_VALUE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0xfffff << 12)) | ((x & 0xfffff) << 12))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG13_AUX_FRAC_MSB_VALUE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG13_AUX_FRAC_MSB_VALUE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 18) & 0x3fff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG13_AUX_FRAC_MSB_VALUE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG13_AUX_FRAC_MSB_VALUE(x: int32) -> int32:
|
||||
return int32((x & 0x3fff) << 18)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG13_AUX_FRAC_MSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG13_AUX_FRAC_MSB_VALUE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x3fff << 18)) | ((x & 0x3fff) << 18))
|
||||
|
||||
|
||||
@portable
|
||||
def ADF5356_REG13_AUX_MOD_MSB_VALUE_GET(reg: TInt32) -> TInt32:
|
||||
def ADF5356_REG13_AUX_MOD_MSB_VALUE_GET(reg: int32) -> int32:
|
||||
return int32((reg >> 4) & 0x3fff)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG13_AUX_MOD_MSB_VALUE(x: TInt32) -> TInt32:
|
||||
def ADF5356_REG13_AUX_MOD_MSB_VALUE(x: int32) -> int32:
|
||||
return int32((x & 0x3fff) << 4)
|
||||
|
||||
@portable
|
||||
def ADF5356_REG13_AUX_MOD_MSB_VALUE_UPDATE(reg: TInt32, x: TInt32) -> TInt32:
|
||||
def ADF5356_REG13_AUX_MOD_MSB_VALUE_UPDATE(reg: int32, x: int32) -> int32:
|
||||
return int32((reg & ~(0x3fff << 4)) | ((x & 0x3fff) << 4))
|
||||
|
||||
ADF5356_NUM_REGS = 14
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
from artiq.language.core import kernel, portable
|
||||
|
||||
from numpy import int32
|
||||
|
||||
from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable
|
||||
from artiq.language.units import us
|
||||
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.mirny import Mirny
|
||||
from artiq.coredevice.spi2 import *
|
||||
|
||||
|
||||
# almazny-specific data
|
||||
ALMAZNY_LEGACY_REG_BASE = 0x0C
|
||||
|
@ -14,6 +19,7 @@ ALMAZNY_LEGACY_OE_SHIFT = 12
|
|||
ALMAZNY_LEGACY_SPIT_WR = 32
|
||||
|
||||
|
||||
@nac3
|
||||
class AlmaznyLegacy:
|
||||
"""
|
||||
Almazny (High frequency mezzanine board for Mirny)
|
||||
|
@ -24,8 +30,15 @@ class AlmaznyLegacy:
|
|||
:param host_mirny: Mirny device Almazny is connected to
|
||||
"""
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
mirny_cpld: KernelInvariant[Mirny]
|
||||
att_mu: Kernel[list[int32]]
|
||||
channel_sw: Kernel[list[int32]]
|
||||
output_enable: Kernel[bool]
|
||||
|
||||
def __init__(self, dmgr, host_mirny):
|
||||
self.mirny_cpld = dmgr.get(host_mirny)
|
||||
self.core = self.mirny_cpld.core
|
||||
self.att_mu = [0x3f] * 4
|
||||
self.channel_sw = [0] * 4
|
||||
self.output_enable = False
|
||||
|
@ -35,7 +48,7 @@ class AlmaznyLegacy:
|
|||
self.output_toggle(self.output_enable)
|
||||
|
||||
@kernel
|
||||
def att_to_mu(self, att):
|
||||
def att_to_mu(self, att: float) -> int32:
|
||||
"""
|
||||
Convert an attenuator setting in dB to machine units.
|
||||
|
||||
|
@ -48,17 +61,17 @@ class AlmaznyLegacy:
|
|||
return mu
|
||||
|
||||
@kernel
|
||||
def mu_to_att(self, att_mu):
|
||||
def mu_to_att(self, att_mu: int32) -> float:
|
||||
"""
|
||||
Convert a digital attenuator setting to dB.
|
||||
|
||||
:param att_mu: attenuator setting in machine units
|
||||
:return: attenuator setting in dB
|
||||
"""
|
||||
return att_mu / 2
|
||||
return float(att_mu) / 2.
|
||||
|
||||
@kernel
|
||||
def set_att(self, channel, att, rf_switch=True):
|
||||
def set_att(self, channel: int32, att: float, rf_switch: bool = True):
|
||||
"""
|
||||
Sets attenuators on chosen shift register (channel).
|
||||
|
||||
|
@ -69,7 +82,7 @@ class AlmaznyLegacy:
|
|||
self.set_att_mu(channel, self.att_to_mu(att), rf_switch)
|
||||
|
||||
@kernel
|
||||
def set_att_mu(self, channel, att_mu, rf_switch=True):
|
||||
def set_att_mu(self, channel: int32, att_mu: int32, rf_switch: bool = True):
|
||||
"""
|
||||
Sets attenuators on chosen shift register (channel).
|
||||
|
||||
|
@ -82,7 +95,7 @@ class AlmaznyLegacy:
|
|||
self._update_register(channel)
|
||||
|
||||
@kernel
|
||||
def output_toggle(self, oe):
|
||||
def output_toggle(self, oe: bool):
|
||||
"""
|
||||
Toggles output on all shift registers on or off.
|
||||
|
||||
|
@ -91,13 +104,13 @@ class AlmaznyLegacy:
|
|||
self.output_enable = oe
|
||||
cfg_reg = self.mirny_cpld.read_reg(1)
|
||||
en = 1 if self.output_enable else 0
|
||||
delay(100 * us)
|
||||
self.core.delay(100. * us)
|
||||
new_reg = (en << ALMAZNY_LEGACY_OE_SHIFT) | (cfg_reg & 0x3FF)
|
||||
self.mirny_cpld.write_reg(1, new_reg)
|
||||
delay(100 * us)
|
||||
self.core.delay(100. * us)
|
||||
|
||||
@kernel
|
||||
def _flip_mu_bits(self, mu):
|
||||
def _flip_mu_bits(self, mu: int32) -> int32:
|
||||
# in this form MSB is actually 0.5dB attenuator
|
||||
# unnatural for users, so we flip the six bits
|
||||
return (((mu & 0x01) << 5)
|
||||
|
@ -108,14 +121,14 @@ class AlmaznyLegacy:
|
|||
| ((mu & 0x20) >> 5))
|
||||
|
||||
@kernel
|
||||
def _update_register(self, ch):
|
||||
def _update_register(self, ch: int32):
|
||||
self.mirny_cpld.write_ext(
|
||||
ALMAZNY_LEGACY_REG_BASE + ch,
|
||||
8,
|
||||
self._flip_mu_bits(self.att_mu[ch]) | (self.channel_sw[ch] << 6),
|
||||
ALMAZNY_LEGACY_SPIT_WR
|
||||
)
|
||||
delay(100 * us)
|
||||
self.core.delay(100. * us)
|
||||
|
||||
@kernel
|
||||
def _update_all_registers(self):
|
||||
|
@ -123,6 +136,7 @@ class AlmaznyLegacy:
|
|||
self._update_register(i)
|
||||
|
||||
|
||||
@nac3
|
||||
class AlmaznyChannel:
|
||||
"""
|
||||
One Almazny channel
|
||||
|
@ -136,12 +150,17 @@ class AlmaznyChannel:
|
|||
:param channel: channel index (0-3)
|
||||
"""
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
mirny_cpld: KernelInvariant[Mirny]
|
||||
channel: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, host_mirny, channel):
|
||||
self.channel = channel
|
||||
self.mirny_cpld = dmgr.get(host_mirny)
|
||||
self.core = self.mirny_cpld.core
|
||||
self.channel = channel
|
||||
|
||||
@portable
|
||||
def to_mu(self, att, enable, led):
|
||||
def to_mu(self, att: float, enable: bool, led: bool) -> int32:
|
||||
"""
|
||||
Convert an attenuation in dB, RF switch state and LED state to machine
|
||||
units.
|
||||
|
@ -151,7 +170,7 @@ class AlmaznyChannel:
|
|||
:param led: LED state (bool)
|
||||
:return: channel setting in machine units
|
||||
"""
|
||||
mu = int32(round(att * 2.))
|
||||
mu = round(att * 2.)
|
||||
if mu >= 64 or mu < 0:
|
||||
raise ValueError("Attenuation out of range")
|
||||
# unfortunate hardware design: bit reverse
|
||||
|
@ -164,7 +183,7 @@ class AlmaznyChannel:
|
|||
return mu
|
||||
|
||||
@kernel
|
||||
def set_mu(self, mu):
|
||||
def set_mu(self, mu: int32):
|
||||
"""
|
||||
Set channel state (machine units).
|
||||
|
||||
|
@ -174,7 +193,7 @@ class AlmaznyChannel:
|
|||
addr=0xc + self.channel, length=8, data=mu, ext_div=32)
|
||||
|
||||
@kernel
|
||||
def set(self, att, enable, led=False):
|
||||
def set(self, att: float, enable: bool, led: bool = False):
|
||||
"""
|
||||
Set attenuation, RF switch, and LED state (SI units).
|
||||
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
from artiq.language.core import *
|
||||
from artiq.language.types import *
|
||||
from numpy import int32
|
||||
|
||||
from artiq.language.core import nac3, extern, kernel, KernelInvariant
|
||||
from artiq.coredevice.core import Core
|
||||
|
||||
|
||||
@syscall(flags={"nounwind"})
|
||||
def cache_get(key: TStr) -> TList(TInt32):
|
||||
@extern
|
||||
def cache_get(key: str) -> list[int32]:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall
|
||||
def cache_put(key: TStr, value: TList(TInt32)) -> TNone:
|
||||
@extern
|
||||
def cache_put(key: str, value: list[int32]):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@nac3
|
||||
class CoreCache:
|
||||
"""Core device cache access"""
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
|
||||
def __init__(self, dmgr, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
||||
@kernel
|
||||
def get(self, key):
|
||||
def get(self, key: str) -> list[int32]:
|
||||
"""Extract a value from the core device cache.
|
||||
After a value is extracted, it cannot be replaced with another value using
|
||||
:meth:`put` until all kernel functions finish executing; attempting
|
||||
|
@ -34,7 +40,7 @@ class CoreCache:
|
|||
return cache_get(key)
|
||||
|
||||
@kernel
|
||||
def put(self, key, value):
|
||||
def put(self, key: str, value: list[int32]):
|
||||
"""Put a value into the core device cache. The value will persist until reboot.
|
||||
|
||||
To remove a value from the cache, call :meth:`put` with an empty list.
|
||||
|
|
|
@ -3,12 +3,17 @@ import logging
|
|||
import traceback
|
||||
import numpy
|
||||
import socket
|
||||
import re
|
||||
import linecache
|
||||
import os
|
||||
from enum import Enum
|
||||
from fractions import Fraction
|
||||
from collections import namedtuple
|
||||
|
||||
from artiq.coredevice import exceptions
|
||||
from artiq import __version__ as software_version
|
||||
from artiq import __artiq_dir__ as artiq_dir
|
||||
|
||||
from sipyco.keepalive import create_connection
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -23,8 +28,6 @@ class Request(Enum):
|
|||
RPCReply = 7
|
||||
RPCException = 8
|
||||
|
||||
SubkernelUpload = 9
|
||||
|
||||
|
||||
class Reply(Enum):
|
||||
SystemInfo = 2
|
||||
|
@ -165,13 +168,116 @@ class CommKernelDummy:
|
|||
def run(self):
|
||||
pass
|
||||
|
||||
def serve(self, embedding_map, symbolizer, demangler):
|
||||
def serve(self, embedding_map, symbolizer):
|
||||
pass
|
||||
|
||||
def check_system_info(self):
|
||||
pass
|
||||
|
||||
|
||||
class SourceLoader:
|
||||
def __init__(self, runtime_root):
|
||||
self.runtime_root = runtime_root
|
||||
|
||||
def get_source(self, filename):
|
||||
with open(os.path.join(self.runtime_root, filename)) as f:
|
||||
return f.read()
|
||||
|
||||
source_loader = SourceLoader(os.path.join(artiq_dir, "soc", "runtime"))
|
||||
|
||||
|
||||
class CoreException:
|
||||
"""Information about an exception raised or passed through the core device."""
|
||||
def __init__(self, exceptions, exception_info, traceback, stack_pointers):
|
||||
self.exceptions = exceptions
|
||||
self.exception_info = exception_info
|
||||
self.traceback = list(traceback)
|
||||
self.stack_pointers = stack_pointers
|
||||
|
||||
first_exception = exceptions[0]
|
||||
name = first_exception[0]
|
||||
if ':' in name:
|
||||
exn_id, self.name = name.split(':', 2)
|
||||
self.id = int(exn_id)
|
||||
else:
|
||||
self.id, self.name = 0, name
|
||||
self.message = first_exception[1]
|
||||
self.params = first_exception[2]
|
||||
|
||||
def append_backtrace(self, record, inlined=False):
|
||||
filename, line, column, function, address = record
|
||||
stub_globals = {"__name__": filename, "__loader__": source_loader}
|
||||
source_line = linecache.getline(filename, line, stub_globals)
|
||||
indentation = re.search(r"^\s*", source_line).end()
|
||||
|
||||
if address is None:
|
||||
formatted_address = ""
|
||||
elif inlined:
|
||||
formatted_address = " (inlined)"
|
||||
else:
|
||||
formatted_address = " (RA=+0x{:x})".format(address)
|
||||
|
||||
filename = filename.replace(artiq_dir, "<artiq>")
|
||||
lines = []
|
||||
if column == -1:
|
||||
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
|
||||
lines.append(" File \"{file}\", line {line}, in {function}{address}".
|
||||
format(file=filename, line=line, function=function,
|
||||
address=formatted_address))
|
||||
else:
|
||||
lines.append(" {}^".format(" " * (column - indentation)))
|
||||
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
|
||||
lines.append(" File \"{file}\", line {line}, column {column},"
|
||||
" in {function}{address}".
|
||||
format(file=filename, line=line, column=column + 1,
|
||||
function=function, address=formatted_address))
|
||||
return lines
|
||||
|
||||
def single_traceback(self, exception_index):
|
||||
# note that we insert in reversed order
|
||||
lines = []
|
||||
last_sp = 0
|
||||
start_backtrace_index = self.exception_info[exception_index][1]
|
||||
zipped = list(zip(self.traceback[start_backtrace_index:],
|
||||
self.stack_pointers[start_backtrace_index:]))
|
||||
exception = self.exceptions[exception_index]
|
||||
name = exception[0]
|
||||
message = exception[1]
|
||||
params = exception[2]
|
||||
if ':' in name:
|
||||
exn_id, name = name.split(':', 2)
|
||||
exn_id = int(exn_id)
|
||||
else:
|
||||
exn_id = 0
|
||||
lines.append("{}({}): {}".format(name, exn_id, message.format(*params)))
|
||||
zipped.append(((exception[3], exception[4], exception[5], exception[6],
|
||||
None, []), None))
|
||||
|
||||
for ((filename, line, column, function, address, inlined), sp) in zipped:
|
||||
# backtrace of nested exceptions may be discontinuous
|
||||
# but the stack pointer must increase monotonically
|
||||
if sp is not None and sp <= last_sp:
|
||||
continue
|
||||
last_sp = sp
|
||||
|
||||
for record in reversed(inlined):
|
||||
lines += self.append_backtrace(record, True)
|
||||
lines += self.append_backtrace((filename, line, column, function,
|
||||
address))
|
||||
|
||||
lines.append("Traceback (most recent call first):")
|
||||
|
||||
return "\n".join(reversed(lines))
|
||||
|
||||
def __str__(self):
|
||||
tracebacks = [self.single_traceback(i) for i in range(len(self.exceptions))]
|
||||
traceback_str = ('\n\nDuring handling of the above exception, ' +
|
||||
'another exception occurred:\n\n').join(tracebacks)
|
||||
return 'Core Device Traceback:\n' +\
|
||||
traceback_str +\
|
||||
'\n\nEnd of Core Device Traceback\n'
|
||||
|
||||
|
||||
def incompatible_versions(v1, v2):
|
||||
if v1.endswith(".beta") or v2.endswith(".beta"):
|
||||
# Beta branches may introduce breaking changes. Check version strictly.
|
||||
|
@ -210,7 +316,6 @@ class CommKernel:
|
|||
self.unpack_float64 = struct.Struct(self.endian + "d").unpack
|
||||
|
||||
self.pack_header = struct.Struct(self.endian + "lB").pack
|
||||
self.pack_int8 = struct.Struct(self.endian + "B").pack
|
||||
self.pack_int32 = struct.Struct(self.endian + "l").pack
|
||||
self.pack_int64 = struct.Struct(self.endian + "q").pack
|
||||
self.pack_float64 = struct.Struct(self.endian + "d").pack
|
||||
|
@ -325,7 +430,7 @@ class CommKernel:
|
|||
self._write(chunk)
|
||||
|
||||
def _write_int8(self, value):
|
||||
self._write(self.pack_int8(value))
|
||||
self._write(value)
|
||||
|
||||
def _write_int32(self, value):
|
||||
self._write(self.pack_int32(value))
|
||||
|
@ -385,19 +490,6 @@ class CommKernel:
|
|||
else:
|
||||
self._read_expect(Reply.LoadCompleted)
|
||||
|
||||
def upload_subkernel(self, kernel_library, id, destination):
|
||||
self._write_header(Request.SubkernelUpload)
|
||||
self._write_int32(id)
|
||||
self._write_int8(destination)
|
||||
self._write_bytes(kernel_library)
|
||||
self._flush()
|
||||
|
||||
self._read_header()
|
||||
if self._read_type == Reply.LoadFailed:
|
||||
raise LoadError(self._read_string())
|
||||
else:
|
||||
self._read_expect(Reply.LoadCompleted)
|
||||
|
||||
def run(self):
|
||||
self._write_empty(Request.RunKernel)
|
||||
self._flush()
|
||||
|
@ -579,9 +671,19 @@ class CommKernel:
|
|||
return_tags = self._read_bytes()
|
||||
|
||||
if service_id == 0:
|
||||
def service(obj, attr, value): return setattr(obj, attr, value)
|
||||
def service(*values):
|
||||
counter = 0
|
||||
for obj in embedding_map.attributes_writeback:
|
||||
old_val = obj["obj"]
|
||||
if "fields" in obj:
|
||||
for name in obj["fields"]:
|
||||
setattr(old_val, name, values[counter])
|
||||
counter += 1
|
||||
else:
|
||||
old_val[:] = values[counter]
|
||||
counter += 1
|
||||
else:
|
||||
service = embedding_map.retrieve_object(service_id)
|
||||
service = embedding_map.retrieve_function(service_id)
|
||||
logger.debug("rpc service: [%d]%r%s %r %r -> %s", service_id, service,
|
||||
(" (async)" if is_async else ""), args, kwargs, return_tags)
|
||||
|
||||
|
@ -650,7 +752,7 @@ class CommKernel:
|
|||
result, result, service)
|
||||
self._flush()
|
||||
|
||||
def _serve_exception(self, embedding_map, symbolizer, demangler):
|
||||
def _serve_exception(self, embedding_map, symbolizer):
|
||||
exception_count = self._read_int32()
|
||||
nested_exceptions = []
|
||||
|
||||
|
@ -674,10 +776,6 @@ class CommKernel:
|
|||
nested_exceptions.append([name, message, params,
|
||||
filename, line, column, function])
|
||||
|
||||
demangled_names = demangler([ex[6] for ex in nested_exceptions])
|
||||
for i in range(exception_count):
|
||||
nested_exceptions[i][6] = demangled_names[i]
|
||||
|
||||
exception_info = []
|
||||
for _ in range(exception_count):
|
||||
sp = self._read_int32()
|
||||
|
@ -694,7 +792,7 @@ class CommKernel:
|
|||
self._process_async_error()
|
||||
|
||||
traceback = list(symbolizer(backtrace))
|
||||
core_exn = exceptions.CoreException(nested_exceptions, exception_info,
|
||||
core_exn = CoreException(nested_exceptions, exception_info,
|
||||
traceback, stack_pointers)
|
||||
|
||||
if core_exn.id == 0:
|
||||
|
@ -723,13 +821,13 @@ class CommKernel:
|
|||
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):
|
||||
while True:
|
||||
self._read_header()
|
||||
if self._read_type == Reply.RPCRequest:
|
||||
self._serve_rpc(embedding_map)
|
||||
elif self._read_type == Reply.KernelException:
|
||||
self._serve_exception(embedding_map, symbolizer, demangler)
|
||||
self._serve_exception(embedding_map, symbolizer)
|
||||
elif self._read_type == Reply.ClockFailure:
|
||||
raise exceptions.ClockFailure
|
||||
else:
|
||||
|
|
|
@ -1,70 +1,39 @@
|
|||
import os, sys
|
||||
import numpy
|
||||
from inspect import getfullargspec
|
||||
from functools import wraps
|
||||
import os, sys, tempfile, subprocess, io
|
||||
from numpy import int32, int64
|
||||
|
||||
from pythonparser import diagnostic
|
||||
|
||||
from artiq import __artiq_dir__ as artiq_dir
|
||||
import nac3artiq
|
||||
|
||||
from artiq.language.core import *
|
||||
from artiq.language.types import *
|
||||
from artiq.language.core import _ConstGenericMarker
|
||||
from artiq.language import core as core_language
|
||||
from artiq.language.units import *
|
||||
|
||||
from artiq.compiler.module import Module
|
||||
from artiq.compiler.embedding import Stitcher
|
||||
from artiq.compiler.targets import RV32IMATarget, RV32GTarget, CortexA9Target
|
||||
from artiq.language.embedding_map import EmbeddingMap
|
||||
|
||||
from artiq.coredevice.comm_kernel import CommKernel, CommKernelDummy
|
||||
# Import for side effects (creating the exception classes).
|
||||
from artiq.coredevice import exceptions
|
||||
|
||||
|
||||
def _render_diagnostic(diagnostic, colored):
|
||||
def shorten_path(path):
|
||||
return path.replace(artiq_dir, "<artiq>")
|
||||
lines = [shorten_path(path) for path in diagnostic.render(colored=colored)]
|
||||
return "\n".join(lines)
|
||||
|
||||
colors_supported = os.name == "posix"
|
||||
class _DiagnosticEngine(diagnostic.Engine):
|
||||
def render_diagnostic(self, diagnostic):
|
||||
sys.stderr.write(_render_diagnostic(diagnostic, colored=colors_supported) + "\n")
|
||||
|
||||
class CompileError(Exception):
|
||||
def __init__(self, diagnostic):
|
||||
self.diagnostic = diagnostic
|
||||
|
||||
def __str__(self):
|
||||
# Prepend a newline so that the message shows up on after
|
||||
# exception class name printed by Python.
|
||||
return "\n" + _render_diagnostic(self.diagnostic, colored=colors_supported)
|
||||
|
||||
|
||||
@syscall
|
||||
def rtio_init() -> TNone:
|
||||
@extern
|
||||
def rtio_init():
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall(flags={"nounwind", "nowrite"})
|
||||
def rtio_get_destination_status(linkno: TInt32) -> TBool:
|
||||
@extern
|
||||
def rtio_get_destination_status(destination: int32) -> bool:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall(flags={"nounwind", "nowrite"})
|
||||
def rtio_get_counter() -> TInt64:
|
||||
@extern
|
||||
def rtio_get_counter() -> int64:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
def get_target_cls(target):
|
||||
if target == "rv32g":
|
||||
return RV32GTarget
|
||||
elif target == "rv32ima":
|
||||
return RV32IMATarget
|
||||
elif target == "cortexa9":
|
||||
return CortexA9Target
|
||||
else:
|
||||
raise ValueError("Unsupported target")
|
||||
artiq_builtins = {
|
||||
"none": none,
|
||||
"virtual": virtual,
|
||||
"_ConstGenericMarker": _ConstGenericMarker,
|
||||
"Option": Option,
|
||||
}
|
||||
|
||||
|
||||
@nac3
|
||||
class Core:
|
||||
"""Core device driver.
|
||||
|
||||
|
@ -78,183 +47,87 @@ class Core:
|
|||
:param ref_multiplier: ratio between the RTIO fine timestamp frequency
|
||||
and the RTIO coarse timestamp frequency (e.g. SERDES multiplication
|
||||
factor).
|
||||
:param analyzer_proxy: name of the core device analyzer proxy to trigger
|
||||
(optional).
|
||||
:param analyze_at_run_end: automatically trigger the core device analyzer
|
||||
proxy after the Experiment's run stage finishes.
|
||||
"""
|
||||
ref_period: KernelInvariant[float]
|
||||
ref_multiplier: KernelInvariant[int32]
|
||||
coarse_ref_period: KernelInvariant[float]
|
||||
|
||||
kernel_invariants = {
|
||||
"core", "ref_period", "coarse_ref_period", "ref_multiplier",
|
||||
}
|
||||
|
||||
def __init__(self, dmgr,
|
||||
host, ref_period,
|
||||
analyzer_proxy=None, analyze_at_run_end=False,
|
||||
ref_multiplier=8,
|
||||
target="rv32g", satellite_cpu_targets={}):
|
||||
def __init__(self, dmgr, host, ref_period, ref_multiplier=8, target="rv32g"):
|
||||
self.ref_period = ref_period
|
||||
self.ref_multiplier = ref_multiplier
|
||||
self.satellite_cpu_targets = satellite_cpu_targets
|
||||
self.target_cls = get_target_cls(target)
|
||||
self.coarse_ref_period = ref_period*ref_multiplier
|
||||
if host is None:
|
||||
self.comm = CommKernelDummy()
|
||||
else:
|
||||
self.comm = CommKernel(host)
|
||||
self.analyzer_proxy_name = analyzer_proxy
|
||||
self.analyze_at_run_end = analyze_at_run_end
|
||||
|
||||
self.first_run = True
|
||||
self.dmgr = dmgr
|
||||
self.core = self
|
||||
self.comm.core = self
|
||||
self.analyzer_proxy = None
|
||||
|
||||
def notify_run_end(self):
|
||||
if self.analyze_at_run_end:
|
||||
self.trigger_analyzer_proxy()
|
||||
self.target = target
|
||||
self.analyzed = False
|
||||
self.compiler = nac3artiq.NAC3(target, artiq_builtins)
|
||||
|
||||
def close(self):
|
||||
self.comm.close()
|
||||
|
||||
def compile(self, function, args, kwargs, set_result=None,
|
||||
attribute_writeback=True, print_as_rpc=True,
|
||||
target=None, destination=0, subkernel_arg_types=[]):
|
||||
try:
|
||||
engine = _DiagnosticEngine(all_errors_are_fatal=True)
|
||||
def compile(self, method, args, kwargs, embedding_map, file_output=None, target=None):
|
||||
if target is not None:
|
||||
# NAC3TODO: subkernels
|
||||
raise NotImplementedError
|
||||
|
||||
stitcher = Stitcher(engine=engine, core=self, dmgr=self.dmgr,
|
||||
print_as_rpc=print_as_rpc,
|
||||
destination=destination, subkernel_arg_types=subkernel_arg_types)
|
||||
stitcher.stitch_call(function, args, kwargs, set_result)
|
||||
stitcher.finalize()
|
||||
if not self.analyzed:
|
||||
self.compiler.analyze(core_language._registered_functions, core_language._registered_classes)
|
||||
self.analyzed = True
|
||||
|
||||
module = Module(stitcher,
|
||||
ref_period=self.ref_period,
|
||||
attribute_writeback=attribute_writeback)
|
||||
target = target if target is not None else self.target_cls()
|
||||
if hasattr(method, "__self__"):
|
||||
obj = method.__self__
|
||||
name = method.__name__
|
||||
else:
|
||||
obj = method
|
||||
name = ""
|
||||
|
||||
library = target.compile_and_link([module])
|
||||
stripped_library = target.strip(library)
|
||||
if file_output is None:
|
||||
return self.compiler.compile_method_to_mem(obj, name, args, embedding_map)
|
||||
else:
|
||||
self.compiler.compile_method_to_file(obj, name, args, file_output, embedding_map)
|
||||
|
||||
return stitcher.embedding_map, stripped_library, \
|
||||
lambda addresses: target.symbolize(library, addresses), \
|
||||
lambda symbols: target.demangle(symbols), \
|
||||
module.subkernel_arg_types
|
||||
except diagnostic.Error as error:
|
||||
raise CompileError(error.diagnostic) from error
|
||||
|
||||
def _run_compiled(self, kernel_library, embedding_map, symbolizer, demangler):
|
||||
def run(self, function, args, kwargs):
|
||||
embedding_map = EmbeddingMap()
|
||||
kernel_library = self.compile(function, args, kwargs, embedding_map)
|
||||
if self.first_run:
|
||||
self.comm.check_system_info()
|
||||
self.first_run = False
|
||||
|
||||
symbolizer = lambda addresses: symbolize(kernel_library, addresses)
|
||||
|
||||
self.comm.load(kernel_library)
|
||||
self.comm.run()
|
||||
self.comm.serve(embedding_map, symbolizer, demangler)
|
||||
|
||||
def run(self, function, args, kwargs):
|
||||
result = None
|
||||
@rpc(flags={"async"})
|
||||
def set_result(new_result):
|
||||
nonlocal result
|
||||
result = new_result
|
||||
embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
|
||||
self.compile(function, args, kwargs, set_result)
|
||||
self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types)
|
||||
self._run_compiled(kernel_library, embedding_map, symbolizer, demangler)
|
||||
return result
|
||||
|
||||
def compile_subkernel(self, sid, subkernel_fn, embedding_map, args, subkernel_arg_types):
|
||||
# pass self to subkernels (if applicable)
|
||||
# assuming the first argument is self
|
||||
subkernel_args = getfullargspec(subkernel_fn.artiq_embedded.function)
|
||||
self_arg = []
|
||||
if len(subkernel_args[0]) > 0:
|
||||
if subkernel_args[0][0] == 'self':
|
||||
self_arg = args[:1]
|
||||
destination = subkernel_fn.artiq_embedded.destination
|
||||
destination_tgt = self.satellite_cpu_targets[destination]
|
||||
target = get_target_cls(destination_tgt)(subkernel_id=sid)
|
||||
object_map, kernel_library, _, _, _ = \
|
||||
self.compile(subkernel_fn, self_arg, {}, attribute_writeback=False,
|
||||
print_as_rpc=False, target=target, destination=destination,
|
||||
subkernel_arg_types=subkernel_arg_types.get(sid, []))
|
||||
if object_map.has_rpc_or_subkernel():
|
||||
raise ValueError("Subkernel must not use RPC or subkernels in other destinations")
|
||||
return destination, kernel_library
|
||||
|
||||
def compile_and_upload_subkernels(self, embedding_map, args, subkernel_arg_types):
|
||||
for sid, subkernel_fn in embedding_map.subkernels().items():
|
||||
destination, kernel_library = \
|
||||
self.compile_subkernel(sid, subkernel_fn, embedding_map,
|
||||
args, subkernel_arg_types)
|
||||
self.comm.upload_subkernel(kernel_library, sid, destination)
|
||||
|
||||
def precompile(self, function, *args, **kwargs):
|
||||
"""Precompile a kernel and return a callable that executes it on the core device
|
||||
at a later time.
|
||||
|
||||
Arguments to the kernel are set at compilation time and passed to this function,
|
||||
as additional positional and keyword arguments.
|
||||
The returned callable accepts no arguments.
|
||||
|
||||
Precompiled kernels may use RPCs and subkernels.
|
||||
|
||||
Object attributes at the beginning of a precompiled kernel execution have the
|
||||
values they had at precompilation time. If up-to-date values are required,
|
||||
use RPC to read them.
|
||||
Similarly, modified values are not written back, and explicit RPC should be used
|
||||
to modify host objects.
|
||||
Carefully review the source code of drivers calls used in precompiled kernels, as
|
||||
they may rely on host object attributes being transfered between kernel calls.
|
||||
Examples include code used to control DDS phase, and Urukul RF switch control
|
||||
via the CPLD register.
|
||||
|
||||
The return value of the callable is the return value of the kernel, if any.
|
||||
|
||||
The callable may be called several times.
|
||||
"""
|
||||
if not hasattr(function, "artiq_embedded"):
|
||||
raise ValueError("Argument is not a kernel")
|
||||
|
||||
result = None
|
||||
@rpc(flags={"async"})
|
||||
def set_result(new_result):
|
||||
nonlocal result
|
||||
result = new_result
|
||||
|
||||
embedding_map, kernel_library, symbolizer, demangler, subkernel_arg_types = \
|
||||
self.compile(function, args, kwargs, set_result, attribute_writeback=False)
|
||||
self.compile_and_upload_subkernels(embedding_map, args, subkernel_arg_types)
|
||||
|
||||
@wraps(function)
|
||||
def run_precompiled():
|
||||
nonlocal result
|
||||
self._run_compiled(kernel_library, embedding_map, symbolizer, demangler)
|
||||
return result
|
||||
|
||||
return run_precompiled
|
||||
self.comm.serve(embedding_map, symbolizer)
|
||||
|
||||
@portable
|
||||
def seconds_to_mu(self, seconds):
|
||||
def seconds_to_mu(self, seconds: float) -> int64:
|
||||
"""Convert seconds to the corresponding number of machine units
|
||||
(RTIO cycles).
|
||||
|
||||
:param seconds: time (in seconds) to convert.
|
||||
"""
|
||||
return numpy.int64(seconds//self.ref_period)
|
||||
return int64(seconds//self.ref_period)
|
||||
|
||||
@portable
|
||||
def mu_to_seconds(self, mu):
|
||||
def mu_to_seconds(self, mu: int64) -> float:
|
||||
"""Convert machine units (RTIO cycles) to seconds.
|
||||
|
||||
:param mu: cycle count to convert.
|
||||
"""
|
||||
return mu*self.ref_period
|
||||
return float(mu)*self.ref_period
|
||||
|
||||
@kernel
|
||||
def get_rtio_counter_mu(self):
|
||||
def delay(self, dt: float):
|
||||
delay_mu(self.seconds_to_mu(dt))
|
||||
|
||||
@kernel
|
||||
def get_rtio_counter_mu(self) -> int64:
|
||||
"""Retrieve the current value of the hardware RTIO timeline counter.
|
||||
|
||||
As the timing of kernel code executed on the CPU is inherently
|
||||
|
@ -267,7 +140,7 @@ class Core:
|
|||
return rtio_get_counter()
|
||||
|
||||
@kernel
|
||||
def wait_until_mu(self, cursor_mu):
|
||||
def wait_until_mu(self, cursor_mu: int64):
|
||||
"""Block execution until the hardware RTIO counter reaches the given
|
||||
value (see :meth:`get_rtio_counter_mu`).
|
||||
|
||||
|
@ -278,7 +151,7 @@ class Core:
|
|||
pass
|
||||
|
||||
@kernel
|
||||
def get_rtio_destination_status(self, destination):
|
||||
def get_rtio_destination_status(self, destination: int32) -> bool:
|
||||
"""Returns whether the specified RTIO destination is up.
|
||||
This is particularly useful in startup kernels to delay
|
||||
startup until certain DRTIO destinations are up."""
|
||||
|
@ -290,7 +163,7 @@ class Core:
|
|||
at the current value of the hardware RTIO counter plus a margin of
|
||||
125000 machine units."""
|
||||
rtio_init()
|
||||
at_mu(rtio_get_counter() + 125000)
|
||||
at_mu(rtio_get_counter() + int64(125000))
|
||||
|
||||
@kernel
|
||||
def break_realtime(self):
|
||||
|
@ -299,26 +172,99 @@ class Core:
|
|||
|
||||
If the time cursor is already after that position, this function
|
||||
does nothing."""
|
||||
min_now = rtio_get_counter() + 125000
|
||||
min_now = rtio_get_counter() + int64(125000)
|
||||
if now_mu() < min_now:
|
||||
at_mu(min_now)
|
||||
|
||||
def trigger_analyzer_proxy(self):
|
||||
"""Causes the core analyzer proxy to retrieve a dump from the device,
|
||||
and distribute it to all connected clients (typically dashboards).
|
||||
|
||||
Returns only after the dump has been retrieved from the device.
|
||||
class RunTool:
|
||||
def __init__(self, pattern, **tempdata):
|
||||
self._pattern = pattern
|
||||
self._tempdata = tempdata
|
||||
self._tempnames = {}
|
||||
self._tempfiles = {}
|
||||
|
||||
def __enter__(self):
|
||||
for key, data in self._tempdata.items():
|
||||
if data is None:
|
||||
fd, filename = tempfile.mkstemp()
|
||||
os.close(fd)
|
||||
self._tempnames[key] = filename
|
||||
else:
|
||||
with tempfile.NamedTemporaryFile(delete=False) as f:
|
||||
f.write(data)
|
||||
self._tempnames[key] = f.name
|
||||
|
||||
cmdline = []
|
||||
for argument in self._pattern:
|
||||
cmdline.append(argument.format(**self._tempnames))
|
||||
|
||||
# https://bugs.python.org/issue17023
|
||||
windows = os.name == "nt"
|
||||
process = subprocess.Popen(cmdline, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||||
universal_newlines=True, shell=windows)
|
||||
stdout, stderr = process.communicate()
|
||||
if process.returncode != 0:
|
||||
raise Exception("{} invocation failed: {}".
|
||||
format(cmdline[0], stderr))
|
||||
|
||||
self._tempfiles["__stdout__"] = io.StringIO(stdout)
|
||||
for key in self._tempdata:
|
||||
if self._tempdata[key] is None:
|
||||
self._tempfiles[key] = open(self._tempnames[key], "rb")
|
||||
return self._tempfiles
|
||||
|
||||
def __exit__(self, exc_typ, exc_value, exc_trace):
|
||||
for file in self._tempfiles.values():
|
||||
file.close()
|
||||
for filename in self._tempnames.values():
|
||||
os.unlink(filename)
|
||||
|
||||
|
||||
def symbolize(library, addresses):
|
||||
if addresses == []:
|
||||
return []
|
||||
|
||||
# We got a list of return addresses, i.e. addresses of instructions
|
||||
# just after the call. Offset them back to get an address somewhere
|
||||
# inside the call instruction (or its delay slot), since that's what
|
||||
# the backtrace entry should point at.
|
||||
last_inlined = None
|
||||
offset_addresses = [hex(addr - 1) for addr in addresses]
|
||||
with RunTool(["llvm-addr2line", "--addresses", "--functions", "--inlines",
|
||||
"--demangle", "--exe={library}"] + offset_addresses,
|
||||
library=library) \
|
||||
as results:
|
||||
lines = iter(results["__stdout__"].read().rstrip().split("\n"))
|
||||
backtrace = []
|
||||
while True:
|
||||
try:
|
||||
address_or_function = next(lines)
|
||||
except StopIteration:
|
||||
break
|
||||
if address_or_function[:2] == "0x":
|
||||
address = int(address_or_function[2:], 16) + 1 # remove offset
|
||||
function = next(lines)
|
||||
inlined = False
|
||||
else:
|
||||
address = backtrace[-1][4] # inlined
|
||||
function = address_or_function
|
||||
inlined = True
|
||||
location = next(lines)
|
||||
|
||||
filename, line = location.rsplit(":", 1)
|
||||
if filename == "??" or filename == "<synthesized>":
|
||||
continue
|
||||
if line == "?":
|
||||
line = -1
|
||||
else:
|
||||
line = int(line)
|
||||
# can't get column out of addr2line D:
|
||||
if inlined:
|
||||
last_inlined.append((filename, line, -1, function, address))
|
||||
else:
|
||||
last_inlined = []
|
||||
backtrace.append((filename, line, -1, function, address,
|
||||
last_inlined))
|
||||
return backtrace
|
||||
|
||||
Raises IOError if no analyzer proxy has been configured, or if the
|
||||
analyzer proxy fails. In the latter case, more details would be
|
||||
available in the proxy log.
|
||||
"""
|
||||
if self.analyzer_proxy is None:
|
||||
if self.analyzer_proxy_name is not None:
|
||||
self.analyzer_proxy = self.dmgr.get(self.analyzer_proxy_name)
|
||||
if self.analyzer_proxy is None:
|
||||
raise IOError("No analyzer proxy configured")
|
||||
else:
|
||||
success = self.analyzer_proxy.trigger()
|
||||
if not success:
|
||||
raise IOError("Analyzer proxy reported failure")
|
||||
|
|
|
@ -142,7 +142,7 @@
|
|||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["dio", "dio_spi", "urukul", "novogorny", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"]
|
||||
"enum": ["dio", "dio_spi", "urukul", "sampler", "suservo", "zotino", "grabber", "mirny", "fastino", "phaser", "hvamp", "shuttler"]
|
||||
},
|
||||
"board": {
|
||||
"type": "string"
|
||||
|
@ -315,10 +315,8 @@
|
|||
"type": "integer"
|
||||
},
|
||||
"pll_en": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"default": 1
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"pll_vco": {
|
||||
"type": "integer"
|
||||
|
@ -331,50 +329,6 @@
|
|||
},
|
||||
"required": ["ports"]
|
||||
}
|
||||
}, {
|
||||
"title": "Novogorny",
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "novogorny"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 1
|
||||
}
|
||||
},
|
||||
"required": ["ports"]
|
||||
}
|
||||
}, {
|
||||
"title": "Sampler",
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": {
|
||||
"const": "sampler"
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"ports": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer"
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 2
|
||||
}
|
||||
},
|
||||
"required": ["ports"]
|
||||
}
|
||||
}, {
|
||||
"title": "SUServo",
|
||||
"if": {
|
||||
|
@ -429,10 +383,8 @@
|
|||
"default": 32
|
||||
},
|
||||
"pll_en": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 1,
|
||||
"default": 1
|
||||
"type": "boolean",
|
||||
"default": true
|
||||
},
|
||||
"pll_vco": {
|
||||
"type": "integer"
|
||||
|
@ -630,7 +582,8 @@
|
|||
"maxItems": 2
|
||||
},
|
||||
"drtio_destination": {
|
||||
"type": "integer"
|
||||
"type": "integer",
|
||||
"default": 4
|
||||
}
|
||||
},
|
||||
"required": ["ports"]
|
||||
|
|
|
@ -5,34 +5,36 @@ the core device's SDRAM, and playing them back at higher speeds than the CPU
|
|||
alone could achieve.
|
||||
"""
|
||||
|
||||
from artiq.language.core import syscall, kernel
|
||||
from artiq.language.types import TInt32, TInt64, TStr, TNone, TTuple, TBool
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import nac3, extern, kernel, Kernel, KernelInvariant
|
||||
from artiq.coredevice.exceptions import DMAError
|
||||
|
||||
from numpy import int64
|
||||
from artiq.coredevice.core import Core
|
||||
|
||||
|
||||
@syscall
|
||||
def dma_record_start(name: TStr) -> TNone:
|
||||
|
||||
@extern
|
||||
def dma_record_start(name: str):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall
|
||||
def dma_record_stop(duration: TInt64, enable_ddma: TBool) -> TNone:
|
||||
@extern
|
||||
def dma_record_stop(duration: int64, enable_ddma: bool):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall
|
||||
def dma_erase(name: TStr) -> TNone:
|
||||
@extern
|
||||
def dma_erase(name: str):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall
|
||||
def dma_retrieve(name: TStr) -> TTuple([TInt64, TInt32, TBool]):
|
||||
@extern
|
||||
def dma_retrieve(name: str) -> tuple[int64, int32, bool]:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
@syscall
|
||||
def dma_playback(timestamp: TInt64, ptr: TInt32, enable_ddma: TBool) -> TNone:
|
||||
@extern
|
||||
def dma_playback(timestamp: int64, ptr: int32, enable_ddma: bool):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@nac3
|
||||
class DMARecordContextManager:
|
||||
"""Context manager returned by :meth:`CoreDMA.record()`.
|
||||
|
||||
|
@ -44,6 +46,9 @@ class DMARecordContextManager:
|
|||
are stored in a newly created trace, and ``now`` is restored to the value
|
||||
it had before the context manager was entered.
|
||||
"""
|
||||
name: Kernel[str]
|
||||
saved_now_mu: Kernel[int64]
|
||||
|
||||
def __init__(self):
|
||||
self.name = ""
|
||||
self.saved_now_mu = int64(0)
|
||||
|
@ -53,21 +58,24 @@ class DMARecordContextManager:
|
|||
def __enter__(self):
|
||||
dma_record_start(self.name) # this may raise, so do it before altering now
|
||||
self.saved_now_mu = now_mu()
|
||||
at_mu(0)
|
||||
at_mu(int64(0))
|
||||
|
||||
@kernel
|
||||
def __exit__(self, type, value, traceback):
|
||||
def __exit__(self):
|
||||
dma_record_stop(now_mu(), self.enable_ddma) # see above
|
||||
at_mu(self.saved_now_mu)
|
||||
|
||||
|
||||
@nac3
|
||||
class CoreDMA:
|
||||
"""Core device Direct Memory Access (DMA) driver.
|
||||
|
||||
Gives access to the DMA functionality of the core device.
|
||||
"""
|
||||
|
||||
kernel_invariants = {"core", "recorder"}
|
||||
core: KernelInvariant[Core]
|
||||
recorder: KernelInvariant[DMARecordContextManager]
|
||||
epoch: Kernel[int32]
|
||||
|
||||
def __init__(self, dmgr, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -75,7 +83,7 @@ class CoreDMA:
|
|||
self.epoch = 0
|
||||
|
||||
@kernel
|
||||
def record(self, name, enable_ddma=False):
|
||||
def record(self, name: str, enable_ddma: bool = False) -> DMARecordContextManager:
|
||||
"""Returns a context manager that will record a DMA trace called ``name``.
|
||||
Any previously recorded trace with the same name is overwritten.
|
||||
The trace will persist across kernel switches.
|
||||
|
@ -92,13 +100,13 @@ class CoreDMA:
|
|||
return self.recorder
|
||||
|
||||
@kernel
|
||||
def erase(self, name):
|
||||
def erase(self, name: str):
|
||||
"""Removes the DMA trace with the given name from storage."""
|
||||
self.epoch += 1
|
||||
dma_erase(name)
|
||||
|
||||
@kernel
|
||||
def playback(self, name):
|
||||
def playback(self, name: str):
|
||||
"""Replays a previously recorded DMA trace. This function blocks until
|
||||
the entire trace is submitted to the RTIO FIFOs."""
|
||||
(advance_mu, ptr, uses_ddma) = dma_retrieve(name)
|
||||
|
@ -106,14 +114,14 @@ class CoreDMA:
|
|||
delay_mu(advance_mu)
|
||||
|
||||
@kernel
|
||||
def get_handle(self, name):
|
||||
def get_handle(self, name: str) -> tuple[int32, int64, int32]:
|
||||
"""Returns a handle to a previously recorded DMA trace. The returned handle
|
||||
is only valid until the next call to :meth:`record` or :meth:`erase`."""
|
||||
(advance_mu, ptr, uses_ddma) = dma_retrieve(name)
|
||||
return (self.epoch, advance_mu, ptr, uses_ddma)
|
||||
|
||||
@kernel
|
||||
def playback_handle(self, handle):
|
||||
def playback_handle(self, handle: tuple[int32, int64, int32]):
|
||||
"""Replays a handle obtained with :meth:`get_handle`. Using this function
|
||||
is much faster than :meth:`playback` for replaying a set of traces repeatedly,
|
||||
but incurs the overhead of managing the handles onto the programmer."""
|
||||
|
|
|
@ -51,11 +51,13 @@ See :mod:`artiq.gateware.rtio.phy.edge_counter` and
|
|||
:meth:`artiq.gateware.eem.DIO.add_std` for the gateware components.
|
||||
"""
|
||||
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.language.core import *
|
||||
from artiq.language.types import *
|
||||
from artiq.coredevice.rtio import (rtio_output, rtio_input_data,
|
||||
rtio_input_timestamped_data)
|
||||
from numpy import int32, int64
|
||||
|
||||
|
||||
CONFIG_COUNT_RISING = 0b0001
|
||||
CONFIG_COUNT_FALLING = 0b0010
|
||||
|
@ -63,12 +65,14 @@ CONFIG_SEND_COUNT_EVENT = 0b0100
|
|||
CONFIG_RESET_TO_ZERO = 0b1000
|
||||
|
||||
|
||||
@nac3
|
||||
class CounterOverflow(Exception):
|
||||
"""Raised when an edge counter value is read which indicates that the
|
||||
counter might have overflowed."""
|
||||
pass
|
||||
|
||||
|
||||
@nac3
|
||||
class EdgeCounter:
|
||||
"""RTIO TTL edge counter driver driver.
|
||||
|
||||
|
@ -84,7 +88,9 @@ class EdgeCounter:
|
|||
the gateware needs to be rebuilt.
|
||||
"""
|
||||
|
||||
kernel_invariants = {"core", "channel", "counter_max"}
|
||||
core: KernelInvariant[Core]
|
||||
channel: KernelInvariant[int32]
|
||||
counter_max: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, channel, gateware_width=31, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -96,7 +102,7 @@ class EdgeCounter:
|
|||
return [(channel, None)]
|
||||
|
||||
@kernel
|
||||
def gate_rising(self, duration):
|
||||
def gate_rising(self, duration: float) -> int64:
|
||||
"""Count rising edges for the given duration and request the total at
|
||||
the end.
|
||||
|
||||
|
@ -110,7 +116,7 @@ class EdgeCounter:
|
|||
return self.gate_rising_mu(self.core.seconds_to_mu(duration))
|
||||
|
||||
@kernel
|
||||
def gate_falling(self, duration):
|
||||
def gate_falling(self, duration: float) -> int64:
|
||||
"""Count falling edges for the given duration and request the total at
|
||||
the end.
|
||||
|
||||
|
@ -124,7 +130,7 @@ class EdgeCounter:
|
|||
return self.gate_falling_mu(self.core.seconds_to_mu(duration))
|
||||
|
||||
@kernel
|
||||
def gate_both(self, duration):
|
||||
def gate_both(self, duration: float) -> int64:
|
||||
"""Count both rising and falling edges for the given duration, and
|
||||
request the total at the end.
|
||||
|
||||
|
@ -138,25 +144,25 @@ class EdgeCounter:
|
|||
return self.gate_both_mu(self.core.seconds_to_mu(duration))
|
||||
|
||||
@kernel
|
||||
def gate_rising_mu(self, duration_mu):
|
||||
def gate_rising_mu(self, duration_mu: int64) -> int64:
|
||||
"""See :meth:`gate_rising`."""
|
||||
return self._gate_mu(
|
||||
duration_mu, count_rising=True, count_falling=False)
|
||||
|
||||
@kernel
|
||||
def gate_falling_mu(self, duration_mu):
|
||||
def gate_falling_mu(self, duration_mu: int64) -> int64:
|
||||
"""See :meth:`gate_falling`."""
|
||||
return self._gate_mu(
|
||||
duration_mu, count_rising=False, count_falling=True)
|
||||
|
||||
@kernel
|
||||
def gate_both_mu(self, duration_mu):
|
||||
def gate_both_mu(self, duration_mu: int64) -> int64:
|
||||
"""See :meth:`gate_both_mu`."""
|
||||
return self._gate_mu(
|
||||
duration_mu, count_rising=True, count_falling=True)
|
||||
|
||||
@kernel
|
||||
def _gate_mu(self, duration_mu, count_rising, count_falling):
|
||||
def _gate_mu(self, duration_mu: int64, count_rising: bool, count_falling: bool) -> int64:
|
||||
self.set_config(
|
||||
count_rising=count_rising,
|
||||
count_falling=count_falling,
|
||||
|
@ -171,8 +177,8 @@ class EdgeCounter:
|
|||
return now_mu()
|
||||
|
||||
@kernel
|
||||
def set_config(self, count_rising: TBool, count_falling: TBool,
|
||||
send_count_event: TBool, reset_to_zero: TBool):
|
||||
def set_config(self, count_rising: bool, count_falling: bool,
|
||||
send_count_event: bool, reset_to_zero: bool):
|
||||
"""Emit an RTIO event at the current timeline position to set the
|
||||
gateware configuration.
|
||||
|
||||
|
@ -197,7 +203,7 @@ class EdgeCounter:
|
|||
rtio_output(self.channel << 8, config)
|
||||
|
||||
@kernel
|
||||
def fetch_count(self) -> TInt32:
|
||||
def fetch_count(self) -> int32:
|
||||
"""Wait for and return count total from previously requested input
|
||||
event.
|
||||
|
||||
|
@ -216,7 +222,7 @@ class EdgeCounter:
|
|||
|
||||
@kernel
|
||||
def fetch_timestamped_count(
|
||||
self, timeout_mu=int64(-1)) -> TTuple([TInt64, TInt32]):
|
||||
self, timeout_mu: int64 = int64(-1)) -> tuple[int64, int32]:
|
||||
"""Wait for and return the timestamp and count total of a previously
|
||||
requested input event.
|
||||
|
||||
|
|
|
@ -1,121 +1,8 @@
|
|||
import builtins
|
||||
import linecache
|
||||
import re
|
||||
import os
|
||||
|
||||
from artiq import __artiq_dir__ as artiq_dir
|
||||
from artiq.coredevice.runtime import source_loader
|
||||
|
||||
|
||||
ZeroDivisionError = builtins.ZeroDivisionError
|
||||
ValueError = builtins.ValueError
|
||||
IndexError = builtins.IndexError
|
||||
RuntimeError = builtins.RuntimeError
|
||||
AssertionError = builtins.AssertionError
|
||||
|
||||
|
||||
class CoreException:
|
||||
"""Information about an exception raised or passed through the core device."""
|
||||
def __init__(self, exceptions, exception_info, traceback, stack_pointers):
|
||||
self.exceptions = exceptions
|
||||
self.exception_info = exception_info
|
||||
self.traceback = list(traceback)
|
||||
self.stack_pointers = stack_pointers
|
||||
|
||||
first_exception = exceptions[0]
|
||||
name = first_exception[0]
|
||||
if ':' in name:
|
||||
exn_id, self.name = name.split(':', 2)
|
||||
self.id = int(exn_id)
|
||||
else:
|
||||
self.id, self.name = 0, name
|
||||
self.message = first_exception[1]
|
||||
self.params = first_exception[2]
|
||||
|
||||
def append_backtrace(self, record, inlined=False):
|
||||
filename, line, column, function, address = record
|
||||
stub_globals = {"__name__": filename, "__loader__": source_loader}
|
||||
source_line = linecache.getline(filename, line, stub_globals)
|
||||
indentation = re.search(r"^\s*", source_line).end()
|
||||
|
||||
if address is None:
|
||||
formatted_address = ""
|
||||
elif inlined:
|
||||
formatted_address = " (inlined)"
|
||||
else:
|
||||
formatted_address = " (RA=+0x{:x})".format(address)
|
||||
|
||||
filename = filename.replace(artiq_dir, "<artiq>")
|
||||
lines = []
|
||||
if column == -1:
|
||||
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
|
||||
lines.append(" File \"{file}\", line {line}, in {function}{address}".
|
||||
format(file=filename, line=line, function=function,
|
||||
address=formatted_address))
|
||||
else:
|
||||
lines.append(" {}^".format(" " * (column - indentation)))
|
||||
lines.append(" {}".format(source_line.strip() if source_line else "<unknown>"))
|
||||
lines.append(" File \"{file}\", line {line}, column {column},"
|
||||
" in {function}{address}".
|
||||
format(file=filename, line=line, column=column + 1,
|
||||
function=function, address=formatted_address))
|
||||
return lines
|
||||
|
||||
def single_traceback(self, exception_index):
|
||||
# note that we insert in reversed order
|
||||
lines = []
|
||||
last_sp = 0
|
||||
start_backtrace_index = self.exception_info[exception_index][1]
|
||||
zipped = list(zip(self.traceback[start_backtrace_index:],
|
||||
self.stack_pointers[start_backtrace_index:]))
|
||||
exception = self.exceptions[exception_index]
|
||||
name = exception[0]
|
||||
message = exception[1]
|
||||
params = exception[2]
|
||||
if ':' in name:
|
||||
exn_id, name = name.split(':', 2)
|
||||
exn_id = int(exn_id)
|
||||
else:
|
||||
exn_id = 0
|
||||
lines.append("{}({}): {}".format(name, exn_id, message.format(*params)))
|
||||
zipped.append(((exception[3], exception[4], exception[5], exception[6],
|
||||
None, []), None))
|
||||
|
||||
for ((filename, line, column, function, address, inlined), sp) in zipped:
|
||||
# backtrace of nested exceptions may be discontinuous
|
||||
# but the stack pointer must increase monotonically
|
||||
if sp is not None and sp <= last_sp:
|
||||
continue
|
||||
last_sp = sp
|
||||
|
||||
for record in reversed(inlined):
|
||||
lines += self.append_backtrace(record, True)
|
||||
lines += self.append_backtrace((filename, line, column, function,
|
||||
address))
|
||||
|
||||
lines.append("Traceback (most recent call first):")
|
||||
|
||||
return "\n".join(reversed(lines))
|
||||
|
||||
def __str__(self):
|
||||
tracebacks = [self.single_traceback(i) for i in range(len(self.exceptions))]
|
||||
traceback_str = ('\n\nDuring handling of the above exception, ' +
|
||||
'another exception occurred:\n\n').join(tracebacks)
|
||||
return 'Core Device Traceback:\n' +\
|
||||
traceback_str +\
|
||||
'\n\nEnd of Core Device Traceback\n'
|
||||
|
||||
|
||||
class InternalError(Exception):
|
||||
"""Raised when the runtime encounters an internal error condition."""
|
||||
artiq_builtin = True
|
||||
|
||||
|
||||
class CacheError(Exception):
|
||||
"""Raised when putting a value into a cache row would violate memory safety."""
|
||||
artiq_builtin = True
|
||||
from artiq.language.core import nac3, UnwrapNoneError
|
||||
from builtins import ZeroDivisionError, ValueError, IndexError, RuntimeError, AssertionError
|
||||
|
||||
|
||||
@nac3
|
||||
class RTIOUnderflow(Exception):
|
||||
"""Raised when the CPU or DMA core fails to submit a RTIO event early
|
||||
enough (with respect to the event's timestamp).
|
||||
|
@ -125,6 +12,7 @@ class RTIOUnderflow(Exception):
|
|||
artiq_builtin = True
|
||||
|
||||
|
||||
@nac3
|
||||
class RTIOOverflow(Exception):
|
||||
"""Raised when at least one event could not be registered into the RTIO
|
||||
input FIFO because it was full (CPU not reading fast enough).
|
||||
|
@ -136,6 +24,7 @@ class RTIOOverflow(Exception):
|
|||
artiq_builtin = True
|
||||
|
||||
|
||||
@nac3
|
||||
class RTIODestinationUnreachable(Exception):
|
||||
"""Raised with a RTIO operation could not be completed due to a DRTIO link
|
||||
being down.
|
||||
|
@ -143,27 +32,32 @@ class RTIODestinationUnreachable(Exception):
|
|||
artiq_builtin = True
|
||||
|
||||
|
||||
@nac3
|
||||
class CacheError(Exception):
|
||||
"""Raised when putting a value into a cache row would violate memory safety."""
|
||||
artiq_builtin = True
|
||||
|
||||
|
||||
@nac3
|
||||
class DMAError(Exception):
|
||||
"""Raised when performing an invalid DMA operation."""
|
||||
artiq_builtin = True
|
||||
|
||||
|
||||
class SubkernelError(Exception):
|
||||
"""Raised when an operation regarding a subkernel is invalid
|
||||
or cannot be completed.
|
||||
"""
|
||||
@nac3
|
||||
class ClockFailure(Exception):
|
||||
"""Raised when RTIO PLL has lost lock."""
|
||||
artiq_builtin = True
|
||||
|
||||
|
||||
class ClockFailure(Exception):
|
||||
"""Raised when RTIO PLL has lost lock."""
|
||||
|
||||
|
||||
@nac3
|
||||
class I2CError(Exception):
|
||||
"""Raised when a I2C transaction fails."""
|
||||
pass
|
||||
artiq_builtin = True
|
||||
|
||||
|
||||
@nac3
|
||||
class SPIError(Exception):
|
||||
"""Raised when a SPI transaction fails."""
|
||||
pass
|
||||
artiq_builtin = True
|
||||
|
||||
|
|
|
@ -3,13 +3,14 @@ streaming DAC.
|
|||
"""
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import kernel, portable, delay, delay_mu
|
||||
from artiq.language.core import nac3, kernel, portable, KernelInvariant
|
||||
from artiq.coredevice.rtio import (rtio_output, rtio_output_wide,
|
||||
rtio_input_data)
|
||||
from artiq.language.units import ns
|
||||
from artiq.language.types import TInt32, TList
|
||||
from artiq.coredevice.core import Core
|
||||
|
||||
|
||||
@nac3
|
||||
class Fastino:
|
||||
"""Fastino 32-channel, 16-bit, 2.5 MS/s per channel streaming DAC
|
||||
|
||||
|
@ -41,7 +42,11 @@ class Fastino:
|
|||
:param log2_width: Width of DAC channel group (logarithm base 2).
|
||||
Value must match the corresponding value in the RTIO PHY (gateware).
|
||||
"""
|
||||
kernel_invariants = {"core", "channel", "width", "t_frame"}
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
channel: KernelInvariant[int32]
|
||||
width: KernelInvariant[int32]
|
||||
t_frame: KernelInvariant[int64]
|
||||
|
||||
def __init__(self, dmgr, channel, core_device="core", log2_width=0):
|
||||
self.channel = channel << 8
|
||||
|
@ -73,15 +78,15 @@ class Fastino:
|
|||
Note: On Fastino gateware before v0.2 this may lead to 0 voltage being emitted
|
||||
transiently.
|
||||
"""
|
||||
self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=1)
|
||||
self.set_cfg(reset=False, afe_power_down=False, dac_clr=False, clr_err=True)
|
||||
delay_mu(self.t_frame)
|
||||
self.set_cfg(reset=0, afe_power_down=0, dac_clr=0, clr_err=0)
|
||||
self.set_cfg(reset=False, afe_power_down=False, dac_clr=False, clr_err=False)
|
||||
delay_mu(self.t_frame)
|
||||
self.set_continuous(0)
|
||||
delay_mu(self.t_frame)
|
||||
self.stage_cic(1)
|
||||
delay_mu(self.t_frame)
|
||||
self.apply_cic(0xffffffff)
|
||||
self.apply_cic(int32(int64(0xffffffff)))
|
||||
delay_mu(self.t_frame)
|
||||
self.set_leds(0)
|
||||
delay_mu(self.t_frame)
|
||||
|
@ -89,7 +94,7 @@ class Fastino:
|
|||
delay_mu(self.t_frame)
|
||||
|
||||
@kernel
|
||||
def write(self, addr, data):
|
||||
def write(self, addr: int32, data: int32):
|
||||
"""Write data to a Fastino register.
|
||||
|
||||
:param addr: Address to write to.
|
||||
|
@ -98,7 +103,7 @@ class Fastino:
|
|||
rtio_output(self.channel | addr, data)
|
||||
|
||||
@kernel
|
||||
def read(self, addr):
|
||||
def read(self, addr: int32):
|
||||
"""Read from Fastino register.
|
||||
|
||||
TODO: untested
|
||||
|
@ -106,12 +111,12 @@ class Fastino:
|
|||
:param addr: Address to read from.
|
||||
:return: The data read.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError()
|
||||
# rtio_output(self.channel | addr | 0x80)
|
||||
# return rtio_input_data(self.channel >> 8)
|
||||
|
||||
@kernel
|
||||
def set_dac_mu(self, dac, data):
|
||||
def set_dac_mu(self, dac: int32, data: int32):
|
||||
"""Write DAC data in machine units.
|
||||
|
||||
:param dac: DAC channel to write to (0-31).
|
||||
|
@ -121,7 +126,7 @@ class Fastino:
|
|||
self.write(dac, data)
|
||||
|
||||
@kernel
|
||||
def set_group_mu(self, dac: TInt32, data: TList(TInt32)):
|
||||
def set_group_mu(self, dac: int32, data: list[int32]):
|
||||
"""Write a group of DAC channels in machine units.
|
||||
|
||||
:param dac: First channel in DAC channel group (0-31). The `log2_width`
|
||||
|
@ -131,24 +136,24 @@ class Fastino:
|
|||
If the list length is less than group size, the remaining
|
||||
DAC channels within the group are cleared to 0 (machine units).
|
||||
"""
|
||||
if dac & (self.width - 1):
|
||||
if dac & (self.width - 1) != 0:
|
||||
raise ValueError("Group index LSBs must be zero")
|
||||
rtio_output_wide(self.channel | dac, data)
|
||||
|
||||
@portable
|
||||
def voltage_to_mu(self, voltage):
|
||||
def voltage_to_mu(self, voltage: float) -> int32:
|
||||
"""Convert SI Volts to DAC machine units.
|
||||
|
||||
:param voltage: Voltage in SI Volts.
|
||||
:return: DAC data word in machine units, 16 bit integer.
|
||||
"""
|
||||
data = int32(round((0x8000/10.)*voltage)) + int32(0x8000)
|
||||
data = int32(round((float(0x8000)/10.)*voltage)) + int32(0x8000)
|
||||
if data < 0 or data > 0xffff:
|
||||
raise ValueError("DAC voltage out of bounds")
|
||||
return data
|
||||
|
||||
@portable
|
||||
def voltage_group_to_mu(self, voltage, data):
|
||||
def voltage_group_to_mu(self, voltage: list[float], data: list[int32]):
|
||||
"""Convert SI Volts to packed DAC channel group machine units.
|
||||
|
||||
:param voltage: List of SI Volt voltages.
|
||||
|
@ -157,12 +162,12 @@ class Fastino:
|
|||
"""
|
||||
for i in range(len(voltage)):
|
||||
v = self.voltage_to_mu(voltage[i])
|
||||
if i & 1:
|
||||
if i & 1 != 0:
|
||||
v = data[i // 2] | (v << 16)
|
||||
data[i // 2] = int32(v)
|
||||
|
||||
@kernel
|
||||
def set_dac(self, dac, voltage):
|
||||
def set_dac(self, dac: int32, voltage: float):
|
||||
"""Set DAC data to given voltage.
|
||||
|
||||
:param dac: DAC channel (0-31).
|
||||
|
@ -171,18 +176,18 @@ class Fastino:
|
|||
self.write(dac, self.voltage_to_mu(voltage))
|
||||
|
||||
@kernel
|
||||
def set_group(self, dac, voltage):
|
||||
def set_group(self, dac: int32, voltage: list[float]):
|
||||
"""Set DAC group data to given voltage.
|
||||
|
||||
:param dac: DAC channel (0-31).
|
||||
:param voltage: Desired output voltage.
|
||||
"""
|
||||
data = [int32(0)] * (len(voltage) // 2)
|
||||
data = [int32(0) for _ in range(len(voltage) // 2)]
|
||||
self.voltage_group_to_mu(voltage, data)
|
||||
self.set_group_mu(dac, data)
|
||||
|
||||
@kernel
|
||||
def update(self, update):
|
||||
def update(self, update: int32):
|
||||
"""Schedule channels for update.
|
||||
|
||||
:param update: Bit mask of channels to update (32 bit).
|
||||
|
@ -190,7 +195,7 @@ class Fastino:
|
|||
self.write(0x20, update)
|
||||
|
||||
@kernel
|
||||
def set_hold(self, hold):
|
||||
def set_hold(self, hold: int32):
|
||||
"""Set channels to manual update.
|
||||
|
||||
:param hold: Bit mask of channels to hold (32 bit).
|
||||
|
@ -198,7 +203,7 @@ class Fastino:
|
|||
self.write(0x21, hold)
|
||||
|
||||
@kernel
|
||||
def set_cfg(self, reset=0, afe_power_down=0, dac_clr=0, clr_err=0):
|
||||
def set_cfg(self, reset: bool = False, afe_power_down: bool = False, dac_clr: bool = False, clr_err: bool = False):
|
||||
"""Set configuration bits.
|
||||
|
||||
:param reset: Reset SPI PLL and SPI clock domain.
|
||||
|
@ -209,11 +214,11 @@ class Fastino:
|
|||
This clears the sticky red error LED. Must be cleared to enable
|
||||
error counting.
|
||||
"""
|
||||
self.write(0x22, (reset << 0) | (afe_power_down << 1) |
|
||||
(dac_clr << 2) | (clr_err << 3))
|
||||
self.write(0x22, (int32(reset) << 0) | (int32(afe_power_down) << 1) |
|
||||
(int32(dac_clr) << 2) | (int32(clr_err) << 3))
|
||||
|
||||
@kernel
|
||||
def set_leds(self, leds):
|
||||
def set_leds(self, leds: int32):
|
||||
"""Set the green user-defined LEDs
|
||||
|
||||
:param leds: LED status, 8 bit integer each bit corresponding to one
|
||||
|
@ -222,14 +227,14 @@ class Fastino:
|
|||
self.write(0x23, leds)
|
||||
|
||||
@kernel
|
||||
def set_continuous(self, channel_mask):
|
||||
def set_continuous(self, channel_mask: int32):
|
||||
"""Enable continuous DAC updates on channels regardless of new data
|
||||
being submitted.
|
||||
"""
|
||||
self.write(0x25, channel_mask)
|
||||
|
||||
@kernel
|
||||
def stage_cic_mu(self, rate_mantissa, rate_exponent, gain_exponent):
|
||||
def stage_cic_mu(self, rate_mantissa: int32, rate_exponent: int32, gain_exponent: int32):
|
||||
"""Stage machine unit CIC interpolator configuration.
|
||||
"""
|
||||
if rate_mantissa < 0 or rate_mantissa >= 1 << 6:
|
||||
|
@ -242,7 +247,7 @@ class Fastino:
|
|||
self.write(0x26, config)
|
||||
|
||||
@kernel
|
||||
def stage_cic(self, rate) -> TInt32:
|
||||
def stage_cic(self, rate: int32) -> int32:
|
||||
"""Compute and stage interpolator configuration.
|
||||
|
||||
This method approximates the desired interpolation rate using a 10 bit
|
||||
|
@ -278,7 +283,7 @@ class Fastino:
|
|||
return rate_mantissa << rate_exponent
|
||||
|
||||
@kernel
|
||||
def apply_cic(self, channel_mask):
|
||||
def apply_cic(self, channel_mask: int32):
|
||||
"""Apply the staged interpolator configuration on the specified channels.
|
||||
|
||||
Each Fastino channel starting with gateware v0.2 includes a fourth order
|
||||
|
|
|
@ -1,51 +0,0 @@
|
|||
# Definitions for using the "FMC DIO 32ch LVDS a" card with the VHDCI-EEM breakout v1.1
|
||||
|
||||
eem_fmc_connections = {
|
||||
0: [0, 8, 2, 3, 4, 5, 6, 7],
|
||||
1: [1, 9, 10, 11, 12, 13, 14, 15],
|
||||
2: [17, 16, 24, 19, 20, 21, 22, 23],
|
||||
3: [18, 25, 26, 27, 28, 29, 30, 31],
|
||||
}
|
||||
|
||||
|
||||
def shiftreg_bits(eem, out_pins):
|
||||
"""
|
||||
Returns the bits that have to be set in the FMC card direction
|
||||
shift register for the given EEM.
|
||||
|
||||
Takes a set of pin numbers (0-7) at the EEM. Return values
|
||||
of this function for different EEMs should be ORed together.
|
||||
"""
|
||||
r = 0
|
||||
for i in range(8):
|
||||
if i not in out_pins:
|
||||
lvds_line = eem_fmc_connections[eem][i]
|
||||
# lines are swapped in pairs to ease PCB routing
|
||||
# at the shift register
|
||||
shift = lvds_line ^ 1
|
||||
r |= 1 << shift
|
||||
return r
|
||||
|
||||
|
||||
dio_bank0_out_pins = set(range(4))
|
||||
dio_bank1_out_pins = set(range(4, 8))
|
||||
urukul_out_pins = {
|
||||
0, # clk
|
||||
1, # mosi
|
||||
3, 4, 5, # cs_n
|
||||
6, # io_update
|
||||
7, # dds_reset
|
||||
}
|
||||
urukul_aux_out_pins = {
|
||||
4, # sw0
|
||||
5, # sw1
|
||||
6, # sw2
|
||||
7, # sw3
|
||||
}
|
||||
zotino_out_pins = {
|
||||
0, # clk
|
||||
1, # mosi
|
||||
3, 4, # cs_n
|
||||
5, # ldac_n
|
||||
7, # clr_n
|
||||
}
|
|
@ -1,19 +1,23 @@
|
|||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import *
|
||||
from artiq.language.types import *
|
||||
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
||||
from artiq.coredevice.core import Core
|
||||
|
||||
|
||||
@nac3
|
||||
class OutOfSyncException(Exception):
|
||||
"""Raised when an incorrect number of ROI engine outputs has been
|
||||
retrieved from the RTIO input FIFO."""
|
||||
pass
|
||||
|
||||
|
||||
@nac3
|
||||
class Grabber:
|
||||
"""Driver for the Grabber camera interface."""
|
||||
kernel_invariants = {"core", "channel_base", "sentinel"}
|
||||
core: KernelInvariant[Core]
|
||||
channel_base: KernelInvariant[int32]
|
||||
sentinel: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, channel_base, res_width=12, count_shift=0,
|
||||
core_device="core"):
|
||||
|
@ -30,7 +34,7 @@ class Grabber:
|
|||
return [(channel_base, "ROI coordinates"), (channel_base + 1, "ROI mask")]
|
||||
|
||||
@kernel
|
||||
def setup_roi(self, n, x0, y0, x1, y1):
|
||||
def setup_roi(self, n: int32, x0: int32, y0: int32, x1: int32, y1: int32):
|
||||
"""
|
||||
Defines the coordinates of a ROI.
|
||||
|
||||
|
@ -54,7 +58,7 @@ class Grabber:
|
|||
delay_mu(c)
|
||||
|
||||
@kernel
|
||||
def gate_roi(self, mask):
|
||||
def gate_roi(self, mask: int32):
|
||||
"""
|
||||
Defines which ROI engines produce input events.
|
||||
|
||||
|
@ -74,15 +78,15 @@ class Grabber:
|
|||
rtio_output((self.channel_base + 1) << 8, mask)
|
||||
|
||||
@kernel
|
||||
def gate_roi_pulse(self, mask, dt):
|
||||
def gate_roi_pulse(self, mask: int32, dt: float):
|
||||
"""Sets a temporary mask for the specified duration (in seconds), before
|
||||
disabling all ROI engines."""
|
||||
self.gate_roi(mask)
|
||||
delay(dt)
|
||||
self.core.delay(dt)
|
||||
self.gate_roi(0)
|
||||
|
||||
@kernel
|
||||
def input_mu(self, data):
|
||||
def input_mu(self, data: list[int32]):
|
||||
"""
|
||||
Retrieves the accumulated values for one frame from the ROI engines.
|
||||
Blocks until values are available.
|
||||
|
@ -100,10 +104,10 @@ class Grabber:
|
|||
|
||||
sentinel = rtio_input_data(channel)
|
||||
if sentinel != self.sentinel:
|
||||
raise OutOfSyncException
|
||||
raise OutOfSyncException()
|
||||
|
||||
for i in range(len(data)):
|
||||
roi_output = rtio_input_data(channel)
|
||||
if roi_output == self.sentinel:
|
||||
raise OutOfSyncException
|
||||
raise OutOfSyncException()
|
||||
data[i] = roi_output
|
||||
|
|
|
@ -2,44 +2,45 @@
|
|||
Non-realtime drivers for I2C chips on the core device.
|
||||
"""
|
||||
|
||||
from numpy import int32
|
||||
|
||||
from artiq.language.core import syscall, kernel
|
||||
from artiq.language.types import TBool, TInt32, TNone
|
||||
from artiq.language.core import nac3, extern, kernel, KernelInvariant
|
||||
from artiq.coredevice.exceptions import I2CError
|
||||
from artiq.coredevice.core import Core
|
||||
|
||||
|
||||
@syscall(flags={"nounwind", "nowrite"})
|
||||
def i2c_start(busno: TInt32) -> TNone:
|
||||
@extern
|
||||
def i2c_start(busno: int32):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall(flags={"nounwind", "nowrite"})
|
||||
def i2c_restart(busno: TInt32) -> TNone:
|
||||
@extern
|
||||
def i2c_restart(busno: int32):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall(flags={"nounwind", "nowrite"})
|
||||
def i2c_stop(busno: TInt32) -> TNone:
|
||||
@extern
|
||||
def i2c_stop(busno: int32):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall(flags={"nounwind", "nowrite"})
|
||||
def i2c_write(busno: TInt32, b: TInt32) -> TBool:
|
||||
@extern
|
||||
def i2c_write(busno: int32, b: int32) -> bool:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall(flags={"nounwind", "nowrite"})
|
||||
def i2c_read(busno: TInt32, ack: TBool) -> TInt32:
|
||||
@extern
|
||||
def i2c_read(busno: int32, ack: bool) -> int32:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall(flags={"nounwind", "nowrite"})
|
||||
def i2c_switch_select(busno: TInt32, address: TInt32, mask: TInt32) -> TNone:
|
||||
@extern
|
||||
def i2c_switch_select(busno: int32, address: int32, mask: int32):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@kernel
|
||||
def i2c_poll(busno, busaddr):
|
||||
def i2c_poll(busno: int32, busaddr: int32) -> bool:
|
||||
"""Poll I2C device at address.
|
||||
|
||||
:param busno: I2C bus number
|
||||
|
@ -53,7 +54,7 @@ def i2c_poll(busno, busaddr):
|
|||
|
||||
|
||||
@kernel
|
||||
def i2c_write_byte(busno, busaddr, data, ack=True):
|
||||
def i2c_write_byte(busno: int32, busaddr: int32, data: int32, ack: bool = True):
|
||||
"""Write one byte to a device.
|
||||
|
||||
:param busno: I2C bus number
|
||||
|
@ -72,7 +73,7 @@ def i2c_write_byte(busno, busaddr, data, ack=True):
|
|||
|
||||
|
||||
@kernel
|
||||
def i2c_read_byte(busno, busaddr):
|
||||
def i2c_read_byte(busno: int32, busaddr: int32) -> int32:
|
||||
"""Read one byte from a device.
|
||||
|
||||
:param busno: I2C bus number
|
||||
|
@ -91,7 +92,7 @@ def i2c_read_byte(busno, busaddr):
|
|||
|
||||
|
||||
@kernel
|
||||
def i2c_write_many(busno, busaddr, addr, data, ack_last=True):
|
||||
def i2c_write_many(busno: int32, busaddr: int32, addr: int32, data: list[int32], ack_last: bool = True):
|
||||
"""Transfer multiple bytes to a device.
|
||||
|
||||
:param busno: I2c bus number
|
||||
|
@ -117,7 +118,7 @@ def i2c_write_many(busno, busaddr, addr, data, ack_last=True):
|
|||
|
||||
|
||||
@kernel
|
||||
def i2c_read_many(busno, busaddr, addr, data):
|
||||
def i2c_read_many(busno: int32, busaddr: int32, addr: int32, data: list[int32]):
|
||||
"""Transfer multiple bytes from a device.
|
||||
|
||||
:param busno: I2c bus number
|
||||
|
@ -142,6 +143,7 @@ def i2c_read_many(busno, busaddr, addr, data):
|
|||
i2c_stop(busno)
|
||||
|
||||
|
||||
@nac3
|
||||
class I2CSwitch:
|
||||
"""Driver for the I2C bus switch.
|
||||
|
||||
|
@ -153,13 +155,18 @@ class I2CSwitch:
|
|||
On the KC705, this chip is used for selecting the I2C buses on the two FMC
|
||||
connectors. HPC=1, LPC=2.
|
||||
"""
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
busno: KernelInvariant[int32]
|
||||
address: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, busno=0, address=0xe8, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.busno = busno
|
||||
self.address = address
|
||||
|
||||
@kernel
|
||||
def set(self, channel):
|
||||
def set(self, channel: int32):
|
||||
"""Enable one channel.
|
||||
|
||||
:param channel: channel number (0-7)
|
||||
|
@ -173,6 +180,7 @@ class I2CSwitch:
|
|||
i2c_switch_select(self.busno, self.address >> 1, 0)
|
||||
|
||||
|
||||
@kernel
|
||||
class TCA6424A:
|
||||
"""Driver for the TCA6424A I2C I/O expander.
|
||||
|
||||
|
@ -181,18 +189,23 @@ class TCA6424A:
|
|||
|
||||
On the NIST QC2 hardware, this chip is used for switching the directions
|
||||
of TTL buffers."""
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
busno: KernelInvariant[int32]
|
||||
address: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, busno=0, address=0x44, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.busno = busno
|
||||
self.address = address
|
||||
|
||||
@kernel
|
||||
def _write24(self, addr, value):
|
||||
def _write24(self, addr: int32, value: int32):
|
||||
i2c_write_many(self.busno, self.address, addr,
|
||||
[value >> 16, value >> 8, value])
|
||||
|
||||
@kernel
|
||||
def set(self, outputs):
|
||||
def set(self, outputs: int32):
|
||||
"""Drive all pins of the chip to the levels given by the
|
||||
specified 24-bit word.
|
||||
|
||||
|
@ -209,19 +222,26 @@ class TCA6424A:
|
|||
self._write24(0x8c, 0) # set all directions to output
|
||||
self._write24(0x84, outputs_le) # set levels
|
||||
|
||||
|
||||
@nac3
|
||||
class PCF8574A:
|
||||
"""Driver for the PCF8574 I2C remote 8-bit I/O expander.
|
||||
|
||||
I2C transactions not real-time, and are performed by the CPU without
|
||||
involving RTIO.
|
||||
"""
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
busno: KernelInvariant[int32]
|
||||
address: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, busno=0, address=0x7c, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.busno = busno
|
||||
self.address = address
|
||||
|
||||
@kernel
|
||||
def set(self, data):
|
||||
def set(self, data: int32):
|
||||
"""Drive data on the quasi-bidirectional pins.
|
||||
|
||||
:param data: Pin data. High bits are weakly driven high
|
||||
|
@ -237,7 +257,7 @@ class PCF8574A:
|
|||
i2c_stop(self.busno)
|
||||
|
||||
@kernel
|
||||
def get(self):
|
||||
def get(self) -> int32:
|
||||
"""Retrieve quasi-bidirectional pin input data.
|
||||
|
||||
:return: Pin data
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
from numpy import int32
|
||||
|
||||
from artiq.experiment import *
|
||||
from artiq.coredevice.i2c import i2c_write_many, i2c_read_many, i2c_poll
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.i2c import I2CSwitch, i2c_write_many, i2c_read_many, i2c_poll
|
||||
|
||||
|
||||
port_mapping = {
|
||||
|
@ -24,7 +25,15 @@ port_mapping = {
|
|||
}
|
||||
|
||||
|
||||
@nac3
|
||||
class KasliEEPROM:
|
||||
core: KernelInvariant[Core]
|
||||
sw0: KernelInvariant[I2CSwitch]
|
||||
sw1: KernelInvariant[I2CSwitch]
|
||||
busno: KernelInvariant[int32]
|
||||
port: KernelInvariant[int32]
|
||||
address: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, port, busno=0,
|
||||
core_device="core", sw0_device="i2c_switch0", sw1_device="i2c_switch1"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -50,10 +59,10 @@ class KasliEEPROM:
|
|||
self.sw1.unset()
|
||||
|
||||
@kernel
|
||||
def write_i32(self, addr, value):
|
||||
def write_i32(self, addr: int32, value: int32):
|
||||
self.select()
|
||||
try:
|
||||
data = [0]*4
|
||||
data = [0 for _ in range(4)]
|
||||
for i in range(4):
|
||||
data[i] = (value >> 24) & 0xff
|
||||
value <<= 8
|
||||
|
@ -63,12 +72,12 @@ class KasliEEPROM:
|
|||
self.deselect()
|
||||
|
||||
@kernel
|
||||
def read_i32(self, addr):
|
||||
def read_i32(self, addr: int32) -> int32:
|
||||
self.select()
|
||||
value = int32(0)
|
||||
try:
|
||||
data = [0]*4
|
||||
data = [0 for _ in range(4)]
|
||||
i2c_read_many(self.busno, self.address, addr, data)
|
||||
value = int32(0)
|
||||
for i in range(4):
|
||||
value <<= 8
|
||||
value |= data[i]
|
||||
|
|
|
@ -1,23 +1,24 @@
|
|||
"""RTIO driver for Mirny (4 channel GHz PLLs)
|
||||
"""
|
||||
|
||||
from artiq.language.core import kernel, delay, portable
|
||||
from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable
|
||||
from artiq.language.units import us
|
||||
|
||||
from numpy import int32
|
||||
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.spi2 import *
|
||||
|
||||
|
||||
SPI_CONFIG = (
|
||||
0 * spi.SPI_OFFLINE
|
||||
| 0 * spi.SPI_END
|
||||
| 0 * spi.SPI_INPUT
|
||||
| 1 * spi.SPI_CS_POLARITY
|
||||
| 0 * spi.SPI_CLK_POLARITY
|
||||
| 0 * spi.SPI_CLK_PHASE
|
||||
| 0 * spi.SPI_LSB_FIRST
|
||||
| 0 * spi.SPI_HALF_DUPLEX
|
||||
0 * SPI_OFFLINE
|
||||
| 0 * SPI_END
|
||||
| 0 * SPI_INPUT
|
||||
| 1 * SPI_CS_POLARITY
|
||||
| 0 * SPI_CLK_POLARITY
|
||||
| 0 * SPI_CLK_PHASE
|
||||
| 0 * SPI_LSB_FIRST
|
||||
| 0 * SPI_HALF_DUPLEX
|
||||
)
|
||||
|
||||
# SPI clock write and read dividers
|
||||
|
@ -32,6 +33,7 @@ WE = 1 << 24
|
|||
PROTO_REV_MATCH = 0x0
|
||||
|
||||
|
||||
@nac3
|
||||
class Mirny:
|
||||
"""
|
||||
Mirny PLL-based RF generator.
|
||||
|
@ -46,8 +48,12 @@ class Mirny:
|
|||
The effect depends on the hardware revision.
|
||||
:param core_device: Core device name (default: "core")
|
||||
"""
|
||||
|
||||
kernel_invariants = {"bus", "core", "refclk", "clk_sel_hw_rev"}
|
||||
core: KernelInvariant[Core]
|
||||
bus: KernelInvariant[SPIMaster]
|
||||
refclk: KernelInvariant[float]
|
||||
clk_sel_hw_rev: Kernel[list[int32]]
|
||||
hw_rev: Kernel[int32]
|
||||
clk_sel: Kernel[int32]
|
||||
|
||||
def __init__(self, dmgr, spi_device, refclk=100e6, clk_sel="XO", core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -81,22 +87,22 @@ class Mirny:
|
|||
# TODO: support clk_div on v1.0 boards
|
||||
|
||||
@kernel
|
||||
def read_reg(self, addr):
|
||||
def read_reg(self, addr: int32) -> int32:
|
||||
"""Read a register"""
|
||||
self.bus.set_config_mu(
|
||||
SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END, 24, SPIT_RD, SPI_CS
|
||||
SPI_CONFIG | SPI_INPUT | SPI_END, 24, SPIT_RD, SPI_CS
|
||||
)
|
||||
self.bus.write((addr << 25))
|
||||
return self.bus.read() & int32(0xFFFF)
|
||||
return self.bus.read() & 0xFFFF
|
||||
|
||||
@kernel
|
||||
def write_reg(self, addr, data):
|
||||
def write_reg(self, addr: int32, data: int32):
|
||||
"""Write a register"""
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24, SPIT_WR, SPI_CS)
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24, SPIT_WR, SPI_CS)
|
||||
self.bus.write((addr << 25) | WE | ((data & 0xFFFF) << 8))
|
||||
|
||||
@kernel
|
||||
def init(self, blind=False):
|
||||
def init(self, blind: bool = False):
|
||||
"""
|
||||
Initialize and detect Mirny.
|
||||
|
||||
|
@ -111,7 +117,7 @@ class Mirny:
|
|||
if not blind:
|
||||
if (reg0 >> 2) & 0x3 != PROTO_REV_MATCH:
|
||||
raise ValueError("Mirny PROTO_REV mismatch")
|
||||
delay(100 * us) # slack
|
||||
self.core.delay(100. * us) # slack
|
||||
|
||||
# select clock source
|
||||
self.clk_sel = self.clk_sel_hw_rev[self.hw_rev]
|
||||
|
@ -120,31 +126,31 @@ class Mirny:
|
|||
raise ValueError("Hardware revision not supported")
|
||||
|
||||
self.write_reg(1, (self.clk_sel << 4))
|
||||
delay(1000 * us)
|
||||
self.core.delay(1000. * us)
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def att_to_mu(self, att):
|
||||
@portable
|
||||
def att_to_mu(self, att: float) -> int32:
|
||||
"""Convert an attenuation setting in dB to machine units.
|
||||
|
||||
:param att: Attenuation setting in dB.
|
||||
:return: Digital attenuation setting.
|
||||
"""
|
||||
code = int32(255) - int32(round(att * 8))
|
||||
code = int32(255) - int32(round(att * 8.))
|
||||
if code < 0 or code > 255:
|
||||
raise ValueError("Invalid Mirny attenuation!")
|
||||
return code
|
||||
|
||||
@kernel
|
||||
def set_att_mu(self, channel, att):
|
||||
def set_att_mu(self, channel: int32, att: int32):
|
||||
"""Set digital step attenuator in machine units.
|
||||
|
||||
:param att: Attenuation setting, 8 bit digital.
|
||||
"""
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 16, SPIT_WR, SPI_CS)
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 16, SPIT_WR, SPI_CS)
|
||||
self.bus.write(((channel | 8) << 25) | (att << 16))
|
||||
|
||||
@kernel
|
||||
def set_att(self, channel, att):
|
||||
def set_att(self, channel: int32, att: float):
|
||||
"""Set digital step attenuator in SI units.
|
||||
|
||||
This method will write the attenuator settings of the selected channel.
|
||||
|
@ -159,11 +165,11 @@ class Mirny:
|
|||
self.set_att_mu(channel, self.att_to_mu(att))
|
||||
|
||||
@kernel
|
||||
def write_ext(self, addr, length, data, ext_div=SPIT_WR):
|
||||
def write_ext(self, addr: int32, length: int32, data: int32, ext_div: int32 = SPIT_WR):
|
||||
"""Perform SPI write to a prefixed address"""
|
||||
self.bus.set_config_mu(SPI_CONFIG, 8, SPIT_WR, SPI_CS)
|
||||
self.bus.write(addr << 25)
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, length, ext_div, SPI_CS)
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, length, ext_div, SPI_CS)
|
||||
if length < 32:
|
||||
data <<= 32 - length
|
||||
self.bus.write(data)
|
||||
|
|
|
@ -1,174 +0,0 @@
|
|||
from artiq.language.core import kernel, delay, portable
|
||||
from artiq.language.units import ns
|
||||
|
||||
from artiq.coredevice import spi2 as spi
|
||||
|
||||
|
||||
SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END |
|
||||
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
|
||||
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE |
|
||||
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
|
||||
|
||||
|
||||
SPI_CS_ADC = 1
|
||||
SPI_CS_SR = 2
|
||||
|
||||
|
||||
@portable
|
||||
def adc_ctrl(channel=1, softspan=0b111, valid=1):
|
||||
"""Build a LTC2335-16 control word"""
|
||||
return (valid << 7) | (channel << 3) | softspan
|
||||
|
||||
|
||||
@portable
|
||||
def adc_softspan(data):
|
||||
"""Return the softspan configuration index from a result packet"""
|
||||
return data & 0x7
|
||||
|
||||
|
||||
@portable
|
||||
def adc_channel(data):
|
||||
"""Return the channel index from a result packet"""
|
||||
return (data >> 3) & 0x7
|
||||
|
||||
|
||||
@portable
|
||||
def adc_data(data):
|
||||
"""Return the ADC value from a result packet"""
|
||||
return (data >> 8) & 0xffff
|
||||
|
||||
|
||||
@portable
|
||||
def adc_value(data, v_ref=5.):
|
||||
"""Convert a ADC result packet to SI units (Volt)"""
|
||||
softspan = adc_softspan(data)
|
||||
data = adc_data(data)
|
||||
g = 625
|
||||
if softspan & 4:
|
||||
g *= 2
|
||||
if softspan & 2:
|
||||
h = 1 << 15
|
||||
else:
|
||||
h = 1 << 16
|
||||
data = -(data & h) + (data & ~h)
|
||||
if softspan & 1:
|
||||
h *= 500
|
||||
else:
|
||||
h *= 512
|
||||
v_per_lsb = v_ref*g/h
|
||||
return data*v_per_lsb
|
||||
|
||||
|
||||
class Novogorny:
|
||||
"""Novogorny ADC.
|
||||
|
||||
Controls the LTC2335-16 8 channel ADC with SPI interface and
|
||||
the switchable gain instrumentation amplifiers using a shift
|
||||
register.
|
||||
|
||||
:param spi_device: SPI bus device name
|
||||
:param cnv_device: CNV RTIO TTLOut channel name
|
||||
:param div: SPI clock divider (default: 8)
|
||||
:param gains: Initial value for PGIA gains shift register
|
||||
(default: 0x0000). Knowledge of this state is not transferred
|
||||
between experiments.
|
||||
:param core_device: Core device name
|
||||
"""
|
||||
kernel_invariants = {"bus", "core", "cnv", "div", "v_ref"}
|
||||
|
||||
def __init__(self, dmgr, spi_device, cnv_device, div=8, gains=0x0000,
|
||||
core_device="core"):
|
||||
self.bus = dmgr.get(spi_device)
|
||||
self.core = dmgr.get(core_device)
|
||||
self.cnv = dmgr.get(cnv_device)
|
||||
self.div = div
|
||||
self.gains = gains
|
||||
self.v_ref = 5. # 5 Volt reference
|
||||
|
||||
@kernel
|
||||
def set_gain_mu(self, channel, gain):
|
||||
"""Set instrumentation amplifier gain of a channel.
|
||||
|
||||
The four gain settings (0, 1, 2, 3) corresponds to gains of
|
||||
(1, 10, 100, 1000) respectively.
|
||||
|
||||
:param channel: Channel index
|
||||
:param gain: Gain setting
|
||||
"""
|
||||
gains = self.gains
|
||||
gains &= ~(0b11 << (channel*2))
|
||||
gains |= gain << (channel*2)
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END,
|
||||
16, self.div, SPI_CS_SR)
|
||||
self.bus.write(gains << 16)
|
||||
self.gains = gains
|
||||
|
||||
@kernel
|
||||
def configure(self, data):
|
||||
"""Set up the ADC sequencer.
|
||||
|
||||
:param data: List of 8 bit control words to write into the sequencer
|
||||
table.
|
||||
"""
|
||||
if len(data) > 1:
|
||||
self.bus.set_config_mu(SPI_CONFIG,
|
||||
8, self.div, SPI_CS_ADC)
|
||||
for i in range(len(data) - 1):
|
||||
self.bus.write(data[i] << 24)
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END,
|
||||
8, self.div, SPI_CS_ADC)
|
||||
self.bus.write(data[len(data) - 1] << 24)
|
||||
|
||||
@kernel
|
||||
def sample_mu(self, next_ctrl=0):
|
||||
"""Acquire a sample:
|
||||
|
||||
Perform a conversion and transfer the sample.
|
||||
|
||||
:param next_ctrl: ADC control word for the next sample
|
||||
:return: The ADC result packet (machine units)
|
||||
"""
|
||||
self.cnv.pulse(40*ns) # t_CNVH
|
||||
delay(560*ns) # t_CONV max
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END,
|
||||
24, self.div, SPI_CS_ADC)
|
||||
self.bus.write(next_ctrl << 24)
|
||||
return self.bus.read()
|
||||
|
||||
@kernel
|
||||
def sample(self, next_ctrl=0):
|
||||
"""Acquire a sample
|
||||
|
||||
.. seealso:: :meth:`sample_mu`
|
||||
|
||||
:param next_ctrl: ADC control word for the next sample
|
||||
:return: The ADC result packet (Volt)
|
||||
"""
|
||||
return adc_value(self.sample_mu(), self.v_ref)
|
||||
|
||||
@kernel
|
||||
def burst_mu(self, data, dt_mu, ctrl=0):
|
||||
"""Acquire a burst of samples.
|
||||
|
||||
If the burst is too long and the sample rate too high, there will be
|
||||
RTIO input overflows.
|
||||
|
||||
High sample rates lead to gain errors since the impedance between the
|
||||
instrumentation amplifier and the ADC is high.
|
||||
|
||||
:param data: List of data values to write result packets into.
|
||||
In machine units.
|
||||
:param dt: Sample interval in machine units.
|
||||
:param ctrl: ADC control word to write during each result packet
|
||||
transfer.
|
||||
"""
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END,
|
||||
24, self.div, SPI_CS_ADC)
|
||||
for i in range(len(data)):
|
||||
t0 = now_mu()
|
||||
self.cnv.pulse(40*ns) # t_CNVH
|
||||
delay(560*ns) # t_CONV max
|
||||
self.bus.write(ctrl << 24)
|
||||
at_mu(t0 + dt_mu)
|
||||
for i in range(len(data)):
|
||||
data[i] = self.bus.read()
|
File diff suppressed because it is too large
Load Diff
|
@ -1,30 +1,31 @@
|
|||
from artiq.language.core import syscall
|
||||
from artiq.language.types import TInt32, TInt64, TList, TNone, TTuple
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import extern
|
||||
|
||||
|
||||
@syscall(flags={"nowrite"})
|
||||
def rtio_output(target: TInt32, data: TInt32) -> TNone:
|
||||
@extern
|
||||
def rtio_output(target: int32, data: int32):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall(flags={"nowrite"})
|
||||
def rtio_output_wide(target: TInt32, data: TList(TInt32)) -> TNone:
|
||||
@extern
|
||||
def rtio_output_wide(target: int32, data: list[int32]):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall(flags={"nowrite"})
|
||||
def rtio_input_timestamp(timeout_mu: TInt64, channel: TInt32) -> TInt64:
|
||||
@extern
|
||||
def rtio_input_timestamp(timeout_mu: int64, channel: int32) -> int64:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall(flags={"nowrite"})
|
||||
def rtio_input_data(channel: TInt32) -> TInt32:
|
||||
@extern
|
||||
def rtio_input_data(channel: int32) -> int32:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall(flags={"nowrite"})
|
||||
def rtio_input_timestamped_data(timeout_mu: TInt64,
|
||||
channel: TInt32) -> TTuple([TInt64, TInt32]):
|
||||
@extern
|
||||
def rtio_input_timestamped_data(timeout_mu: int64,
|
||||
channel: int32) -> tuple[int64, int32]:
|
||||
"""Wait for an input event up to timeout_mu on the given channel, and
|
||||
return a tuple of timestamp and attached data, or (-1, 0) if the timeout is
|
||||
reached."""
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
import os
|
||||
|
||||
from artiq import __artiq_dir__ as artiq_dir
|
||||
|
||||
|
||||
class SourceLoader:
|
||||
def __init__(self, runtime_root):
|
||||
self.runtime_root = runtime_root
|
||||
|
||||
def get_source(self, filename):
|
||||
with open(os.path.join(self.runtime_root, filename)) as f:
|
||||
return f.read()
|
||||
|
||||
source_loader = SourceLoader(os.path.join(artiq_dir, "soc", "runtime"))
|
|
@ -1,13 +1,17 @@
|
|||
from artiq.language.core import kernel, delay, portable
|
||||
from numpy import int32
|
||||
|
||||
from artiq.language.core import nac3, kernel, portable, Kernel, KernelInvariant
|
||||
from artiq.language.units import ns
|
||||
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.spi2 import *
|
||||
from artiq.coredevice.ttl import TTLOut
|
||||
|
||||
|
||||
SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END |
|
||||
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
|
||||
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE |
|
||||
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
|
||||
SPI_CONFIG = (0*SPI_OFFLINE | 0*SPI_END |
|
||||
0*SPI_INPUT | 0*SPI_CS_POLARITY |
|
||||
0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE |
|
||||
0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
|
||||
|
||||
|
||||
SPI_CS_ADC = 0 # no CS, SPI_END does not matter, framing is done with CNV
|
||||
|
@ -15,7 +19,7 @@ SPI_CS_PGIA = 1 # separate SPI bus, CS used as RCLK
|
|||
|
||||
|
||||
@portable
|
||||
def adc_mu_to_volt(data, gain=0, corrected_fs=True):
|
||||
def adc_mu_to_volt(data: int32, gain: int32 = 0, corrected_fs: bool = True) -> float:
|
||||
"""Convert ADC data in machine units to Volts.
|
||||
|
||||
:param data: 16 bit signed ADC word
|
||||
|
@ -24,19 +28,21 @@ def adc_mu_to_volt(data, gain=0, corrected_fs=True):
|
|||
Should be True for Samplers' revisions after v2.1. False for v2.1 and earlier.
|
||||
:return: Voltage in Volts
|
||||
"""
|
||||
volt_per_lsb = 0.
|
||||
if gain == 0:
|
||||
volt_per_lsb = 20.48 / (1 << 16) if corrected_fs else 20. / (1 << 16)
|
||||
volt_per_lsb = 20.48 / float(1 << 16) if corrected_fs else 20. / float(1 << 16)
|
||||
elif gain == 1:
|
||||
volt_per_lsb = 2.048 / (1 << 16) if corrected_fs else 2. / (1 << 16)
|
||||
volt_per_lsb = 2.048 / float(1 << 16) if corrected_fs else 2. / float(1 << 16)
|
||||
elif gain == 2:
|
||||
volt_per_lsb = .2048 / (1 << 16) if corrected_fs else .2 / (1 << 16)
|
||||
volt_per_lsb = .2048 / float(1 << 16) if corrected_fs else .2 / float(1 << 16)
|
||||
elif gain == 3:
|
||||
volt_per_lsb = 0.02048 / (1 << 16) if corrected_fs else .02 / (1 << 16)
|
||||
volt_per_lsb = 0.02048 / float(1 << 16) if corrected_fs else .02 / float(1 << 16)
|
||||
else:
|
||||
raise ValueError("invalid gain")
|
||||
return data * volt_per_lsb
|
||||
return float(data)* volt_per_lsb
|
||||
|
||||
|
||||
@nac3
|
||||
class Sampler:
|
||||
"""Sampler ADC.
|
||||
|
||||
|
@ -53,7 +59,13 @@ class Sampler:
|
|||
:param hw_rev: Sampler's hardware revision string (default 'v2.2')
|
||||
:param core_device: Core device name
|
||||
"""
|
||||
kernel_invariants = {"bus_adc", "bus_pgia", "core", "cnv", "div", "corrected_fs"}
|
||||
core: KernelInvariant[Core]
|
||||
bus_adc: KernelInvariant[SPIMaster]
|
||||
bus_pgia: KernelInvariant[SPIMaster]
|
||||
cnv: KernelInvariant[TTLOut]
|
||||
div: KernelInvariant[int32]
|
||||
gains: Kernel[int32]
|
||||
corrected_fs: KernelInvariant[bool]
|
||||
|
||||
def __init__(self, dmgr, spi_adc_device, spi_pgia_device, cnv_device,
|
||||
div=8, gains=0x0000, hw_rev="v2.2", core_device="core"):
|
||||
|
@ -77,13 +89,13 @@ class Sampler:
|
|||
|
||||
Sets up SPI channels.
|
||||
"""
|
||||
self.bus_adc.set_config_mu(SPI_CONFIG | spi.SPI_INPUT | spi.SPI_END,
|
||||
self.bus_adc.set_config_mu(SPI_CONFIG | SPI_INPUT | SPI_END,
|
||||
32, self.div, SPI_CS_ADC)
|
||||
self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END,
|
||||
self.bus_pgia.set_config_mu(SPI_CONFIG | SPI_END,
|
||||
16, self.div, SPI_CS_PGIA)
|
||||
|
||||
@kernel
|
||||
def set_gain_mu(self, channel, gain):
|
||||
def set_gain_mu(self, channel: int32, gain: int32):
|
||||
"""Set instrumentation amplifier gain of a channel.
|
||||
|
||||
The four gain settings (0, 1, 2, 3) corresponds to gains of
|
||||
|
@ -99,21 +111,21 @@ class Sampler:
|
|||
self.gains = gains
|
||||
|
||||
@kernel
|
||||
def get_gains_mu(self):
|
||||
def get_gains_mu(self) -> int32:
|
||||
"""Read the PGIA gain settings of all channels.
|
||||
|
||||
:return: The PGIA gain settings in machine units.
|
||||
"""
|
||||
self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||
self.bus_pgia.set_config_mu(SPI_CONFIG | SPI_END | SPI_INPUT,
|
||||
16, self.div, SPI_CS_PGIA)
|
||||
self.bus_pgia.write(self.gains << 16)
|
||||
self.bus_pgia.set_config_mu(SPI_CONFIG | spi.SPI_END,
|
||||
self.bus_pgia.set_config_mu(SPI_CONFIG | SPI_END,
|
||||
16, self.div, SPI_CS_PGIA)
|
||||
self.gains = self.bus_pgia.read() & 0xffff
|
||||
return self.gains
|
||||
|
||||
@kernel
|
||||
def sample_mu(self, data):
|
||||
def sample_mu(self, data: list[int32]):
|
||||
"""Acquire a set of samples.
|
||||
|
||||
Perform a conversion and transfer the samples.
|
||||
|
@ -127,8 +139,8 @@ class Sampler:
|
|||
The `data` list will always be filled with the last item
|
||||
holding to the sample from channel 7.
|
||||
"""
|
||||
self.cnv.pulse(30*ns) # t_CNVH
|
||||
delay(450*ns) # t_CONV
|
||||
self.cnv.pulse(30.*ns) # t_CNVH
|
||||
self.core.delay(450.*ns) # t_CONV
|
||||
mask = 1 << 15
|
||||
for i in range(len(data)//2):
|
||||
self.bus_adc.write(0)
|
||||
|
@ -139,7 +151,7 @@ class Sampler:
|
|||
data[i - 1] = -(val & mask) + (val & ~mask)
|
||||
|
||||
@kernel
|
||||
def sample(self, data):
|
||||
def sample(self, data: list[float]):
|
||||
"""Acquire a set of samples.
|
||||
|
||||
.. seealso:: :meth:`sample_mu`
|
||||
|
@ -147,7 +159,7 @@ class Sampler:
|
|||
:param data: List of floating point data samples to fill.
|
||||
"""
|
||||
n = len(data)
|
||||
adc_data = [0]*n
|
||||
adc_data = [0 for _ in range(n)]
|
||||
self.sample_mu(adc_data)
|
||||
for i in range(n):
|
||||
channel = i + 8 - len(data)
|
||||
|
|
|
@ -1,18 +1,21 @@
|
|||
from artiq.language.core import *
|
||||
from artiq.language.types import *
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable, Option, none
|
||||
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.spi2 import *
|
||||
from artiq.language.units import us
|
||||
|
||||
|
||||
@portable
|
||||
def shuttler_volt_to_mu(volt):
|
||||
def shuttler_volt_to_mu(volt: float) -> int32:
|
||||
"""Return the equivalent DAC code. Valid input range is from -10 to
|
||||
10 - LSB.
|
||||
"""
|
||||
return round((1 << 16) * (volt / 20.0)) & 0xffff
|
||||
return round(float(1 << 16) * (volt / 20.0)) & 0xffff
|
||||
|
||||
|
||||
@nac3
|
||||
class Config:
|
||||
"""Shuttler configuration registers interface.
|
||||
|
||||
|
@ -28,10 +31,13 @@ class Config:
|
|||
:param channel: RTIO channel number of this interface.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariants = {
|
||||
"core", "channel", "target_base", "target_read",
|
||||
"target_gain", "target_offset", "target_clr"
|
||||
}
|
||||
core: KernelInvariant[Core]
|
||||
channel: KernelInvariant[int32]
|
||||
target_base: KernelInvariant[int32]
|
||||
target_read: KernelInvariant[int32]
|
||||
target_gain: KernelInvariant[int32]
|
||||
target_offset: KernelInvariant[int32]
|
||||
target_clr: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, channel, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -43,7 +49,7 @@ class Config:
|
|||
self.target_clr = 1 * (1 << 5)
|
||||
|
||||
@kernel
|
||||
def set_clr(self, clr):
|
||||
def set_clr(self, clr: int32):
|
||||
"""Set/Unset waveform phase clear bits.
|
||||
|
||||
Each bit corresponds to a Shuttler waveform generator core. Setting a
|
||||
|
@ -57,7 +63,7 @@ class Config:
|
|||
rtio_output(self.target_base | self.target_clr, clr)
|
||||
|
||||
@kernel
|
||||
def set_gain(self, channel, gain):
|
||||
def set_gain(self, channel: int32, gain: int32):
|
||||
"""Set the 16-bits pre-DAC gain register of a Shuttler Core channel.
|
||||
|
||||
The `gain` parameter represents the decimal portion of the gain
|
||||
|
@ -70,7 +76,7 @@ class Config:
|
|||
rtio_output(self.target_base | self.target_gain | channel, gain)
|
||||
|
||||
@kernel
|
||||
def get_gain(self, channel):
|
||||
def get_gain(self, channel: int32) -> int32:
|
||||
"""Return the pre-DAC gain value of a Shuttler Core channel.
|
||||
|
||||
:param channel: The Shuttler Core channel.
|
||||
|
@ -81,7 +87,7 @@ class Config:
|
|||
return rtio_input_data(self.channel)
|
||||
|
||||
@kernel
|
||||
def set_offset(self, channel, offset):
|
||||
def set_offset(self, channel: int32, offset: int32):
|
||||
"""Set the 16-bits pre-DAC offset register of a Shuttler Core channel.
|
||||
|
||||
.. seealso::
|
||||
|
@ -93,7 +99,7 @@ class Config:
|
|||
rtio_output(self.target_base | self.target_offset | channel, offset)
|
||||
|
||||
@kernel
|
||||
def get_offset(self, channel):
|
||||
def get_offset(self, channel: int32) -> int32:
|
||||
"""Return the pre-DAC offset value of a Shuttler Core channel.
|
||||
|
||||
:param channel: The Shuttler Core channel.
|
||||
|
@ -104,6 +110,7 @@ class Config:
|
|||
return rtio_input_data(self.channel)
|
||||
|
||||
|
||||
@nac3
|
||||
class DCBias:
|
||||
"""Shuttler Core cubic DC-bias spline.
|
||||
|
||||
|
@ -125,7 +132,9 @@ class DCBias:
|
|||
:param channel: RTIO channel number of this DC-bias spline interface.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariants = {"core", "channel", "target_o"}
|
||||
core: KernelInvariant[Core]
|
||||
channel: KernelInvariant[int32]
|
||||
target_o: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, channel, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -133,7 +142,7 @@ class DCBias:
|
|||
self.target_o = channel << 8
|
||||
|
||||
@kernel
|
||||
def set_waveform(self, a0: TInt32, a1: TInt32, a2: TInt64, a3: TInt64):
|
||||
def set_waveform(self, a0: int32, a1: int32, a2: int64, a3: int64):
|
||||
"""Set the DC-bias spline waveform.
|
||||
|
||||
Given `a(t)` as defined in :class:`DCBias`, the coefficients should be
|
||||
|
@ -166,12 +175,12 @@ class DCBias:
|
|||
a0,
|
||||
a1,
|
||||
a1 >> 16,
|
||||
a2 & 0xFFFF,
|
||||
(a2 >> 16) & 0xFFFF,
|
||||
(a2 >> 32) & 0xFFFF,
|
||||
a3 & 0xFFFF,
|
||||
(a3 >> 16) & 0xFFFF,
|
||||
(a3 >> 32) & 0xFFFF,
|
||||
int32(a2 & int64(0xFFFF)),
|
||||
int32((a2 >> 16) & int64(0xFFFF)),
|
||||
int32((a2 >> 32) & int64(0xFFFF)),
|
||||
int32(a3 & int64(0xFFFF)),
|
||||
int32((a3 >> 16) & int64(0xFFFF)),
|
||||
int32((a3 >> 32) & int64(0xFFFF)),
|
||||
]
|
||||
|
||||
for i in range(len(coef_words)):
|
||||
|
@ -179,6 +188,7 @@ class DCBias:
|
|||
delay_mu(int64(self.core.ref_multiplier))
|
||||
|
||||
|
||||
@nac3
|
||||
class DDS:
|
||||
"""Shuttler Core DDS spline.
|
||||
|
||||
|
@ -204,7 +214,9 @@ class DDS:
|
|||
:param channel: RTIO channel number of this DC-bias spline interface.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariants = {"core", "channel", "target_o"}
|
||||
core: KernelInvariant[Core]
|
||||
channel: KernelInvariant[int32]
|
||||
target_o: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, channel, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -212,8 +224,8 @@ class DDS:
|
|||
self.target_o = channel << 8
|
||||
|
||||
@kernel
|
||||
def set_waveform(self, b0: TInt32, b1: TInt32, b2: TInt64, b3: TInt64,
|
||||
c0: TInt32, c1: TInt32, c2: TInt32):
|
||||
def set_waveform(self, b0: int32, b1: int32, b2: int64, b3: int64,
|
||||
c0: int32, c1: int32, c2: int32):
|
||||
"""Set the DDS spline waveform.
|
||||
|
||||
Given `b(t)` and `c(t)` as defined in :class:`DDS`, the coefficients
|
||||
|
@ -256,12 +268,12 @@ class DDS:
|
|||
b0,
|
||||
b1,
|
||||
b1 >> 16,
|
||||
b2 & 0xFFFF,
|
||||
(b2 >> 16) & 0xFFFF,
|
||||
(b2 >> 32) & 0xFFFF,
|
||||
b3 & 0xFFFF,
|
||||
(b3 >> 16) & 0xFFFF,
|
||||
(b3 >> 32) & 0xFFFF,
|
||||
int32(b2 & int64(0xFFFF)),
|
||||
int32((b2 >> 16) & int64(0xFFFF)),
|
||||
int32((b2 >> 32) & int64(0xFFFF)),
|
||||
int32(b3 & int64(0xFFFF)),
|
||||
int32((b3 >> 16) & int64(0xFFFF)),
|
||||
int32((b3 >> 32) & int64(0xFFFF)),
|
||||
c0,
|
||||
c1,
|
||||
c1 >> 16,
|
||||
|
@ -274,13 +286,16 @@ class DDS:
|
|||
delay_mu(int64(self.core.ref_multiplier))
|
||||
|
||||
|
||||
@nac3
|
||||
class Trigger:
|
||||
"""Shuttler Core spline coefficients update trigger.
|
||||
|
||||
:param channel: RTIO channel number of the trigger interface.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariants = {"core", "channel", "target_o"}
|
||||
core: KernelInvariant[Core]
|
||||
channel: KernelInvariant[int32]
|
||||
target_o: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, channel, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -288,7 +303,7 @@ class Trigger:
|
|||
self.target_o = channel << 8
|
||||
|
||||
@kernel
|
||||
def trigger(self, trig_out):
|
||||
def trigger(self, trig_out: int32):
|
||||
"""Triggers coefficient update of (a) Shuttler Core channel(s).
|
||||
|
||||
Each bit corresponds to a Shuttler waveform generator core. Setting
|
||||
|
@ -302,15 +317,15 @@ class Trigger:
|
|||
rtio_output(self.target_o, trig_out)
|
||||
|
||||
|
||||
RELAY_SPI_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
|
||||
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
|
||||
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE |
|
||||
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
|
||||
RELAY_SPI_CONFIG = (0*SPI_OFFLINE | 1*SPI_END |
|
||||
0*SPI_INPUT | 0*SPI_CS_POLARITY |
|
||||
0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE |
|
||||
0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
|
||||
|
||||
ADC_SPI_CONFIG = (0*spi.SPI_OFFLINE | 0*spi.SPI_END |
|
||||
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
|
||||
1*spi.SPI_CLK_POLARITY | 1*spi.SPI_CLK_PHASE |
|
||||
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
|
||||
ADC_SPI_CONFIG = (0*SPI_OFFLINE | 0*SPI_END |
|
||||
0*SPI_INPUT | 0*SPI_CS_POLARITY |
|
||||
1*SPI_CLK_POLARITY | 1*SPI_CLK_PHASE |
|
||||
0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
|
||||
|
||||
# SPI clock write and read dividers
|
||||
# CS should assert at least 9.5 ns after clk pulse
|
||||
|
@ -333,6 +348,7 @@ _AD4115_REG_CH0 = 0x10
|
|||
_AD4115_REG_SETUPCON0 = 0x20
|
||||
|
||||
|
||||
@nac3
|
||||
class Relay:
|
||||
"""Shuttler AFE relay switches.
|
||||
|
||||
|
@ -347,7 +363,8 @@ class Relay:
|
|||
:param spi_device: SPI bus device name.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariant = {"core", "bus"}
|
||||
core: KernelInvariant[Core]
|
||||
bus: KernelInvariant[SPIMaster]
|
||||
|
||||
def __init__(self, dmgr, spi_device, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -364,7 +381,7 @@ class Relay:
|
|||
RELAY_SPI_CONFIG, 16, SPIT_RELAY_WR, CS_RELAY | CS_LED)
|
||||
|
||||
@kernel
|
||||
def enable(self, en: TInt32):
|
||||
def enable(self, en: int32):
|
||||
"""Enable/Disable relay switches of corresponding channels.
|
||||
|
||||
Each bit corresponds to the relay switch of a channel. Asserting a bit
|
||||
|
@ -377,20 +394,22 @@ class Relay:
|
|||
self.bus.write(en << 16)
|
||||
|
||||
|
||||
@nac3
|
||||
class ADC:
|
||||
"""Shuttler AFE ADC (AD4115) driver.
|
||||
|
||||
:param spi_device: SPI bus device name.
|
||||
:param core_device: Core device name.
|
||||
"""
|
||||
kernel_invariant = {"core", "bus"}
|
||||
core: KernelInvariant[Core]
|
||||
bus: KernelInvariant[SPIMaster]
|
||||
|
||||
def __init__(self, dmgr, spi_device, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.bus = dmgr.get(spi_device)
|
||||
|
||||
@kernel
|
||||
def read_id(self) -> TInt32:
|
||||
def read_id(self) -> int32:
|
||||
"""Read the product ID of the ADC.
|
||||
|
||||
The expected return value is 0x38DX, the 4 LSbs are don't cares.
|
||||
|
@ -412,86 +431,86 @@ class ADC:
|
|||
after the transfer appears to interrupt the start-up sequence.
|
||||
"""
|
||||
self.bus.set_config_mu(ADC_SPI_CONFIG, 32, SPIT_ADC_WR, CS_ADC)
|
||||
self.bus.write(0xffffffff)
|
||||
self.bus.write(0xffffffff)
|
||||
self.bus.write(-1)
|
||||
self.bus.write(-1)
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC)
|
||||
self.bus.write(0xffffffff)
|
||||
ADC_SPI_CONFIG | SPI_END, 32, SPIT_ADC_WR, CS_ADC)
|
||||
self.bus.write(-1)
|
||||
|
||||
@kernel
|
||||
def read8(self, addr: TInt32) -> TInt32:
|
||||
def read8(self, addr: int32) -> int32:
|
||||
"""Read from 8 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:return: Read-back register content.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||
ADC_SPI_CONFIG | SPI_END | SPI_INPUT,
|
||||
16, SPIT_ADC_RD, CS_ADC)
|
||||
self.bus.write((addr | 0x40) << 24)
|
||||
return self.bus.read() & 0xff
|
||||
|
||||
@kernel
|
||||
def read16(self, addr: TInt32) -> TInt32:
|
||||
def read16(self, addr: int32) -> int32:
|
||||
"""Read from 16 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:return: Read-back register content.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||
ADC_SPI_CONFIG | SPI_END | SPI_INPUT,
|
||||
24, SPIT_ADC_RD, CS_ADC)
|
||||
self.bus.write((addr | 0x40) << 24)
|
||||
return self.bus.read() & 0xffff
|
||||
|
||||
@kernel
|
||||
def read24(self, addr: TInt32) -> TInt32:
|
||||
def read24(self, addr: int32) -> int32:
|
||||
"""Read from 24 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:return: Read-back register content.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT,
|
||||
ADC_SPI_CONFIG | SPI_END | SPI_INPUT,
|
||||
32, SPIT_ADC_RD, CS_ADC)
|
||||
self.bus.write((addr | 0x40) << 24)
|
||||
return self.bus.read() & 0xffffff
|
||||
|
||||
@kernel
|
||||
def write8(self, addr: TInt32, data: TInt32):
|
||||
def write8(self, addr: int32, data: int32):
|
||||
"""Write to 8 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:param data: Data to be written.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END, 16, SPIT_ADC_WR, CS_ADC)
|
||||
ADC_SPI_CONFIG | SPI_END, 16, SPIT_ADC_WR, CS_ADC)
|
||||
self.bus.write(addr << 24 | (data & 0xff) << 16)
|
||||
|
||||
@kernel
|
||||
def write16(self, addr: TInt32, data: TInt32):
|
||||
def write16(self, addr: int32, data: int32):
|
||||
"""Write to 16 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:param data: Data to be written.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END, 24, SPIT_ADC_WR, CS_ADC)
|
||||
ADC_SPI_CONFIG | SPI_END, 24, SPIT_ADC_WR, CS_ADC)
|
||||
self.bus.write(addr << 24 | (data & 0xffff) << 8)
|
||||
|
||||
@kernel
|
||||
def write24(self, addr: TInt32, data: TInt32):
|
||||
def write24(self, addr: int32, data: int32):
|
||||
"""Write to 24 bit register.
|
||||
|
||||
:param addr: Register address.
|
||||
:param data: Data to be written.
|
||||
"""
|
||||
self.bus.set_config_mu(
|
||||
ADC_SPI_CONFIG | spi.SPI_END, 32, SPIT_ADC_WR, CS_ADC)
|
||||
ADC_SPI_CONFIG | SPI_END, 32, SPIT_ADC_WR, CS_ADC)
|
||||
self.bus.write(addr << 24 | (data & 0xffffff))
|
||||
|
||||
@kernel
|
||||
def read_ch(self, channel: TInt32) -> TFloat:
|
||||
def read_ch(self, channel: int32) -> float:
|
||||
"""Sample a Shuttler channel on the AFE.
|
||||
|
||||
It performs a single conversion using profile 0 and setup 0, on the
|
||||
|
@ -505,9 +524,9 @@ class ADC:
|
|||
self.write16(_AD4115_REG_SETUPCON0, 0x1300)
|
||||
self.single_conversion()
|
||||
|
||||
delay(100*us)
|
||||
self.core.delay(100.*us)
|
||||
adc_code = self.read24(_AD4115_REG_DATA)
|
||||
return ((adc_code / (1 << 23)) - 1) * 2.5 / 0.1
|
||||
return ((float(adc_code) / float(1 << 23)) - 1.) * 2.5 / 0.1
|
||||
|
||||
@kernel
|
||||
def single_conversion(self):
|
||||
|
@ -558,10 +577,10 @@ class ADC:
|
|||
self.reset()
|
||||
# Although the datasheet claims 500 us reset wait time, only waiting
|
||||
# for ~500 us can result in DOUT pin stuck in high
|
||||
delay(2500*us)
|
||||
self.core.delay(2500.*us)
|
||||
|
||||
@kernel
|
||||
def calibrate(self, volts, trigger, config, samples=[-5.0, 0.0, 5.0]):
|
||||
def calibrate(self, volts: list[DCBias], trigger: Trigger, config: Config, samples: Option[list[float]] = none):
|
||||
"""Calibrate the Shuttler waveform generator using the ADC on the AFE.
|
||||
|
||||
It finds the average slope rate and average offset by samples, and
|
||||
|
@ -586,33 +605,35 @@ class ADC:
|
|||
:param samples: A list of sample voltages for calibration. There must
|
||||
be at least 2 samples to perform slope rate calculation.
|
||||
"""
|
||||
assert len(volts) == 16
|
||||
assert len(samples) > 1
|
||||
samples_l = samples.unwrap() if samples.is_some() else [-5.0, 0.0, 5.0]
|
||||
|
||||
measurements = [0.0] * len(samples)
|
||||
assert len(volts) == 16
|
||||
assert len(samples_l) > 1
|
||||
|
||||
measurements = [0.0 for _ in range(len(samples_l))]
|
||||
|
||||
for ch in range(16):
|
||||
# Find the average slope rate and offset
|
||||
for i in range(len(samples)):
|
||||
for i in range(len(samples_l)):
|
||||
self.core.break_realtime()
|
||||
volts[ch].set_waveform(
|
||||
shuttler_volt_to_mu(samples[i]), 0, 0, 0)
|
||||
shuttler_volt_to_mu(samples_l[i]), 0, int64(0), int64(0))
|
||||
trigger.trigger(1 << ch)
|
||||
measurements[i] = self.read_ch(ch)
|
||||
|
||||
# Find the average output slope
|
||||
slope_sum = 0.0
|
||||
for i in range(len(samples) - 1):
|
||||
slope_sum += (measurements[i+1] - measurements[i])/(samples[i+1] - samples[i])
|
||||
slope_avg = slope_sum / (len(samples) - 1)
|
||||
for i in range(len(samples_l) - 1):
|
||||
slope_sum += (measurements[i+1] - measurements[i])/(samples_l[i+1] - samples_l[i])
|
||||
slope_avg = slope_sum / float(len(samples_l) - 1)
|
||||
|
||||
gain_code = int32(1 / slope_avg * (2 ** 16)) & 0xffff
|
||||
gain_code = int32(1. / slope_avg * float(2 ** 16)) & 0xffff
|
||||
|
||||
# Scale the measurements by 1/slope, find average offset
|
||||
offset_sum = 0.0
|
||||
for i in range(len(samples)):
|
||||
offset_sum += (measurements[i] / slope_avg) - samples[i]
|
||||
offset_avg = offset_sum / len(samples)
|
||||
for i in range(len(samples_l)):
|
||||
offset_sum += (measurements[i] / slope_avg) - samples_l[i]
|
||||
offset_avg = offset_sum / float(len(samples_l))
|
||||
|
||||
offset_code = shuttler_volt_to_mu(-offset_avg)
|
||||
|
||||
|
|
|
@ -7,8 +7,10 @@ Output event replacement is not supported and issuing commands at the same
|
|||
time is an error.
|
||||
"""
|
||||
|
||||
from artiq.language.core import syscall, kernel, portable, delay_mu
|
||||
from artiq.language.types import TInt32, TNone
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import nac3, Kernel, KernelInvariant, kernel, portable, extern
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
||||
|
||||
|
||||
|
@ -33,6 +35,7 @@ SPI_LSB_FIRST = 0x40
|
|||
SPI_HALF_DUPLEX = 0x80
|
||||
|
||||
|
||||
@nac3
|
||||
class SPIMaster:
|
||||
"""Core device Serial Peripheral Interface (SPI) bus master.
|
||||
|
||||
|
@ -62,12 +65,14 @@ class SPIMaster:
|
|||
:meth:`update_xfer_duration_mu`
|
||||
:param core_device: Core device name
|
||||
"""
|
||||
kernel_invariants = {"core", "ref_period_mu", "channel"}
|
||||
core: KernelInvariant[Core]
|
||||
ref_period_mu: KernelInvariant[int64]
|
||||
channel: KernelInvariant[int32]
|
||||
xfer_duration_mu: Kernel[int64]
|
||||
|
||||
def __init__(self, dmgr, channel, div=0, length=0, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.ref_period_mu = self.core.seconds_to_mu(
|
||||
self.core.coarse_ref_period)
|
||||
self.ref_period_mu = self.core.seconds_to_mu(self.core.coarse_ref_period)
|
||||
assert self.ref_period_mu == self.core.ref_multiplier
|
||||
self.channel = channel
|
||||
self.update_xfer_duration_mu(div, length)
|
||||
|
@ -77,12 +82,12 @@ class SPIMaster:
|
|||
return [(channel, None)]
|
||||
|
||||
@portable
|
||||
def frequency_to_div(self, f):
|
||||
def frequency_to_div(self, f: float) -> int32:
|
||||
"""Convert a SPI clock frequency to the closest SPI clock divider."""
|
||||
return int(round(1/(f*self.core.mu_to_seconds(self.ref_period_mu))))
|
||||
return round(1./(f*self.core.mu_to_seconds(self.ref_period_mu)))
|
||||
|
||||
@kernel
|
||||
def set_config(self, flags, length, freq, cs):
|
||||
def set_config(self, flags: int32, length: int32, freq: float, cs: int32):
|
||||
"""Set the configuration register.
|
||||
|
||||
* If ``SPI_CS_POLARITY`` is cleared (``cs`` active low, the default),
|
||||
|
@ -149,7 +154,7 @@ class SPIMaster:
|
|||
self.set_config_mu(flags, length, self.frequency_to_div(freq), cs)
|
||||
|
||||
@kernel
|
||||
def set_config_mu(self, flags, length, div, cs):
|
||||
def set_config_mu(self, flags: int32, length: int32, div: int32, cs: int32):
|
||||
"""Set the ``config`` register (in SPI bus machine units).
|
||||
|
||||
.. seealso:: :meth:`set_config`
|
||||
|
@ -176,7 +181,7 @@ class SPIMaster:
|
|||
delay_mu(self.ref_period_mu)
|
||||
|
||||
@portable
|
||||
def update_xfer_duration_mu(self, div, length):
|
||||
def update_xfer_duration_mu(self, div: int32, length: int32):
|
||||
"""Calculate and set the transfer duration.
|
||||
|
||||
This method updates the SPI transfer duration which is used
|
||||
|
@ -199,10 +204,10 @@ class SPIMaster:
|
|||
:param div: SPI clock divider (see: :meth:`set_config_mu`)
|
||||
:param length: SPI transfer length (see: :meth:`set_config_mu`)
|
||||
"""
|
||||
self.xfer_duration_mu = ((length + 1)*div + 1)*self.ref_period_mu
|
||||
self.xfer_duration_mu = int64((length + 1)*div + 1)*self.ref_period_mu
|
||||
|
||||
@kernel
|
||||
def write(self, data):
|
||||
def write(self, data: int32):
|
||||
"""Write SPI data to shift register register and start transfer.
|
||||
|
||||
* The ``data`` register and the shift register are 32 bits wide.
|
||||
|
@ -230,7 +235,7 @@ class SPIMaster:
|
|||
delay_mu(self.xfer_duration_mu)
|
||||
|
||||
@kernel
|
||||
def read(self):
|
||||
def read(self) -> int32:
|
||||
"""Read SPI data submitted by the SPI core.
|
||||
|
||||
For bit alignment and bit ordering see :meth:`set_config`.
|
||||
|
@ -242,21 +247,22 @@ class SPIMaster:
|
|||
return rtio_input_data(self.channel)
|
||||
|
||||
|
||||
@syscall(flags={"nounwind", "nowrite"})
|
||||
def spi_set_config(busno: TInt32, flags: TInt32, length: TInt32, div: TInt32, cs: TInt32) -> TNone:
|
||||
@extern
|
||||
def spi_set_config(busno: int32, flags: int32, length: int32, div: int32, cs: int32):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall(flags={"nounwind", "nowrite"})
|
||||
def spi_write(busno: TInt32, data: TInt32) -> TNone:
|
||||
@extern
|
||||
def spi_write(busno: int32, data: int32):
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@syscall(flags={"nounwind", "nowrite"})
|
||||
def spi_read(busno: TInt32) -> TInt32:
|
||||
@extern
|
||||
def spi_read(busno: int32) -> int32:
|
||||
raise NotImplementedError("syscall not simulated")
|
||||
|
||||
|
||||
@nac3
|
||||
class NRTSPIMaster:
|
||||
"""Core device non-realtime Serial Peripheral Interface (SPI) bus master.
|
||||
Owns one non-realtime SPI bus.
|
||||
|
@ -269,12 +275,15 @@ class NRTSPIMaster:
|
|||
|
||||
See :class:`SPIMaster` for a description of the methods.
|
||||
"""
|
||||
core: KernelInvariant[Core]
|
||||
busno: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, busno=0, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.busno = busno
|
||||
|
||||
@kernel
|
||||
def set_config_mu(self, flags=0, length=8, div=6, cs=1):
|
||||
def set_config_mu(self, flags: int32 = 0, length: int32 = 8, div: int32 = 6, cs: int32 = 1):
|
||||
"""Set the ``config`` register.
|
||||
|
||||
In many cases, the SPI configuration is already set by the firmware
|
||||
|
@ -283,9 +292,9 @@ class NRTSPIMaster:
|
|||
spi_set_config(self.busno, flags, length, div, cs)
|
||||
|
||||
@kernel
|
||||
def write(self, data=0):
|
||||
def write(self, data: int32 = 0):
|
||||
spi_write(self.busno, data)
|
||||
|
||||
@kernel
|
||||
def read(self):
|
||||
def read(self) -> int32:
|
||||
return spi_read(self.busno)
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
from artiq.language.core import kernel, delay, delay_mu, portable
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import *
|
||||
from artiq.language.units import us, ns
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.rtio import rtio_output, rtio_input_data
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from artiq.coredevice import urukul, sampler
|
||||
from artiq.coredevice.spi2 import SPI_END, SPIMaster
|
||||
from artiq.coredevice.urukul import CFG_MASK_NU, CPLD
|
||||
from artiq.coredevice.ad9910 import AD9910
|
||||
from artiq.coredevice.sampler import adc_mu_to_volt as sampler_adc_mu_to_volt, SPI_CONFIG as SAMPLER_SPI_CONFIG, SPI_CS_PGIA as SAMPLER_SPI_CS_PGIA
|
||||
|
||||
|
||||
COEFF_WIDTH = 18
|
||||
|
@ -17,20 +22,21 @@ COEFF_SHIFT = 11
|
|||
|
||||
|
||||
@portable
|
||||
def y_mu_to_full_scale(y):
|
||||
def y_mu_to_full_scale(y: int32) -> float:
|
||||
"""Convert servo Y data from machine units to units of full scale."""
|
||||
return y / Y_FULL_SCALE_MU
|
||||
return float(y) / float(Y_FULL_SCALE_MU)
|
||||
|
||||
|
||||
@portable
|
||||
def adc_mu_to_volts(x, gain, corrected_fs=True):
|
||||
def adc_mu_to_volts(x: int32, gain: int32, corrected_fs: bool = True) -> float:
|
||||
"""Convert servo ADC data from machine units to Volt."""
|
||||
val = (x >> 1) & 0xffff
|
||||
mask = 1 << 15
|
||||
val = -(val & mask) + (val & ~mask)
|
||||
return sampler.adc_mu_to_volt(val, gain, corrected_fs)
|
||||
return sampler_adc_mu_to_volt(val, gain, corrected_fs)
|
||||
|
||||
|
||||
@nac3
|
||||
class SUServo:
|
||||
"""Sampler-Urukul Servo parent and configuration device.
|
||||
|
||||
|
@ -65,8 +71,15 @@ class SUServo:
|
|||
:param sampler_hw_rev: Sampler's revision string
|
||||
:param core_device: Core device name
|
||||
"""
|
||||
kernel_invariants = {"channel", "core", "pgia", "cplds", "ddses",
|
||||
"ref_period_mu", "corrected_fs"}
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
pgia: KernelInvariant[SPIMaster]
|
||||
ddses: KernelInvariant[list[AD9910]]
|
||||
cplds: KernelInvariant[list[CPLD]]
|
||||
channel: KernelInvariant[int32]
|
||||
gains: Kernel[int32]
|
||||
ref_period_mu: KernelInvariant[int64]
|
||||
corrected_fs: KernelInvariant[bool]
|
||||
|
||||
def __init__(self, dmgr, channel, pgia_device,
|
||||
cpld_devices, dds_devices,
|
||||
|
@ -102,12 +115,12 @@ class SUServo:
|
|||
This method does not alter the profile configuration memory
|
||||
or the channel controls.
|
||||
"""
|
||||
self.set_config(enable=0)
|
||||
delay(3*us) # pipeline flush
|
||||
self.set_config(enable=False)
|
||||
self.core.delay(3.*us) # pipeline flush
|
||||
|
||||
self.pgia.set_config_mu(
|
||||
sampler.SPI_CONFIG | spi.SPI_END,
|
||||
16, 4, sampler.SPI_CS_PGIA)
|
||||
SAMPLER_SPI_CONFIG | SPI_END,
|
||||
16, 4, SAMPLER_SPI_CS_PGIA)
|
||||
|
||||
for i in range(len(self.cplds)):
|
||||
cpld = self.cplds[i]
|
||||
|
@ -115,12 +128,12 @@ class SUServo:
|
|||
|
||||
cpld.init(blind=True)
|
||||
prev_cpld_cfg = cpld.cfg_reg
|
||||
cpld.cfg_write(prev_cpld_cfg | (0xf << urukul.CFG_MASK_NU))
|
||||
cpld.cfg_write(prev_cpld_cfg | (0xf << CFG_MASK_NU))
|
||||
dds.init(blind=True)
|
||||
cpld.cfg_write(prev_cpld_cfg)
|
||||
|
||||
@kernel
|
||||
def write(self, addr, value):
|
||||
def write(self, addr: int32, value: int32):
|
||||
"""Write to servo memory.
|
||||
|
||||
This method advances the timeline by one coarse RTIO cycle.
|
||||
|
@ -136,7 +149,7 @@ class SUServo:
|
|||
delay_mu(self.ref_period_mu)
|
||||
|
||||
@kernel
|
||||
def read(self, addr):
|
||||
def read(self, addr: int32) -> int32:
|
||||
"""Read from servo memory.
|
||||
|
||||
This method does not advance the timeline but consumes all slack.
|
||||
|
@ -149,7 +162,7 @@ class SUServo:
|
|||
return rtio_input_data(self.channel)
|
||||
|
||||
@kernel
|
||||
def set_config(self, enable):
|
||||
def set_config(self, enable: bool):
|
||||
"""Set SU Servo configuration.
|
||||
|
||||
This method advances the timeline by one servo memory access.
|
||||
|
@ -164,10 +177,10 @@ class SUServo:
|
|||
Disabling takes up to two servo cycles (~2.3 µs) to clear the
|
||||
processing pipeline.
|
||||
"""
|
||||
self.write(CONFIG_ADDR, enable)
|
||||
self.write(CONFIG_ADDR, int32(enable))
|
||||
|
||||
@kernel
|
||||
def get_status(self):
|
||||
def get_status(self) -> int32:
|
||||
"""Get current SU Servo status.
|
||||
|
||||
This method does not advance the timeline but consumes all slack.
|
||||
|
@ -188,7 +201,7 @@ class SUServo:
|
|||
return self.read(CONFIG_ADDR)
|
||||
|
||||
@kernel
|
||||
def get_adc_mu(self, adc):
|
||||
def get_adc_mu(self, adc: int32) -> int32:
|
||||
"""Get the latest ADC reading (IIR filter input X0) in machine units.
|
||||
|
||||
This method does not advance the timeline but consumes all slack.
|
||||
|
@ -206,7 +219,7 @@ class SUServo:
|
|||
return self.read(STATE_SEL | (adc << 1) | (1 << 8))
|
||||
|
||||
@kernel
|
||||
def set_pgia_mu(self, channel, gain):
|
||||
def set_pgia_mu(self, channel: int32, gain: int32):
|
||||
"""Set instrumentation amplifier gain of a ADC channel.
|
||||
|
||||
The four gain settings (0, 1, 2, 3) corresponds to gains of
|
||||
|
@ -222,7 +235,7 @@ class SUServo:
|
|||
self.gains = gains
|
||||
|
||||
@kernel
|
||||
def get_adc(self, channel):
|
||||
def get_adc(self, channel: int32) -> float:
|
||||
"""Get the latest ADC reading (IIR filter input X0).
|
||||
|
||||
This method does not advance the timeline but consumes all slack.
|
||||
|
@ -243,13 +256,19 @@ class SUServo:
|
|||
return adc_mu_to_volts(val, gain, self.corrected_fs)
|
||||
|
||||
|
||||
@nac3
|
||||
class Channel:
|
||||
"""Sampler-Urukul Servo channel
|
||||
|
||||
:param channel: RTIO channel number
|
||||
:param servo_device: Name of the parent SUServo device
|
||||
"""
|
||||
kernel_invariants = {"channel", "core", "servo", "servo_channel"}
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
servo: KernelInvariant[SUServo]
|
||||
channel: KernelInvariant[int32]
|
||||
servo_channel: KernelInvariant[int32]
|
||||
dds: KernelInvariant[AD9910]
|
||||
|
||||
def __init__(self, dmgr, channel, servo_device):
|
||||
self.servo = dmgr.get(servo_device)
|
||||
|
@ -266,7 +285,7 @@ class Channel:
|
|||
return [(channel, None)]
|
||||
|
||||
@kernel
|
||||
def set(self, en_out, en_iir=0, profile=0):
|
||||
def set(self, en_out: bool, en_iir: bool = False, profile: int32 = 0):
|
||||
"""Operate channel.
|
||||
|
||||
This method does not advance the timeline. Output RF switch setting
|
||||
|
@ -282,10 +301,10 @@ class Channel:
|
|||
:param profile: Active profile (0-31)
|
||||
"""
|
||||
rtio_output(self.channel << 8,
|
||||
en_out | (en_iir << 1) | (profile << 2))
|
||||
int32(en_out) | (int32(en_iir) << 1) | (profile << 2))
|
||||
|
||||
@kernel
|
||||
def set_dds_mu(self, profile, ftw, offs, pow_=0):
|
||||
def set_dds_mu(self, profile: int32, ftw: int32, offs: int32, pow_: int32 = 0):
|
||||
"""Set profile DDS coefficients in machine units.
|
||||
|
||||
.. seealso:: :meth:`set_amplitude`
|
||||
|
@ -302,7 +321,7 @@ class Channel:
|
|||
self.servo.write(base + 2, pow_)
|
||||
|
||||
@kernel
|
||||
def set_dds(self, profile, frequency, offset, phase=0.):
|
||||
def set_dds(self, profile: int32, frequency: float, offset: float, phase: float = 0.):
|
||||
"""Set profile DDS coefficients.
|
||||
|
||||
This method advances the timeline by four servo memory accesses.
|
||||
|
@ -321,7 +340,7 @@ class Channel:
|
|||
self.set_dds_mu(profile, ftw, offs, pow_)
|
||||
|
||||
@kernel
|
||||
def set_dds_offset_mu(self, profile, offs):
|
||||
def set_dds_offset_mu(self, profile: int32, offs: int32):
|
||||
"""Set only IIR offset in DDS coefficient profile.
|
||||
|
||||
See :meth:`set_dds_mu` for setting the complete DDS profile.
|
||||
|
@ -333,7 +352,7 @@ class Channel:
|
|||
self.servo.write(base + 4, offs)
|
||||
|
||||
@kernel
|
||||
def set_dds_offset(self, profile, offset):
|
||||
def set_dds_offset(self, profile: int32, offset: float):
|
||||
"""Set only IIR offset in DDS coefficient profile.
|
||||
|
||||
See :meth:`set_dds` for setting the complete DDS profile.
|
||||
|
@ -344,7 +363,7 @@ class Channel:
|
|||
self.set_dds_offset_mu(profile, self.dds_offset_to_mu(offset))
|
||||
|
||||
@portable
|
||||
def dds_offset_to_mu(self, offset):
|
||||
def dds_offset_to_mu(self, offset: float) -> int32:
|
||||
"""Convert IIR offset (negative setpoint) from units of full scale to
|
||||
machine units (see :meth:`set_dds_mu`, :meth:`set_dds_offset_mu`).
|
||||
|
||||
|
@ -352,10 +371,10 @@ class Channel:
|
|||
rounding and representation as two's complement, ``offset=1`` can not
|
||||
be represented while ``offset=-1`` can.
|
||||
"""
|
||||
return int(round(offset * (1 << COEFF_WIDTH - 1)))
|
||||
return round(offset * float(1 << COEFF_WIDTH - 1))
|
||||
|
||||
@kernel
|
||||
def set_iir_mu(self, profile, adc, a1, b0, b1, dly=0):
|
||||
def set_iir_mu(self, profile: int32, adc: int32, a1: int32, b0: int32, b1: int32, dly: int32 = 0):
|
||||
"""Set profile IIR coefficients in machine units.
|
||||
|
||||
The recurrence relation is (all data signed and MSB aligned):
|
||||
|
@ -395,7 +414,7 @@ class Channel:
|
|||
self.servo.write(base + 7, b0)
|
||||
|
||||
@kernel
|
||||
def set_iir(self, profile, adc, kp, ki=0., g=0., delay=0.):
|
||||
def set_iir(self, profile: int32, adc: int32, kp: float, ki: float = 0., g: float = 0., delay: float = 0.):
|
||||
"""Set profile IIR coefficients.
|
||||
|
||||
This method advances the timeline by four servo memory accesses.
|
||||
|
@ -437,23 +456,23 @@ class Channel:
|
|||
A_NORM = 1 << COEFF_SHIFT
|
||||
COEFF_MAX = 1 << COEFF_WIDTH - 1
|
||||
|
||||
kp *= B_NORM
|
||||
kp *= float(B_NORM)
|
||||
if ki == 0.:
|
||||
# pure P
|
||||
a1 = 0
|
||||
b1 = 0
|
||||
b0 = int(round(kp))
|
||||
b0 = round(kp)
|
||||
else:
|
||||
# I or PI
|
||||
ki *= B_NORM*T_CYCLE/2.
|
||||
ki *= float(B_NORM)*T_CYCLE/2.
|
||||
if g == 0.:
|
||||
c = 1.
|
||||
a1 = A_NORM
|
||||
else:
|
||||
c = 1./(1. + ki/(g*B_NORM))
|
||||
a1 = int(round((2.*c - 1.)*A_NORM))
|
||||
b0 = int(round(kp + ki*c))
|
||||
b1 = int(round(kp + (ki - 2.*kp)*c))
|
||||
c = 1./(1. + ki/(g*float(B_NORM)))
|
||||
a1 = round((2.*c - 1.)*float(A_NORM))
|
||||
b0 = round(kp + ki*c)
|
||||
b1 = round(kp + (ki - 2.*kp)*c)
|
||||
if b1 == -b0:
|
||||
raise ValueError("low integrator gain and/or gain limit")
|
||||
|
||||
|
@ -461,11 +480,11 @@ class Channel:
|
|||
b1 >= COEFF_MAX or b1 < -COEFF_MAX):
|
||||
raise ValueError("high gains")
|
||||
|
||||
dly = int(round(delay/T_CYCLE))
|
||||
dly = round(delay/T_CYCLE)
|
||||
self.set_iir_mu(profile, adc, a1, b0, b1, dly)
|
||||
|
||||
@kernel
|
||||
def get_profile_mu(self, profile, data):
|
||||
def get_profile_mu(self, profile: int32, data: list[int32]):
|
||||
"""Retrieve profile data.
|
||||
|
||||
Profile data is returned in the ``data`` argument in machine units
|
||||
|
@ -483,10 +502,10 @@ class Channel:
|
|||
base = (self.servo_channel << 8) | (profile << 3)
|
||||
for i in range(len(data)):
|
||||
data[i] = self.servo.read(base + i)
|
||||
delay(4*us)
|
||||
self.core.delay(4.*us)
|
||||
|
||||
@kernel
|
||||
def get_y_mu(self, profile):
|
||||
def get_y_mu(self, profile: int32) -> int32:
|
||||
"""Get a profile's IIR state (filter output, Y0) in machine units.
|
||||
|
||||
The IIR state is also know as the "integrator", or the DDS amplitude
|
||||
|
@ -504,7 +523,7 @@ class Channel:
|
|||
return self.servo.read(STATE_SEL | (self.servo_channel << 5) | profile)
|
||||
|
||||
@kernel
|
||||
def get_y(self, profile):
|
||||
def get_y(self, profile: int32) -> float:
|
||||
"""Get a profile's IIR state (filter output, Y0).
|
||||
|
||||
The IIR state is also know as the "integrator", or the DDS amplitude
|
||||
|
@ -522,7 +541,7 @@ class Channel:
|
|||
return y_mu_to_full_scale(self.get_y_mu(profile))
|
||||
|
||||
@kernel
|
||||
def set_y_mu(self, profile, y):
|
||||
def set_y_mu(self, profile: int32, y: int32):
|
||||
"""Set a profile's IIR state (filter output, Y0) in machine units.
|
||||
|
||||
The IIR state is also know as the "integrator", or the DDS amplitude
|
||||
|
@ -542,7 +561,7 @@ class Channel:
|
|||
self.servo.write(STATE_SEL | (self.servo_channel << 5) | profile, y)
|
||||
|
||||
@kernel
|
||||
def set_y(self, profile, y):
|
||||
def set_y(self, profile: int32, y: float) -> int32:
|
||||
"""Set a profile's IIR state (filter output, Y0).
|
||||
|
||||
The IIR state is also know as the "integrator", or the DDS amplitude
|
||||
|
@ -557,7 +576,7 @@ class Channel:
|
|||
:param profile: Profile number (0-31)
|
||||
:param y: IIR state in units of full scale
|
||||
"""
|
||||
y_mu = int(round(y * Y_FULL_SCALE_MU))
|
||||
y_mu = round(y * float(Y_FULL_SCALE_MU))
|
||||
if y_mu < 0 or y_mu > (1 << 17) - 1:
|
||||
raise ValueError("Invalid SUServo y-value!")
|
||||
self.set_y_mu(profile, y_mu)
|
||||
|
|
|
@ -6,10 +6,10 @@ replacement. For example, pulses of "zero" length (e.g. :meth:`TTLInOut.on`
|
|||
immediately followed by :meth:`TTLInOut.off`, without a delay) are suppressed.
|
||||
"""
|
||||
|
||||
import numpy
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import *
|
||||
from artiq.language.types import *
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.rtio import (rtio_output, rtio_input_timestamp,
|
||||
rtio_input_data)
|
||||
from artiq.coredevice.exceptions import RTIOOverflow
|
||||
|
@ -22,6 +22,7 @@ from artiq.coredevice.exceptions import RTIOOverflow
|
|||
# 3 Set input sensitivity and sample
|
||||
|
||||
|
||||
@nac3
|
||||
class TTLOut:
|
||||
"""RTIO TTL output driver.
|
||||
|
||||
|
@ -29,7 +30,9 @@ class TTLOut:
|
|||
|
||||
:param channel: channel number
|
||||
"""
|
||||
kernel_invariants = {"core", "channel", "target_o"}
|
||||
core: KernelInvariant[Core]
|
||||
channel: KernelInvariant[int32]
|
||||
target_o: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, channel, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
|
@ -45,7 +48,7 @@ class TTLOut:
|
|||
pass
|
||||
|
||||
@kernel
|
||||
def set_o(self, o):
|
||||
def set_o(self, o: bool):
|
||||
rtio_output(self.target_o, 1 if o else 0)
|
||||
|
||||
@kernel
|
||||
|
@ -65,7 +68,7 @@ class TTLOut:
|
|||
self.set_o(False)
|
||||
|
||||
@kernel
|
||||
def pulse_mu(self, duration):
|
||||
def pulse_mu(self, duration: int64):
|
||||
"""Pulse the output high for the specified duration
|
||||
(in machine units).
|
||||
|
||||
|
@ -75,16 +78,17 @@ class TTLOut:
|
|||
self.off()
|
||||
|
||||
@kernel
|
||||
def pulse(self, duration):
|
||||
def pulse(self, duration: float):
|
||||
"""Pulse the output high for the specified duration
|
||||
(in seconds).
|
||||
|
||||
The time cursor is advanced by the specified duration."""
|
||||
self.on()
|
||||
delay(duration)
|
||||
self.core.delay(duration)
|
||||
self.off()
|
||||
|
||||
|
||||
@nac3
|
||||
class TTLInOut:
|
||||
"""RTIO TTL input/output driver.
|
||||
|
||||
|
@ -111,8 +115,13 @@ class TTLInOut:
|
|||
|
||||
:param channel: channel number
|
||||
"""
|
||||
kernel_invariants = {"core", "channel", "gate_latency_mu",
|
||||
"target_o", "target_oe", "target_sens", "target_sample"}
|
||||
core: KernelInvariant[Core]
|
||||
channel: KernelInvariant[int32]
|
||||
gate_latency_mu: KernelInvariant[int32]
|
||||
target_o: KernelInvariant[int32]
|
||||
target_oe: KernelInvariant[int32]
|
||||
target_sens: KernelInvariant[int32]
|
||||
target_sample: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, channel, gate_latency_mu=None,
|
||||
core_device="core"):
|
||||
|
@ -137,7 +146,7 @@ class TTLInOut:
|
|||
return [(channel, None)]
|
||||
|
||||
@kernel
|
||||
def set_oe(self, oe):
|
||||
def set_oe(self, oe: bool):
|
||||
rtio_output(self.target_oe, 1 if oe else 0)
|
||||
|
||||
@kernel
|
||||
|
@ -167,7 +176,7 @@ class TTLInOut:
|
|||
self.set_oe(False)
|
||||
|
||||
@kernel
|
||||
def set_o(self, o):
|
||||
def set_o(self, o: bool):
|
||||
rtio_output(self.target_o, 1 if o else 0)
|
||||
|
||||
@kernel
|
||||
|
@ -191,7 +200,7 @@ class TTLInOut:
|
|||
self.set_o(False)
|
||||
|
||||
@kernel
|
||||
def pulse_mu(self, duration):
|
||||
def pulse_mu(self, duration: int64):
|
||||
"""Pulse the output high for the specified duration
|
||||
(in machine units).
|
||||
|
||||
|
@ -201,22 +210,22 @@ class TTLInOut:
|
|||
self.off()
|
||||
|
||||
@kernel
|
||||
def pulse(self, duration):
|
||||
def pulse(self, duration: float):
|
||||
"""Pulse the output high for the specified duration
|
||||
(in seconds).
|
||||
|
||||
The time cursor is advanced by the specified duration."""
|
||||
self.on()
|
||||
delay(duration)
|
||||
self.core.delay(duration)
|
||||
self.off()
|
||||
|
||||
# Input API: gating
|
||||
@kernel
|
||||
def _set_sensitivity(self, value):
|
||||
def _set_sensitivity(self, value: int32):
|
||||
rtio_output(self.target_sens, value)
|
||||
|
||||
@kernel
|
||||
def gate_rising_mu(self, duration):
|
||||
def gate_rising_mu(self, duration: int64) -> int64:
|
||||
"""Register rising edge events for the specified duration
|
||||
(in machine units).
|
||||
|
||||
|
@ -231,7 +240,7 @@ class TTLInOut:
|
|||
return now_mu()
|
||||
|
||||
@kernel
|
||||
def gate_falling_mu(self, duration):
|
||||
def gate_falling_mu(self, duration: int64) -> int64:
|
||||
"""Register falling edge events for the specified duration
|
||||
(in machine units).
|
||||
|
||||
|
@ -246,7 +255,7 @@ class TTLInOut:
|
|||
return now_mu()
|
||||
|
||||
@kernel
|
||||
def gate_both_mu(self, duration):
|
||||
def gate_both_mu(self, duration: int64) -> int64:
|
||||
"""Register both rising and falling edge events for the specified
|
||||
duration (in machine units).
|
||||
|
||||
|
@ -261,7 +270,7 @@ class TTLInOut:
|
|||
return now_mu()
|
||||
|
||||
@kernel
|
||||
def gate_rising(self, duration):
|
||||
def gate_rising(self, duration: float) -> int64:
|
||||
"""Register rising edge events for the specified duration
|
||||
(in seconds).
|
||||
|
||||
|
@ -271,12 +280,12 @@ class TTLInOut:
|
|||
convenience when used with :meth:`count`/:meth:`timestamp_mu`.
|
||||
"""
|
||||
self._set_sensitivity(1)
|
||||
delay(duration)
|
||||
self.core.delay(duration)
|
||||
self._set_sensitivity(0)
|
||||
return now_mu()
|
||||
|
||||
@kernel
|
||||
def gate_falling(self, duration):
|
||||
def gate_falling(self, duration: float) -> int64:
|
||||
"""Register falling edge events for the specified duration
|
||||
(in seconds).
|
||||
|
||||
|
@ -287,12 +296,12 @@ class TTLInOut:
|
|||
|
||||
"""
|
||||
self._set_sensitivity(2)
|
||||
delay(duration)
|
||||
self.core.delay(duration)
|
||||
self._set_sensitivity(0)
|
||||
return now_mu()
|
||||
|
||||
@kernel
|
||||
def gate_both(self, duration):
|
||||
def gate_both(self, duration: float) -> int64:
|
||||
"""Register both rising and falling edge events for the specified
|
||||
duration (in seconds).
|
||||
|
||||
|
@ -302,12 +311,12 @@ class TTLInOut:
|
|||
convenience when used with :meth:`count`/:meth:`timestamp_mu`.
|
||||
"""
|
||||
self._set_sensitivity(3)
|
||||
delay(duration)
|
||||
self.core.delay(duration)
|
||||
self._set_sensitivity(0)
|
||||
return now_mu()
|
||||
|
||||
@kernel
|
||||
def count(self, up_to_timestamp_mu):
|
||||
def count(self, up_to_timestamp_mu: int64) -> int32:
|
||||
"""Consume RTIO input events until the hardware timestamp counter has
|
||||
reached the specified timestamp and return the number of observed
|
||||
events.
|
||||
|
@ -355,12 +364,12 @@ class TTLInOut:
|
|||
ttl_input.count(ttl_input.gate_rising(100 * us))
|
||||
"""
|
||||
count = 0
|
||||
while rtio_input_timestamp(up_to_timestamp_mu + self.gate_latency_mu, self.channel) >= 0:
|
||||
while rtio_input_timestamp(up_to_timestamp_mu + int64(self.gate_latency_mu), self.channel) >= int64(0):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
@kernel
|
||||
def timestamp_mu(self, up_to_timestamp_mu):
|
||||
def timestamp_mu(self, up_to_timestamp_mu: int64) -> int64:
|
||||
"""Return the timestamp of the next RTIO input event, or -1 if the
|
||||
hardware timestamp counter reaches the given value before an event is
|
||||
received.
|
||||
|
@ -378,7 +387,7 @@ class TTLInOut:
|
|||
:return: The timestamp (in machine units) of the first event received;
|
||||
-1 on timeout.
|
||||
"""
|
||||
return rtio_input_timestamp(up_to_timestamp_mu + self.gate_latency_mu, self.channel)
|
||||
return rtio_input_timestamp(up_to_timestamp_mu + int64(self.gate_latency_mu), self.channel)
|
||||
|
||||
# Input API: sampling
|
||||
@kernel
|
||||
|
@ -390,7 +399,7 @@ class TTLInOut:
|
|||
rtio_output(self.target_sample, 0)
|
||||
|
||||
@kernel
|
||||
def sample_get(self):
|
||||
def sample_get(self) -> int32:
|
||||
"""Returns the value of a sample previously obtained with
|
||||
:meth:`sample_input`.
|
||||
|
||||
|
@ -402,7 +411,7 @@ class TTLInOut:
|
|||
return rtio_input_data(self.channel)
|
||||
|
||||
@kernel
|
||||
def sample_get_nonrt(self):
|
||||
def sample_get_nonrt(self) -> int32:
|
||||
"""Convenience function that obtains the value of a sample
|
||||
at the position of the time cursor, breaks realtime, and
|
||||
returns the sample value."""
|
||||
|
@ -413,7 +422,7 @@ class TTLInOut:
|
|||
|
||||
# Input API: watching
|
||||
@kernel
|
||||
def watch_stay_on(self):
|
||||
def watch_stay_on(self) -> bool:
|
||||
"""Checks that the input is at a high level at the position
|
||||
of the time cursor and keep checking until :meth:`watch_done`
|
||||
is called.
|
||||
|
@ -428,13 +437,13 @@ class TTLInOut:
|
|||
return rtio_input_data(self.channel) == 1
|
||||
|
||||
@kernel
|
||||
def watch_stay_off(self):
|
||||
def watch_stay_off(self) -> bool:
|
||||
"""Like :meth:`watch_stay_on`, but for low levels."""
|
||||
rtio_output(self.target_sample, 1) # gate rising
|
||||
return rtio_input_data(self.channel) == 0
|
||||
|
||||
@kernel
|
||||
def watch_done(self):
|
||||
def watch_done(self) -> bool:
|
||||
"""Stop watching the input at the position of the time cursor.
|
||||
|
||||
Returns ``True`` if the input has not changed state while it
|
||||
|
@ -446,13 +455,14 @@ class TTLInOut:
|
|||
rtio_output(self.target_sens, 0)
|
||||
success = True
|
||||
try:
|
||||
while rtio_input_timestamp(now_mu() + self.gate_latency_mu, self.channel) != -1:
|
||||
while rtio_input_timestamp(now_mu() + int64(self.gate_latency_mu), self.channel) != int64(-1):
|
||||
success = False
|
||||
except RTIOOverflow:
|
||||
success = False
|
||||
return success
|
||||
|
||||
|
||||
@nac3
|
||||
class TTLClockGen:
|
||||
"""RTIO TTL clock generator driver.
|
||||
|
||||
|
@ -464,35 +474,37 @@ class TTLClockGen:
|
|||
:param channel: channel number
|
||||
:param acc_width: accumulator width in bits
|
||||
"""
|
||||
kernel_invariants = {"core", "channel", "target", "acc_width"}
|
||||
core: KernelInvariant[Core]
|
||||
channel: KernelInvariant[int32]
|
||||
target: KernelInvariant[int32]
|
||||
acc_width: KernelInvariant[int32]
|
||||
|
||||
def __init__(self, dmgr, channel, acc_width=24, core_device="core"):
|
||||
self.core = dmgr.get(core_device)
|
||||
self.channel = channel
|
||||
self.target = channel << 8
|
||||
|
||||
self.acc_width = numpy.int64(acc_width)
|
||||
self.acc_width = acc_width
|
||||
|
||||
@staticmethod
|
||||
def get_rtio_channels(channel, **kwargs):
|
||||
return [(channel, None)]
|
||||
|
||||
@portable
|
||||
def frequency_to_ftw(self, frequency):
|
||||
def frequency_to_ftw(self, frequency: float) -> int32:
|
||||
"""Returns the frequency tuning word corresponding to the given
|
||||
frequency.
|
||||
"""
|
||||
return round(2**self.acc_width*frequency*self.core.coarse_ref_period)
|
||||
return round(2.**float(self.acc_width)*frequency*self.core.coarse_ref_period)
|
||||
|
||||
@portable
|
||||
def ftw_to_frequency(self, ftw):
|
||||
def ftw_to_frequency(self, ftw: int32) -> float:
|
||||
"""Returns the frequency corresponding to the given frequency tuning
|
||||
word.
|
||||
"""
|
||||
return ftw/self.core.coarse_ref_period/2**self.acc_width
|
||||
return float(ftw)/self.core.coarse_ref_period/2.**float(self.acc_width)
|
||||
|
||||
@kernel
|
||||
def set_mu(self, frequency):
|
||||
def set_mu(self, frequency: int32):
|
||||
"""Set the frequency of the clock, in machine units, at the current
|
||||
position of the time cursor.
|
||||
|
||||
|
@ -512,7 +524,7 @@ class TTLClockGen:
|
|||
rtio_output(self.target, frequency)
|
||||
|
||||
@kernel
|
||||
def set(self, frequency):
|
||||
def set(self, frequency: float):
|
||||
"""Like :meth:`set_mu`, but using Hz."""
|
||||
self.set_mu(self.frequency_to_ftw(frequency))
|
||||
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from numpy import int32, int64
|
||||
|
||||
from artiq.language.core import kernel, delay, portable, at_mu, now_mu
|
||||
from artiq.language.core import *
|
||||
from artiq.language.units import us, ms
|
||||
from artiq.language.types import TInt32, TFloat, TBool
|
||||
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.spi2 import *
|
||||
from artiq.coredevice.ttl import TTLOut, TTLClockGen
|
||||
|
||||
SPI_CONFIG = (0 * spi.SPI_OFFLINE | 0 * spi.SPI_END |
|
||||
0 * spi.SPI_INPUT | 1 * spi.SPI_CS_POLARITY |
|
||||
0 * spi.SPI_CLK_POLARITY | 0 * spi.SPI_CLK_PHASE |
|
||||
0 * spi.SPI_LSB_FIRST | 0 * spi.SPI_HALF_DUPLEX)
|
||||
|
||||
SPI_CONFIG = (0 * SPI_OFFLINE | 0 * SPI_END |
|
||||
0 * SPI_INPUT | 1 * SPI_CS_POLARITY |
|
||||
0 * SPI_CLK_POLARITY | 0 * SPI_CLK_PHASE |
|
||||
0 * SPI_LSB_FIRST | 0 * SPI_HALF_DUPLEX)
|
||||
|
||||
# SPI clock write and read dividers
|
||||
SPIT_CFG_WR = 2
|
||||
|
@ -57,8 +61,8 @@ DEFAULT_PROFILE = 7
|
|||
|
||||
|
||||
@portable
|
||||
def urukul_cfg(rf_sw, led, profile, io_update, mask_nu,
|
||||
clk_sel, sync_sel, rst, io_rst, clk_div):
|
||||
def urukul_cfg(rf_sw: int32, led: int32, profile: int32, io_update: int32, mask_nu: int32,
|
||||
clk_sel: int32, sync_sel: int32, rst: int32, io_rst: int32, clk_div: int32) -> int32:
|
||||
"""Build Urukul CPLD configuration register"""
|
||||
return ((rf_sw << CFG_RF_SW) |
|
||||
(led << CFG_LED) |
|
||||
|
@ -74,56 +78,36 @@ def urukul_cfg(rf_sw, led, profile, io_update, mask_nu,
|
|||
|
||||
|
||||
@portable
|
||||
def urukul_sta_rf_sw(sta):
|
||||
def urukul_sta_rf_sw(sta: int32) -> int32:
|
||||
"""Return the RF switch status from Urukul status register value."""
|
||||
return (sta >> STA_RF_SW) & 0xf
|
||||
|
||||
|
||||
@portable
|
||||
def urukul_sta_smp_err(sta):
|
||||
def urukul_sta_smp_err(sta: int32) -> int32:
|
||||
"""Return the SMP_ERR status from Urukul status register value."""
|
||||
return (sta >> STA_SMP_ERR) & 0xf
|
||||
|
||||
|
||||
@portable
|
||||
def urukul_sta_pll_lock(sta):
|
||||
def urukul_sta_pll_lock(sta: int32) -> int32:
|
||||
"""Return the PLL_LOCK status from Urukul status register value."""
|
||||
return (sta >> STA_PLL_LOCK) & 0xf
|
||||
|
||||
|
||||
@portable
|
||||
def urukul_sta_ifc_mode(sta):
|
||||
def urukul_sta_ifc_mode(sta: int32) -> int32:
|
||||
"""Return the IFC_MODE status from Urukul status register value."""
|
||||
return (sta >> STA_IFC_MODE) & 0xf
|
||||
|
||||
|
||||
@portable
|
||||
def urukul_sta_proto_rev(sta):
|
||||
def urukul_sta_proto_rev(sta: int32) -> int32:
|
||||
"""Return the PROTO_REV value from Urukul status register value."""
|
||||
return (sta >> STA_PROTO_REV) & 0x7f
|
||||
|
||||
|
||||
class _RegIOUpdate:
|
||||
def __init__(self, cpld):
|
||||
self.cpld = cpld
|
||||
|
||||
@kernel
|
||||
def pulse(self, t: TFloat):
|
||||
cfg = self.cpld.cfg_reg
|
||||
self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE))
|
||||
delay(t)
|
||||
self.cpld.cfg_write(cfg)
|
||||
|
||||
|
||||
class _DummySync:
|
||||
def __init__(self, cpld):
|
||||
self.cpld = cpld
|
||||
|
||||
@kernel
|
||||
def set_mu(self, ftw: TInt32):
|
||||
pass
|
||||
|
||||
|
||||
@nac3
|
||||
class CPLD:
|
||||
"""Urukul CPLD SPI router and configuration interface.
|
||||
|
||||
|
@ -162,7 +146,17 @@ class CPLD:
|
|||
front panel SMA with no clock connected), then the ``init()`` method of
|
||||
the DDS channels can fail with the error message ``PLL lock timeout``.
|
||||
"""
|
||||
kernel_invariants = {"refclk", "bus", "core", "io_update", "clk_div"}
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
refclk: KernelInvariant[float]
|
||||
bus: KernelInvariant[SPIMaster]
|
||||
io_update: KernelInvariant[TTLOut]
|
||||
clk_div: KernelInvariant[int32]
|
||||
dds_reset: KernelInvariant[Option[TTLOut]]
|
||||
sync: KernelInvariant[Option[TTLClockGen]]
|
||||
cfg_reg: Kernel[int32]
|
||||
att_reg: Kernel[int32]
|
||||
sync_div: Kernel[int32]
|
||||
|
||||
def __init__(self, dmgr, spi_device, io_update_device=None,
|
||||
dds_reset_device=None, sync_device=None,
|
||||
|
@ -179,15 +173,19 @@ class CPLD:
|
|||
if io_update_device is not None:
|
||||
self.io_update = dmgr.get(io_update_device)
|
||||
else:
|
||||
self.io_update = _RegIOUpdate(self)
|
||||
self.io_update = _RegIOUpdate(self.core, self)
|
||||
# NAC3TODO
|
||||
raise NotImplementedError
|
||||
if dds_reset_device is not None:
|
||||
self.dds_reset = dmgr.get(dds_reset_device)
|
||||
self.dds_reset = Some(dmgr.get(dds_reset_device))
|
||||
else:
|
||||
self.dds_reset = none
|
||||
if sync_device is not None:
|
||||
self.sync = dmgr.get(sync_device)
|
||||
self.sync = Some(dmgr.get(sync_device))
|
||||
if sync_div is None:
|
||||
sync_div = 2
|
||||
else:
|
||||
self.sync = _DummySync(self)
|
||||
self.sync = none
|
||||
assert sync_div is None
|
||||
sync_div = 0
|
||||
|
||||
|
@ -199,7 +197,7 @@ class CPLD:
|
|||
self.sync_div = sync_div
|
||||
|
||||
@kernel
|
||||
def cfg_write(self, cfg: TInt32):
|
||||
def cfg_write(self, cfg: int32):
|
||||
"""Write to the configuration register.
|
||||
|
||||
See :func:`urukul_cfg` for possible flags.
|
||||
|
@ -207,13 +205,13 @@ class CPLD:
|
|||
:param cfg: 24 bit data to be written. Will be stored at
|
||||
:attr:`cfg_reg`.
|
||||
"""
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 24,
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 24,
|
||||
SPIT_CFG_WR, CS_CFG)
|
||||
self.bus.write(cfg << 8)
|
||||
self.cfg_reg = cfg
|
||||
|
||||
@kernel
|
||||
def sta_read(self) -> TInt32:
|
||||
def sta_read(self) -> int32:
|
||||
"""Read the status register.
|
||||
|
||||
Use any of the following functions to extract values:
|
||||
|
@ -226,13 +224,13 @@ class CPLD:
|
|||
|
||||
:return: The status register value.
|
||||
"""
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END | spi.SPI_INPUT, 24,
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END | SPI_INPUT, 24,
|
||||
SPIT_CFG_RD, CS_CFG)
|
||||
self.bus.write(self.cfg_reg << 8)
|
||||
return self.bus.read()
|
||||
|
||||
@kernel
|
||||
def init(self, blind: TBool = False):
|
||||
def init(self, blind: bool = False):
|
||||
"""Initialize and detect Urukul.
|
||||
|
||||
Resets the DDS I/O interface and verifies correct CPLD gateware
|
||||
|
@ -250,12 +248,12 @@ class CPLD:
|
|||
proto_rev = urukul_sta_proto_rev(self.sta_read())
|
||||
if proto_rev != STA_PROTO_REV_MATCH:
|
||||
raise ValueError("Urukul proto_rev mismatch")
|
||||
delay(100 * us) # reset, slack
|
||||
self.core.delay(100. * us) # reset, slack
|
||||
self.cfg_write(cfg)
|
||||
if self.sync_div:
|
||||
at_mu(now_mu() & ~0xf) # align to RTIO/2
|
||||
if self.sync_div != 0:
|
||||
at_mu(now_mu() & ~int64(0xf)) # align to RTIO/2
|
||||
self.set_sync_div(self.sync_div) # 125 MHz/2 = 1 GHz/16
|
||||
delay(1 * ms) # DDS wake up
|
||||
self.core.delay(1. * ms) # DDS wake up
|
||||
|
||||
@kernel
|
||||
def io_rst(self):
|
||||
|
@ -264,7 +262,7 @@ class CPLD:
|
|||
self.cfg_write(self.cfg_reg & ~(1 << CFG_IO_RST))
|
||||
|
||||
@kernel
|
||||
def cfg_sw(self, channel: TInt32, on: TBool):
|
||||
def cfg_sw(self, channel: int32, on: bool):
|
||||
"""Configure the RF switches through the configuration register.
|
||||
|
||||
These values are logically OR-ed with the LVDS lines on EEM1.
|
||||
|
@ -280,15 +278,15 @@ class CPLD:
|
|||
self.cfg_write(c)
|
||||
|
||||
@kernel
|
||||
def cfg_switches(self, state: TInt32):
|
||||
def cfg_switches(self, state: int32):
|
||||
"""Configure all four RF switches through the configuration register.
|
||||
|
||||
:param state: RF switch state as a 4 bit integer.
|
||||
"""
|
||||
self.cfg_write((self.cfg_reg & ~0xf) | state)
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def mu_to_att(self, att_mu: TInt32) -> TFloat:
|
||||
@portable
|
||||
def mu_to_att(self, att_mu: int32) -> float:
|
||||
"""Convert a digital attenuation setting to dB.
|
||||
|
||||
:param att_mu: Digital attenuation setting.
|
||||
|
@ -296,20 +294,20 @@ class CPLD:
|
|||
"""
|
||||
return (255 - (att_mu & 0xff)) / 8
|
||||
|
||||
@portable(flags={"fast-math"})
|
||||
def att_to_mu(self, att: TFloat) -> TInt32:
|
||||
@portable
|
||||
def att_to_mu(self, att: float) -> int32:
|
||||
"""Convert an attenuation setting in dB to machine units.
|
||||
|
||||
:param att: Attenuation setting in dB.
|
||||
:return: Digital attenuation setting.
|
||||
"""
|
||||
code = int32(255) - int32(round(att * 8))
|
||||
code = 255 - round(att * 8.)
|
||||
if code < 0 or code > 255:
|
||||
raise ValueError("Invalid urukul.CPLD attenuation!")
|
||||
return code
|
||||
|
||||
@kernel
|
||||
def set_att_mu(self, channel: TInt32, att: TInt32):
|
||||
def set_att_mu(self, channel: int32, att: int32):
|
||||
"""Set digital step attenuator in machine units.
|
||||
|
||||
This method will also write the attenuator settings of the three
|
||||
|
@ -325,20 +323,20 @@ class CPLD:
|
|||
self.set_all_att_mu(a)
|
||||
|
||||
@kernel
|
||||
def set_all_att_mu(self, att_reg: TInt32):
|
||||
def set_all_att_mu(self, att_reg: int32):
|
||||
"""Set all four digital step attenuators (in machine units).
|
||||
|
||||
.. seealso:: :meth:`set_att_mu`
|
||||
|
||||
:param att_reg: Attenuator setting string (32 bit)
|
||||
"""
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32,
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
|
||||
SPIT_ATT_WR, CS_ATT)
|
||||
self.bus.write(att_reg)
|
||||
self.att_reg = att_reg
|
||||
|
||||
@kernel
|
||||
def set_att(self, channel: TInt32, att: TFloat):
|
||||
def set_att(self, channel: int32, att: float):
|
||||
"""Set digital step attenuator in SI units.
|
||||
|
||||
This method will write the attenuator settings of all four channels.
|
||||
|
@ -353,7 +351,7 @@ class CPLD:
|
|||
self.set_att_mu(channel, self.att_to_mu(att))
|
||||
|
||||
@kernel
|
||||
def get_att_mu(self) -> TInt32:
|
||||
def get_att_mu(self) -> int32:
|
||||
"""Return the digital step attenuator settings in machine units.
|
||||
|
||||
The result is stored and will be used in future calls of
|
||||
|
@ -363,18 +361,18 @@ class CPLD:
|
|||
|
||||
:return: 32 bit attenuator settings
|
||||
"""
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_INPUT, 32,
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_INPUT, 32,
|
||||
SPIT_ATT_RD, CS_ATT)
|
||||
self.bus.write(0) # shift in zeros, shift out current value
|
||||
self.bus.set_config_mu(SPI_CONFIG | spi.SPI_END, 32,
|
||||
self.bus.set_config_mu(SPI_CONFIG | SPI_END, 32,
|
||||
SPIT_ATT_WR, CS_ATT)
|
||||
delay(10 * us)
|
||||
self.core.delay(10. * us)
|
||||
self.att_reg = self.bus.read()
|
||||
self.bus.write(self.att_reg) # shift in current value again and latch
|
||||
return self.att_reg
|
||||
|
||||
@kernel
|
||||
def get_channel_att_mu(self, channel: TInt32) -> TInt32:
|
||||
def get_channel_att_mu(self, channel: int32) -> int32:
|
||||
"""Get digital step attenuator value for a channel in machine units.
|
||||
|
||||
The result is stored and will be used in future calls of
|
||||
|
@ -389,7 +387,7 @@ class CPLD:
|
|||
return int32((self.get_att_mu() >> (channel * 8)) & 0xff)
|
||||
|
||||
@kernel
|
||||
def get_channel_att(self, channel: TInt32) -> TFloat:
|
||||
def get_channel_att(self, channel: int32) -> float:
|
||||
"""Get digital step attenuator value for a channel in SI units.
|
||||
|
||||
.. seealso:: :meth:`get_channel_att_mu`
|
||||
|
@ -402,7 +400,7 @@ class CPLD:
|
|||
return self.mu_to_att(self.get_channel_att_mu(channel))
|
||||
|
||||
@kernel
|
||||
def set_sync_div(self, div: TInt32):
|
||||
def set_sync_div(self, div: int32):
|
||||
"""Set the SYNC_IN AD9910 pulse generator frequency
|
||||
and align it to the current RTIO timestamp.
|
||||
|
||||
|
@ -416,10 +414,11 @@ class CPLD:
|
|||
ftw_max = 1 << 4
|
||||
ftw = ftw_max // div
|
||||
assert ftw * div == ftw_max
|
||||
self.sync.set_mu(ftw)
|
||||
if self.sync.is_some():
|
||||
self.sync.unwrap().set_mu(ftw)
|
||||
|
||||
@kernel
|
||||
def set_profile(self, profile: TInt32):
|
||||
def set_profile(self, profile: int32):
|
||||
"""Set the PROFILE pins.
|
||||
|
||||
The PROFILE pins are common to all four DDS channels.
|
||||
|
@ -429,3 +428,24 @@ class CPLD:
|
|||
cfg = self.cfg_reg & ~(7 << CFG_PROFILE)
|
||||
cfg |= (profile & 7) << CFG_PROFILE
|
||||
self.cfg_write(cfg)
|
||||
|
||||
|
||||
@nac3
|
||||
class _RegIOUpdate:
|
||||
core: KernelInvariant[Core]
|
||||
cpld: KernelInvariant[CPLD]
|
||||
|
||||
def __init__(self, core, cpld):
|
||||
self.core = core
|
||||
self.cpld = cpld
|
||||
|
||||
@kernel
|
||||
def pulse_mu(self, t: int64):
|
||||
cfg = self.cpld.cfg_reg
|
||||
self.cpld.cfg_write(cfg | (1 << CFG_IO_UPDATE))
|
||||
delay_mu(t)
|
||||
self.cpld.cfg_write(cfg)
|
||||
|
||||
@kernel
|
||||
def pulse(self, t: float):
|
||||
self.pulse_mu(self.core.seconds_to_mu(t))
|
||||
|
|
|
@ -4,19 +4,23 @@ Output event replacement is not supported and issuing commands at the same
|
|||
time is an error.
|
||||
"""
|
||||
|
||||
from artiq.language.core import kernel
|
||||
from artiq.coredevice import spi2 as spi
|
||||
from numpy import int32
|
||||
|
||||
from artiq.language.core import nac3, kernel
|
||||
from artiq.coredevice.spi2 import *
|
||||
from artiq.coredevice.ad53xx import SPI_AD53XX_CONFIG, AD53xx
|
||||
|
||||
_SPI_SR_CONFIG = (0*spi.SPI_OFFLINE | 1*spi.SPI_END |
|
||||
0*spi.SPI_INPUT | 0*spi.SPI_CS_POLARITY |
|
||||
0*spi.SPI_CLK_POLARITY | 0*spi.SPI_CLK_PHASE |
|
||||
0*spi.SPI_LSB_FIRST | 0*spi.SPI_HALF_DUPLEX)
|
||||
|
||||
_SPI_SR_CONFIG = (0*SPI_OFFLINE | 1*SPI_END |
|
||||
0*SPI_INPUT | 0*SPI_CS_POLARITY |
|
||||
0*SPI_CLK_POLARITY | 0*SPI_CLK_PHASE |
|
||||
0*SPI_LSB_FIRST | 0*SPI_HALF_DUPLEX)
|
||||
|
||||
_SPI_CS_DAC = 1
|
||||
_SPI_CS_SR = 2
|
||||
|
||||
|
||||
@nac3
|
||||
class Zotino(AD53xx):
|
||||
""" Zotino 32-channel, 16-bit 1MSPS DAC.
|
||||
|
||||
|
@ -42,8 +46,8 @@ class Zotino(AD53xx):
|
|||
div_read=div_read, core=core)
|
||||
|
||||
@kernel
|
||||
def set_leds(self, leds):
|
||||
""" Sets the states of the 8 user LEDs.
|
||||
def set_leds(self, leds: int32):
|
||||
"""Sets the states of the 8 user LEDs.
|
||||
|
||||
:param leds: 8-bit word with LED state
|
||||
"""
|
||||
|
|
|
@ -13,7 +13,6 @@ from artiq.gui.entries import procdesc_to_entry, ScanEntry
|
|||
from artiq.gui.fuzzy_select import FuzzySelectWidget
|
||||
from artiq.gui.tools import (LayoutWidget, WheelFilter,
|
||||
log_level_to_name, get_open_file_name)
|
||||
from artiq.tools import parse_devarg_override, unparse_devarg_override
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -306,7 +305,7 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
flush = self.flush
|
||||
flush.setToolTip("Flush the pipeline (of current- and higher-priority "
|
||||
"experiments) before starting the experiment")
|
||||
self.layout.addWidget(flush, 2, 2)
|
||||
self.layout.addWidget(flush, 2, 2, 1, 2)
|
||||
|
||||
flush.setChecked(scheduling["flush"])
|
||||
|
||||
|
@ -314,20 +313,6 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
scheduling["flush"] = bool(checked)
|
||||
flush.stateChanged.connect(update_flush)
|
||||
|
||||
devarg_override = QtWidgets.QComboBox()
|
||||
devarg_override.setEditable(True)
|
||||
devarg_override.lineEdit().setPlaceholderText("Override device arguments")
|
||||
devarg_override.lineEdit().setClearButtonEnabled(True)
|
||||
devarg_override.insertItem(0, "core:analyze_at_run_end=True")
|
||||
self.layout.addWidget(devarg_override, 2, 3)
|
||||
|
||||
devarg_override.setCurrentText(options["devarg_override"])
|
||||
|
||||
def update_devarg_override(text):
|
||||
options["devarg_override"] = text
|
||||
devarg_override.editTextChanged.connect(update_devarg_override)
|
||||
self.devarg_override = devarg_override
|
||||
|
||||
log_level = QtWidgets.QComboBox()
|
||||
log_level.addItems(log_levels)
|
||||
log_level.setCurrentIndex(1)
|
||||
|
@ -348,7 +333,6 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
if "repo_rev" in options:
|
||||
repo_rev = QtWidgets.QLineEdit()
|
||||
repo_rev.setPlaceholderText("current")
|
||||
repo_rev.setClearButtonEnabled(True)
|
||||
repo_rev_label = QtWidgets.QLabel("Revision:")
|
||||
repo_rev_label.setToolTip("Experiment repository revision "
|
||||
"(commit ID) to use")
|
||||
|
@ -481,9 +465,6 @@ class _ExperimentDock(QtWidgets.QMdiSubWindow):
|
|||
return
|
||||
|
||||
try:
|
||||
if "devarg_override" in expid:
|
||||
self.devarg_override.setCurrentText(
|
||||
unparse_devarg_override(expid["devarg_override"]))
|
||||
self.log_level.setCurrentIndex(log_levels.index(
|
||||
log_level_to_name(expid["log_level"])))
|
||||
if ("repo_rev" in expid and
|
||||
|
@ -657,8 +638,7 @@ class ExperimentManager:
|
|||
else:
|
||||
# mutated by _ExperimentDock
|
||||
options = {
|
||||
"log_level": logging.WARNING,
|
||||
"devarg_override": ""
|
||||
"log_level": logging.WARNING
|
||||
}
|
||||
if expurl[:5] == "repo:":
|
||||
options["repo_rev"] = None
|
||||
|
@ -753,14 +733,7 @@ class ExperimentManager:
|
|||
entry_cls = procdesc_to_entry(argument["desc"])
|
||||
argument_values[name] = entry_cls.state_to_value(argument["state"])
|
||||
|
||||
try:
|
||||
devarg_override = parse_devarg_override(options["devarg_override"])
|
||||
except:
|
||||
logger.error("Failed to parse device argument overrides for %s", expurl)
|
||||
return
|
||||
|
||||
expid = {
|
||||
"devarg_override": devarg_override,
|
||||
"log_level": options["log_level"],
|
||||
"file": file,
|
||||
"class_name": class_name,
|
||||
|
|
|
@ -1,249 +0,0 @@
|
|||
# Tester device database
|
||||
|
||||
core_addr = "192.168.1.70"
|
||||
|
||||
device_db = {
|
||||
"core": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {
|
||||
"host": core_addr,
|
||||
"ref_period": 1e-9,
|
||||
"analyzer_proxy": "core_analyzer"
|
||||
}
|
||||
},
|
||||
"core_log": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port": 1068,
|
||||
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_moninj": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1383,
|
||||
"port": 1384,
|
||||
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_analyzer": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1385,
|
||||
"port": 1386,
|
||||
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_cache": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.cache",
|
||||
"class": "CoreCache"
|
||||
},
|
||||
"core_dma": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.dma",
|
||||
"class": "CoreDMA"
|
||||
},
|
||||
|
||||
"i2c_switch0": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.i2c",
|
||||
"class": "I2CSwitch",
|
||||
"arguments": {"address": 0xe0}
|
||||
},
|
||||
"i2c_switch1": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.i2c",
|
||||
"class": "I2CSwitch",
|
||||
"arguments": {"address": 0xe2}
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
# DIO (EEM5) starting at RTIO channel 0
|
||||
for i in range(8):
|
||||
device_db["ttl" + str(i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLInOut" if i < 4 else "TTLOut",
|
||||
"arguments": {"channel": i},
|
||||
}
|
||||
device_db["ttl{}_counter".format(i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.edge_counter",
|
||||
"class": "EdgeCounter",
|
||||
"arguments": {"channel": 8 + i},
|
||||
}
|
||||
|
||||
|
||||
# Urukul (EEM1) starting at RTIO channel 12
|
||||
device_db.update(
|
||||
eeprom_urukul0={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.kasli_i2c",
|
||||
"class": "KasliEEPROM",
|
||||
"arguments": {"port": "EEM1"}
|
||||
},
|
||||
spi_urukul0={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.spi2",
|
||||
"class": "SPIMaster",
|
||||
"arguments": {"channel": 12}
|
||||
},
|
||||
ttl_urukul0_sync={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLClockGen",
|
||||
"arguments": {"channel": 13, "acc_width": 4}
|
||||
},
|
||||
ttl_urukul0_io_update={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 14}
|
||||
},
|
||||
ttl_urukul0_sw0={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 15}
|
||||
},
|
||||
ttl_urukul0_sw1={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 16}
|
||||
},
|
||||
ttl_urukul0_sw2={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 17}
|
||||
},
|
||||
ttl_urukul0_sw3={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 18}
|
||||
},
|
||||
urukul0_cpld={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.urukul",
|
||||
"class": "CPLD",
|
||||
"arguments": {
|
||||
"spi_device": "spi_urukul0",
|
||||
"io_update_device": "ttl_urukul0_io_update",
|
||||
"sync_device": "ttl_urukul0_sync",
|
||||
"refclk": 125e6,
|
||||
"clk_sel": 2
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
for i in range(4):
|
||||
device_db["urukul0_ch" + str(i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ad9910",
|
||||
"class": "AD9910",
|
||||
"arguments": {
|
||||
"pll_n": 32,
|
||||
"chip_select": 4 + i,
|
||||
"cpld_device": "urukul0_cpld",
|
||||
"sw_device": "ttl_urukul0_sw" + str(i),
|
||||
"sync_delay_seed": "eeprom_urukul0:" + str(64 + 4*i),
|
||||
"io_update_delay": "eeprom_urukul0:" + str(64 + 4*i),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Sampler (EEM3) starting at RTIO channel 19
|
||||
device_db["spi_sampler0_adc"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.spi2",
|
||||
"class": "SPIMaster",
|
||||
"arguments": {"channel": 19}
|
||||
}
|
||||
device_db["spi_sampler0_pgia"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.spi2",
|
||||
"class": "SPIMaster",
|
||||
"arguments": {"channel": 20}
|
||||
}
|
||||
device_db["spi_sampler0_cnv"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 21},
|
||||
}
|
||||
device_db["sampler0"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.sampler",
|
||||
"class": "Sampler",
|
||||
"arguments": {
|
||||
"spi_adc_device": "spi_sampler0_adc",
|
||||
"spi_pgia_device": "spi_sampler0_pgia",
|
||||
"cnv_device": "spi_sampler0_cnv"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Zotino (EEM4) starting at RTIO channel 22
|
||||
device_db["spi_zotino0"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.spi2",
|
||||
"class": "SPIMaster",
|
||||
"arguments": {"channel": 22}
|
||||
}
|
||||
device_db["ttl_zotino0_ldac"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 23}
|
||||
}
|
||||
device_db["ttl_zotino0_clr"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 24}
|
||||
}
|
||||
device_db["zotino0"] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.zotino",
|
||||
"class": "Zotino",
|
||||
"arguments": {
|
||||
"spi_device": "spi_zotino0",
|
||||
"ldac_device": "ttl_zotino0_ldac",
|
||||
"clr_device": "ttl_zotino0_clr"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
device_db.update(
|
||||
led0={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 25}
|
||||
},
|
||||
led1={
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": 26}
|
||||
},
|
||||
)
|
||||
|
||||
device_db.update(
|
||||
i2c_switch="i2c_switch0",
|
||||
|
||||
ttl_out="ttl4",
|
||||
ttl_out_serdes="ttl4",
|
||||
|
||||
loop_out="ttl4",
|
||||
loop_in="ttl0",
|
||||
loop_in_counter="ttl0_counter",
|
||||
|
||||
# Urukul CPLD with sync and io_update, IFC MODE 0b1000
|
||||
urukul_cpld="urukul0_cpld",
|
||||
# Urukul AD9910 with switch TTL, internal 125 MHz MMCX connection
|
||||
urukul_ad9910="urukul0_ch0",
|
||||
)
|
|
@ -1,21 +0,0 @@
|
|||
from artiq.experiment import *
|
||||
|
||||
|
||||
class IdleKernel(EnvExperiment):
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("led0")
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
start_time = now_mu() + self.core.seconds_to_mu(500*ms)
|
||||
while self.core.get_rtio_counter_mu() < start_time:
|
||||
pass
|
||||
self.core.reset()
|
||||
while True:
|
||||
self.led0.pulse(250*ms)
|
||||
delay(125*ms)
|
||||
self.led0.pulse(125*ms)
|
||||
delay(125*ms)
|
||||
self.led0.pulse(125*ms)
|
||||
delay(250*ms)
|
|
@ -1,52 +0,0 @@
|
|||
core_addr = "192.168.1.70"
|
||||
|
||||
device_db = {
|
||||
"core": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {
|
||||
"host": core_addr,
|
||||
"ref_period": 1e-9,
|
||||
"analyzer_proxy": "core_analyzer"
|
||||
}
|
||||
},
|
||||
"core_log": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port": 1068,
|
||||
"command": "aqctl_corelog -p {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_moninj": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1383,
|
||||
"port": 1384,
|
||||
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_analyzer": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1385,
|
||||
"port": 1386,
|
||||
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_cache": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.cache",
|
||||
"class": "CoreCache"
|
||||
},
|
||||
"core_dma": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.dma",
|
||||
"class": "CoreDMA"
|
||||
},
|
||||
}
|
||||
|
||||
for i in range(3):
|
||||
device_db["led" + str(i)] = {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.ttl",
|
||||
"class": "TTLOut",
|
||||
"arguments": {"channel": i << 16},
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
from artiq.experiment import *
|
||||
|
||||
|
||||
class Blink(EnvExperiment):
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.leds = [self.get_device("led0"), self.get_device("led2")]
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
self.core.reset()
|
||||
|
||||
while True:
|
||||
for led in self.leds:
|
||||
led.pulse(200*ms)
|
||||
delay(200*ms)
|
|
@ -1,61 +1,82 @@
|
|||
from artiq.experiment import *
|
||||
from artiq.coredevice.shuttler import shuttler_volt_to_mu
|
||||
from numpy import int32, int64
|
||||
|
||||
DAC_Fs_MHZ = 125
|
||||
from artiq.experiment import *
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.ttl import TTLOut
|
||||
from artiq.coredevice.shuttler import (
|
||||
shuttler_volt_to_mu,
|
||||
Config as ShuttlerConfig,
|
||||
Trigger as ShuttlerTrigger,
|
||||
DCBias as ShuttlerDCBias,
|
||||
DDS as ShuttlerDDS,
|
||||
Relay as ShuttlerRelay,
|
||||
ADC as ShuttlerADC)
|
||||
|
||||
|
||||
DAC_Fs_MHZ = 125.
|
||||
CORDIC_GAIN = 1.64676
|
||||
|
||||
@portable
|
||||
def shuttler_phase_offset(offset_degree):
|
||||
return round(offset_degree / 360 * (2 ** 16))
|
||||
def shuttler_phase_offset(offset_degree: float) -> int32:
|
||||
return round(offset_degree / 360. * float(2 ** 16))
|
||||
|
||||
@portable
|
||||
def shuttler_freq_mu(freq_mhz):
|
||||
def shuttler_freq_mu(freq_mhz: float) -> int32:
|
||||
return round(float(2) ** 32 / DAC_Fs_MHZ * freq_mhz)
|
||||
|
||||
@portable
|
||||
def shuttler_chirp_rate_mu(freq_mhz_per_us):
|
||||
def shuttler_chirp_rate_mu(freq_mhz_per_us: float) -> int32:
|
||||
return round(float(2) ** 32 * freq_mhz_per_us / (DAC_Fs_MHZ ** 2))
|
||||
|
||||
@portable
|
||||
def shuttler_freq_sweep(start_f_MHz, end_f_MHz, time_us):
|
||||
return shuttler_chirp_rate_mu((end_f_MHz - start_f_MHz)/(time_us))
|
||||
def shuttler_freq_sweep(start_f_MHz: float, end_f_MHz: float, time_us: float) -> int32:
|
||||
return shuttler_chirp_rate_mu((end_f_MHz - start_f_MHz)/time_us)
|
||||
|
||||
@portable
|
||||
def shuttler_volt_amp_mu(volt):
|
||||
def shuttler_volt_amp_mu(volt: float) -> int32:
|
||||
return shuttler_volt_to_mu(volt)
|
||||
|
||||
@portable
|
||||
def shuttler_volt_damp_mu(volt_per_us):
|
||||
return round(float(2) ** 32 * (volt_per_us / 20) / DAC_Fs_MHZ)
|
||||
def shuttler_volt_damp_mu(volt_per_us: float) -> int32:
|
||||
return round(float(2) ** 32 * (volt_per_us / 20.) / DAC_Fs_MHZ)
|
||||
|
||||
@portable
|
||||
def shuttler_volt_ddamp_mu(volt_per_us_square):
|
||||
return round(float(2) ** 48 * (volt_per_us_square / 20) * 2 / (DAC_Fs_MHZ ** 2))
|
||||
def shuttler_volt_ddamp_mu(volt_per_us_square: float) -> int64:
|
||||
return round64(float(2) ** 48 * (volt_per_us_square / 20.) * 2. / (DAC_Fs_MHZ ** 2))
|
||||
|
||||
@portable
|
||||
def shuttler_volt_dddamp_mu(volt_per_us_cube):
|
||||
return round(float(2) ** 48 * (volt_per_us_cube / 20) * 6 / (DAC_Fs_MHZ ** 3))
|
||||
def shuttler_volt_dddamp_mu(volt_per_us_cube: float) -> int64:
|
||||
return round64(float(2) ** 48 * (volt_per_us_cube / 20.) * 6. / (DAC_Fs_MHZ ** 3))
|
||||
|
||||
@portable
|
||||
def shuttler_dds_amp_mu(volt):
|
||||
def shuttler_dds_amp_mu(volt: float) -> int32:
|
||||
return shuttler_volt_amp_mu(volt / CORDIC_GAIN)
|
||||
|
||||
@portable
|
||||
def shuttler_dds_damp_mu(volt_per_us):
|
||||
def shuttler_dds_damp_mu(volt_per_us: float) -> int32:
|
||||
return shuttler_volt_damp_mu(volt_per_us / CORDIC_GAIN)
|
||||
|
||||
@portable
|
||||
def shuttler_dds_ddamp_mu(volt_per_us_square):
|
||||
def shuttler_dds_ddamp_mu(volt_per_us_square: float) -> int64:
|
||||
return shuttler_volt_ddamp_mu(volt_per_us_square / CORDIC_GAIN)
|
||||
|
||||
@portable
|
||||
def shuttler_dds_dddamp_mu(volt_per_us_cube):
|
||||
def shuttler_dds_dddamp_mu(volt_per_us_cube: float) -> int64:
|
||||
return shuttler_volt_dddamp_mu(volt_per_us_cube / CORDIC_GAIN)
|
||||
|
||||
@nac3
|
||||
class Shuttler(EnvExperiment):
|
||||
core: KernelInvariant[Core]
|
||||
shuttler0_leds: KernelInvariant[list[TTLOut]]
|
||||
shuttler0_config: KernelInvariant[ShuttlerConfig]
|
||||
shuttler0_trigger: KernelInvariant[ShuttlerTrigger]
|
||||
shuttler0_dcbias: KernelInvariant[list[ShuttlerDCBias]]
|
||||
shuttler0_dds: KernelInvariant[list[ShuttlerDDS]]
|
||||
shuttler0_relay: KernelInvariant[ShuttlerRelay]
|
||||
shuttler0_adc: KernelInvariant[ShuttlerADC]
|
||||
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("core_dma")
|
||||
self.setattr_device("scheduler")
|
||||
self.shuttler0_leds = [ self.get_device("shuttler0_led{}".format(i)) for i in range(2) ]
|
||||
self.setattr_device("shuttler0_config")
|
||||
|
@ -64,12 +85,6 @@ class Shuttler(EnvExperiment):
|
|||
self.shuttler0_dds = [ self.get_device("shuttler0_dds{}".format(i)) for i in range(16) ]
|
||||
self.setattr_device("shuttler0_relay")
|
||||
self.setattr_device("shuttler0_adc")
|
||||
|
||||
|
||||
@kernel
|
||||
def record(self):
|
||||
with self.core_dma.record("example_waveform"):
|
||||
self.example_waveform()
|
||||
|
||||
@kernel
|
||||
def init(self):
|
||||
|
@ -84,35 +99,32 @@ class Shuttler(EnvExperiment):
|
|||
self.core.break_realtime()
|
||||
self.init()
|
||||
|
||||
self.record()
|
||||
example_waveform_handle = self.core_dma.get_handle("example_waveform")
|
||||
|
||||
print("Example Waveforms are on OUT0 and OUT1")
|
||||
print_rpc("Example Waveforms are on OUT0 and OUT1")
|
||||
self.core.break_realtime()
|
||||
while not(self.scheduler.check_termination()):
|
||||
delay(1*s)
|
||||
self.core_dma.playback_handle(example_waveform_handle)
|
||||
self.core.delay(1.*s)
|
||||
self.example_waveform()
|
||||
|
||||
@kernel
|
||||
def shuttler_reset(self):
|
||||
for i in range(16):
|
||||
self.shuttler_channel_reset(i)
|
||||
# To avoid RTIO Underflow
|
||||
delay(50*us)
|
||||
self.core.delay(50.*us)
|
||||
|
||||
@kernel
|
||||
def shuttler_channel_reset(self, ch):
|
||||
def shuttler_channel_reset(self, ch: int32):
|
||||
self.shuttler0_dcbias[ch].set_waveform(
|
||||
a0=0,
|
||||
a1=0,
|
||||
a2=0,
|
||||
a3=0,
|
||||
a2=int64(0),
|
||||
a3=int64(0),
|
||||
)
|
||||
self.shuttler0_dds[ch].set_waveform(
|
||||
b0=0,
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
b2=int64(0),
|
||||
b3=int64(0),
|
||||
c0=0,
|
||||
c1=0,
|
||||
c2=0,
|
||||
|
@ -163,13 +175,13 @@ class Shuttler(EnvExperiment):
|
|||
## Step 2 ##
|
||||
start_f_MHz = 0.01
|
||||
end_f_MHz = 0.05
|
||||
duration_us = 500
|
||||
duration_us = 500.
|
||||
# OUT0 and OUT1 have their frequency and phase aligned at 500us
|
||||
self.shuttler0_dds[0].set_waveform(
|
||||
b0=shuttler_dds_amp_mu(1.0),
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
b2=int64(0),
|
||||
b3=int64(0),
|
||||
c0=0,
|
||||
c1=shuttler_freq_mu(start_f_MHz),
|
||||
c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us),
|
||||
|
@ -177,22 +189,22 @@ class Shuttler(EnvExperiment):
|
|||
self.shuttler0_dds[1].set_waveform(
|
||||
b0=shuttler_dds_amp_mu(1.0),
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
b2=int64(0),
|
||||
b3=int64(0),
|
||||
c0=0,
|
||||
c1=shuttler_freq_mu(end_f_MHz),
|
||||
c2=0,
|
||||
)
|
||||
self.shuttler0_trigger.trigger(0b11)
|
||||
delay(500*us)
|
||||
self.core.delay(500.*us)
|
||||
|
||||
## Step 3 ##
|
||||
# OUT0 and OUT1 has 180 degree phase difference
|
||||
self.shuttler0_dds[0].set_waveform(
|
||||
b0=shuttler_dds_amp_mu(1.0),
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
b2=int64(0),
|
||||
b3=int64(0),
|
||||
c0=shuttler_phase_offset(180.0),
|
||||
c1=shuttler_freq_mu(end_f_MHz),
|
||||
c2=0,
|
||||
|
@ -200,7 +212,7 @@ class Shuttler(EnvExperiment):
|
|||
# Phase and Output Setting of OUT1 is retained
|
||||
# if the channel is not triggered or config is not cleared
|
||||
self.shuttler0_trigger.trigger(0b1)
|
||||
delay(500*us)
|
||||
self.core.delay(500.*us)
|
||||
|
||||
## Step 4 ##
|
||||
# b(0) = 0, b(250) = 8.545, b(500) = 0
|
||||
|
@ -208,7 +220,7 @@ class Shuttler(EnvExperiment):
|
|||
b0=0,
|
||||
b1=shuttler_dds_damp_mu(0.06835937),
|
||||
b2=shuttler_dds_ddamp_mu(-0.0001367187),
|
||||
b3=0,
|
||||
b3=int64(0),
|
||||
c0=0,
|
||||
c1=shuttler_freq_mu(end_f_MHz),
|
||||
c2=0,
|
||||
|
@ -217,26 +229,26 @@ class Shuttler(EnvExperiment):
|
|||
b0=0,
|
||||
b1=shuttler_dds_damp_mu(0.06835937),
|
||||
b2=shuttler_dds_ddamp_mu(-0.0001367187),
|
||||
b3=0,
|
||||
b3=int64(0),
|
||||
c0=0,
|
||||
c1=0,
|
||||
c2=0,
|
||||
)
|
||||
self.shuttler0_trigger.trigger(0b11)
|
||||
delay(500*us)
|
||||
self.core.delay(500.*us)
|
||||
|
||||
## Step 5 ##
|
||||
self.shuttler0_dcbias[0].set_waveform(
|
||||
a0=shuttler_volt_amp_mu(-5.0),
|
||||
a1=int32(shuttler_volt_damp_mu(0.01)),
|
||||
a2=0,
|
||||
a3=0,
|
||||
a2=int64(0),
|
||||
a3=int64(0),
|
||||
)
|
||||
self.shuttler0_dds[0].set_waveform(
|
||||
b0=shuttler_dds_amp_mu(1.0),
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
b2=int64(0),
|
||||
b3=int64(0),
|
||||
c0=0,
|
||||
c1=shuttler_freq_mu(end_f_MHz),
|
||||
c2=0,
|
||||
|
@ -244,59 +256,59 @@ class Shuttler(EnvExperiment):
|
|||
self.shuttler0_dcbias[1].set_waveform(
|
||||
a0=shuttler_volt_amp_mu(-5.0),
|
||||
a1=int32(shuttler_volt_damp_mu(0.01)),
|
||||
a2=0,
|
||||
a3=0,
|
||||
a2=int64(0),
|
||||
a3=int64(0),
|
||||
)
|
||||
self.shuttler0_dds[1].set_waveform(
|
||||
b0=0,
|
||||
b1=0,
|
||||
b2=0,
|
||||
b3=0,
|
||||
b2=int64(0),
|
||||
b3=int64(0),
|
||||
c0=0,
|
||||
c1=0,
|
||||
c2=0,
|
||||
)
|
||||
self.shuttler0_trigger.trigger(0b11)
|
||||
delay(1000*us)
|
||||
self.core.delay(1000.*us)
|
||||
|
||||
## Step 6 ##
|
||||
self.shuttler0_dcbias[0].set_waveform(
|
||||
a0=shuttler_volt_amp_mu(-2.5),
|
||||
a1=int32(shuttler_volt_damp_mu(0.01)),
|
||||
a2=0,
|
||||
a3=0,
|
||||
a2=int64(0),
|
||||
a3=int64(0),
|
||||
)
|
||||
self.shuttler0_dds[0].set_waveform(
|
||||
b0=0,
|
||||
b1=shuttler_dds_damp_mu(0.06835937),
|
||||
b2=shuttler_dds_ddamp_mu(-0.0001367187),
|
||||
b3=0,
|
||||
b3=int64(0),
|
||||
c0=0,
|
||||
c1=shuttler_freq_mu(start_f_MHz),
|
||||
c2=shuttler_freq_sweep(start_f_MHz, end_f_MHz, duration_us),
|
||||
)
|
||||
self.shuttler0_trigger.trigger(0b1)
|
||||
self.shuttler_channel_reset(1)
|
||||
delay(500*us)
|
||||
self.core.delay(500.*us)
|
||||
|
||||
## Step 7 ##
|
||||
self.shuttler0_dcbias[0].set_waveform(
|
||||
a0=shuttler_volt_amp_mu(2.5),
|
||||
a1=int32(shuttler_volt_damp_mu(-0.01)),
|
||||
a2=0,
|
||||
a3=0,
|
||||
a2=int64(0),
|
||||
a3=int64(0),
|
||||
)
|
||||
self.shuttler0_dds[0].set_waveform(
|
||||
b0=0,
|
||||
b1=shuttler_dds_damp_mu(-0.06835937),
|
||||
b2=shuttler_dds_ddamp_mu(0.0001367187),
|
||||
b3=0,
|
||||
b3=int64(0),
|
||||
c0=shuttler_phase_offset(180.0),
|
||||
c1=shuttler_freq_mu(end_f_MHz),
|
||||
c2=shuttler_freq_sweep(end_f_MHz, start_f_MHz, duration_us),
|
||||
)
|
||||
self.shuttler0_trigger.trigger(0b1)
|
||||
delay(500*us)
|
||||
self.core.delay(500.*us)
|
||||
|
||||
## Step 8 ##
|
||||
self.shuttler0_relay.enable(0)
|
||||
|
@ -308,7 +320,7 @@ class Shuttler(EnvExperiment):
|
|||
for i in range(2):
|
||||
for j in range(3):
|
||||
self.shuttler0_leds[i].pulse(.1*s)
|
||||
delay(.1*s)
|
||||
self.core.delay(.1*s)
|
||||
|
||||
@kernel
|
||||
def relay_init(self):
|
||||
|
|
|
@ -5,11 +5,7 @@ device_db = {
|
|||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {
|
||||
"host": core_addr,
|
||||
"ref_period": 1e-9,
|
||||
"analyzer_proxy": "core_analyzer"
|
||||
}
|
||||
"arguments": {"host": core_addr, "ref_period": 1e-9}
|
||||
},
|
||||
"core_log": {
|
||||
"type": "controller",
|
||||
|
@ -24,13 +20,6 @@ device_db = {
|
|||
"port": 1384,
|
||||
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_analyzer": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1385,
|
||||
"port": 1386,
|
||||
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_cache": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.cache",
|
||||
|
|
|
@ -1,7 +1,24 @@
|
|||
from numpy import int32
|
||||
|
||||
from artiq.experiment import *
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.ttl import TTLOut
|
||||
from artiq.coredevice.suservo import SUServo, Channel as SUServoChannel
|
||||
|
||||
@nac3
|
||||
class SUServoDemo(EnvExperiment):
|
||||
core: KernelInvariant[Core]
|
||||
led0: KernelInvariant[TTLOut]
|
||||
suservo0: KernelInvariant[SUServo]
|
||||
suservo0_ch0: KernelInvariant[SUServoChannel]
|
||||
suservo0_ch1: KernelInvariant[SUServoChannel]
|
||||
suservo0_ch2: KernelInvariant[SUServoChannel]
|
||||
suservo0_ch3: KernelInvariant[SUServoChannel]
|
||||
suservo0_ch4: KernelInvariant[SUServoChannel]
|
||||
suservo0_ch5: KernelInvariant[SUServoChannel]
|
||||
suservo0_ch6: KernelInvariant[SUServoChannel]
|
||||
suservo0_ch7: KernelInvariant[SUServoChannel]
|
||||
|
||||
class SUServo(EnvExperiment):
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("led0")
|
||||
|
@ -9,40 +26,37 @@ class SUServo(EnvExperiment):
|
|||
for i in range(8):
|
||||
self.setattr_device("suservo0_ch{}".format(i))
|
||||
|
||||
def run(self):
|
||||
self.init()
|
||||
|
||||
def p(self, d):
|
||||
@rpc
|
||||
def p(self, d: list[int32]):
|
||||
mask = 1 << 18 - 1
|
||||
for name, val in zip("ftw1 b1 pow cfg offset a1 ftw0 b0".split(), d):
|
||||
val = -(val & mask) + (val & ~mask)
|
||||
print("{}: {:#x} = {}".format(name, val, val))
|
||||
|
||||
@rpc(flags={"async"})
|
||||
def p1(self, adc, asf, st):
|
||||
@rpc # NAC3TODO (flags={"async"})
|
||||
def p1(self, adc: float, asf: float, st: int32):
|
||||
print("ADC: {:10s}, ASF: {:10s}, clipped: {}".format(
|
||||
"#"*int(adc), "#"*int(asf*10), (st >> 8) & 1), end="\r")
|
||||
|
||||
@kernel
|
||||
def init(self):
|
||||
self.core.break_realtime()
|
||||
def run(self):
|
||||
self.core.reset()
|
||||
self.led()
|
||||
|
||||
self.suservo0.init()
|
||||
delay(1*us)
|
||||
self.core.delay(1.*us)
|
||||
# ADC PGIA gain
|
||||
for i in range(8):
|
||||
self.suservo0.set_pgia_mu(i, 0)
|
||||
delay(10*us)
|
||||
self.core.delay(10.*us)
|
||||
# DDS attenuator
|
||||
self.suservo0.cpld0.set_att(0, 10.)
|
||||
delay(1*us)
|
||||
self.suservo0.cplds[0].set_att(0, 10.)
|
||||
self.core.delay(1.*us)
|
||||
# Servo is done and disabled
|
||||
assert self.suservo0.get_status() & 0xff == 2
|
||||
|
||||
# set up profile 0 on channel 0:
|
||||
delay(120*us)
|
||||
self.core.delay(120.*us)
|
||||
self.suservo0_ch0.set_y(
|
||||
profile=0,
|
||||
y=0. # clear integrator
|
||||
|
@ -61,39 +75,39 @@ class SUServo(EnvExperiment):
|
|||
self.suservo0_ch0.set_dds(
|
||||
profile=0,
|
||||
offset=-.5, # 5 V with above PGIA settings
|
||||
frequency=71*MHz,
|
||||
frequency=71.*MHz,
|
||||
phase=0.)
|
||||
# enable RF, IIR updates and profile 0
|
||||
self.suservo0_ch0.set(en_out=1, en_iir=1, profile=0)
|
||||
self.suservo0_ch0.set(en_out=True, en_iir=True, profile=0)
|
||||
# enable global servo iterations
|
||||
self.suservo0.set_config(enable=1)
|
||||
self.suservo0.set_config(enable=True)
|
||||
|
||||
# check servo enabled
|
||||
assert self.suservo0.get_status() & 0x01 == 1
|
||||
delay(10*us)
|
||||
self.core.delay(10.*us)
|
||||
|
||||
# read back profile data
|
||||
data = [0] * 8
|
||||
data = [0 for _ in range(8)]
|
||||
self.suservo0_ch0.get_profile_mu(0, data)
|
||||
self.p(data)
|
||||
delay(10*ms)
|
||||
self.core.delay(10.*ms)
|
||||
|
||||
while True:
|
||||
self.suservo0.set_config(0)
|
||||
delay(10*us)
|
||||
self.suservo0.set_config(False)
|
||||
self.core.delay(10.*us)
|
||||
v = self.suservo0.get_adc(7)
|
||||
delay(30*us)
|
||||
self.core.delay(30.*us)
|
||||
w = self.suservo0_ch0.get_y(0)
|
||||
delay(20*us)
|
||||
self.core.delay(20.*us)
|
||||
x = self.suservo0.get_status()
|
||||
delay(10*us)
|
||||
self.suservo0.set_config(1)
|
||||
self.core.delay(10.*us)
|
||||
self.suservo0.set_config(True)
|
||||
self.p1(v, w, x)
|
||||
delay(20*ms)
|
||||
self.core.delay(20.*ms)
|
||||
|
||||
@kernel
|
||||
def led(self):
|
||||
self.core.break_realtime()
|
||||
for i in range(3):
|
||||
self.led0.pulse(.1*s)
|
||||
delay(.1*s)
|
||||
self.core.delay(.1*s)
|
||||
|
|
|
@ -9,11 +9,7 @@ device_db = {
|
|||
"type": "local",
|
||||
"module": "artiq.coredevice.core",
|
||||
"class": "Core",
|
||||
"arguments": {
|
||||
"host": core_addr,
|
||||
"ref_period": 1e-9,
|
||||
"analyzer_proxy": "core_analyzer"
|
||||
}
|
||||
"arguments": {"host": core_addr, "ref_period": 1e-9}
|
||||
},
|
||||
"core_log": {
|
||||
"type": "controller",
|
||||
|
@ -28,13 +24,6 @@ device_db = {
|
|||
"port": 1384,
|
||||
"command": "aqctl_moninj_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_analyzer": {
|
||||
"type": "controller",
|
||||
"host": "::1",
|
||||
"port_proxy": 1385,
|
||||
"port": 1386,
|
||||
"command": "aqctl_coreanalyzer_proxy --port-proxy {port_proxy} --port-control {port} --bind {bind} " + core_addr
|
||||
},
|
||||
"core_cache": {
|
||||
"type": "local",
|
||||
"module": "artiq.coredevice.cache",
|
||||
|
|
|
@ -1,21 +1,26 @@
|
|||
from artiq.experiment import *
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.ttl import TTLOut
|
||||
|
||||
|
||||
@nac3
|
||||
class IdleKernel(EnvExperiment):
|
||||
core: KernelInvariant[Core]
|
||||
led: KernelInvariant[TTLOut]
|
||||
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("led")
|
||||
|
||||
@kernel
|
||||
def run(self):
|
||||
start_time = now_mu() + self.core.seconds_to_mu(500*ms)
|
||||
start_time = now_mu() + self.core.seconds_to_mu(500.*ms)
|
||||
while self.core.get_rtio_counter_mu() < start_time:
|
||||
pass
|
||||
self.core.reset()
|
||||
while True:
|
||||
self.led.pulse(250*ms)
|
||||
delay(125*ms)
|
||||
self.led.pulse(125*ms)
|
||||
delay(125*ms)
|
||||
self.led.pulse(125*ms)
|
||||
delay(250*ms)
|
||||
self.led.pulse(250.*ms)
|
||||
self.core.delay(125.*ms)
|
||||
self.led.pulse(125.*ms)
|
||||
self.core.delay(125.*ms)
|
||||
self.led.pulse(125.*ms)
|
||||
self.core.delay(250.*ms)
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
from artiq.experiment import *
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.ttl import TTLOut
|
||||
|
||||
|
||||
@nac3
|
||||
class BlinkForever(EnvExperiment):
|
||||
core: KernelInvariant[Core]
|
||||
led: KernelInvariant[TTLOut]
|
||||
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("led")
|
||||
|
@ -10,5 +15,5 @@ class BlinkForever(EnvExperiment):
|
|||
def run(self):
|
||||
self.core.reset()
|
||||
while True:
|
||||
self.led.pulse(100*ms)
|
||||
delay(100*ms)
|
||||
self.led.pulse(100.*ms)
|
||||
self.core.delay(100.*ms)
|
||||
|
|
|
@ -1,20 +1,30 @@
|
|||
from time import sleep
|
||||
|
||||
from artiq.experiment import *
|
||||
from artiq.coredevice.core import Core
|
||||
|
||||
# NAC3TODO https://git.m-labs.hk/M-Labs/nac3/issues/282
|
||||
|
||||
@rpc
|
||||
def sleep_rpc():
|
||||
sleep(1)
|
||||
|
||||
|
||||
@nac3
|
||||
class CorePause(EnvExperiment):
|
||||
core: KernelInvariant[Core]
|
||||
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
self.setattr_device("scheduler")
|
||||
|
||||
@kernel
|
||||
def k(self):
|
||||
print("kernel starting")
|
||||
print_rpc("kernel starting")
|
||||
while not self.scheduler.check_pause():
|
||||
print("main kernel loop running...")
|
||||
sleep(1)
|
||||
print("kernel exiting")
|
||||
print_rpc("main kernel loop running...")
|
||||
sleep_rpc()
|
||||
print_rpc("kernel exiting")
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
|
|
|
@ -1,10 +1,16 @@
|
|||
from operator import itemgetter
|
||||
|
||||
from artiq.experiment import *
|
||||
from artiq.coredevice.core import Core
|
||||
from artiq.coredevice.ad9914 import AD9914
|
||||
|
||||
|
||||
@nac3
|
||||
class DDSSetter(EnvExperiment):
|
||||
"""DDS Setter"""
|
||||
|
||||
core: KernelInvariant[Core]
|
||||
|
||||
def build(self):
|
||||
self.setattr_device("core")
|
||||
|
||||
|
@ -24,10 +30,10 @@ class DDSSetter(EnvExperiment):
|
|||
}
|
||||
|
||||
@kernel
|
||||
def set_dds(self, dds, frequency):
|
||||
def set_dds(self, dds: AD9914, frequency: float):
|
||||
self.core.break_realtime()
|
||||
dds.set(frequency)
|
||||
delay(200*ms)
|
||||
self.core.delay(200.*ms)
|
||||
|
||||
def run(self):
|
||||
for k, v in self.dds.items():
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue