forked from M-Labs/artiq
1
0
Fork 0

language/core: add docstrings

This commit is contained in:
Sebastien Bourdeauducq 2014-09-18 17:44:11 +08:00
parent f5167f21fb
commit 7e45cd62ef
1 changed files with 169 additions and 1 deletions

View File

@ -1,3 +1,5 @@
"""Core ARTIQ extensions to the Python language."""
from collections import namedtuple from collections import namedtuple
from fractions import Fraction from fractions import Fraction
from copy import copy from copy import copy
@ -6,6 +8,29 @@ from artiq.language import units
class int64(int): 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 pass
def _make_int64_op_method(int_method): def _make_int64_op_method(int_method):
@ -33,14 +58,88 @@ for _op_name in ("add", "sub", "mul", "floordiv", "mod",
def round64(x): 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)) return int64(round(x))
def array(element, count): 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)] return [copy(element) for i in range(count)]
class AutoContext: 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 = "" parameters = ""
implicit_core = True implicit_core = True
@ -62,13 +161,24 @@ class AutoContext:
self.build() self.build()
def get_missing_value(self, parameter): 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: try:
return getattr(self, parameter) return getattr(self, parameter)
except AttributeError: except AttributeError:
return self.mvs.get_missing_value(parameter) return self.mvs.get_missing_value(parameter)
def build(self): 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 pass
@ -76,6 +186,25 @@ _KernelFunctionInfo = namedtuple("_KernelFunctionInfo", "core_name k_function")
def kernel(arg): 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): if isinstance(arg, str):
def real_decorator(k_function): def real_decorator(k_function):
def run_on_core(exp, *k_args, **k_kwargs): def run_on_core(exp, *k_args, **k_kwargs):
@ -108,6 +237,12 @@ _time_manager = _DummyTimeManager()
def set_time_manager(time_manager): 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 global _time_manager
_time_manager = time_manager _time_manager = time_manager
@ -121,6 +256,10 @@ _syscall_manager = _DummySyscallManager()
def set_syscall_manager(syscall_manager): 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 global _syscall_manager
_syscall_manager = syscall_manager _syscall_manager = syscall_manager
@ -130,6 +269,10 @@ kernel_globals = "sequential", "parallel", "delay", "now", "at", "syscall"
class _Sequential: 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): def __enter__(self):
_time_manager.enter_sequential() _time_manager.enter_sequential()
@ -139,6 +282,14 @@ sequential = _Sequential()
class _Parallel: 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): def __enter__(self):
_time_manager.enter_parallel() _time_manager.enter_parallel()
@ -148,16 +299,33 @@ parallel = _Parallel()
def delay(duration): def delay(duration):
"""Increases the RTIO time by the given amount.
"""
_time_manager.take_time(duration) _time_manager.take_time(duration)
def now(): def now():
"""Retrieves the current RTIO time, in microcycles.
"""
return _time_manager.get_time() return _time_manager.get_time()
def at(time): def at(time):
"""Sets the RTIO time to the specified absolute value.
"""
_time_manager.set_time(time) _time_manager.set_time(time)
def syscall(*args): 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) return _syscall_manager.do(*args)