forked from M-Labs/artiq
language/core: add docstrings
This commit is contained in:
parent
f5167f21fb
commit
7e45cd62ef
@ -1,3 +1,5 @@
|
||||
"""Core ARTIQ extensions to the Python language."""
|
||||
|
||||
from collections import namedtuple
|
||||
from fractions import Fraction
|
||||
from copy import copy
|
||||
@ -6,6 +8,29 @@ from artiq.language import units
|
||||
|
||||
|
||||
class int64(int):
|
||||
"""64-bit integers for static compilation.
|
||||
|
||||
When this class is used instead of Python's ``int``, the static compiler
|
||||
stores the corresponding variable on 64 bits instead of 32.
|
||||
|
||||
When used in the interpreter, it behaves as ``int`` and the results of
|
||||
integer operations involving it are also ``int64`` (which matches the
|
||||
size promotion rules of the static compiler). This way, it is possible to
|
||||
specify 64-bit size annotations on constants that are passed to the
|
||||
kernels.
|
||||
|
||||
Example:
|
||||
|
||||
>>> a = int64(1)
|
||||
>>> b = int64(3) + 2
|
||||
>>> isinstance(a, int64)
|
||||
True
|
||||
>>> isinstance(b, int64)
|
||||
True
|
||||
>>> a + b
|
||||
6
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
def _make_int64_op_method(int_method):
|
||||
@ -33,14 +58,88 @@ for _op_name in ("add", "sub", "mul", "floordiv", "mod",
|
||||
|
||||
|
||||
def round64(x):
|
||||
"""Rounds to a 64-bit integer.
|
||||
|
||||
This function is equivalent to ``int64(round(x))`` but, when targeting
|
||||
static compilation, prevents overflow when the rounded value is too large
|
||||
to fit in a 32-bit integer.
|
||||
|
||||
"""
|
||||
return int64(round(x))
|
||||
|
||||
|
||||
def array(element, count):
|
||||
"""Creates an array.
|
||||
|
||||
The array is initialized with the value of ``element`` repeated ``count``
|
||||
times. Elements can be read and written using the regular Python index
|
||||
syntax.
|
||||
|
||||
For static compilation, ``count`` must be a fixed integer.
|
||||
|
||||
Arrays of arrays are supported.
|
||||
|
||||
"""
|
||||
return [copy(element) for i in range(count)]
|
||||
|
||||
|
||||
class AutoContext:
|
||||
"""Base class to automate device and parameter management.
|
||||
|
||||
Drivers and experiments should in most cases overload this class to
|
||||
obtain the parameters and devices (including the core device) that they
|
||||
need.
|
||||
|
||||
This class sets all its ``__init__`` keyword arguments as attributes. It
|
||||
then iterates over each element in its ``parameters`` attribute and, if
|
||||
they are not already existing, requests them from ``mvs`` (Missing Value
|
||||
Supplier).
|
||||
|
||||
A ``AutoContext`` instance can be used as MVS. If the requested parameter
|
||||
is within its attributes, the value of that attribute is returned.
|
||||
Otherwise, the request is forwarded to the parent MVS.
|
||||
|
||||
All keyword arguments are set as object attributes. This enables setting
|
||||
parameters of a lower-level ``AutoContext`` object using keyword arguments
|
||||
without having those explicitly listed in the upper-level ``AutoContext``
|
||||
parameter list.
|
||||
|
||||
At the top-level, it is possible to have a MVS that issues requests to a
|
||||
database and hardware management system.
|
||||
|
||||
:param parameters: A string containing the parameters that the object must have.
|
||||
It must be a space-separated list of valid Python identifiers.
|
||||
Default: empty.
|
||||
:param implicit_core: Automatically adds ``core`` to the parameter list.
|
||||
Default: True.
|
||||
|
||||
Example:
|
||||
|
||||
>>> class SubExperiment(AutoContext):
|
||||
... parameters = "foo bar"
|
||||
...
|
||||
... def run():
|
||||
... do_something(self.foo, self.bar)
|
||||
...
|
||||
>>> class MainExperiment(AutoContext):
|
||||
... parameters = "bar1 bar2 offset"
|
||||
...
|
||||
... def build(self):
|
||||
... self.exp1 = SubExperiment(self, bar=self.bar1)
|
||||
... self.exp2 = SubExperiment(self, bar=self.bar2)
|
||||
... self.exp3 = SubExperiment(self, bar=self.bar2 + self.offset)
|
||||
...
|
||||
>>> def run():
|
||||
... self.exp1.run()
|
||||
... self.exp2.run()
|
||||
... self.exp3.run()
|
||||
...
|
||||
>>> # does not require a database.
|
||||
>>> a = MainExperiment(foo=1, bar1=2, bar2=3, offset=0)
|
||||
>>> # "foo" and "offset" are automatically retrieved from the database.
|
||||
>>> b = MainExperiment(db_mvs, bar1=2, bar2=3)
|
||||
|
||||
"""
|
||||
parameters = ""
|
||||
implicit_core = True
|
||||
|
||||
@ -62,13 +161,24 @@ class AutoContext:
|
||||
self.build()
|
||||
|
||||
def get_missing_value(self, parameter):
|
||||
"""Attempts to retrieve ``parameter`` from the object's attributes.
|
||||
If not present, forwards the request to the parent MVS.
|
||||
|
||||
The presence of this method makes ``AutoContext`` act as a MVS.
|
||||
"""
|
||||
try:
|
||||
return getattr(self, parameter)
|
||||
except AttributeError:
|
||||
return self.mvs.get_missing_value(parameter)
|
||||
|
||||
def build(self):
|
||||
""" Overload this function to add sub-experiments"""
|
||||
"""This is called by ``__init__`` after the parameter initialization
|
||||
is done.
|
||||
|
||||
The user may overload this method to complete the object's
|
||||
initialization with all parameters available.
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@ -76,6 +186,25 @@ _KernelFunctionInfo = namedtuple("_KernelFunctionInfo", "core_name k_function")
|
||||
|
||||
|
||||
def kernel(arg):
|
||||
"""This decorator marks an object's method for execution on the core
|
||||
device.
|
||||
|
||||
When a decorated method is called from the Python interpreter, the ``core``
|
||||
attribute of the object is retrieved and used as core device driver. The
|
||||
core device driver will typically compile, transfer and run the method
|
||||
(kernel) on the device.
|
||||
|
||||
When kernels call another method:
|
||||
- if the method is a kernel for the same core device, is it compiled
|
||||
and sent in the same binary. Calls between kernels happen entirely on
|
||||
the device.
|
||||
- if the method is a regular Python method (not a kernel), it generates
|
||||
a remote procedure call (RPC) for execution on the host.
|
||||
|
||||
The decorator takes an optional parameter that defaults to ``core`` and
|
||||
specifies the name of the attribute to use as core device driver.
|
||||
|
||||
"""
|
||||
if isinstance(arg, str):
|
||||
def real_decorator(k_function):
|
||||
def run_on_core(exp, *k_args, **k_kwargs):
|
||||
@ -108,6 +237,12 @@ _time_manager = _DummyTimeManager()
|
||||
|
||||
|
||||
def set_time_manager(time_manager):
|
||||
"""Set the time manager used for simulating kernels by running them
|
||||
directly inside the Python interpreter. The time manager responds to the
|
||||
entering and leaving of parallel/sequential blocks, delays, etc. and
|
||||
provides a time-stamped logging facility for events.
|
||||
|
||||
"""
|
||||
global _time_manager
|
||||
_time_manager = time_manager
|
||||
|
||||
@ -121,6 +256,10 @@ _syscall_manager = _DummySyscallManager()
|
||||
|
||||
|
||||
def set_syscall_manager(syscall_manager):
|
||||
"""Set the system call manager used for simulating the core device's
|
||||
runtime in the Python interpreter.
|
||||
|
||||
"""
|
||||
global _syscall_manager
|
||||
_syscall_manager = syscall_manager
|
||||
|
||||
@ -130,6 +269,10 @@ kernel_globals = "sequential", "parallel", "delay", "now", "at", "syscall"
|
||||
|
||||
|
||||
class _Sequential:
|
||||
"""In a sequential block, statements are executed one after another, with
|
||||
the time increasing as one moves down the statement list.
|
||||
|
||||
"""
|
||||
def __enter__(self):
|
||||
_time_manager.enter_sequential()
|
||||
|
||||
@ -139,6 +282,14 @@ sequential = _Sequential()
|
||||
|
||||
|
||||
class _Parallel:
|
||||
"""In a parallel block, all top-level statements start their execution at
|
||||
the same time.
|
||||
|
||||
The execution time of a parallel block is the execution time of its longest
|
||||
statement. A parallel block may contain sequential blocks, which themselves
|
||||
may contain parallel blocks, etc.
|
||||
|
||||
"""
|
||||
def __enter__(self):
|
||||
_time_manager.enter_parallel()
|
||||
|
||||
@ -148,16 +299,33 @@ parallel = _Parallel()
|
||||
|
||||
|
||||
def delay(duration):
|
||||
"""Increases the RTIO time by the given amount.
|
||||
|
||||
"""
|
||||
_time_manager.take_time(duration)
|
||||
|
||||
|
||||
def now():
|
||||
"""Retrieves the current RTIO time, in microcycles.
|
||||
|
||||
"""
|
||||
return _time_manager.get_time()
|
||||
|
||||
|
||||
def at(time):
|
||||
"""Sets the RTIO time to the specified absolute value.
|
||||
|
||||
"""
|
||||
_time_manager.set_time(time)
|
||||
|
||||
|
||||
def syscall(*args):
|
||||
"""Invokes a service of the runtime.
|
||||
|
||||
Kernels use this function to interface to the outside world: program RTIO
|
||||
events, make RPCs, etc.
|
||||
|
||||
Only drivers should normally use ``syscall``.
|
||||
|
||||
"""
|
||||
return _syscall_manager.do(*args)
|
||||
|
Loading…
Reference in New Issue
Block a user