Example #1
0
class Trigger(object):
    """Base class to derive from."""
    def __init__(self):
        self.log = SimLog("cocotb.%s" % (self.__class__.__name__), id(self))
        self.signal = None
        self.primed = False

    def prime(self, *args):
        """FIXME: document"""
        self.primed = True

    def unprime(self):
        """Remove any pending callbacks if necessary."""
        self.primed = False

    def __del__(self):
        """Ensure if a trigger drops out of scope we remove any pending
        callbacks."""
        self.unprime()

    def __str__(self):
        return self.__class__.__name__

    @property
    def _outcome(self):
        return outcomes.Value(self)

    # Once 2.7 is dropped, this can be run unconditionally
    if sys.version_info >= (3, 3):
        exec_(
            textwrap.dedent("""
        def __await__(self):
            # hand the trigger back to the scheduler trampoline
            return (yield self)
        """))
Example #2
0
class Trigger(with_metaclass(abc.ABCMeta)):
    """Base class to derive from."""
    __slots__ = ('primed', '__weakref__')

    def __init__(self):
        self.primed = False

    @lazy_property
    def log(self):
        return SimLog("cocotb.%s" % (self.__class__.__name__), id(self))

    @abc.abstractmethod
    def prime(self, callback):
        """Set a callback to be invoked when the trigger fires.

        The callback will be invoked with a single argumement, `self`.

        Subclasses must override this, but should end by calling the base class
        method.

        Do not call this directly within coroutines, it is intended to be used
        only by the scheduler.
        """
        self.primed = True

    def unprime(self):
        """Remove the callback, and perform cleanup if necessary.

        After being unprimed, a Trigger may be reprimed again in future.
        Calling `unprime` multiple times is allowed, subsequent calls should be
        a no-op.

        Subclasses may override this, but should end by calling the base class
        method.

        Do not call this directly within coroutines, it is intended to be used
        only by the scheduler.
        """
        self.primed = False

    def __del__(self):
        # Ensure if a trigger drops out of scope we remove any pending callbacks
        self.unprime()

    def __str__(self):
        return self.__class__.__name__

    @property
    def _outcome(self):
        """The result that `yield this_trigger` produces in a coroutine.

        The default is to produce the trigger itself, which is done for
        ease of use with :class:`~cocotb.triggers.First`.
        """
        return outcomes.Value(self)

    # Once 2.7 is dropped, this can be run unconditionally
    if sys.version_info >= (3, 3):
        exec_(textwrap.dedent("""
        def __await__(self):
            # hand the trigger back to the scheduler trampoline
            return (yield self)
        """))
