This is a quick write-up about cocotb ReadWrite and ReadOnly. Starting with the cocotb documentation for cocotb.triggers.

class cocotb.triggers.ReadOnly[source]
Fires when the current simulation timestep moves to the read-only phase.
The read-only phase is entered when the current timestep no longer has any further delta steps. This will be a point where all the signal values are stable as there are no more RTL events scheduled for the timestep. The simulator will not allow scheduling of more events in this timestep. Useful for monitors which need to wait for all processes to execute (both RTL and cocotb) to ensure sampled signal values are final.

class cocotb.triggers.ReadWrite[source]
Fires when the read-write portion of the simulation cycles is reached.

Here is a small example of how to use those triggers in a cocotb coroutine.

    async def execute(self, pc_addr):
        self.dut.pc.value = pc_addr
        await RisingEdge(self.dut.clk)
        await ReadWrite()

Well, at this point, we should be wondering what read-write portion and read-only phase mean.

Deep dive Link to heading

Starting with the trigger ReadWrite, we can see that prime defines the trigger with simulator.register_rwsynch_callback, which jumps to the C++ side.

class ReadWrite(GPITrigger, metaclass=_ParameterizedSingletonAndABC):
    """Fires when the read-write portion of the simulation cycles is reached."""

    __slots__ = ()

    @classmethod
    def __singleton_key__(cls):
        return None

    def __init__(self):
        GPITrigger.__init__(self)

    def prime(self, callback):
        if self.cbhdl is None:
            self.cbhdl = simulator.register_rwsynch_callback(callback, self)
            if self.cbhdl is None:
                raise TriggerException("Unable set up %s Trigger" % (str(self)))
        GPITrigger.prime(self, callback)

In register_rwsynch_callback, it registers the callback with cocotb GPI using gpi_register_readwrite_callback.

    gpi_cb_hdl hdl = gpi_register_readwrite_callback(
        (gpi_function_t)handle_gpi_callback, cb_data);

    PyObject *rv = gpi_hdl_New(hdl);

gpi_register_readwrite_callback calls register_readwrite_callback from the implementation. It’s almost always VPI.

gpi_cb_hdl gpi_register_readwrite_callback(int (*gpi_function)(void *),
                                           void *gpi_cb_data) {
    // It should not matter which implementation we use for this so just pick
    // the first one
    GpiCbHdl *gpi_hdl = registered_impls[0]->register_readwrite_callback(
        gpi_function, gpi_cb_data);
    if (!gpi_hdl) {
        LOG_ERROR("Failed to register a readwrite callback");
        return NULL;
    } else {
        return gpi_hdl;
    }
}

Finally, VpiReadWriteCbHdl is called to register the callback.

GpiCbHdl *VpiImpl::register_readwrite_callback(int (*cb_func)(void *),
                                               void *cb_data) {
    auto cb_hdl = new VpiReadWriteCbHdl(this);
    auto err = cb_hdl->arm();
    // LCOV_EXCL_START
    if (err) {
        delete cb_hdl;
        return NULL;
    }
    // LCOV_EXCL_STOP
    cb_hdl->set_cb_info(cb_func, cb_data);
    return cb_hdl;
}

Note that VpiReadWriteCbHdl registers the callback with cbReadWriteSynch.

VpiReadWriteCbHdl::VpiReadWriteCbHdl(GpiImplInterface *impl) : VpiCbHdl(impl) {
    cb_data.reason = cbReadWriteSynch;
}

For ReadOnly, it’s similar but it sets cbReadOnlySynch.

VpiReadOnlyCbHdl::VpiReadOnlyCbHdl(GpiImplInterface *impl) : VpiCbHdl(impl) {
    cb_data.reason = cbReadOnlySynch;
}

Looking at the LRM for the definitions of cbReadOnlySynch and cbReadWriteSynch which is consistent with cocotb documentation. basically, you can’t write in cbReadOnlySynch as it may schedule events in the current time.

cbReadWriteSynch Callback shall occur after execution of events for a specified time. This time
may be before or after nonblocking assignment events have been processed.

cbReadOnlySynch Callback shall occur the same as for cbReadWriteSynch, except that writing
values or scheduling events before the next scheduled event is not allowed.