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)