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

Blocking Assignments

= looks like the assignment in C and behaves like it on the surface: evaluate the right side, store it on the left, carry on. The resemblance is exactly close enough to be dangerous. This article is about what = does inside an always block, where it is the right tool, and where it is not.

What blocks

The word refers to the flow inside one block:

always @(*) begin
    temp = a & b;       // line 1
    y    = temp | c;    // line 2
end

Line 1 finishes, and temp has its new value, before line 2 starts, so line 2 reads the value line 1 just wrote. Each assignment blocks the next line until it is done. Within one block, statements run top to bottom and each sees the effect of the ones above it.

That is procedural, and for combinational logic it is useful. You can name intermediate results and build an expression in steps:

always @(*) begin
    c1 = a[0] & b[0];
    c2 = (a[1] & b[1]) | (a[1] & c1) | (b[1] & c1);

    sum[0] = a[0] ^ b[0];
    sum[1] = a[1] ^ b[1] ^ c1;
    sum[2] = a[2] ^ b[2] ^ c2;
end

Sequential text, combinational circuit

The block above reads like steps. What the synthesiser takes from it is a function: y equals (a & b) | c. The temp does not exist in the netlist. It is substituted away,

$$y = temp \mid c, \quad temp = a ,&, b \quad \Rightarrow \quad y = (a ,&, b) \mid c$$

and the result is two gates. The sequential reading is a convenience for the writer; the hardware is a combinational function of the inputs, with no memory in it.

A mux, done right

`timescale 1ns / 1ps
`default_nettype none

module mux4to1 #(
    parameter WIDTH = 8
) (
    input  wire [WIDTH-1:0] d0, d1, d2, d3,
    input  wire [1:0]       sel,
    output reg  [WIDTH-1:0] y
);
    always @(*) begin
        case (sel)
            2'b00:   y = d0;
            2'b01:   y = d1;
            2'b10:   y = d2;
            2'b11:   y = d3;
            default: y = {WIDTH{1'bx}};   // cannot happen; keeps the case full
        endcase
    end

endmodule

Every assignment is =, the block runs whenever any input changes, and the result is a mux tree with no state. The default matters, and section 5.3 says why: a case that leaves y unassigned for some value of sel becomes a latch.

A priority encoder

An encoder that reports the index of the highest active request is the natural use of the top-to-bottom order:

`timescale 1ns / 1ps
`default_nettype none

module priority_encoder #(
    parameter N = 8
) (
    input  wire [N-1:0]         req,
    output reg  [$clog2(N)-1:0] grant,
    output reg                  valid
);
    integer i;

    always @(*) begin
        grant = {$clog2(N){1'b0}};      // defaults: no request
        valid = 1'b0;

        for (i = 0; i < N; i = i + 1) begin
            if (req[i]) begin
                grant = i[$clog2(N)-1:0];   // a later hit overwrites an earlier one
                valid = 1'b1;
            end
        end
    end

endmodule

The loop runs from 0 upwards and each active request overwrites grant, so the last one found, the highest index, wins. Run the loop downwards and the lowest wins. The order of the statements is the priority, and the hardware is a chain of muxes in which the later ones override the earlier. The two defaults at the top guarantee that every output is assigned on every path, which is the pattern that keeps latches out of combinational blocks.

Where it goes wrong

always @(posedge clk) begin
    q = d;
end

Alone, with nothing else touching q, this simulates correctly. Add a second block that reads q:

always @(posedge clk) begin
    q = d;
end

always @(posedge clk) begin
    r = q + 1;
end

Both fire on the same edge. If the first runs first, r gets d + 1. If the second runs first, r gets the old q plus one. The order is the simulator’s choice, can differ between tools and between runs of the same tool, and the failures are intermittent. This is the race from the first article, and the tool that removes it is the non-blocking assignment of the next.

Put mathematically: a combinational block describes $y = f(a, b, c, \ldots)$, a function of the present inputs. A clocked block describes $q[n] = f(q[n-1], d[n])$, a state update from one cycle to the next. Blocking assignment gives the first its sequential convenience. Non-blocking assignment gives the second the “read old, then update all” behaviour that a flip-flop has.

A testbench for the encoder

`timescale 1ns / 1ps
`default_nettype none

module tb_priority_encoder;

    parameter N = 8;

    reg  [N-1:0]         req;
    wire [$clog2(N)-1:0] grant;
    wire                 valid;

    priority_encoder #(.N(N)) uut (.req(req), .grant(grant), .valid(valid));

    initial begin
        $dumpfile("tb.vcd");
        $dumpvars(0, tb_priority_encoder);

        req = 8'b0000_0000; #10; $display("req=%b grant=%0d valid=%b", req, grant, valid);
        req = 8'b0000_0001; #10; $display("req=%b grant=%0d valid=%b", req, grant, valid);
        req = 8'b0010_0000; #10; $display("req=%b grant=%0d valid=%b", req, grant, valid);
        req = 8'b0001_0110; #10; $display("req=%b grant=%0d valid=%b", req, grant, valid);   // 1, 2, 4: expect 4
        req = 8'b1111_1111; #10; $display("req=%b grant=%0d valid=%b", req, grant, valid);   // expect 7

        $finish;
    end

endmodule
req=00000000 grant=0 valid=0
req=00000001 grant=0 valid=1
req=00100000 grant=5 valid=1
req=00010110 grant=4 valid=1
req=11111111 grant=7 valid=1

Practice Exercise

For each block, is the blocking assignment right, wrong, or a race?

// A
always @(*) begin
    t1 = x ^ y;
    t2 = x & y;
    z  = t1 | t2;
end

// B
always @(posedge clk) begin
    if (en)
        q = d;
end

// C
always @(posedge clk) begin
    stage1 = data_in;
    stage2 = stage1;
    stage3 = stage2;
end

// D
always @(*) begin
    if (sel)
        out = in_a;
end
Solution

A is right: a combinational block building z = (x ^ y) | (x & y) through two named intermediates; the synthesiser reduces it to x | y.

B is wrong: = in a clocked block. It works alone and races as soon as another block reads q. Use <=.

C is wrong in a way that shows up immediately. Each line reads the value the previous line just wrote, so all three stages take data_in in the same cycle and the pipeline is three flip-flops with no delay between them. With <= each stage takes the old value of the one before it.

D is a latch: when sel is 0, out is not assigned and has to hold. Give it a default at the top of the block.

The next article is the non-blocking assignment, and the two-phase time step that makes it work.

โ† always and initial Non-Blocking Assignments โ†’