( ESNUG 466 Item 5 ) -------------------------------------------- [07/12/07]

Subject: Operator gotchas

5.1 Self-determined operations versus context-determined operations

     Gotcha: Misunderstanding operator rules can lead to unexpected
             simulation results.

What should happen if a 4-bit vector is ANDed with a 6-bit vector, and
result is assigned to an 8-bit vector? Are the results be different if one
or both of the AND operands are signed or unsigned?  Does the result change
if the vector to which the operation is assigned is signed or unsigned?

Verilog and System Verilog are "loosely typed" languages.  Loosely typed
does not mean there are no data type rules.  Rather, loosely typed
means that the language has built-in rules for performing operations
on various data types, and for assigning one data type to another data
type.  The most subtle of these rules is whether an operator is
"self-determined" or "context-determined".  If an engineer does not
understand the difference between these two operation types, he or she
may find the result of the operation to be different than expected.
GOTCHA!  (Self-determined versus context-determined operations also
affect the gotchas described in Secs 5.2, 5.3 and 5.4, which
follow.)

A context-determined operator looks at the size and data types of the
complete statement before performing its operation.  All operands in
the statement are expanded to the largest vector size of any operand
before the operations are performed.  Consider the following example:

   logic [5:0] a = 6'b010101;   // 6-bit vector
   logic [3:0] b = 4'b1111;     // 4-bit vector
   logic [7:0] c;               // 8-bit vector

   c = a & b;                   // results in 8-bit 00000101

In this example, the context of the bitwise AND operation includes the
vector sizes of a, b and c. The largest vector size is 8 bits.  Therefore,
before doing the operation, the 4-bit vector and the 6-bit vector are
expanded to 8 bit vectors, as follows:

In context, the operation is:

                       a:      010101 (6-bits)
                      &b:  &     1111 (4-bits)
                           ------------------
                       c:    ???????? (8-bits)

After expansion, the operation is:

                       a:    00010101 (8-bits)
                      &b:  & 00001111 (8-bits)
                           ------------------
                       c:    00000101 (8-bits)

Why were a and b left-extended with zeros?  That question is answered in
Sec 5.2, which discusses zero-extension and sign-extension in Verilog.

A self-determined operator is only affected by the data types of its
operands.  The context in which the operation is performed does not affect
the operation.  For example, a unary AND operation will AND all the bits
of its operand together without changing the size of the operand.

   logic [5:0] a = 6'b010101;   // 6-bit vector
   logic [3:0] b = 4'b1111;     // 4-bit vector
   logic [7:0] c;               // 8-bit vector

   c = a & &b;                  // results in 8-bit 00000001

In this example, the unary AND of b is self-determined.  The vector sizes
of a and c have no bearing on the unary AND of b.  The result of ANDing
the bits of 4'b1111 together is a 1'b1.

If the self-determined operation is part of a compound expression, as in
the example above, then the result of the self-determined operation becomes
part of the context for the rest of the statement. Thus:
 
In context, the operation is:

                       a:      010101 (6-bits)
                      &b:  &        1 (1-bit result of &b)
                           ------------------
                       c:    ???????? (8-bits)

After expansion, the operation is:

                       a:    00010101 (8-bits)
                      &b:  & 00000001 (8-bits)
                           ------------------
                       c:    00000001 (8-bits)

What if &b had been context determined?  In context, b would first be
expanded to 8 bits wide, becoming 00001111. The unary AND of this value is
1'b0, instead of 1'b1.  The result of a & &b would be 00000000, which would
be the wrong answer.  But this is not a gotcha, because the unary AND
operation is self-determined, and therefore gets the right answer.

