Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 31

Blocking vs. Non-Blocking Assignments

Recommended resources

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.

Behavioral Non-Blocking Assignments in Verilog
This diagram shows the intended hardware: a two-stage shift register. At each clock edge, the value at `d` is captured by the first flip-flop (`q1`), and the value that *was* at `q1` is captured by the second flip-flop (`q2`).

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:

  1. At the positive clock edge, the always block is triggered.
  2. The simulator evaluates all right-hand sides (RHS): It gets the current value of d and the current (old) value of q1.
  3. At the end of the time step, it updates the left-hand sides (LHS): q1 gets the value of d, and q2 gets the old value of q1.

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:

  1. At the positive clock edge, the always block is triggered.
  2. The first statement q1 = d; executes and completes. The value of q1 is immediately updated to the value of d.
  3. The second statement q2 = q1; executes. Since q1 has already been updated, this statement is effectively q2 = 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:

  1. Is the always block sequential (posedge clk) or combinational (*)?
  2. Apply the correct assignment operator (<= for sequential, = for combinational).
  3. 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 always block 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 for count.
  • 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 enable signal to create the valid output. 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 inside always @(*) blocks.
  • Non-blocking assignments (<=) schedule updates to happen concurrently at the end of a time step. Use them for sequential logic inside always @(posedge clk) blocks.
  • Never mix blocking and non-blocking assignments for the same variable or within the same always block.

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.