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.