( 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
|
|