Hello! In our last lesson, we built our first piece of sequential logic: a D-type flip-flop with a synchronous reset. I asked you to follow a simple rule: always use the non-blocking assignment operator (<=) inside a clocked always block.
Today, we're going to explore the "why" behind that rule. This lesson dives into one of the most fundamental and often misunderstood topics in Verilog: the difference between blocking (=) and non-blocking (<=) assignments. Mastering this is crucial for writing correct and predictable hardware descriptions. Our goal is to differentiate between them and establish clear guidelines for when to use each.
1. The Two Types of Procedural Assignments
In Verilog's procedural blocks (like initial and always), there are two ways to assign a value to a variable. While they look similar, their behavior is fundamentally different and has profound implications for the hardware you create.
- Blocking Assignment (
=): Assignments happen sequentially, one after the other, within a block. An assignment with=must complete before the next statement in the block is executed. This is similar to how assignments work in standard software languages like C or Python. - Non-Blocking Assignment (
<=): Assignments are scheduled to happen concurrently. When the block is triggered (e.g., by a clock edge), all the right-hand side (RHS) expressions are evaluated first. Then, at the end of the time step, all the left-hand side (LHS) variables are updated with the results.
Let's start with a video that gives a clear overview and then demonstrates these concepts with code.
27 - Blocking and Nonblocking Assignment
This video provides an excellent introduction to the different assignment types in Verilog.
Watch the first section of the video to get a clear definition of procedural assignments and the introduction to the blocking (=) and non-blocking (<=) variants. Pay attention to the distinction between how they update the left-hand side. Introduction to Assignments
2. The Golden Rule of Sequential Logic
In the previous lesson, you created a D flip-flop. Now, let's see what happens when we try to connect two of them together to make a simple 2-bit shift register. The intention is for data to move from input d to q1 on the first clock cycle, and then from q1 to q2 on the second clock cycle.