How to avoid this gotcha: Verilog generally does the right thing.  Verilog's
rules of self-determined and context-determined operations behave the way
hardware behaves (at least most of the time).  The gotcha is in not
understanding how Verilog and System Verilog operators are evaluated, and
therefore expecting a different result.  The only way to avoid the gotcha is
proper education on Verilog and System Verilog. Table 1, below, should help.
This table lists the Verilog and System Verilog operators, and whether they
are self-determined or context-determined.

  Table 1: Determination of Operand Size and Sign Extension1

  ==========================|===============|===============================
  |                         |    Operand    |                              |
  |        Operator         |   Extension   |               Notes          |
  |                         | Determined By |                              |
  |=========================|===============|==============================|
  | Assignment statements   |    context    | Both sides of assignment     |
  | = <=                    |               | affect size extension. Only  |
  |                         |               | right-hand side affects      |
  |                         |               | sign extension2.             |
  |-------------------------|---------------|------------------------------|
  | Assignment operations   |    context    | Both sides of assignment     |
  | += -= *= /= %= &= |= ^= |               | affect size extension. Left  |
  |                         |               | operand is part of right-    |
  |                         |               | hand side assignment context |
  |                         |               | (e.g. a += b expands to      |
  |                         |               | a = a + b).                  |
  |-------------------------|---------------|------------------------------|
  | Assignment operations   |   see notes   | Left operand is context-     |
  | <<= >>= <<<= >>>=       |               | determined. Right operand is |
  |                         |               | self-determined. Left operand|
  |                         |               | is part of the right-hand    |
  |                         |               | side assignment context.     |
  |                         |               | (e.g.a <<= b expands to      |
  |                         |               | a = a << b)                  |
  |-------------------------|---------------|------------------------------|
  | Conditional             |   see notes   | First operand (the condition)|
  | ?:                      |               | is self determined. Second   |
  |                         |               | and third operands are       |
  |                         |               | context determined.          |
  |-------------------------|---------------|------------------------------|
  | Arithmetic              |    context    |                              |
  | + - * / %               |               |                              |
  |-------------------------|---------------|------------------------------|
  | Arithmetic Power        |    see notes  | Left operand (base) is       |
  | **                      |               | context-determined. Right    |
  |                         |               | operand (exponent) is self-  |
  |                         |               | determined.                  |
  |-------------------------|---------------|------------------------------|
  | Increment and Decrement |     self      |                              |
  | ++ --                   |               |                              |
  |-------------------------|---------------|------------------------------|
  | Unary Reduction         |     self      | Result is a self-determined, |
  | ~ & ~& | ~| ^ ~^ ^~     |               | unsigned, 1-bit value.       |
  |-------------------------|---------------|------------------------------|
  | Bitwise                 |    context    |                              |
  | ~ & | ^ ~^ ^~           |               |                              |
  |-------------------------|---------------|------------------------------|
  | Shift                   |   see notes   | Left operand is context-     |
  | << <<< >> >>>           |               | determined. Right operand    |
  |                         |               | (shift factor) is self-      |
  |                                         | determined.                  |
  |-------------------------|---------------|------------------------------|
  | Unary Logical           |     self      | Result is a self-determined, |
  | !                       |               | unsigned, 1-bit value.       |
  |-------------------------|---------------|------------------------------|
  | Binary Logical          |     self      | Result is a self-determined, |
  | && ||                   |               | unsigned, 1-bit value.       |
  |-------------------------|---------------|------------------------------|
  | Equality                |    context    | Result is a self-determined, |
  | == != === !== ==? !=?   |               | unsigned, 1-bit value.       |
  |-------------------------|---------------|------------------------------|
  | Relational              |    context    | Result is a self-determined, |
  | < <= > >=               |               | unsigned, 1-bit value.       |
  |-------------------------|---------------|------------------------------|
  | Concatenation           |     self      | Result is unsigned.          |
  | {} {{}}                 |               |                              |
  |-------------------------|---------------|------------------------------|
  | Bit and Part Select     |     self      | Result is unsigned.          |
  | [ ] [ : ] [ +: ] [ -: ] |               |                              |
  ==========================|===============|===============================

This table only reflects operations where the operands are vectors.  There
are also rules for when operands are real (floating point) numbers, unpacked
structures, and unpacked arrays, which are not covered in this paper.

An assignment in an expression can be on the right-hand side of another
assignment (e.g. d = (a = b + 5) + c;).  In this case, the left-hand side
expression of the assignment-in-an-expression is part of the context of the
right-hand side of the assignment statement (i.e. a in the example does not
affect the sign context of b + 5, but does affect the sign context of
the + c operation).

Additional note: If a context-determined operation is an operand to a
self-determined operation, the context of the context-determined operation
is limited to its operands, instead of the full statement.  E.g., in
d = a >> (b + c);, the context of the ADD operation is only b and c.


5.2 Operation size and sign extension in assignment statements

    Gotcha: In an assignment statement, size extension context is
            dependent on both sides of the assignment, whereas sign
            extension context is only dependent on one side of the
            assignment.

