SystemVerilog always_comb, always_ff. New and Improved.

Verilog engineers will be familiar with using Verilog always to code recurring procedures like sequential logic (if not, refer to my article Verilog Always Block for RTL Modeling), and most will have used always @(*) to code combinational logic. SystemVerilog defines four forms of always procedures: always, always_comb, always_ff, always_latch. What do the three new always procedures bring, and should you be using them? This article will attempt to answer these questions.

Verilog always @*

Verilog 2001 first introduced always @* as a shortcut to code combinational logic. The always @* is intended to infer a complete sensitivity list for both RTL simulation and synthesis from the contents of the block, saving a designer from having to manually specify a sensitivity list to code combinational logic. However, it does not infer a complete sensitivity list when the always @* block contains functions. Namely, it will not infer sensitivity to signals that are externally referenced in a function or a task that is called from the always block. It will only be sensitive to the signals passed into the function or task. Here is an example that illustrates the behaviour:

module test;
  logic a, b, c, always_d, always_comb_d;

  function logic my_func(input logic m_c);
    my_func = a | b | m_c;
  endfunction

  always @*
    always_d = my_func(c);

  always_comb
    always_comb_d = my_func(c);

  initial begin
    $monitor("@%0t: a = %d, b = %d, c = %d, always_d = %d, always_com_d = %d", $time, a, b, c, always_d, always_comb_d);
  end
  
  initial begin
    a = 0;
    b = 0;
    c = 0;
    #10 a = 1;
    #10 b = 1;
    #10 c = 1;
  end
endmodule

When the code is executed, it gives the following output. Notice that change on a, b does not trigger the always @* block to be reevaluated, but change on c does.

@0: a = 0, b = 0, c = 0, always_d = 0, always_com_d = 0
@10: a = 1, b = 0, c = 0, always_d = 0, always_com_d = 1
@20: a = 1, b = 1, c = 0, always_d = 0, always_com_d = 1
@30: a = 1, b = 1, c = 1, always_d = 1, always_com_d = 1

SystemVerilog always_comb

SystemVerilog always_comb solves this limitation. It improves upon always @* in some other ways as well:

  • always_comb automatically executes once at time zero, whereas always @* waits until a change occurs on a signal in the inferred sensitivity list.
  • always_comb is sensitive to changes within the contents of a function, whereas always @* is only sensitive to changes to the arguments of a function. (However, it doesn’t infer sensitivity from tasks. If a task takes zero time, you can use a void function instead to infer sensitivity.)
  • Variables on the left-hand side of assignments within an always_comb procedure, including variables from the contents of a called function, cannot be written to by any other processes, whereas always @* permits multiple processes to write to the same variable.
  • Statements in an always_comb cannot include those that block, have blocking timing or event controls, or fork-join statements.
  • always_comb is sensitive to expressions in immediate assertions within the procedure and within the contents of a function called in the procedure, whereas always @* is sensitive to expressions in immediate assertions within the procedure only.

In short, SystemVerilog always_comb is a better version of always @* and should always (pun intended) be used.

Update: always_comb coding style to watch out for

Despite the improvements of always_comb, there is a coding style that you need to watch out for in your code. Consider the following code examples:

// Example 1
always_comb begin
   b = a;
   c = b; // c = a
end
// Example 2
always_comb begin
   c = b; // starting value of c = previous value of a
   b = a; // changing b does not re-trigger the always_comb in this time step
end

Conventional wisdom would be that both code examples should behave and execute the same way. However, that is not always the case! There is a clause in the SystemVerilog language manual that defines the implicit sensitivity list of an always_comb block. It states that any expression that is written within the always_comb block or within any function called within the always_comb block is excluded from the implicit sensitivity list. Therefore, the specification compliant behaviour should be that c = a in example 1, but c is assigned the previous value of a in example 2!

The specification compliant behaviour isn’t what most people would expect. Different tools also behave differently when they encounter this coding style. Therefore, the best solution is to avoid this kind of coding in your always_comb block altogether!

SystemVerilog always_ff

SystemVerilog always_ff procedure is used to model sequential flip-flop logic. It has similar improvements compared to using plain Verilog always. A always_ff procedure adds a restriction that it can contain one and only one event control and no blocking timing controls. Variables written on the left-hand side of assignments within always_ff, including variables from contents of a called function, cannot be written by other processes. Also, it is recommended that software tools perform additional checks to ensure code within the procedure models flip-flop behaviour. However, these checks are not defined in the SystemVerilog LRM.

SystemVerilog always_latch

Finally, SystemVerilog always_latch is used to model latch logic. It has identical rules to always_comb, and the SystemVerilog LRM recommends software tools perform additional checks to ensure code within the procedure models latch behaviour.

The three new SystemVerilog always procedures bring some enhanced capabilities. SystemVerilog always_comb, in particular, improves upon the Verilog always @* in several positive ways, and is undoubtedly the most useful of the three. I would recommend using all three in all newly written SystemVerilog code, if not for their new features, at least to convey design intent.

Do you have other experiences or gotchas with using (or not using) the new SystemVerilog always procedures? Leave a comment below!

References

8 thoughts on “SystemVerilog always_comb, always_ff. New and Improved.”

  1. In “Update: always_comb coding style to watch out for”, the Example 2 breaks the rule saying the lhs cannot be written to by other processes (ie. other always_comb).
    Assigning “c” in two different always_comb is wrong, shouldn’t be used and 99% sure is forbidden by Modelsim/Questa/Vivado/Quartus and more.

    Reply
    • Hi Alexis. Yes you’re absolutely correct. I was trying to illustrate the difference between two different code snippets. I was intending the two snippets to be considered separately, not together as one module/file. I’ve tried to make that more clear by separating the text into two separate snippets.

      Reply

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.