Example #3
0
class RunningCoroutine(object):
    """Per instance wrapper around an function to turn it into a coroutine.

    Provides the following:

        coro.join() creates a Trigger that will fire when this coroutine
        completes.

        coro.kill() will destroy a coroutine instance (and cause any Join
        triggers to fire.
    """
    def __init__(self, inst, parent):
        if hasattr(inst, "__name__"):
            self.__name__ = "%s" % inst.__name__
            self.log = SimLog("cocotb.coroutine.%s" % self.__name__, id(self))
        else:
            self.log = SimLog("cocotb.coroutine.fail")

        if sys.version_info[:2] >= (3, 5) and inspect.iscoroutine(inst):
            self._natively_awaitable = True
            self._coro = inst.__await__()
        else:
            self._natively_awaitable = False
            self._coro = inst
        self._started = False
        self._callbacks = []
        self._parent = parent
        self.__doc__ = parent._func.__doc__
        self.module = parent._func.__module__
        self.funcname = parent._func.__name__
        self._outcome = None

        if not hasattr(self._coro, "send"):
            self.log.error("%s isn't a valid coroutine! Did you use the yield "
                           "keyword?" % self.funcname)
            raise CoroutineComplete()

    @property
    def retval(self):
        if self._outcome is None:
            raise RuntimeError("coroutine is not complete")
        return self._outcome.get()

    @property
    def _finished(self):
        return self._outcome is not None

    def __iter__(self):
        return self

    def __str__(self):
        return str(self.__name__)

    def _advance(self, outcome):
        """
        Advance to the next yield in this coroutine

        :param outcome: The `outcomes.Outcome` object to resume with.
        :returns: The object yielded from the coroutine

        If the coroutine returns or throws an error, self._outcome is set, and
        this throws `CoroutineComplete`.
        """
        try:
            self._started = True
            return outcome.send(self._coro)
        except ReturnValue as e:
            self._outcome = outcomes.Value(e.retval)
            raise CoroutineComplete()
        except StopIteration as e:
            retval = getattr(e, 'value', None)  # for python >=3.3
            self._outcome = outcomes.Value(retval)
            raise CoroutineComplete()
        except BaseException as e:
            self._outcome = outcomes.Error(e)
            raise CoroutineComplete()

    def send(self, value):
        return self._coro.send(value)

    def throw(self, exc):
        return self._coro.throw(exc)

    def close(self):
        return self._coro.close()

    def kill(self):
        """Kill a coroutine."""
        if self._outcome is not None:
            # already finished, nothing to kill
            return

        self.log.debug("kill() called on coroutine")
        # todo: probably better to throw an exception for anyone waiting on the coroutine
        self._outcome = outcomes.Value(None)
        cocotb.scheduler.unschedule(self)

    def join(self):
        """Return a trigger that will fire when the wrapped coroutine exits."""
        return cocotb.triggers.Join(self)

    def has_started(self):
        return self._started

    def __nonzero__(self):
        """Provide boolean testing
            if the coroutine has finished return false
            otherwise return true"""
        return not self._finished

    # Once 2.7 is dropped, this can be run unconditionally
    if sys.version_info >= (3, 3):
        exec_(textwrap.dedent("""
        def __await__(self):
            # It's tempting to use `return (yield from self._coro)` here,
            # which bypasses the scheduler. Unfortunately, this means that
            # we can't keep track of the result or state of the coroutine,
            # things which we expose in our public API. If you want the
            # efficiency of bypassing the scheduler, remove the `@coroutine`
            # decorator from your `async` functions.

            # Hand the coroutine back to the scheduler trampoline.
            return (yield self)
        """))

    __bool__ = __nonzero__

    def sort_name(self):
        if self.stage is None:
            return "%s.%s" % (self.module, self.funcname)
        else:
            return "%s.%d.%s" % (self.module, self.stage, self.funcname)
Example #4
0
class Trigger(with_metaclass(abc.ABCMeta)):
    """Base class to derive from."""

    # __dict__ is needed here for the `.log` lazy_property below to work.
    # The implementation of `_PyObject_GenericGetAttrWithDict` suggests that
    # despite its inclusion, __slots__ will overall give speed and memory
    # improvements:
    #  - the `__dict__` is not actually constructed until it's needed, and that
    #    only happens if the `.log` attribute is used, where performance
    #    concerns no longer matter.
    #  - Attribute setting and getting will still go through the slot machinery
    #    first, as "data descriptors" take priority over dict access
    __slots__ = ('primed', '__weakref__', '__dict__')

    def __init__(self):
        self.primed = False

    @lazy_property
    def log(self):
        return SimLog("cocotb.%s" % (self.__class__.__name__), id(self))

    @abc.abstractmethod
    def prime(self, callback):
        """Set a callback to be invoked when the trigger fires.

        The callback will be invoked with a single argumement, `self`.

        Subclasses must override this, but should end by calling the base class
        method.

        Do not call this directly within coroutines, it is intended to be used
        only by the scheduler.
        """
        self.primed = True

    def unprime(self):
        """Remove the callback, and perform cleanup if necessary.

        After being unprimed, a Trigger may be reprimed again in future.
        Calling `unprime` multiple times is allowed, subsequent calls should be
        a no-op.

        Subclasses may override this, but should end by calling the base class
        method.

        Do not call this directly within coroutines, it is intended to be used
        only by the scheduler.
        """
        self.primed = False

    def __del__(self):
        # Ensure if a trigger drops out of scope we remove any pending callbacks
        self.unprime()

    def __str__(self):
        return self.__class__.__name__

    @property
    def _outcome(self):
        """The result that `yield this_trigger` produces in a coroutine.

        The default is to produce the trigger itself, which is done for
        ease of use with :class:`~cocotb.triggers.First`.
        """
        return outcomes.Value(self)

    # Once 2.7 is dropped, this can be run unconditionally
    if sys.version_info >= (3, 3):
        exec_(textwrap.dedent("""
        def __await__(self):
            # hand the trigger back to the scheduler trampoline
            return (yield self)
        """))