Operation sign extension is controlled by the operands of the operator, and
possibly the context in which the operation is performed.  A self-determined
operator is only affected by the data types of its operands.  A context-
determined operator is affected by the size and data types of all operands
in the full expression.  Table 1 in Sec 5.1 lists which operators are self-
determined and which are context-determined.

Before a context-determined operation is evaluated, its operands are first
expanded to the largest vector width in the operation context.  There are 3
steps involved in this operand expansion, and these steps use different
context rules!

  Step 1. Evaluate the size and sign that will result from all
          self-determined operations on the right-hand and left-hand
          side of the assignment.  This information will be used in
          the subsequent steps.

  Step 2. Determine the largest vector size in the context.  The context
          is the largest vector on both the right-hand and left-hand
          side of assignment statements.

  Step 3. Expand all context-determined operands to the largest vector
          size by left-extending each operand.  The expansion will either
          zero-extend or sign-extend, based on the operation context, as
          follows:

          - If any operand or self-determined operation result on the
            right-hand side of the assignment is unsigned, then all
            operands and self-determined operation results on the
            right-hand side are treated as unsigned, and the smaller
            vectors are left-extended with zeros.

          - If all operands and self-determined operation results on the
            right-hand side of the assignment are signed, then all
            operands and self-determined operation results on the
            right-hand side are left-extended using sign extension.

Note the difference is steps 2 and 3!  The context for largest vector size
is both sides of an assignment statement, whereas the context for sign
extension is just the right-hand side of the assignment containing the
operation.

Verilog's rules for operand expansion map to how hardware works.  The
following examples illustrate cases Verilog's rules work as one would
expect (no gotchas).

   logic        [3:0] u1, u2;    // unsigned 4-bit vectors
   logic signed [3:0] s1, s2;    // signed 4-bit vectors

   logic        [7:0] u3;        // unsigned 8-bit vector
   logic signed [7:0] s3;        // signed 8-bit vector
   logic              o;         // unsigned 1-bit vector

   u3 = u1 + u2;   // zero extension (unsigned = unsigned + unsigned)

   s3 = s1 + s2;   // sign extension (signed = signed + signed)

   s3 = s1 + 1;    // sign extension (signed = signed + signed)

   s3++;           // sign extension (expands to s3 = s3 + 1, which is
                   // signed = signed + signed)

   u3 += 2'b11;    // zero extension (expands to u3 = u3 + 2'b11, which
                   // is unsigned = unsigned + unsigned)

   s3 += 2'sb11;   // sign extension (expands to s3 = s3 + 2'sb11, which
                   // is signed = signed + signed)

A gotcha can occur is when an engineer forgets Verilog's rules for operator
expansion rules.  The following examples show a few circumstances where an
engineer might see different results than expected, if the rules for zero
extension versus sign extension are not well understood.  These examples use
the same declarations as the examples above.

   s3 = u1 + u2;  // GOTCHA? zero extension, even though S3 is signed type
                  // Rule: signed left-hand side does affect sign extension
                  // context of operands on right-hand side

   u3 = s1 + s2;  // GOTCHA? sign extension, even though U3 is unsigned type
                  // Rule: unsigned left-hand side does not affect sign
                  // extension context of operands on right-hand side

   s3 = s1 + u2;  // GOTCHA? zero extension, even if s1 and S3 are signed
                  // Rule: unsigned type on right-hand side means the
                  // entire right-hand side context is unsigned

   s3 = s1 + 1'b1;  // GOTCHA? zero extension, even if s1 and S3 are signed
                    // Rule: unsigned type on right-hand side means the
                    // entire right-hand side context is unsigned

   s3 += 2'b11;   // GOTCHA? zero extension, even though s3 is signed
                  // (operation is same as: s3 = s3 +2'b11)
                  // Rule: unsigned type on right-hand side means the
                  // entire right-hand side context is unsigned

   u3 += 2'sb11;  // GOTCHA? zero extension, even if 2'sb11 is signed
                  // (operation is same as: u3 = u3 +2'sb11)
                  // Rule: unsigned type on right-hand side means the
                  // entire right-hand side context is unsigned

