Keywords: blocking assignment, non-blocking assignment, parallel
Procedural assignment is an assignment within an initial or always statement block, and the assignment objects are types such as registers, integers, reals, etc.
After these variables are assigned, their values remain unchanged until they are assigned new values again.
Continuous assignment is always active; any change in an operand will affect the result of the expression. Procedural assignment only takes effect when the statement is executed. This is the difference between continuous assignment and procedural assignment.
Verilog procedural assignment includes two types of statements: blocking assignment and non-blocking assignment.
Blocking assignment
Blocking assignment is sequential execution, meaning that before the next statement is executed, the current statement must have completed.
Blocking assignment statements use the equal sign=as the assignment operator.
In the previous simulations, the assignment statements in initial blocks all used blocking assignment.
Non-blocking assignment
Non-blocking assignment is a parallel execution statement, meaning the next statement executes simultaneously with the current statement; it does not block the execution of subsequent statements in the same block.
Non-blocking assignment statements use the less-than-or-equal sign<=as the assignment operator.
Use the following code to simulate blocking and non-blocking assignments to illustrate the difference between the two procedural assignments.
Example
module test ;
reg [3:0] ai, bi ;
reg [3:0] ai2, bi2 ;
reg [3:0] value_blk ;
reg [3:0] value_non ;
reg [3:0] value_non2 ;
initial begin
ai = 4'd1 ; //(1)
bi = 4'd2 ; //(2)
ai2 = 4'd7 ; //(3)
bi2 = 4'd8 ; //(4)
#20 ; //(5)
//non-block-assigment with block-assignment
ai = 4'd3 ; //(6)
bi = 4'd4 ; //(7)
value_blk = ai + bi ; //(8)
value_non <= ai + bi ; //(9)
//non-block-assigment itself
ai2 <= 4'd5 ; //(10)
bi2 <= 4'd6 ; //(11)
value_non2 <= ai2 + bi2 ; //(12)
end
//stop the simulation
always begin
#10 ;
if ($time >= 1000) $finish ;
end
endmodule
The simulation results are as follows:
Statements (1)-(8) are all blocking assignments and execute sequentially.
Before 20ns, the values of signals ai and bi change. Due to the characteristics of procedural assignment, value_blk = ai + bi has not been executed, so before 20ns, the value of value_blk is X (unknown state).
After 20ns, the values of signals ai and bi change again. When value_blk = ai + bi is executed, signal value_blk uses the new values of signals ai and bi to obtain the calculation result 7.
Statements (9)-(12) are all non-blocking assignments and execute in parallel.
First, although (9)-(12) are executed concurrently, their execution order is still after (8), so when signal value_non = ai + bi is calculated, it also uses the new values of signals ai and bi, resulting in 7.
Second, (10)-(12) are executed concurrently, so when value_non2 = ai2 + bi2 is calculated, it does not care about the latest non-blocking assignment results of signals ai2 and bi2. That is, value_non2 uses the old values of signals ai2 and bi2, and the result is 4'hF.

Use non-blocking assignment to avoid race conditions
The above simulation code is only intended to help readers better understand the difference between blocking and non-blocking assignments. In actual Verilog design, remember not to mix blocking and non-blocking assignments within the same procedural structure. When the two assignment styles are mixed, timing is difficult to control and unexpected results can easily occur.
More often, when designing circuits, non-blocking assignment is mostly used in always sequential logic blocks, and blocking assignment is mostly used in always combinational logic blocks. When simulating circuits, blocking assignment is generally used in initial blocks.
As shown below, to implement the function of swapping the values of two registers on the rising clock edge, blocking assignment is used in two always blocks.
Because the statements in the two always blocks execute simultaneously, but the execution order of a=b and b=a cannot be determined, this creates a race condition.
But no matter which executes first (related to the compiler, etc.), without considering timing issues, their execution order is always sequential, and in the end the values of a and b are always equal. The effect of swapping the two register values is not achieved.
Example
a = b ;
end
always @(posedge clk) begin
b = a;
end
However, if non-blocking assignment is used in the always blocks, the above race condition can be avoided.
As shown below, the statements in the two always blocks execute in parallel. The operands on the right-hand side of the assignment use the old values from the previous clock cycle. At this time, a<=b and b<=a can execute without interfering with each other, achieving the purpose of swapping register values.
Example
a <= b ;
end
always @(posedge clk) begin
b <= a;
end
Of course, the following code can also achieve the function of swapping register values, but it is clearly not as simple and intuitive as directly using non-blocking assignment in always blocks.
Example
temp = a ;
a = b ;
b = temp ;
end
Source code download
Download