Module 5: Behavioral Verilog for Synthesis ยท 5.4 Loops in Verilog

while, repeat and forever

Verilog has four loops. for with constant bounds is the one for RTL. The other three, repeat, while and forever, live almost entirely in testbenches, and the reason is the one from the last article: a synthesiser has to know how many copies of the body to build before anything runs, and a loop whose end depends on data cannot tell it.

Loop Synthesises Ends For
for yes, with constant bounds at a constant replicated hardware
repeat tool-dependent after a fixed count testbench cycle counts
while rarely on a condition testbench waits
forever never never clocks and monitors

repeat

repeat (n) runs its body a fixed number of times, with no index:

repeat (10) begin
    @(posedge clk);
end

Some synthesisers accept a constant repeat as an unrolled loop and some reject it, so in a design file write the for. In a testbench it is the clearest way to say “wait this many cycles”:

initial begin
    rst_n = 1'b0;
    repeat (5) @(posedge clk);      // five cycles of reset
    rst_n = 1'b1;

    repeat (100) @(posedge clk);
    $display("done after 105 cycles");
    $finish;
end

Having no index means you cannot compute anything from the iteration number; if you need stimulus[i], use a for.

while

while (condition) is the most general loop and the one to use least in RTL. A tool could in principle unroll a while it can prove terminates after a constant number of iterations, and a few accept the obvious cases, but the same loop written as a for is unambiguous and universally supported, so write that. Where while belongs is a testbench waiting for the design:

initial begin
    // ... stimulus ...
    while (!done) begin
        @(posedge clk);
    end
    if (result !== expected)
        $display("FAIL: result=%0d expected=%0d", result, expected);
end

If you catch yourself writing while or forever in a design file, stop. What you are trying to express is a process that takes several cycles, and that is a counter or a state machine, written with registers.

forever

forever never ends and has no hardware meaning at all. Its job is the clock:

`timescale 1ns / 1ps
`default_nettype none

module tb_my_design;

    reg clk;
    reg rst_n;

    wire [7:0] result;
    wire       valid;

    my_design u_dut (.clk(clk), .rst_n(rst_n), .result(result), .valid(valid));

    initial begin
        $dumpfile("tb_my_design.vcd");
        $dumpvars(0, tb_my_design);
    end

    initial begin
        clk = 1'b0;
        forever #5 clk = ~clk;      // 10 ns period
    end

    initial begin
        rst_n = 1'b0;
        repeat (3) @(posedge clk);
        rst_n = 1'b1;
        // ... stimulus ...
        #500 $finish;
    end

endmodule

always #5 clk = ~clk; with a separate initial clk = 0; does the same and is the more common spelling; forever inside an initial is handy when the clock needs a starting delay. The other use is a watchdog, so that a hung simulation stops instead of running until the disk fills:

initial begin
    #100_000;
    $display("WATCHDOG: time limit exceeded, possible hang");
    $finish;
end

Practice Exercise

For each construct: does it synthesise, and if so to what; if not, where does it belong?

// 1
integer i;
always @(*) begin
    out = 8'h00;
    for (i = 0; i < 8; i = i + 1)
        out[i] = in_a[i] & in_b[i];
end

// 2
initial begin
    forever @(posedge clk)
        $display("clk rose at %0t", $time);
end

// 3
integer k;
always @(*) begin
    found = 1'b0;
    for (k = 7; k >= 0; k = k - 1) begin
        if (data[k] && !found) begin
            msb_pos = k[2:0];
            found   = 1'b1;
        end
    end
end

// 4
while (!rx_done) @(posedge clk);

// 5
integer m;
always @(*) begin
    result = 16'h0000;
    for (m = 0; m < WIDTH; m = m + 1)
        result = result + {{15{1'b0}}, data[m]};
end
Solution

1 synthesises: eight AND gates in parallel, the same as out = in_a & in_b. 2 does not: a monitor printing on every edge, testbench only. 3 synthesises: a priority encoder for the highest set bit, unrolled to eight conditional steps, the found flag giving the priority; also leaves msb_pos unassigned when nothing is set, so it wants a default. 4 does not: a testbench wait on a runtime signal. 5 synthesises if WIDTH is a parameter, to a popcount adder tree; if WIDTH is a port, the bound is not constant and synthesis fails.

Design files get for with constant bounds. Testbenches get the rest. The next article uses for to build four bit-manipulation circuits that scale with a parameter.

โ† for Loops and Synthesis Loops as Hardware Generators โ†’