A compound expression can contain a mix of self-determined operations and
context determined operations.  In this case, the resultant type (not the
actual result) of the self-determined operation is used to determine the
types that will be used by the context-determined operations.  The following
examples use the same declarations as the previous examples.

 {o,u3} = u1 + u2; // First evaluate the self-determined concatenation on
                   // the left-hand side.  This affects the size context
                   // of operations on the right-hand side (expanded
                   // to the 9-bit size of the concatenation result)

   u3 = u1 + |u2;  // First do unary OR of 8-bit vector u3 (self-determined)
                   // then zero-extend the 1-bit unary OR result to 8 bits
                   // before doing the context determined math operation

   s3 = s1 + |s2;  // GOTCHA? First do unary OR of 4-bit vector s2 (self-
                   // determined), then zero-extend s1 and the 1-bit
                   // unary OR result to 8 bits (even though s1 is a signed
                   // type, the |s2 result is unsigned, and therefore the
                   // right-hand side context is unsigned)

The gotcha of zero extension vs. sign extension illustrated in this section
is, in reality, a useful feature of Verilog and System Verilog.  A single
operator token, such as +, can model an adder with or without overflow,
depending on the largest vector size in the context of the operation.  The
same + operator can model either a signed adder or an unsigned adder, again
depending on the context of the operation.

How to avoid this gotcha: It comes from not understanding when vector
expansion will occur, and whether the vector will be zero-extended or
sign-extended.  To avoid this gotcha, engineers must know the underlying
"loosely typed" rules of Verilog and System Verilog.  Once the rules are
understood, engineers must use the correct sizes and data types for the
intended type of operation.

Verilog-2001 provides control over the signed-ness of an operand with
the $signed() and $unsigned() functions. System Verilog gives engineers
more control over the application of these expansion rules through the
use of type casting, size casting, and signed-ness casting.  For
example (assuming the same declarations as in the examples above):

   s3 = s1 + u2;               // GOTCHA? zero extension (u2 is unsigned)

   s3 = 8'(s1) + signed'(u2);  // cast s1 to 32 bits (self-determined)
                               // cast u2 to signed and do sign extension


5.3 Signed arithmetic

    Gotcha: Apparent signed arithmetic operations can use unsigned
            arithmetic, or incorrect sign extension.

Sec 4.1 discussed some of the gotchas with literal number sign extension
rules, and Sec 5.2 covered gotchas with sign extension in operations.  This
section covers important gotchas when performing arithmetic operations on
signed data.  Verilog overloads the math operators so that they can
represent several types of hardware.  For example, the + operator can
represent:

 - An adder of any bit width with no carry-in or carry-out
 - An adder of any bit width with no carry-in but with carry-out
 - An adder of any bit width with carry-in and with carry-out
 - An unsigned adder
 - A signed adder
 - A single-precision floating point adder
 - A double-precision adder

The type of arithmetic performed is controlled by the types of the operands,
and the context of the operation.  In order to perform signed operations,
all operands must be signed.  Arithmetic operators are context-dependent,
meaning not only must the operands to the arithmetic operator be signed, all
other operands on the right-hand side of an assignment must also be signed.

The example below is a signed adder with no gotchas, that simulates and
synthesizes correctly.

   module signed_adder_no_carry_in
   (input  logic signed [3:0] a, b,   // signed 4-bit inputs
     output logic signed [3:0] sum,    // signed 4-bit output
     output logic              co);    // unsigned 1-bit output

     assign {co,sum} = a + b;         // signed 5-bit adder
   endmodule

In the example above, the left-hand side concatenation is a self-determined
expression that defines a 5-bit unsigned vector.  The size of the left-hand
side affects the right-hand side ADD operation, but the signed-ness of the
left-hand side has no bearing on operations.  All operands on the right-hand
side of the assignment are signed, which does affect the add operation.  In
this context, the ADD operator performs a 5-bit signed operation.

Using an unsigned carry-in.  The next example is almost the same, but adds a
1-bit carry-in input.  This example has a gotcha!  It does not simulate or
synthesize as a signed adder.

   module signed_adder_with_carry_in
   (input  logic signed [3:0] a, b,   // signed 4-bit inputs
    input  logic              ci,     // unsigned 4-bit inputs
    output logic signed [3:0] sum,    // signed 4-bit output
    output logic              co);    // unsigned 1-bit output

     assign {co,sum} = a + b + ci;    // GOTCHA: unsigned 5-bit adder
   endmodule

