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) """))
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) """))
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)
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) """))