Logo

Verilog Procedural Blocks

07 Sep 2021
8 mins

Verilog is a concurrent programming language, which means that different blocks of the Verilog program run in parallel. The main reason for the concurrent nature is the fact that in hardware each block of the module will operate individually in parallel once it is powered on. The greater number of hardware block means more operation will happen in parallel and thus more processing power. Also, the hardware once turned no will go on operating until it is powered off. Thus, to model the hardware in Verilog, the Verilog program also runs indefinitely until the simulation is terminated. To achieve all these behaviours, Verilog provides with procedural blocks. Let’s look into the different procedural blocks.

Initial Block

This procedural block is executed only once when the Verilog simulation begins, i.e., at time step 0. As said earlier, hardware will go on operating unless the power is switched off, thus this procedural block cannot be realised into actual hardware, i.e., it is not synthesizable. This block is mainly used in test bench or to do some house keeping activities which would not directly influence the design.

initial keyword is used to declare an initial procedural block. If the block consists of more than one statement, the statements are enclosed within begin end statement. If more than one initial block is declared in a module, all the blocks will run concurrently, i.e., will run parallelly and all will start at the same time at 0-time step.

Example

In the below example, 3 initial blocks are declared. From the output we can see that all the blocks are running parallelly. In time step 0, all the blocks start executing, thus at 0-time step, the value of a = 5 as defined in block_1. Then after 5-time steps, the value of a is changed by block_2 and it becomes 9. Again, at t = 9, the value of a is changed by block_1. Also, when simulation started the monitor statement in block_3 also executed. $monitor prints the statement, whenever the value of the variable or net changes. Thus, writing it once prints the value of a 3 times. $monitor will be discussed in more details in later articles.

In below code, note that each block is assigned some name using :. This is called name blocks, and all the blocks can be assigned name if needed. This can be beneficial in some cases as it lets us disable the blocks.

module initial_block;
    reg [3:0] a;
    initial begin:block_1
        a = 4'd5;
        #9;
        a = 4'd2;
    end
    initial begin:block_2
        #5;
        a = 4'd9;
    end
    initial begin:block_3
        $monitor("At [%0t], a = %0d", $time, a);
    end
endmodule
Output
# At [0], a = 5
# At [5], a = 9
# At [9], a = 2

Always Block

This procedural block will execute at each time step or at some events like clock edge, change in some variable, net, etc. As this block goes on executing until the simulation is stopped, this can be realized in real hardware, i.e., they are synthesizable. This block generally represents a sequential circuit as sequential circuits also operate on some event like clock edge, etc.

always keyword is used to define an always procedural block. This is also followed by a begin and end statement if more than 1 statement are present in always block. To trigger the execution of always block on some event we use @() and inside parenthesis we write the name of variable or net upon which execution will depend. This can be a single identifier or a list of identifiers, also known as sensitivity list.

Example

In the following example, we can see that the always block goes on executing until the simulation is stopped using $finish function. Initial block is used in the code to initialize the variables with some initial value. Always block cannot be used to initialize initial values as always block will always execute the initialization and thus every time the initial value will be loaded. Play with the code in the EDA Playground and try running the code without the initial block.

In real hardware, initial block cannot exist and thus in that scenario, reset signal is used to initialize values to variable. Initial block is used here for demonstration only.

block_1 in the below code is an always block without a sensitivity list and thus will execute at every time step and will toggle the value of clk every 5-time steps.
block_2 is an always block which will trigger only at the positive edge of the clk, i.e., when clk changes its value from 0 to 1. Whenever the block is executed it is increasing the value of a by 1 as we can see in the output.

module always_block;
    reg [3:0] a;
    reg clk;

    initial begin
        a = 0;
        clk = 0;
        $monitor("At [%0t], a = %0d", $time,a);
        #50 $finish;
    end

    always begin:block_1
        #5 clk = ~clk;
    end

    always@(posedge clk) begin:block_2
        a = a+1;
    end
endmodule
Output
# At [0], a = 0
# At [5], a = 1
# At [15], a = 2
# At [25], a = 3
# At [35], a = 4
# At [45], a = 5
# ** Note: $finish    : proc_blocks.v(25)
#    Time: 50 ns  Iteration: 0  Instance: /always_block

Race around condition

As we have seen that different procedural blocks run parallelly, it may happen that the same variable is getting assigned with different value in 2 different blocks. In this condition, it is hard to exactly know which value will be assigned to the variable leading to an indeterminate state. This condition is known as race around condition.