In simulation, the only indication that there is a problem is in the value
of the result when either a or b is negative.  In synthesis, DC will issue
a warning message to the effect that a and b were coerced to unsigned
types.  The reason for this coercion is that Verilog's arithmetic operators
are context-dependent.  Even though a and b are signed, one of the operands
in the compound expression, ci, is unsigned.  Therefore, all operands are
converted to unsigned values before any context dependent operation is
performed. GOTCHA!

Using a signed carry-in.  Declaring the 1-bit carry-in input as a signed
type seems like it would solve the problem, illustrated below.

   module signed_adder_with_carry_in
   (input  logic signed [3:0] a, b,   // signed 4-bit inputs
    input  logic signed       ci,     // signed 4-bit inputs
    output logic signed [3:0] sum,    // signed 4-bit output
    output logic              co);    // unsigned 1-bit output

     assign {co,sum} = a + b + ci;    // GOTCHA: ci is subtracted
   endmodule

Now all operands on the right-hand side are signed, and so a signed
operation will be performed, right? GOTCHA!

The example above does do signed arithmetic, but it does incorrect sign
extension -- at least it is incorrect for the intended signed adder model.
The gotcha again relates to the ADD operator being context-dependent.  As
such, all operands are first expanded to the vector size of the largest
operand, which is the 5-bit self-determined concatenate operator on the
left-hand side of the assignment.  Before the addition operations are
performed, a, b and ci are sign-extended to be 5-bits wide.  This is
correct for a and b, but is the wrong thing to do for ci.  If ci has a
value of zero, sign-extending it to 5 bits will be 5'b00000, which is
still zero.  However, if ci is one, sign-extending it to 5 bits will be
5'b11111, which is negative 1, instead of positive 1.  The result of the
ADD operation when carry-in is set is  a + b + -1. GOTCHA!

Using sign casting. Verilog-2001 introduced the $signed and $unsigned
conversion functions, and System Verilog adds sign casting.  These allow
changing the signedness of an operand.  The following example uses sign
casting to try to fix the signed adder problem.

  input  logic              ci,           // unsigned 4-bit inputs
  ...
  assign {co,sum} = a + b + signed'(ci);  // GOTCHA: ci is subtracted

Casting the sign of the carry-in introduces the same gotcha as declaring
carry-in as signed.  When carry-in is set, it is sign-extended to 5 bits,
making the carry-in a negative 1.  GOTCHA!

How to avoid this gotcha: The real problem is that a signed 1-bit value
cannot represent both a value and a sign bit.  Declaring or casting a 1-bit
value to signed creates a value where the value and the sign bit are the
same bit. T he correct way to avoid this signed arithmetic gotcha is to
cast the 1-bit carry-in input to a 2-bit signed expression, as follows:

  assign {co,sum} = a + b + signed'({1'b0,ci});  // signed 5-bit adder

The signed'({1'b0,ci}) operation creates a 2-bit signed operand, with the
sign bit always zero.  When the 2-bit signed value is sign-extended to the
size of the largest vector in the expression context, the sign extension
will zero-extend, maintaining the positive value of the carry-in bit.


5.4 Bit select and part select operations

    Gotcha: The result of a bit select or part select operation
            is always unsigned.

Selecting a bit of a vector, or a part of a vector, is an operation.  The
bit-select and part-select operators always return an unsigned value, even
if the vector itself is signed.  This change in signed-ness can be
unexpected, and is another source for signed arithmetic gotchas.

   parameter SIZE = 31;

   logic signed [SIZE:0] a, b;        // signed vectors
   logic signed [SIZE:0] sum1, sum2;  // signed vectors
   logic signed [   7:0] sum3;        // 8-bit signed vector

   assign sum1 = a + b;                  // signed adder

   assign sum2 = a[SIZE:0] + b[SIZE:0];  // GOTCHA! unsigned adder

   assign sum3 = a[7:0] + b[7:0];        // GOTCHA! unsigned adder

The two gotchas above occur because the result of a part-select operation
is always unsigned, and bit-select and part-select operations are self-
determined (and therefore evaluated before the context-determined ADD
operation).  The context for the ADD operation is unsigned.