Let's see why non-blocking assignments (<=) are essential to model this correctly.
Using Non-Blocking (<=) Assignments (The Correct Way)
// CORRECT - Models a true register chain
always @(posedge clk) begin
q1 <= d; // Schedule update for q1
q2 <= q1; // Schedule update for q2 using the OLD value of q1
end
Here's how this works:
- At the positive clock edge, the
alwaysblock is triggered. - The simulator evaluates all right-hand sides (RHS): It gets the current value of
dand the current (old) value ofq1. - At the end of the time step, it updates the left-hand sides (LHS):
q1gets the value ofd, andq2gets the old value ofq1.
This correctly models two separate flip-flops in a chain.
Using Blocking (=) Assignments (The Incorrect Way)
// WRONG - Creates a wire, not a register chain
always @(posedge clk) begin
q1 = d; // Update q1 immediately
q2 = q1; // Update q2 with the NEW value of q1
end
Here's the problem:
- At the positive clock edge, the
alwaysblock is triggered. - The first statement
q1 = d;executes and completes. The value ofq1is immediately updated to the value ofd. - The second statement
q2 = q1;executes. Sinceq1has already been updated, this statement is effectivelyq2 = d;.
As a result, both q1 and q2 get the value of d in the same clock cycle. The synthesis tool will likely create one flip-flop for q1 and just connect q2 to q1 with a wire, or even optimize q2 away entirely. The shift register behavior is lost.
This video demonstrates this exact problem and its solution.
27 - Blocking and Nonblocking Assignment
This part of the video you watched earlier specifically addresses how these assignment types affect sequential circuits.
Watch the section on sequential circuits. It vividly illustrates how blocking assignments fail to create the intended shift register, while non-blocking assignments work perfectly. This directly explains why we follow our first rule.
This leads us to our first, and most important, rule:
Rule #1: When modeling sequential logic (i.e., in an always @(posedge clk) block), use non-blocking assignments (<=).
3. The Golden Rule of Combinational Logic
So, if non-blocking assignments are so good at modeling hardware, why not use them everywhere? Let's consider a simple combinational circuit described with an always @(*) block.
Intended logic: out = (a & b) | c;
We might break this down with an intermediate signal:temp = a & b;out = temp | c;
Using Blocking (=) Assignments (The Correct Way)
// CORRECT - Models a combinational logic path
always @(*) begin
temp = a & b; // temp is updated immediately
out = temp | c; // out is calculated using the NEW value of temp
end
This works exactly as you would expect. The value of temp is calculated, and that new value is immediately used to calculate out. This describes a pure combinational path.
Using Non-Blocking (<=) Assignments (The Incorrect Way)
// WRONG - Can infer a latch or cause simulation mismatches
always @(*) begin
temp <= a & b; // temp is scheduled for an update
out <= temp | c; // out is scheduled using the OLD value of temp
end
This code has a subtle but critical flaw. Because non-blocking assignments are used, the out signal is calculated using the value of temp from before the always block was triggered. The new value of temp (from a & b) will only be visible in the next simulation event. This can cause simulation results that don't match the synthesized hardware and can lead synthesis tools to infer an unwanted latch to hold the old value of temp.
This leads to our second rule:
Rule #2: When modeling combinational logic (i.e., in an always @(*) block), use blocking assignments (=).
4. Summary and Common Mistakes
Understanding and applying these two rules will save you countless hours of debugging. The ChipVerify article below provides a fantastic summary and highlights common mistakes that beginners often make.
Verilog Blocking & Non-Blocking
This article is an excellent reference for this topic.
First, review the "Assignment Types Comparison" table at the top for a quick summary. Next, read the sections "When to Use Each Type" and, most importantly, "Common Beginner Mistakes". These sections reinforce the two rules and show you exactly what will go wrong if you break them, including mixing assignment types for the same variable.
The key takeaways are summarized in this table, based on the one from the article you just read:
| Aspect | Blocking (=) |
Non-Blocking (<=) |
|---|---|---|
| Execution | Sequential (one after another) | Concurrent (scheduled in parallel) |
| LHS Update | Immediately | At the end of the current time-step |
| Use Case | Combinational Logic (always @(*)) |
Sequential Logic (always @(posedge clk)) |
| Result | Wires, logic gates | Registers, flip-flops |
5. Practice: Fix the Bugs!
Let's apply what you've learned. The following Verilog module is full of assignment-type bugs. Your task is to identify and fix them based on the two rules.
Remember the rules:
- Is the
alwaysblock sequential (posedge clk) or combinational (*)? - Apply the correct assignment operator (
<=for sequential,=for combinational). - Ensure a single variable isn't assigned with both types.
module buggy_design (
input wire clk,
input wire rst_n,
input wire [7:0] data_in,
input wire load,
output reg [7:0] data_out,
output reg valid
);
reg [7:0] shift_reg;
reg [2:0] count;
reg enable;
// Block 1: Sequential logic
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
shift_reg = 8'h00;
count = 3'b000;
end else if (load) begin
shift_reg = data_in;
count = 3'b000;
end else begin
shift_reg = {shift_reg[6:0], 1'b0};
count <= count + 1; // Mixed assignments!
end
end
// Block 2: Combinational logic
always @(*) begin
enable <= (count == 3'b111);
data_out <= shift_reg;
end
// Block 3: Sequential logic
always @(posedge clk) begin
valid = enable;
end
endmodule
Click here for the corrected code and explanation
module fixed_design (
input wire clk,
input wire rst_n,
input wire [7:0] data_in,
input wire load,
output reg [7:0] data_out,
output reg valid
);
reg [7:0] shift_reg;
reg [2:0] count;
reg enable;
// Block 1: Sequential logic - USE NON-BLOCKING (<=)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
shift_reg <= 8'h00;
count <= 3'b000;
end else if (load) begin
shift_reg <= data_in;
count <= 3'b000;
end else begin
shift_reg <= {shift_reg[6:0], 1'b0};
count <= count + 1;
end
end
// Block 2: Combinational logic - USE BLOCKING (=)
always @(*) begin
enable = (count == 3'b111);
data_out = shift_reg;
end
// Block 3: Sequential logic - USE NON-BLOCKING (<=)
always @(posedge clk) begin
valid <= enable;
end
endmodule
Explanation of Fixes:
- Block 1: This is a clocked
alwaysblock that describes registers (shift_reg,count). Therefore, all assignments inside it must be non-blocking (<=). The original code incorrectly used blocking (=) assignments, which could lead to race conditions and unpredictable behavior, especially since it was mixed with a non-blocking assignment forcount. - Block 2: This
always @(*)block describes pure combinational logic. Its outputs (enable,data_out) should reflect the current inputs (count,shift_reg) immediately. Therefore, blocking assignments (=) must be used. - Block 3: This block describes a single flip-flop that registers the
enablesignal to create thevalidoutput. As it's sequential logic, it must use a non-blocking assignment (<=).
Conclusion
Congratulations on tackling one of the trickiest concepts in Verilog! Getting this right is a sign of moving from a beginner to an intermediate RTL designer.
Key Takeaways:
- Blocking assignments (
=) execute sequentially and update values immediately. Use them for combinational logic insidealways @(*)blocks. - Non-blocking assignments (
<=) schedule updates to happen concurrently at the end of a time step. Use them for sequential logic insidealways @(posedge clk)blocks. - Never mix blocking and non-blocking assignments for the same variable or within the same
alwaysblock.
With this solid foundation, we are ready to build more complex sequential circuits. In the next lesson, we will use our knowledge to implement a parameterized N-bit register with a load enable signal, putting Rule #1 into practice once again.