To understand race around condition with respect to actual hardware through the example given below. This is a 2 port SRAM, which means 2 devices can access the same SRAM simultaneously. Now it can happen that both the devices are writing data on the location, or it may happen that one of the device it writing data on some location and the other device is reading from the same location.
In 1st case, the data which arrives later in the SRAM will be stored in that memory location. The arrival of the data cannot be guaranteed as the path delay depend on many factors. Thus, it leads to a state where the output is not determinate. This is known as Write-Write race around condition.

sram-race-around0x00x10x20x30x40x50x6

In 2nd case, data which is sent by the SRAM to the device, which is reading the data, will also depend upon the arrival of the data from the device which is writing it. If the write data arrives before the data is sent, then a new value will be sent to the reading device, otherwise old data will be sent. This is also leads to a state where the output from the SRAM to the reading device is not determinate. This is known as Write-Read race around condition.

sram-race-around0x00x10x20x30x40x50x6

Thus, we can see that the race around condition which happens in Verilog, is the representation of actual hardware. If you have not read our article on the Verilog program flow, do check it out. It will help understand the race around condition and how to tackle it.
Carrying forward the concept learned, let’s see some examples of race around condition in Verilog code. In the below example, the highlighted line will cause race around condition.

module race_condition;
    reg [3:0] a, b;
    reg clk;

    always@(posedge clk) begin
        a = 4'd5; 
    end

    always@(posedge clk) begin
        b = a; 
    end
endmodule

We will discuss ways to prevent the race around condition, in the next article, where will discuss various assignment operators in details.

Named Blocks and how to disable it

We have seen that the block can be named using : after the begin statement. This is known as named block or a labelled block. Naming a block is beneficial as it gives us the flexibility to disable a block. To disable a block we use disable keyword followed by the block name.
Syntax is disable block_name;

Example

In the below example, block_1 is disabled from the second initial block. It must be noted that in block_1 a delay is given to prevent race condition. Without delay it may happen that the display statement from block_1 executes before the second intial block disables it.

module disable_demo;
    initial begin: block_1
        #2;
        $display("Displaying from block 1");
    end

    initial begin
        $display("Disabling block 1");
        disable block_1; 
    end
endmodule
Output
# Disabling block 1

In Verilog, there is no break or continue statement, but that can be emulated in Verilog, by disabling the block.

Break

Break statement is a quite common keyword in C, which helps to come out of a loop when some condition is met. Let’s see how we can emulate break in Verilog. In below code, a counter is implemented which stops counting when its value becomes 4. An infinite loop is used inside a count block and when the condition is met, the code disables the count block inside which it is present. Thus, the program comes out of the loop and executes the remaining statements.

We can have multiple blocks inside the initial or always block as defined in below example. Begin and end can be compared to the {} we use in many programming language. It binds multiple statement in a single block. Thus, with initial or always block we use begin and end if there are multiple statements. But it doesn’t mean that we cannot declare multiple blocks inside it.

module break_demo;
    reg [3:0] a;
    initial begin
        a = 0;
        begin: count 
            forever 
            begin
                a = a + 1;
                $display("a = %0d", a);
                if (a > 3) 
                    disable count; 
            end
        end
        $display("Terminated Loop");
    end
endmodule
Output
# a = 1
# a = 2
# a = 3
# a = 4
# Terminated Loop
Try this code in EDA Playground

Continue

Continue statement is another common keyword in C. This is used to skip certain iterations of the loop when some condition is met. In simple words, whenever a condition is met it will not execute the remaining statements in the loop and go to next loop. Lets see how we can emulate continue in Verilog.
The code given below, prints the value of i if it is not divisible by 3. Whenever the condition is met it disables the count block. Thus, statements present after the disable statement will not execute and the program will move to the next iteration. In this way continue is emulated in Verilog.

module continue_demo;
    reg [3:0] i;
    initial begin
        i = 0;
        begin
            for(i=1; i<=10; i=i+1) 
            begin: count 
                if (i % 3 == 0) 
                    disable count; 
                $display("i = %0d", i); 
            end
        end
        $display("Terminated Loop");
    end
endmodule
Output
# i = 1
# i = 2
# i = 4
# i = 5
# i = 7
# i = 8
# i = 10
# Terminated Loop
Try this code in EDA Playground

The difference in implementation of break and continue is that for break, block within which the loop is declared is disabled and for continue the block of loop statement is disabled.