Thoughts on IC development, EDA, and hardware design languages.

SystemVerilog Insights: always @* and always_comb are not the same !!

Posted by: Trent McClements | Posted on: February 3rd, 2015 | 8 Comments

Share on LinkedIn0Share on Facebook0Tweet about this on Twitter0Share on Google+0Email this to someone

Always_comb is one of a number of new process types added to SystemVerilog to give developers more control and improve code readability. On the surface always_comb seems like a convenient short-hand for always @*, but, surprisingly, that’s not actually the case. always_comb is actually quite different than always @*, and you should be aware of how if you plan on using it!

Before we dig too deeply into the details there is one thing about always_comb that I would like to point out. Much to my dismay, always_comb does not prevent accidental latch inference (something I’m sure we’ve all done with the Verilog always procedure). The SystemVerilog LRM does not declare latch inference within always_comb procedures as illegal. All that is suggested, but not required, of your SystemVerilog parser is to warn the user in the case of latch inference within an always_comb procedure. It’s an interesting choice, especially given that SystemVerilog brought in the always_latch and always_ff procedures too!

With that detail out of the way, let’s take a look at the differences between always @* and always_comb. In generally, these fall into four areas — time, sensitivity, content, and exclusivity of variables.

When looking at the always_comb‘s execution time keep in mind the procedures intent — to model combinational logic. As such, the always_comb procedure, unlike always @*, executes at time zero. This makes sense as the outputs of a combinational logic model should have an initial value that depends on the values of the input signals. This is not possible with the always @* procedure as it’s outputs values are slave to the sensitivity list and thus require a delta on a sensitivity list signal to get updated.

Both always_comb and always @* have inferred sensitivity lists. The inference, however, is different between the two procedures. always_comb‘s sensitivity goes a little deeper than always @*. always_comb is sensitive to changes inside a function as well as expressions in immediate assertions within functions. always @* is not sensitive to either. Both procedures are sensitive to changes to the inputs of functions and to expressions in assertions used directly within the procedures. For example:
always_comb begin
comb_out <= comb_in0 && comb_func(comb_in1, comb_in2);
Assert00: assert (comb_in0 == comb_sensitive) else if (comb_ignored) $exit();
function comb_func;
input in0, in1;
Assert01: assert (in0 == func_sensitive) $exit();
return in0 && in1;

In the above code the always_comb procedure is sensitive to comb_in0, comb_in1, comb_in2, comb_sensitive and func_sensitive. The always_comb is not sensitive to comb_ignored because it is not in the immediate assertion statement but rather in the action block of the assertion. If the above always_comb procedure was changed to always @* then the procedure’s implicit sensitivity list would only include comb_in0, comb_in1, comb_in2 and comb_sensitive.

The SystemVerilog LRM restricts the type of statements allowed within an always_comb procedure. This also makes sense in the context of a model for combinational logic. Blocking statements, fork-join statements, and statements with blocking timing or event controls are forbidden within an always_comb procedure.

One very interesting aspect of always_comb is that the procedure owns the variables assigned with itself. That is, no other process is allowed to assign to a variable that an always_comb procedure assigns to. always @* has no such exclusivity. With that said, always_comb does not own assignment to the complete variable, only the portions it assigns to. So, the following is legal:
reg [1:0] a;
a[1] = 1'b0;
a[0] = 1'b1;

In the end, the always @* and always_comb are more like close siblings than identical twins. They both have similar abilities but are forced to live under close, but not identical, rules. It’s important to be aware of these differences, especially with respect to procedure sensitivity and execution time, as they can have surprising impacts on your simulations!

Have you had any experience with always_comb or perhaps useful “rule of thumb” guidelines on when to use always @* instead of always_comb (or vise-versa)? We’d love to hear about it in the comments!


Share on LinkedIn0Share on Facebook0Tweet about this on Twitter0Share on Google+0Email this to someone

Comments (8)

  1. Pingback: SystemVerilog always_latch and always_ff -- are they useful? - Invionics

  2. KM - Reply
    June 16, 2015

    For the Exclusivity,

    reg a;

    a = 1′b1;

    initial begin
    a = 1′b0;
    This above code give error.

    For the same functionality,

    reg a;


    initial begin

    function void (input bit x);
    a = x;
    This doesn’t give error. Any comment’s on this?

  3. Pingback: SystemVerilog always_comb, always_ff - Verilog Pro

  4. NA - Reply
    October 14, 2015

    Lots of wrong info in this article. Non-blocking statements are not allowed in always_comb:

    always_comb begin
    comb_out <= comb_in0 && comb_func(comb_in1, comb_in2);

    Please correct this code example. Always_comb is meant to have blocking statements.

  5. VERI - Reply
    October 19, 2015

    It is certainly usually recommended to only use blocking assignments with always_comb as it is used to infer combinational logic but it is legal System Verilog. The LRM section “ Combinational logic always_comb procedureā€ references the following examples, using both blocking and nonblocking assignments:

    a = b & c;
    d <= #1ns b & c;

    For the simple scenario referenced in this post, the use of a nonblocking assignment is not an issue but one should definitely proceed with caution when using nonblocking or a mix of blocking and nonblocking assignments in always_comb.

  6. WilliamKags - Reply
    June 19, 2016

    Major thankies for the forum. Great. Delo

  7. David Ellis - Reply
    October 22, 2016

    LRM states: Statements in an always_comb shall not include those that block, have blocking timing or event
    controls, or fork-join statements.

    blocking timing or event controls” …
    This would mean that #2 a = 1′b0; would be illegal. Forward progress does not continue until #2 later which is illegal in an always_comb block.

    “those that block”
    might include functions that block, …

    I think that means it has to “evaluate and schedule” in 0 time. The schedule can have delay, such as in the example above from the LRM (delaying 1 ns).

    The <= simply directs the change to occur during the NBA phase of the (possibly delayed time) cycle. Effectively hiding that transition from any other rhs in that always_comb block.

    It is also interesting that you can do:

    always_comb begin
    a = 1'b0;
    a = 1'b1;

    which results in BOTH assignments being done. Just one following the other (ordered).
    with the net effect of the result of the last one being the one at the end of the time slot.
    This is used a lot in conjunction with a for () loop…

    weirdness ensues when you mix the two however.

    always_comb begin
    a = 1'b0;
    a = 1'b1;
    a <= 1'bZ;
    a = 1'b0;

    The non-blocking assign would happen at the same time the final blocking assign was in the active event region, while the NBA of 'Z' would be in the NBA event region.
    AFTER the blocking '0 finishes, the NBA 'Z would move to the active event region and execute, leaving a as 'Z.

    Obviously, that is one reason never to mix the two, and why the common rule is to not ever use a non-blocking in an always_comb.
    There is 0 benefit.

  8. Annie - Reply
    February 18, 2018

    In the case of sensitivity, what is “comb_sensitive” and why can it only occur only in the assert line without any earlier declaration? How come both always @* and always_comb are sensitive to it?
    Many thanks.

Leave a Comment