Verilog Crash Course Lesson 4 / 8
Lesson 4 · Describing Logic

Sequential Logic & the Blocking/Non-Blocking Rule

Learning objective Describe clocked memory with always @(posedge clk), and understand precisely — not just as a memorized rule — why sequential logic must use <= (non-blocking) while combinational logic uses = (blocking). This is the single most consequential rule in practical Verilog.

Describing a flip-flop

A D flip-flop samples its input on a clock edge and holds that value until the next edge. In Verilog:

module dff (
    input  wire clk,
    input  wire d,
    output reg  q
);
    always @(posedge clk)
        q <= d;
endmodule

@(posedge clk) means "this block does something at the instant clk rises from 0 to 1, and at no other time." That's the entire definition of edge-triggered memory: everything else about the module's behavior — what happens between edges — is "hold the last value," which is exactly what a real flip-flop's physical storage does. Unlike always @(*), you write the sensitivity explicitly here (posedge clk), because you specifically want "only on this edge," not "on any change to anything read inside."

Adding reset

Real designs need a way to force known state at startup. Two common shapes — recognize both, and default to synchronous unless your course specifies otherwise:

Synchronous reset

always @(posedge clk) begin
    if (rst)
        q <= 1'b0;
    else
        q <= d;
end

Reset only takes effect on a clock edge. Simpler timing analysis; the common default in modern ASIC-oriented style guides.

Asynchronous reset

always @(posedge clk or posedge rst) begin
    if (rst)
        q <= 1'b0;
    else
        q <= d;
end

Reset forces q to 0 immediately, independent of the clock. Common in many intro courses and FPGA examples; note the extra or posedge rst in the sensitivity list.

Why <= exists at all

Here's the question worth sitting with: real hardware has no notion of "statement order" inside a clock edge. Every flip-flop in a register bank samples its D input at the exact same physical instant. If your RTL has to model "swap the contents of two registers on this clock edge" or "shift a value through a chain of flip-flops," you need each flip-flop's new value to depend on the old values of everything else — not on updates other statements in the same block already made. Blocking assignment (=) can't express that; non-blocking (<=) exists specifically to express it.

= blocking<= non-blocking
When the update happens Immediately — the very next statement sees the new value. Deferred to the end of the current simulation time step. Every non-blocking assignment scheduled in this time step reads its right-hand side from values as they were before any of them updated.
Mental model Ordinary software assignment. "Everyone reads the old values first; then everyone updates at once" — exactly how real flip-flops behave on a clock edge.
Use for Combinational always @(*) (Lesson 3). Sequential always @(posedge clk) (this lesson).

See it break: a register swap

Nothing makes this concrete like watching blocking assignment fail at something non-blocking handles correctly: swapping two registers on a clock edge.

Blocking — wrong
always @(posedge clk) begin
    a = b;
    b = a;
end
a = 1
b = 2

Initial values before the clock edge.

Non-blocking — correct
always @(posedge clk) begin
    a <= b;
    b <= a;
end
a = 1
b = 2

Initial values before the clock edge.

Click Step twice to walk through one clock edge. With blocking assignment, statement 2 reads the value statement 1 just wrote — so both registers end up holding b's original value; the swap is destroyed. With non-blocking, both right-hand sides are captured from the values that existed before the edge, so the update that lands is a genuine swap, matching what two real flip-flops sampling each other's old outputs would do.

The guideline, and where it comes from This isn't folklore — it's formalized in Clifford Cummings' widely-cited paper "Nonblocking Assignments in Verilog Synthesis, Coding Styles That Kill!" (linked in full in the references). The guidelines that matter for a crash course:
  1. Sequential logic (always @(posedge clk)) → use <=.
  2. Combinational logic (always @(*)) → use =.
  3. Never mix = and <= in the same always block.

The same collapsing failure shows up any time you model a chain of flip-flops with blocking assignment — for instance a 2-stage shift register written as q1 = d; q2 = q1; inside one clocked block. Because q1 is already updated by the time the second line runs, q2 ends up tracking d one cycle earlier than it should, silently collapsing two stages of delay into one. It's the identical bug as the swap above, just with different symptoms — which is why the fix is always the same guideline, not a special case.

Q: Why can't a chain of flip-flops (e.g. a shift register) be correctly modeled with blocking assignments in one clocked always block?
Because blocking assignments are illegal syntax inside always @(posedge clk). Because each statement would see values already updated by earlier statements in the same edge, instead of every register sampling the pre-edge values simultaneously like real hardware. Because blocking assignments make the simulator run slower.
Try it Take the dff module above and turn it into a 4-bit register: change d/q to [3:0], add a synchronous active-high reset that clears q to 4'b0000. Then write a 2-stage shift register for a single bit (d_in → q1 → q2, one clocked always block, non-blocking assignments) and trace by hand what q1 and q2 hold after each of the first three clock edges, starting from d_in = 1, 0, 1.

New terms — flip-flop, edge-triggered, blocking/non-blocking assignment, synchronous/asynchronous reset — are in the glossary.

Previous← Combinational Logic Contents⌂ All lessons