How to avoid a race condition

Hi Dave,
This question is especially for you, as I am quoting your statements below here from your previous post in 2015.
A race condition in SystemVerilog is an simulation modeling artifact where the order of execution between two different constructs cannot be guaranteed. The most common race condition is when there are multiple processes reading and writing the same variable synchronized to the same event region (like the edge of a clock). SystemVerilog does not guarantee the order of execution between the reading and writing, so there is no way to know if the “read” captures the old or the new value of that variable. (bdreku, this can happen just as easily within a program block or module)

The solution to this problem is to move either the reading or writing of that variable to a different event region. The most common way to do this is to use a non-blocking assignment (q <= d)for the writing of that variable. That postpones actual update of that variable to a later region. Now all the other process that try to read that variable in the current event region are guaranteed to the “old” value of that variable regardless of the writing process executes before or after the reading process. This technique is the way Verilog RTL designers have been coding for years, and works equally as well for testbench verification.

Testbench writers have a few other options, including using a different event for writing and reading (like the inverse edge of the clock), and clocking blocks to offset the reading and writing from a synchronous clock edge.

Real hardware does not have the same kind of problem with race conditions. Propagation delays through registers usually prevent simultaneous reading and writing, but physical issues like clock skew and setup/hold requirements can produce timing problems where it is difficult to predict reading the old or new value of a signal.

QUESTION:
What I understood so far that, to avoid race condition, usually in the uvm driver we use the non-blocking assignment and also we use clocking block in the interface. Do we really need both or one way (especially clocking block) is sufficient? as I thought probably uvm driver with non-blocking assignment might not be sufficient as DUT might also have non-blocking assignment(driving)/sampling and that might match the uvm driver non-blocking assignment, so in that case reading or writing of a given port/variable might happen in the same event region, unless DUT sample inputs only using blocking assignment. Though I thought clocking block might be sufficient as it introduces the delays and hence you always get old value regardless of blocking/nonblocking assignments(driving)/samplings Is that all correct understanding? or I have mixed them in my head.

Thank you,
Mega

In reply to megamind:

SystemVerilog is a collection of technologies from different languages and there can be more than one way to accomplish the same thing. This holds true for most programming languages— they all borrow from each other.

As far as non-blocking assignments versus clocking blocks, you should pick one methodology and not mix them, at least within the same driver/interface pair.

You are correct in observing that races can be eliminated if both DUT and testbench use non-blocking assignments correctly. Clocking blocks are more tolerant with designs that have not used non-blocking assignments or gate-level descriptions with inaccurate timing. However, those cases will be more prone to race conditions within the DUT.

In reply to dave_59:

Hi Dave,
First of all, thank you for your great effort on answering questions in the forum. I wanted to know what would happen if we DUT is written in VHDL? Do same conditions holds true for all cases? As far as I know, VHDL already uses non-blocking by default. I guess if we are using one of the suggested methods (clocking blocks or non-blocking assignment in testbench), it would be enough. Thanks in advance.