Blocking and non-blocking assignments are two ways of programming when a variable gets updated within Verilog simulation.
In C++, Java, or Python, a = b always means the same thing: “Copy b’s value into a right now.”
In Verilog, we have two assignment operators: = and <=. They seem harmless, but confusing them is the number one cause of undetectable errors, random behavior, and Race Conditions.
Today, we are going to understand why they exist and which convention prevents most errors.
Two Philosophies of Time
To understand the difference, we need to visualize how the simulator (and hardware) works internally.
Blocking Assignment (=)
This is the “classic” software assignment. It’s called blocking because it “blocks” the execution of the block until it is completed.
- Lines are executed sequentially, one after the other.
- The value is updated immediately. The next line of code already sees the new value.
Analogy: It’s like passing a note in class. I write the note, pass it to you, you read it, and pass it to the next person. The message travels sequentially.
Non-Blocking Assignment (<=)
This one is exclusive to HDLs. It’s called non-blocking because it doesn’t stop execution.
- The simulator reads the value on the right and “schedules” the update for the end of the current time step.
- All assignments within the block occur in parallel.
- The next line of code still sees the old value, because the update hasn’t been “applied” yet.
Analogy: It’s like the teacher shouts, “Everyone write the result on the board!” All students write at the same time. No one can copy what their neighbor wrote in that same instant because everyone is writing simultaneously.
The Shift Register Example
Let’s see what happens when we use the wrong operator trying to create a Shift Register (a pipeline where data moves A -> B -> C).
Assume initially: A = 1, B = 0, C = 0.
The Common Mistake Using =
// ❌ BAD: Trying to create sequential logic with blocking assignment
always @(posedge clk) begin
B = A; // Step 1: B takes A's value (1) IMMEDIATELY.
C = B; // Step 2: C takes B's value... which is now 1!
endResult: In a single clock cycle, A’s value has traveled to C. The description no longer represents the two pipeline stages we intended: the computed value for C directly depends on A within the same event.
The Correct Way Using <=
// ✅ GOOD: Correct sequential logic
always @(posedge clk) begin
B <= A; // "Schedules" that B will have A's current value (1).
C <= B; // "Schedules" that C will have B's current value (0, the old value).
end
// --- END OF CYCLE ---
// Now changes are applied all at once.Result:
Bbecomes 1.Cbecomes 0. The data has moved only one position, as it should in a shift register.
Race Conditions
A Race Condition occurs when the result of your circuit depends on “luck” (or more technically, on the arbitrary order in which the simulator executes things).
Imagine you have two separate always blocks interacting with each other.
// Block 1
always @(posedge clk) a = b;
// Block 2
always @(posedge clk) b = a;If you use =, the result depends on which block the simulator executes first (something you cannot control).
- If Block 1 runs first:
achanges, then Block 2 uses the newa. - If Block 2 runs first:
bchanges, then Block 1 uses the newb.
This creates a simulation race condition and can make the simulated model not match the intention of the synthesized circuit.
If you use <=:
always @(posedge clk) a <= b;
always @(posedge clk) b <= a;Order doesn’t matter. Both read the old values at the beginning, and both write at the end. The result is a perfect and stable swap.
What Hardware is Generated?
It’s important to understand that the operator alone does not determine the hardware. The sensitivity list, conditions, and block dependencies determine whether combinational logic or registers are inferred; = and <= primarily control the order of updates in the model.
With = (blocking)
In a combinational block, it allows a later assignment to immediately use the value calculated by an earlier one. In a block with posedge clk, it can still infer flip-flops, although its semantics can introduce races or eliminate intended logic stages.
With <= (non-blocking)
In a sequential block, it makes all expressions read the previous state and applies updates at the end of the simulation step. This is why it clearly represents the simultaneous behavior of flip-flops.
A Practical Convention
To avoid headaches, the FPGA design community follows a strict standard. If you follow it, you will never have Race Condition problems.
| Circuit Type | Verilog Block | Operator to Use |
|---|---|---|
| Combinational Logic | always @(*) | = |
| Sequential Logic | always @(posedge clk) | <= |
| Continuous Assignment | assign (outside always) | = |
Never mix! Never use = and <= inside the same always block. It is syntactically valid, but the behavior is so confusing that it is considered a serious design error.