I wrote a previous post about clock and Timer triggers in cocotb. Considering that all triggers yield to core scheduler, I thought to do another trigger (Posedge) and the trampoline.
Class hierarchy Link to heading
Starting with FallingEdge where it takes the signal handle, In this example, dut.clk is passed to FallingEdge
await FallingEdge(dut.clk)
FallingEdge sets the edge type for generic _EdgeBase
class FallingEdge(_EdgeBase):
"""Fires on the falling edge of *signal*, on a transition from ``1`` to ``0``."""
__slots__ = ()
_edge_type = 2
_EdgeBase defines the prime that registers callback with the simulator.
class _EdgeBase(GPITrigger, metaclass=_ParameterizedSingletonAndABC):
def __init__(self, signal):
super().__init__()
self.signal = signal
def prime(self, callback):
if self.cbhdl is None:
self.cbhdl = simulator.register_value_change_callback(
self.signal._handle, callback, type(self)._edge_type, self
)
super().prime(callback)
GPITrigger is almost empty base class which extends Trigger
class GPITrigger(Trigger):
__slots__ = ("cbhdl",)
def __init__(self):
Trigger.__init__(self)
self.cbhdl = None
Trigger defines the __await__ that yields self to the scheduler.
class Trigger(Awaitable):
def __init__(self):
self.primed = False
def prime(self, callback):
self.primed = True
def __await__(self):
# hand the trigger back to the scheduler trampoline
return (yield self)
prime and trampoline Link to heading
To explain the trampoline, We will have to dig into the scheduler. _schedule method is called on the trigger <NullTrigger for Start <Test dff_simple_test> at 0x7f8d48bc2cc0>. Note that result is returned to _advance.
956 result = coroutine._advance(send_outcome)
957
973 if not coroutine.done():
979 try:
980 result = self._trigger_from_any(result)
981 except TypeError as exc:
984 result = NullTrigger(outcome=outcomes.Error(exc))
985
986 self._resume_coro_upon(coroutine, result)
And _resume_coro_upon calls prime
612 def _resume_coro_upon(self, coro, trigger):
...
...
634 try:
635 print("trigger: Before prime", trigger)
636 trigger.prime(self._react)
The next point of interest is where _react is called by the callback from simulator callback registered by the prime above.
361
362 def _react(self, trigger):
381 # start the event loop
382 self._is_reacting = True
383 try:
385 self._event_loop(trigger)
_react calls _event_loop
389 def _event_loop(self, trigger):
480 for coro in self._scheduling:
...
...
488 self._schedule(coro, trigger=trigger)
_event_loop calls _schedule
934 def _schedule(self, coroutine, trigger=None):
...
...
947 with self._task_context(coroutine):
948 if trigger is None:
949 send_outcome = outcomes.Value(None)
950 else:
951 send_outcome = trigger._outcome
952 if _debug:
953 self.log.debug(f"Scheduling with {send_outcome}")
954
955 coroutine._trigger = None
956 result = coroutine._advance(send_outcome)
and _advance sends to outcome which makes yield self above returns and __await__ returns.
205 def _advance(self, outcome: outcomes.Outcome) -> typing.Any:
...
...
215 try:
216 self._started = True
218 return outcome.send(self._coro)