This post is about ReadOnly and ReadWrite triggers, which I believe the most important concept to know in cocotb. Mainly because this is one of the gotchas I fall for frequently. In cocotb, starting a coroutine is easy, but deciding when to drive and read signals from the DUT is tricky. So, we will start with the cocotb timing model.
Cocotb Timing Model Link to heading
Cocotb Timing Model describes the Time Step as one or more evaluation cycles of HDL and cocotb events.
cocotb’s timing model is a simplification of the VPI‘s, VHPI‘s, and FLI‘s timing model, made by choosing the common subset of the most important aspects of those timing models.
Like the aforementioned timing models, cocotb’s is organized around time steps. A time step is a single point in simulated time, comprised of one or more evaluation cycles. For example, there is a time step that occurs on a rising clock edge, and the next time step is typically on the falling clock edge. Moving between time steps will advance simulated time.
Time Step Link to heading
There are 5 phases in a cocotb time step that the cocotb event loop cycles through to move to the next time step.
The time step starts in the Beginning of Time Step phase and ends in the End of Time Step phase. Evaluation cycles occur between these two points, with the simulator running in the HDL Evaluation phases, and cocotb code reacting in the Values Change and Values Settle phases.
Beginning of Time Step Link to heading
The first phase executes before handing over control to the simulator. This means after Timer or NextTimeStep, we do not have the new signal value yet at that simulation time step.
This is the phase where the NextTimeStep and Timer triggers will return. This phase begins the time step before any HDL code has executed or any signal or variable have changed value.
HDL Evaluation Link to heading
This is fully executed by the simulator to run the actual RTL (processes and continuous assignments).
This phase represents the time spent in the simulator evaluating always or process blocks, continuous assignments, or other HDL code.
If a signal or variable passed to a ValueChange, RisingEdge, or FallingEdge trigger changes value accordingly, the simulator will enter the Values Change phase.
Values Change Link to heading
If a coroutine is waiting for a trigger that depends on a signal, it will trigger here. And if a signal changes here, it can trigger HDL evaluation again. So, cocotb will switch between value changes and HDL evaluation until there are no events left in the cocotb event loop.
This is the phase where the ValueChange, RisingEdge, or FallingEdge triggers will return
Values Settle Link to heading
At this stage, all signals are stable and can be read safely. But writing a signal here can trigger another HDL evaluation.
This is the phase where the ReadWrite trigger will return.
In this phase, users can read and write values on any signal or variable. If they do write, the simulator will re-enter the HDL Evaluation phase
End of Time Step Link to heading
At this stage, we can’t write signals in cocotb because this is the end of the current time step.
This is the phase where the ReadOnly trigger will return.
ReadWrite Link to heading
Now that we have gone through the timing model, let’s see the pattern for ReadWrite. Let’s assume we are driving req to high to waiting for ack. There are two problems here:
- We need to make sure that signals are stable before driving
req - We need to make sure to read
ackcorrectly. This is more serious problem becauseRisingEdgetriggers inValues Changewhich could sample early value of ack before NBA kicks in.
await RisingEdge(dut.clk)
await dut.req.value = 1
for i in range(10)
await RisingEdge(dut.ack)
if (dut.ack.value):
print('Detected Ack')
dut.req.value = 0
Using ReadWrite at both locations make sure we drive and sample correctly.
await RisingEdge(dut.clk)
await ReadWrite()
dut.req.value = 1
for i in range(10)
await RisingEdge(dut.ack)
await ReadWrite()
if (dut.ack.value):
print('Detected Ack')
dut.req.value = 0
ReadOnly Link to heading
For ReadOnly, we are trying to solve the reading problem but here it simple because we don’t want to write any signals in time step. This makes it useful for reading a signals in a monitor after the HDL finished active and NBA.
while not dut.ack.value:
await RisingEdge(dut.ack)
await ReadOnly()
data = dut.data.value