Verilog is a powerful hardware description language, but it comes with its own set of quirks, especially when it comes to simulation vs. synthesis. Writing correct and reliable RTL (Register Transfer Level) code isn’t just about getting your design to “work”, it’s about making sure it behaves consistently across tools. That’s where coding guidelines come in.
In this blog post, we’ll go through 6 essential Verilog coding guidelines that help avoid common mistakes like race conditions, unintended latches, and simulation-synthesis mismatches. These aren’t just arbitrary rules—they’re based on years of real-world design experience and are widely followed in both academia and industry.
By following these guidelines, Verilog RTL will be:
- More predictable in simulation,
- More synthesizable for real hardware,
- And easier to maintain and debug.
Guideline 1: When modelling combinational logic with an always block, use blocking assignments
The blocking assignment operator is an equal sign (“=”). A blocking assignment gets its name because a blocking assignment must evaluate the RHS arguments and complete the assignment without interruption from any other Verilog statement. The assignment is said to “block” other assignments until the current assignment has been completed.Execution of blocking assignments can be viewed as a one-step process that is evaluating the RHS (right-hand side equation) and update the LHS (left-hand side expression) of the blocking assignment without interruption from any other Verilog statement. A blocking assignment “blocks” trailing assignments in the same always block from occurring until after the current assignment has been completed.
Bad combinational logic coding style using non-blocking assignments
If only a single assignment is made in the always block, using either blocking or nonblocking assignments will work, but in the interest of developing good coding habits, one should always use blocking assignments to code combinational logic. For coding simple combinational always blocks this would work, but if multiple assignments are included in the always block,
such as in the one in the above example, using nonblocking assignments with no delays will either simulate incorrectly, or require additional sensitivity list entries and multiple passes through the always block to simulate correctly. The code shown in example builds the y-output from three sequentially executed statements. Since nonblocking assignments evaluate the RHS expressions before updating the LHS variables, the values of tmp1 and tmp2 were the original values of these two variables upon entry to this always block and not the values that will be updated at the end of the simulation time step. The y-output will reflect the old values of tmp1 and tmp2, not the values calculated in the current pass of the always block.

Inefficient multi-pass combinational logic coding style with nonblocking assignments
In the above example tmp1 and tmp2 have been added to the sensitivity list. As described when the nonblocking assignments update the LHS variables in the nonblocking assign update events queue, the always block will self-trigger and update the y-outputs with the newly calculated tmp1 and tmp2 values. The y-output value will now be correct after taking two passes through the always block. Multiple passes through an always block equates to degraded simulation performance and should be avoided if a reasonable alternative exists.
Efficient combinational logic coding style using blocking assignments
Guideline 2: When modeling sequential logic with an always block, use Non-blocking assignments
In the above code-segment, at the positive edge of clk, both the always blocks get triggered. So, b is being written into (through the first always block), while it is being read also (through the second always block).
Assume that the first always block gets triggered before the second one. b gets updated. Then, the second always block gets triggered. ‘a’ sees the updated value of b. Thus, in effect, the value of c percolates all the way to a through b.
Consider another scenario. The second always block gets triggered before the first one. In this case, a sees the old value of b, and, then, b gets updated. So, depending on the sequence chosen by the simulator, the value of ‘a’ could be either c (i.e. the new value of b) or the old value of b.
The solution to deal with this kind of ambiguity is also very simple. For a sequential element, we should use a Non-Blocking Assignment (NBA). With an NBA, the Right-Hand-Side is read immediately, but the updating of the Left-Hand-Side happens after all the reads scheduled for the current time have already taken place
So, irrespective of what sequence is used for triggering the blocks, the events will occur in the following sequence:
(i) b and a will decide what values they should go to, but they will not actually get updated. The relative sequence of the two always blocks is still undeterminable, but that does not make any difference.
(ii) Subsequently, b and a will get updated. This updating happens after all read of Right-Hand-Side have already taken place. Since ‘a’ has already decided what value to go to (in the first step itself), so any change in value of b is not going to impact the value of a.
Guideline 3: When modeling both sequential and combinational logic within the same always block, use nonblocking assignments.
It is sometimes convenient to group simple combinational equations with sequential logic equations. When combining combinational and sequential code into a single always block, code the always block as a sequential always block with nonblocking assignments as shown in the below example.
The same could also be implemented as two separate always blocks, one with purely combinational logic coded with blocking assignments and one with purely sequential logic coded with nonblocking assignments, as shown in the below example
Guideline 4: Do not mix blocking and nonblocking assignments in the same always block.
Verilog permits blocking and nonblocking assignments to be freely mixed inside an always block. In general, mixing blocking and nonblocking assignments in the same always block is a poor coding style, even if Verilog permits it.
The above example simulates correctly, but Synopsys tools will report a syntax error because the blocking assignment is made inside a inferred a sequential block.
Guideline 5: Do not make assignments to the same variable from more than one always block.
Making multiple assignments to the same variable from more than one always block is a Verilog race condition, even when using blocking and nonblocking assignments.Write-Write race occurs when multiple values are being written into a signal at the same time.
When b changes, both the always blocks get triggered. It is possible that in one of the always blocks, err_flag is supposed to take the value of 1, while, in the other, it’s supposed to take the value of 0. The value that err_flag finally takes will depend on the sequence in which these always blocks get triggered. The one that triggers later – will determine the final value.The race is being created because of the assignments in the two always blocks being made to
the same signal.
The simplest solution to this ambiguity is to avoid updating a signal in more than one concurrent block. A signal should be updated in only one concurrent statement.
Guideline 6: Use $strobe to display values that have been assigned using nonblocking assignments.
Nonblocking assignments are updated after all $display commands. The below output from the above simulation shows that the $display command was executed in the active events queue, before the nonblocking assign update events were executed
$display: a = 0
$monitor: a = 1
$strobe : a = 1