Always block

Before we dive into always blocks, let's review basic digital logic design concepts. Understanding the distinction between combinational and sequential logic is fundamental to digital design and verification. It helps engineers decide which type of logic to use for a specific task or objective.

Combinatorial Logic: The output solely depends on the current input. There's no memory of past inputs. Basic gates like AND, OR, XOR are examples of combinational logic.

Sequential Logic: The output depends on both the current input and past inputs, thus having memory. Flip-flops, latches, and state machines, which can remember past states, use sequential logic.

always block

The always block is a fundamental construct in Verilog and SystemVerilog used to model both combinational and sequential digital circuits. It allows for a block of code to be executed repeatedly in response to changes in its sensitivity list.

Here's a simplified structure of an always block:

always @(sensitivity list)
begin
    // Code to be executed
end

The sensitivity list can include one or more signals or events that, when they change or occur, trigger the execution of the code within the block.

always_comb

SystemVerilog introduces always_comb and always_ff for better readability and less error-prone code compared to Verilog's always.

always_comb automatically includes all variables in the block in the sensitivity list and behaves like combinational logic. This means it reacts immediately when any input changes, just like combinational hardware circuits.

module tb;
    reg a, b;
    wire y;

    always_comb begin
        y = a & b;
    end
endmodule

In this example, y is updated as soon as a or b changes.

always_ff

always_ff, on the other hand, is used for describing sequential logic and expects an edge-triggered event in the sensitivity list. It is typically used with clock and reset signals.

module tb;
    reg clk;
    reg rst;
    reg data;

    always_ff @(posedge clk or negedge rst) begin
        if (!rst)
            data <= 0;
        else
            data <= data + 1;
    end
endmodule

Here, data gets updated on a rising edge of clk or a falling edge of rst.

Building on from the last tutorial, here is a structure of a module in SystemVerilog showing always block. We will expand on this diagram and add more blocks and containers as we move forward in this tutorial.

Types of Sensitivity Lists

  • Edge-Sensitive: Using posedge or negedge before a signal in the sensitivity list creates an edge-sensitive list, meaning the block will trigger on the rising or falling edge of that signal, respectively.

  • Level-Sensitive: Without an edge keyword, the always block will trigger on any change in the specified signal. This is generally not recommended in always_ff or always_comb blocks due to the potential for unclear triggering conditions.

Let's create an example that demonstrates all three types: always @(*) (level-sensitive), always @(posedge clk or negedge rst) (edge-sensitive), and always_comb.

module tb;
    reg clk, rst, a, b;
    reg [31:0] counter;
    logic out1, out2;

    // Level-sensitive block (used for combinational logic)
    always @(*) begin
        out1 = a ^ b;
    end

    // Edge-sensitive block (used for sequential logic)
    always @(posedge clk or negedge rst) begin
        if (rst == 1'b0)
            counter <= 0;
        else
            counter <= counter + 1;
    end

    // Combinational block (another way to model combinational logic)
    always_comb begin
        out2 = a | b;
    end
endmodule

Explanation:

  1. always @(*): This block is sensitive to any changes in a or b. If either of these signals change, out1 is recomputed. This is often used for modeling combinational logic.

  2. always @(posedge clk or negedge rst): This block is sensitive to the rising edge (posedge) of clk or the falling edge (negedge) of rst. If rst is low, counter is reset to zero. If there's a rising edge on clk (and rst is not low), counter increments by one. This is typically used to model sequential logic like flip-flops or registers.

  3. always_comb: This block, introduced in SystemVerilog, is intended for modeling combinational logic. It implicitly includes all variables in its body as part of the sensitivity list. Here, if a or b changes, out2 is recalculated as the OR of a and b.

Comparing always block with always_comb block

always block gives you control over the specific conditions that trigger the execution of its statements, which can be useful for creating more complex or specialized testing scenarios. It requires you to explicitly state the sensitivity list, providing you with an avenue to mimic specific hardware scenarios.

always_comb block automatically reacts to any changes in its inputs. This makes it ideal for verifying that a block of code behaves correctly in response to any change in its input signals. It reduces the risk of missing edge conditions in the sensitivity list, which can lead to untested scenarios in your testbench. This automatic sensitivity to changes in inputs helps to create robust, exhaustive test cases in your verification environment.

Pitfalls of using always blocks incorrectly

IMPORTANT: an always block is continuously active throughout simulation time. See below how always blocks and slow down or freeze your simulation.

An always block can potentially hang a simulation when it leads to a situation known as an infinite loop. This happens when the conditions of the always block continually satisfy the sensitivity list, causing the block to be executed indefinitely.

Here's an example:

module myModule;
 reg a;
 initial a = 0;
 always @(a) begin
   a = ~a;
 end
endmodule

In this case, the variable a is changed inside the always block, and because a is in the sensitivity list, this will cause the always block to be triggered again. Consequently, this creates a non-stopping cycle of execution causing an infinite loop, which results in the simulation hanging.

It's essential to be mindful of the logic inside the always block to prevent these scenarios. In real-world design and verification tasks, it's also a good practice to add timeout mechanisms to catch and handle such potential hang situations.

It's important to note that an always block is continuously active throughout simulation time. Its behavior is closely tied to the event-driven nature of hardware description languages, where specific changes or events can trigger certain actions. In this context, always blocks provide a way to describe how certain parts of a digital circuit should react to such changes or events.

Have a Question?

Feel free to ask your question in the comments below.

Please Login to ask a question.

Login Now