How to avoid this gotcha: Since the assignment to sum2 is selecting the full
vectors of a and b, one easy way to avoid this gotcha is to just not do a
part-select, as in the assignment to sum1.  However, code is often generated
by software tools, which may automatically use part-selects, even when the
full vector is being selected.  Part selects are also commonly used in
heavily parameterized models, where vector sizes can be redefined.  For the
sum3 example, above, there is no choice but to do a part-select, since only
part of the a and b vectors are being used.  When a part-select of a signed
vector must be used, the correct modeling style is to cast the result of the
part-select to a signed value.  Either the Verilog-2001 $signed function or
System Verilog sign casting can be used.  For example:

  assign sum2 = $signed(a[SIZE:0]) + $signed(b[SIZE:0]);

  assign sum3 = signed'(a[7:0]) + signed'(b[7:0]);


5.5 Increment, decrement and assignment operations

    Gotcha: Increment, decrement, and assignment operations perform
            blocking assignments.

System Verilog provides the C-like ++ and -- increment/decrement operators,
and the C-like assignment operators such as +=, -=, *= and /=.  The usage of
the operators is intuitive and useful in C programming, and that intuitive
usage carries over to modeling verification testbenches in System Verilog.
But there is a gotcha when using these operators for modeling hardware.  All
of these new operators behave as blocking assignments when updating their
target variable.  Blocking assignments are only appropriate for representing
combinational logic.  If these operators are used to model sequential logic,
then a simulation race condition is likely to occur.  The following example
illustrates such a race condition.

   always_ff @(posedge clock, posedge reset)
     if (reset)           fifo_write_ptr = 0;
     else if (!fifo_full) fifo_write_ptr++;

   always_ff @(posedge clock)
     if (fifo_write_ptr == 15) fifo_full <= 1;
     else                      fifo_full <= 0;

The preceding is not a good design example.  It does show this gotcha of
using the ++ operator in sequential logic.  The first procedural block
modifies the value of fifo_write_ptr on a clock edge.  In parallel, and
possibly in a very different location in the source code, the second
procedural block is reading the value of fifo_write_ptr on the same clock
edge.  Because the ++ operator preforms a blocking assignment update to
fifo_write_ptr, the update can occur before or after the second block has
sampled the value.  Both event orders are legal.  It is very likely that
two different simulators will function differently for this example.

How to avoid this gotcha: The System Verilog increment/decrement operators
and the assignment operators should not be used in sequential logic blocks.
These operators should only be used in combinational logic blocks, as a
for-loop increment, and in contexts where the increment/decrement operand
is not being read by a concurrent process.


5.6 Pre-increment versus post-increment operations

    Gotcha: pre-increment versus post-increment can affect the
            result of some expressions.

Pop Quiz: The following two lines of code do the same thing, right?

   sum = i++;

   sum = i+1;

Answer: No! GOTCHA!

Like the C language, the System Verilog ++ increment operator (or --
decrement operator) can be placed before a variable name (e.g. ++i) or
after a variable name (e.g. i++).  These two usages are referred to as
a pre-increment or a post-increment, respectively.  The result of the
operation is the same; the variable is incremented by 1.  In many
contexts, pre-increment and post-increment can be used interchangeably.
In a for-loop step assignment, for example, either pre- or post-increment
can be used, with the same results.

   for (int i=0; i<=255; ++i) ... ;

   for (int i=0; i<255; i++) ... ;

The two examples are functionally the same because ++i and i++ are
stand-alone statements. Nothing is using the value of i in the same
statement in which it is incremented.  The statement which follows (the
i<=255 test in this example) will see the new value of i, regardless
of whether it is a pre-increment or a post-increment.

The gotcha, which comes straight from the C language, is when the value of
the variable is used within the same statement in which it is being
incremented (or decremented).  If the increment operator is before the
variable name, the variable is incremented before the value is used in that
same statement (pre-increment).  If the increment operator is placed after
the variable, then the value of the variable is used first in the same
statement, and then incremented (post-increment).

   i = 10;

   j = i++;  // assign i to j, then increment i; j gets 10

   j = ++i;  // increment i, then assign result to j; j gets 11

The effects of pre- and post-increment are less obvious in some contexts.
For example:

   i = 16;
   while (i--) ... ; // test i, then decrement; loop will execute 16 times

   while (--i) ... ; // decrement i, then test; loop will execute 15 times

