Module 5: Behavioral Verilog for Synthesis ยท 5.1 Procedural Blocks: always and initial

Non-Blocking Assignments

The last article ended on a race: two clocked blocks sharing a signal, with blocking assignments, and a result that depends on which block the simulator runs first. The non-blocking assignment, <=, is the fix, and it is not a convention; it is a mechanism designed to make sequential logic simulate correctly whatever order the blocks run in.

Two phases

When a clock edge fires, every block sensitive to it becomes active and the simulator runs them one at a time in some order. With <= the time step has two parts.

In the first, each block runs. At q <= d, the right-hand side is evaluated now and the value saved, and the update of q is put on a list for later. The block carries on; q has not changed.

In the second, after every active block has finished, all the saved updates are applied together.

So during the first phase every block reads every signal as it was at the start of the step, and no block sees any other block’s <= until the second phase, when they all land at once. The order in the first phase no longer matters.

The flip-flop

`timescale 1ns / 1ps
`default_nettype none

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

endmodule

At the edge, d is read, its value saved, the update scheduled; after all blocks have run, q takes it. q gets the value d had at the edge, and if some other block changes d in the same step with a <=, q does not see that. That is what a flip-flop does: it samples at the edge.

The swap

Exchange two registers in one cycle. With blocking assignments:

// wrong
always @(posedge clk) begin
    a = b;
    b = a;     // reads the a that was just overwritten with b
end

After the first line a holds the old b; the second line copies that back into b, and both hold the old b. The software fix is a temporary. With non-blocking assignments no temporary is needed:

always @(posedge clk) begin
    a <= b;    // save old b for a
    b <= a;    // save old a for b; a has not changed yet
end
phase 1, line 1 read b, schedule a := old b
phase 1, line 2 read a, still the old a, schedule b := old a
phase 2 apply both

They swap. It works whether the two lines are in one block or in two, because the second phase collects updates from every block. It is the two cross-connected flip-flops of section 4.1, and the simulator now behaves like them.

A pipeline

`timescale 1ns / 1ps
`default_nettype none

module pipeline_3stage #(
    parameter WIDTH = 8
) (
    input  wire             clk,
    input  wire             rst,
    input  wire [WIDTH-1:0] data_in,
    output wire [WIDTH-1:0] stage3_out
);
    reg [WIDTH-1:0] stage1, stage2, stage3;

    always @(posedge clk) begin
        if (rst) begin
            stage1 <= {WIDTH{1'b0}};
            stage2 <= {WIDTH{1'b0}};
            stage3 <= {WIDTH{1'b0}};
        end else begin
            stage1 <= data_in;
            stage2 <= stage1;      // the old stage1
            stage3 <= stage2;      // the old stage2
        end
    end

    assign stage3_out = stage3;

endmodule

Feed it A then B. At the first edge, phase 1 reads data_in as A, stage1 as its old value, stage2 as its old value; phase 2 puts A in stage1. At the second edge, phase 1 reads B, and reads stage1 as A, because the first edge’s update has long since landed; phase 2 puts B in stage1 and A in stage2. After the third edge A is in stage3. Three stages, three cycles (Figure 1).

Two clock cycles, two phases each
Figure 1: Reads in the first phase see old values; updates land in the second

With blocking assignments the same three lines collapse: stage2 = stage1 reads the A just written, and so does stage3, and all three hold A after one edge. Three flip-flops in the netlist, no pipeline in the simulation, and a gate-level simulation that disagrees with the RTL one.

The rules, exactly

Use <= for every assignment in a clocked block. Use = for every assignment in a combinational block. Never mix them in one block. The language allows mixing, and the result is an ordering that depends on where each statement sits, that experienced people cannot reason about and that some tools synthesise wrongly without a warning. A count = count + 1 among the <= lines of a clocked block updates count in the first phase, and any <= after it that reads count sees the new value, which breaks the whole model.

The templates

With synchronous reset:

`timescale 1ns / 1ps
`default_nettype none

module dff_sync_reset #(
    parameter WIDTH = 1
) (
    input  wire             clk,
    input  wire             rst,        // synchronous, active high
    input  wire [WIDTH-1:0] d,
    output reg  [WIDTH-1:0] q
);
    always @(posedge clk) begin
        if (rst)
            q <= {WIDTH{1'b0}};
        else
            q <= d;
    end

endmodule

With asynchronous reset:

`timescale 1ns / 1ps
`default_nettype none

module dff_async_reset #(
    parameter WIDTH = 1
) (
    input  wire             clk,
    input  wire             rst_n,      // asynchronous, active low
    input  wire [WIDTH-1:0] d,
    output reg  [WIDTH-1:0] q
);
    always @(posedge clk or negedge rst_n) begin
        if (!rst_n)
            q <= {WIDTH{1'b0}};
        else
            q <= d;
    end

endmodule

The reset in the second sensitivity list, on its falling edge, is what makes it asynchronous, and the synthesiser maps it to the clear pin of the library flip-flop.

Practice Exercise

// set A: two blocks on one clock
always @(posedge clk) begin
    a <= b;
end
always @(posedge clk) begin
    b <= a;
end

// set B: one block
always @(posedge clk) begin
    b <= d_in;
    c <= b;
end

For set A starting from a = 3, b = 7, what are they after one edge and after two? For set B starting from b = 0, c = 0, d_in = 5, what after one edge and after two?

Solution

Set A. Edge 1: phase 1 saves b = 7 for a and a = 3 for b; phase 2 applies: a = 7, b = 3. Edge 2: the same the other way: a = 3, b = 7. Two cross-connected flip-flops swap every cycle.

Set B. Edge 1: phase 1 saves d_in = 5 for b and the old b = 0 for c; phase 2: b = 5, c = 0. Edge 2: saves 5 for b and the now-current b = 5 for c: b = 5, c = 5. A two-stage pipeline: c holds what d_in held two edges ago.

The two phases are the working model, and they are all you need day to day. The last article of the section goes one level down, to the scheduler the standard actually defines, delta cycles, and why $display sometimes shows you the wrong value.

โ† Blocking Assignments The Scheduler โ†’