The only way to avoid this gotcha is to fully understand how pre- and post-
increment/decrement work.  Both types of operations are useful, but need to
be used with prudence.


5.7 Operations modifying same variable multiple times in an assignment

    Gotcha: The evaluation order is undefined when a compound
            expression modifies the same variable multiple times
            on the right-hand side of an assignment statement.

SV has assignment operators (such as += and -=), and increment/decrement
operators (++ and --).  These operators both read and modify the value of
their operand.  Two examples are:

   j = ++i;         // increment i, then assign result to j

   j = (i += 1);    // increment i, then assign result to j

Both of these examples modify a variable on the right-hand side of the
assignment statement before making the assignment.  There is a gotcha,
however, if the same variable is modified multiple times in the same
expression. For example:

   i = 10;
   j = --i + ++i;

In this example, the value of i is both read and modified multiple
times on the right-hand side of the assignment statement. The gotcha
is that the System Verilog standard does not guarantee the order of
evaluation and execution of these multiple read/writes to the same
variable in the same expression. After execution, the value of j in
this example could be 19, 20 or 21 (and perhaps even other values),
depending upon the relative ordering of the increment operation and
the decrement operation. Some possible scenarios are:

This gotcha can be avoided by not using operators which make multiple
reads and writes to a variable within the same statement.  Design Compiler
does not permit these types of operations, because of the indeterminate
results.


5.8 Operator evaluation short circuiting

    Gotcha: Simulation might not evaluate all operation operands
            in some circumstances.

Software simulation does not always evaluate statements exactly the
same way as hardware.  Consider the following example:

   always_ff @(posedge clock)
     if (mem_en && write) mem[addr] <= data_in;

In this example, the logical-AND operator ( && ) checks for both
mem_en and write to be true.  In hardware, this is an AND gate.  The two
inputs are continuously evaluated, and affect the output of the AND
gate.  In simulation, however, the logical operation is performed from
left-to-right.  If mem_en is false, then the result of the logical and
operation is known, without having to evaluate write.  Exiting an
operation when the answer is known, but before all operands have been
evaluated is referred to as operation short circuiting.  The Verilog
standard allows, but does not require, software tools to short circuit
logical-AND, logical-OR and the ?: conditional operations.  The Verilog
standard is not clear as to whether other operators can short circuit.
It neither expressly permitted nor expressly prohibited.

Does short circuiting matter?  Not in the preceding example.  Simulation
results of the logical-AND operation will match the behavior of actual
hardware.  Now consider a slightly different example:

   always_ff @(posedge clock)
     if ( f(in1, out1) && f(in2, out2) ) ...

   function f(input [7:0] d_in, output [7:0] d_out);
     d_out = d_in + 1;
     if (d_out == 255) return 0;
     else              return 1;
   endfunction

The function in this example modifies the value passed into it and
passes the value back as a function output argument.  In addition, the
function returns a status flag.  The function is called twice, on the
right-side and the left-side of the && operator.  In hardware, the
logical-AND operator can be implemented as an AND gate, and the
function status return is replicated as combinational logic to each
input of the gate.  As combinational logic, both out1 and out2 are
continuously updated to reflect their input values.  In software,
however, the two functions are evaluated from left-to-right.  If the
return of the first function call is 0, then the operation might
short-circuit.  If short circuiting does occur, then the function is
not called the second time, and out2 is not updated to reflect the
value of in2.  GOTCHA!

The only way to avoid this gotcha is to avoid operands with side effects.
A side effect occurs when the operand modifies a value when the operand is
evaluated.  If the operands do not have side effects, then the behavior of
short circuiting will correctly match hardware behavior.
Index    Next->Item








   
 Sign up for the DeepChip newsletter.
Email
 Read what EDA tool users really think.


Feedback About Wiretaps ESNUGs SIGN UP! Downloads Trip Reports Advertise

"Relax. This is a discussion. Anything said here is just one engineer's opinion. Email in your dissenting letter and it'll be published, too."
This Web Site Is Modified Every 2-3 Days
Copyright 1991-2024 John Cooley.  All Rights Reserved.
| Contact John Cooley | Webmaster | Legal | Feedback Form |

   !!!     "It's not a BUG,
  /o o\  /  it's a FEATURE!"
 (  >  )
  \ - / 
  _] [_     (jcooley 1991)