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

Subject: Programming gotchas

6.1 Assignments in expressions

    Gotcha: System Verilog allows assignments within expressions,
            with the same gotchas as C.

    Gotcha: System Verilog's syntax is different than C, confusing
            programmers familiar with C.

In Verilog, assignments are not allowed within an expression.  Therefore,
the common C gotcha of if (a=b) is illegal.  Unfortunately, this also means
the useful application of an assignment within an expression is also
illegal, such as: while (data = fscanf(...) ... .

System Verilog extends Verilog, and adds the ability to make an assignment
within an expression.  Thus, with System Verilog, the intentional usage of
this capability, such as to exit a loop on zero or NULL, is legal.  But, to
prevent unintentional uses of this capability, such as if (a=b), System
Verilog requires that the assignment be enclosed in an expression. Thus:

   if (a=b) ...       // illegal in System Verilog

   if ( (a=b) ) ...   // legal in System Verilog, but probably not useful

   while ((a=b)) ...  // legal in System Verilog, and can be useful

Ironically, in an effort to prevent the common C gotcha of if (a=b), the
System Verilog syntax becomes a gotcha.  Speaking from the personal
experience of one of the authors, programmers familiar with C will
attempt, more than once, to use the C-like syntax, and then wonder why
the tool is reporting a syntax error.  Is the error because, like
Verilog, assignments in an expression are not allowed?  Is the error
because the tool has not implemented the capability?  No, it is an
error because System Verilog's syntax is different than C's.  GOTCHA!

How to avoid this gotcha: The System Verilog syntax can help prevent
the infamous C gotcha of if (a=b).  The gotcha of a different syntax
cannot be avoided, however.  Engineers must learn, and remember, that C
and System Verilog use a different syntax to make an assignment within
an expression.


6.2 Procedural block activation

    Gotcha: Initial procedural blocks can activate in any order
            relative to always procedural blocks.

A common gotcha for new users to Verilog and System Verilog is understanding
the scheduling and/or use of initial procedural blocks.  Because of the name
"initial", many engineers mistakenly believe that this block is executed
before any always procedural blocks.  Other engineers mistakenly believe
just the opposite, thinking that initial blocks are guaranteed to execute
after all always blocks are active.  These are incorrect assumptions!  The
Verilog and System Verilog standards state that all procedural blocks,
regardless of their type, become active at time zero, and in any order.
Verilog initial blocks have no precedence over always blocks, nor do always
blocks have any precedence over initial blocks. As each procedural block is
activated, a simulator can, but is not required to, execute statements in
the block until a Verilog timing control is encountered.

The false assumption that initial procedural blocks will execute before any
always procedural blocks, or vice-versa, can lead engineers to create
stimulus that does not give the same results on different simulators.  A
simple, but common, example is of this false assumption is:

   module test;
     logic reset;
     ... // other declarations
     chip dut (.reset, ...);  // instance of design under test

     initial begin
       reset = 1;  // activate reset at time zero
       #10 reset = 0;
       ...
     end
   endmodule

   module chip (input reset, ...);

     always @(posedge clock or posedge reset)
       if (reset) q = 0;
       else       q = d;

   endmodule

In this example, it is false to assume that either the initial procedural
block in the testbench or the always procedural block in the design will
activate first.  The Verilog standard allows either procedural block to be
activated before the other.  If a simulator activates the always procedural
block first, it will encounter the @ timing control in the sensitivity list
and suspend execution while waiting for a positive edge of clock or reset.
Then, when the initial procedural block activates and changes reset to a 1,
the always block will sense the change and the flip-flop will reset at time
zero.  On the other hand, if the initial procedural block executes first,
reset will be set to a 1 before the always block is activated.  Then, when
the always block is activated, the positive edge of reset will already
have occurred, and the flip-flop will not reset at time zero.  Different
simulators can, and usually do, activate multiple procedural blocks in a
different order.  GOTCHA!

This gotcha is avoided by proper education and understanding of the Verilog
event scheduling of concurrent statements.  In the example above, the fix is
to make the time zero assignment to reset a nonblocking assignment.  The
scheduling of nonblocking assignments guarantees that all procedural blocks,
whether initial or always, have been activated, in any order, before the
assignment takes place.


6.3 Combinational logic sensitivity lists

    Gotcha: @* might not infer a complete combinational logic
            sensitivity list.

Verilog always procedural blocks are used to model designs for synthesis.
The synthesizable RTL modeling style requires that an always block have an
edge sensitive timing control (the @ token) specified immediately following
the always keyword.  This time control is referred to as the procedural
block's sensitivity list.  When modeling combinational logic, if the
sensitivity list is not complete, then the outputs of the block will not be
updated for all possible input changes.  This behavior models a latch in
simulation.  However, synthesis will assume a complete sensitivity list and
build combinational logic instead of a latch.  The simulation results of the
RTL model and the synthesized gate-level model will not match.  GOTCHA!

How to avoid this gotcha: The Verilog 2001 standard added an @* wildcard
sensitivity list that infers a complete sensitivity list for both simulation
and synthesis.  However, the @* has a gotcha that will be discussed later in
this section.  System Verilog introduced two specialized procedural blocks
that infer a complete sensitivity list, always_comb and always_latch.  The
preferred method to avoid sensitivity list gotchas is to use the System
Verilog procedural blocks.

Verilog's @* has a subtle gotcha that is not widely known in the design
community.  @* will only infer sensitivity to signals directly referenced in
the always block.  It will not infer sensitivity to signals that are
externally referenced in a function or a task that is called from the always
block.  That is, the @* will only be sensitive to the signals passed into
the function or task.  The following example illustrates this gotcha:

   module chip (input  wire  [ 7:0] a, b,
                input  wire  [15:0] max_prod,
                input  wire  [ 8:0] max_sum,
                input  wire         error,
                output logic [ 8:0] sum_out,
                output logic [15:0] mult_out);

     function [8:0] mult (input [7:0] m, n);
       mult = 0;
       if (!error)             // error is an external signal
         mult = m * n;
       if (mult > max_prod)    // max_prod is an external signal
         mult = max_prod;
     endfunction

     task sum (input [7:0] p, q, output [8:0] sum_out);
      sum_out = 0;
      if (!error)              // error is an external signal
        sum_out = p + q;
      if (sum_out > max_sum)   // max_sum is an external signal
        sum_out = max_sum;
     endtask

     always @* begin           // @* will only be sensitive to a and b
       sum(a, b, sum_out);     // @* will not be sensitive to max_prod,
                               //   max_sum or error
       mult_out = mult(a, b);
     end
   endmodule

In the preceding example, the sensitivity list inferred by @* will not be
complete, and therefore will not correctly represent combinational logic in
RTL simulations.  Synthesis will assume a complete sensitivity list, leading
to a mismatch in RTL simulation versus the gate-level simulation.  GOTCHA!

To avoid this gotcha for function calls, the System Verilog always_comb
should be used instead of always @*.  The always_comb procedural block will
descend into function calls to infer its sensitivity list.  However,
always_comb does not descend into task calls.  If a task style subroutine
is needed, a System Verilog void function should be used.  Void functions
are used like a task, but have the syntax and semantic restrictions of a
function, and always_comb will descend into the function to infer a complete
sensitivity list.


6.4 Arrays in sensitivity lists

    Gotcha: It is not straightforward to explicitly specify a
            combinational logic sensitivity list when the combinational
            logic reads values from an array.

A subtlety that is not well understood is combinational logic sensitivity
when the logic reads a value from an array.  For example:

   logic [31:0] mem_array [0:1023];   // array of 32-bit variables

   always @( /* WHAT GOES HERE? */ )  // need combinational sensitivity
     data = mem_array[addr];

In order to accurately model true hardware combinational logic behavior,
what should the sensitivity include?  Should the logic only be sensitive to
changes in addr, or should it also be sensitive to changes in the contents
of mem_array being selected by addr?  If sensitive to changes in the
contents of mem_array, which address of the array?

The answer, in actual hardware, is that data will continually reflect the
value that is currently being selected from the array.  If the address
changes, data will reflect that change.  If the contents of the array
location currently being indexed changes, data will also reflect
that change.

The problem, and gotcha, is that this behavior is not so easy to model
at the RTL level, using an explicit sensitivity list.  In essence, the
sensitivity list only needs to be sensitive to changes on two things:
addr, and the location in mem_array currently selected by addr.  But,
an explicit sensitivity list needs to be hard coded before simulation
is run, which means the value of addr is not known at the time the
model is written.  Therefore, the explicit sensitivity list needs to be
sensitive to changes on any and all locations of mem_array, rather
than just the current location.

To be sensitive to the entire array, it would seem reasonable to write:

   always @( addr or mem_array )   // ERROR!
     data = mem_array[addr];

Unfortunately, the example above is a syntax error.  Neither Verilog nor
System Verilog allow explicitly naming an entire array in a sensitivity
list.  Only explicit selects from an array can be listed.  For example:

   always @( addr or mem_array[0] or mem_array[1] or mem_array[2] ... )
     data = mem_array[addr];

This example will work, but it is not practical to explicitly list every
array location.  Even the relatively small one-dimensional array used in
this example, which has 1024 addresses, would be tedious to code.

What about the following example?  Will it be sensitive to both addr and the
value of the mem_array location currently selected by addr?

   always @( mem_array[addr] )   // Does this work?
     data = mem_array[addr];

The answer is... It almost works.  The example above is sensitive to a
change in value of mem_array at the location currently indexed by addr.
However, it is not sensitive to changes on addr.  If addr changes, data
will not be re-evaluated to reflect the change.  GOTCHA!

How to avoid this gotcha: There are three ways to properly model
combinational logic sensitivity when reading from an array.  The best
way is to use Verilog's always @* or System Verilog's always_comb to
infer the sensitivity list.  Both constructs will infer a correct
sensitivity list.  Using always_comb has an added advantage of triggering
once at simulation time zero even if nothing in the sensitivity list
changed.  This ensures that the outputs of the combinational logic match
the inputs at the beginning of simulation.

   always @*      // This works correctly
     data = mem_array[addr];

   always_comb    // This works correctly
     data = mem_array[addr];

The Verilog-1995 solution to this gotcha is to explicitly specify a
sensitivity list that includes the select address and an array select
with that address. For example:

   always @( addr, mem_array[addr] )   // This works correctly
     data = mem_array[addr];

The third method is to use a continuous assignment instead of a
procedural block to model the combinational logic.  This will work
correctly, but has the limitation that continuous assignments cannot
directly use programming statements.

   assign data = mem_array[addr];


6.5 Vectors in sequential logic sensitivity lists

    Gotcha: A sequential logic sensitivity list triggers on changes
            to the least-significant bit of the vector.

A sensitivity list can trigger on changes to a vector, which, in the right
context, is useful and important.

   logic [15:0] address, data;

   always @(address or data)  // OK: trigger on change to any bit of
                              // vectors
     ...

There is a gotcha if the sensitivity list contains a posedge or negedge edge
qualifier on a vector.  In this case, the edge event will only trigger on a
change to the least-significant bit of the vector.

   always @(posedge address)  // GOTCHA! edge detect on a vector
     ...

How to avoid this gotcha: Only single bit items should be used with posedge
or negedge edge events.  If a vector is to be used, then specify which bit
is to be used for the edge detect trigger.  The code above rewritten:

   always @(posedge address[15])  // trigger to change on MSB of vector
     ...


6.6 Operations in sensitivity lists

    Gotcha: Operations in sensitivity lists only trigger on changes
            to the operation result.

Occasionally, an engineer might mistakenly use the vertical bar " | " OR
operator instead of the "or" keyword as a delimiter in a sensitivity list.
The code compiles without any errors, but does not function as expected.
GOTCHA!

The @ symbol is typically used to monitor a list of identifiers used as
event triggers for a procedural block sensitivity list.  The Verilog
standard also allows @ to monitor an event expression.

   always @(a or b)      // "or" is a separator, not an operator
     sum = a + b;

   always @(a | b)       // GOTCHA! "|" is an operator, not a separator
     sum = a + b;

   always @(a && b)      // GOTCHA!
     sum = a + b;

   always @(a == b)      // GOTCHA!
     sum = a + b;

When an operation is used in a sensitivity list, the @ token will trigger on
a change to the result of the operation.  It will not trigger on changes to
the operands.  In the always @(a | b) example above, if a is 1, and b
changes, the result of the OR operation will not change, and the procedural
block will not trigger.

Why does Verilog allow this gotcha? Using expressions in the sensitivity
list can be useful for modeling concise, verification monitors or high-level
bus-functional models.  It is strictly a behavioral coding style, and it is
one that is rarely used.  An example use might be to trigger on a change to
a true/false test, such as always @(address1 != address2).  The procedural
block sensitivity list will trigger if the expression changes from false to
true (0 to 1), or vice-versa.

When modeling combinational logic, the best way to avoid this gotcha is to
use the System Verilog always_comb or always_latch procedural blocks.  These
procedural blocks automatically infer a correct sensitivity list, which
removes any possibility of typos or mistakes.  The Verilog @* can also be
used, but this has its own gotcha (see Sec 6.3).  When modeling sequential
logic, engineers need to be careful to avoid using operations within a
sensitivity list.


6.7 Sequential blocks with begin..end groups

    Gotcha: Resetable sequential procedural blocks with a begin..end
            block can contain statements that do not function correctly.

A common modeling style is to place a begin..end block around the code in
initial and always procedural blocks, even when the procedural block
contains just one statement.  Some companies even mandate this modeling
style.  For example:

   always @(State) begin
     NextState = WAITE;                      // first statement in block
     case (State)                            // second statement in block
       WAITE: if (ready) NextState = LOAD;
       LOAD:  if (done)  NextState = WAITE;
     endcase
   end

This modeling style has a gotcha when modeling resetable sequential logic,
such as flip-flops.  A synthesis requirement is that a resetable sequential
procedural block should only contain a single if..else statement (though
each branch of the if..else might contain multiple statements).  An example
of a correct sequential procedural block is:

   always @(posedge clock or negedge resetN)
     if (!resetN) State <= RESET;
     else         State <= NextState;

The purpose of begin..end is to group multiple statements together so that
they are semantically a single statement.  If there is only one statement in
a procedural block, the begin..end is not required.  In a combinational
logic procedural block, specifying begin..end when it is not needed is extra
typing, but does not cause any problems.  When modeling sequential logic
that has reset functionality, however, adding begin..end can cause modeling
errors.  This error is that the resetable sequential block should only
contain a single if..else statement, but the begin..end allows additional
statements without a simulation syntax error.  For example:

   always @(posedge clock or negedge resetN) begin
     if (!resetN) State <= RESET;
     else         State <= NextState;
     fsm_out <= decode_func(NextState);  // second statement in block
   end

This example will simulate, and, if the simulation results are not
analyzed carefully, it may appear that fsm_out behaves as a flip-flop
that is set on a positive edge of clock.  GOTCHA!

In the example above, fsm_out is not part of the if..else decision
for the reset logic.  This means fsm_out does not get reset by the
reset logic.  Even worse, is that when a reset occurs, the fsm_out
assignment will be executed, asynchronous to the clock, which is not
flip-flop behavior.

This gotcha is an example where Verilog allows engineers to prove what
won't work in hardware.  The Synopsys DC synthesis compiler will not
allow statements outside of an if..else statement in resetable
sequential procedural blocks.  The example above can be simulated (and
proven to not work correctly), but cannot be synthesized.

How to avoid this gotcha: The modeling style of using begin..end in
all procedural blocks is not appropriate for resetable sequential
procedural blocks.  Indeed, a good modeling style is to mandate that
begin..end not be used in sequential procedural blocks that have
reset logic.


6.8 Sequential blocks with partial resets

    Gotcha: Resetable sequential procedural blocks can incorrectly
            only reset some of the outputs.

A syntactically legal, but functionally incorrect, flip-flop model is
illustrated below:

   always @(posedge clock or negedge resetN)
     if (!resetN)
     begin
       q1 <= 0;
       q2 <= 0;
       q3 <= 0;
     end
     else
     begin
       q1 <= ~q4;
       q2 <= q1;
       q3 <= q2;
       q4 <= q3;
     end

The problem with the example above is that q4 is not part of the reset
logic, but is part of the clocked logic.  Because q4 is not reset, it is not
the same type of flip-flop as q1, q2 and q3.

This modeling mistake will simulate and synthesize as working hardware, so
technically, it is not a gotcha.  However, to implement this functionality,
DC will add additional gates to the synthesized circuit, that is both area
and timing inefficient.  These unwanted, and probably unexpected, extra
gates is a gotcha.

The way to avoid this gotcha is careful modeling.  Designers need to be sure
that the same variables are assigned values in both branches of the if..else
decision.  System Verilog cross coverage can be useful in verifying that all
variables that are assigned values on a clock are also reset.


6.9 Blocking assignments in sequential procedural blocks

    Gotcha: Sequential logic blocks can have combinational logic
            blocking assignments.

Verilog has two types of assignments: Blocking assignments (e.g. a = b) have
the simulation behavior of hardware combinational logic.  Nonblocking
assignments (e.g. q <= d) have the behavior of hardware sequential logic
with a clock-to-q propagation.

The following example illustrates a very common Verilog coding gotcha.  The
example uses a blocking assignment where a nonblocking assignment would
normally be used.  The use of blocking assignments in a clocked procedural
block is not a syntax error.  The example proves that a shift register will
not work if a flip-flop does not have a clock-to-q delay.

   always @(posedge clock)
   begin  // NOT a shift register
     q1 = d;    // load d into q1 without a clock-to-q delay
     q2 = q1;   // load q1 to q2
   end

Why does Verilog allow blocking assignments in sequential procedural blocks
if they result in simulation race conditions?  For two reasons.  One reason
is that if the sequential logic block uses a temporary variable that is
assigned and read within the block, that assignment needs to be made with a
blocking assignment.  A second reason is the underlying philosophy of
Verilog that a hardware description and verification language needs to be
able to prove what will work correctly, and what won't work correctly, in
hardware.

In the example above, if q1 and q2 were positive edge triggered flip-flops,
then this example would represent a shift register, where d is loaded into
flip-flop q1 on a positive edge of clock, and then shifted into q2 on the
next positive edge of clock.  Using simulation, however, it can be proven
that this example does not behave as a shift register.  Verilog's blocking
assignment to q1 "blocks" the evaluation of the statement that follows it,
until the value of q1 has been updated.  This means that the value of d
passes directly to q2 on the first clock edge, rather than being shifted
through a flip-flop with a clock-to-q delay.  In other words, the example
has proven that a flip-flop without a clock-to-q propagation behavior will
not function properly in hardware.

As an aside, the DC synthesis compiler will recognize that q1 behaves like
a buffer, rather than a flip-flop.  If the value of q1 is not used outside
of the procedural block, then DC will optimize out the buffer, and d will
be directly loaded into q2.

How to avoid this gotcha: Engineers should adopt a modeling style that
requires the use of nonblocking assignments in sequential procedural
blocks.  Code checking tools such as LEDA can help enforce this coding
style.  However, engineers also need to fully understand the difference
in how blocking and nonblocking assignments work.  There will arise the
occasional exception to the rule, where a blocking assignment is
needed within a sequential procedural block.  Only by understanding how
these two assignments work, will engineers know when to correctly make
an exception to the rule.

6.10 Evaluation of true/false on 4-state values

     Gotcha: A true/false test can result in three answers.

Is the expression 4'b010Z true or false?  At least one bit in the vector is
a logic 1, so does this mean the expression is true?

How Verilog and System Verilog evaluate an expression as being true or false
is not always well understood.  The issue is that the expression can be a
vector of any bit width, and can be either a 2-state expression (each bit
might have the logic values 0 or 1), or a 4-state expression (each bit might
have the logic values 0, 1, Z or X).

The rules for evaluating whether a 2-state expression is true or false are:

 - If all bits of the expression are zero, then the expression is false.
 - If any bits of the expression is one, then the expression is true.

The rules for evaluating whether a 4-state expression is true or false are:

 - If all bits of the expression are zero, then the expression is false.
 - If any bits of the expression is one, and (GOTCHA!) no bits are Z or
   X, then the expression is true.
 - If any bits of an expression are X or Z, then the expression is
  unknown in a true/false test.

The rules for 2-state true/false evaluations are simple and intuitive.  They
match the C language (and other languages).  The gotcha is not understanding
how a vector with 4-state values is evaluated as true or false.

This gotcha can only be avoided by understanding how 4-state values are
evaluated as true or false.  Both design and verification engineers need to
know that, in order for a 4-state expression to be true or false, no bits
in the expression can have a Z or X value.


6.11 Mixing up the not operator ( ! ) and invert operator ( ~ )

     Gotcha: The not operator and the inverse operator can be used
             incorrectly.

Engineers new to Verilog, and even a few veterans, sometimes misuse the
Verilog logical not operator and the bitwise invert operator.  In some
cases, the results of these operations happen to be the same, but, in other
cases, they yield very different results.  Consider the following example:

   logic a;     // 1-bit 4-state variable
   logic [1:0] b;  // 2-bit 4-state variable

   initial begin
     a = 1;
     b = 1;

     if (!a) ... // evaluates as FALSE
     if (~a) ... // evaluates as FALSE

     if (!b) ... // evaluates as FALSE
     if (~b) ... // evaluates as TRUE -- GOTCHA!
   end

The gotcha is the logical not operator inverts the results of a true/false
test.  This means the true/false evaluation is done first, and then the
1-bit true/false result is inverted.  On the other hand, the bitwise invert
operator simply inverts the value of each bit of a vector.  If this
operation is used in the context of a true/false test, the bit inversion
occurs first, and the true/false evaluation is performed second, possibly on
a multi-bit value.  Inverting the bits of a vector, and then testing to see
whether it is true or false is not the same as testing whether the vector is
true or false, and then inverting the result of that test.  GOTCHA!

How to avoid this gotcha: The bitwise invert operator should never be used
to negate logical true/false tests.  Logical test negation should use the
logical not operator.


6.12 Nested if..else blocks

     Gotcha: Nesting of if statements can lead to incorrect matching
             of if..else pairs.

The else branch of a Verilog if..else statement is optional.  This can lead
to confusion when if..else statements are nested within other if..else
statements, and some of the optional else statements are not specified.
Which else goes to which if? The following example is a gotcha...

   if (a >= 5)
     if (a <= 10)
       $display (" 'a' is between 5 and 10");
   else
     $display (" 'a' is less than 5");   // GOTCHA!

The indentation of the code above implies that the else statement goes with
the first if statement, but that is not how Verilog works (and indentation
does not change the language rules).  Verilog language rules state that an
else statement is automatically associated with the nearest previous if
statement that does not have an else.  Therefore, the example above, with
correct indentation and $display statements, is actually:

   if (a >= 5)
     if (a <= 10)
       $display (" 'a' is between 5 and 10");
     else
       $display (" 'a' is greater than 10");

How to avoid this gotcha: The automatic if..else association of Verilog can
be overridden using begin...end to explicitly show which statements belong
within an if branch.  The first example, above, can be corrected:

   if (a >= 5) begin
     if (a <= 10)
       $display (" 'a' is between 5 and 10");
   end
   else
     $display (" 'a' is less than 5"); // CORRECT!

A good language-aware text editor can also help avoid this gotcha.  A good
editor can properly indent nested if..else statements, making it more
obvious which else goes with which if.


6.13 Casez/casex masks in case expressions

     Gotcha: Masked bits can be specified on either side of a case
             statement comparison.

Verilog's casez and casex statements allow bits to be masked out from the
case comparisons.  With casez, any bits set to Z or ? are masked out.  With
casex, any bits set to X, Z or ? are masked out.  These constructs are
useful for concisely modeling many types of hardware, as well as in
verification code.  An example of using the wildcard casex statements is:

   always_comb begin
     casex (instruction)
       4'b0???: opcode = instruction[2:0]; // only test upper bit
       4'b1000: opcode = 3'b001;
       ...  // decode other valid instructions
       default: begin
                  $display ("ERROR: invalid instruction!");
                  opcode = 3'bxxx;
                end
     endcase
   end

In the preceding example, the mask bits are set in the first case item,
using 4'b0???.  The intent is that, if the left-most bit of instruction is
0, the other bits do not need to be evaluated.  After all possible valid
instructions have been decoded, a default branch is used to trap a design
problem, should an invalid instruction occur.

What case branch will be taken if there is a design problem, and
instruction has the value 4'bxxxx?  The intuitive answer is that the default
branch will be taken, and an invalid instruction will be reported.  GOTCHA!

The casex and casez statements allow the mask bit to be set on either side
of the comparison.  In the preceding example, if instruction has a value of
4'bxxxx or (or 4'bzzzz), all bits are masked from the comparison, which
means the first branch of the case statement will be executed.

How to avoid this gotcha: A partial solution is to use casez instead of
casex.  In the example used in this section, if a casez were used, a
design problem that causes an instruction of 4'bxxxx (or even just an
X in the left-most bit) will not be masked, and an invalid instruction
will be reported by the default branch.  However, a design problem that
cause an instruction of 4'bzzzz (or just a Z in the left-most bit)
will still be masked, and an invalid instruction will not be trapped.

System Verilog offers two solutions to this gotcha.  The first solution is a
special one-sided, wildcard comparison operator, ==? (there is also a !=?
operator).  This wildcard operator works similar to casex, in that bits can
be masked from the comparison using X, Z or ?.  However, the mask bits can
only be set in the left-hand side of the comparison.  In the following, any
X or Z bits in instruction will not be masked, and the invalid instruction
will be trapped:

   if (instruction ==? 4'b0???) opcode = instruction[2:0];
   else if ... // decode other valid instructions
   else begin
          $display ("ERROR: invalid instruction!");
          opcode = 3'bxxx;
        end

A second fix to the gotcha is the System Verilog case() inside statement.
This statement allows mask bits to be used in the case items using X, Z,
or ?, as with casex.  But, case() inside uses a one-way, asymmetric masking
for the comparison.  Any X or Z bits in the case expression are not masked.
In the following example, any X or Z bits in instruction will not be masked,
and the invalid instruction will be trapped:

   always_comb begin
     case (instruction) inside
       4'b0???: opcode = instruction[2:0]; // only test upper bit
       4'b1000: opcode = 3'b001;
       ...  // decode other valid instructions
       default: begin
                  $display ("ERROR: invalid instruction!");
                  opcode = 3'bxxx;
                end
     endcase
   end


6.14 Incomplete or redundant decisions

     Gotcha: Incomplete case statements or if..else decision statements,
             and redundant decision select items that can result in
             design errors.

Verilog's if..else and case statements (including casez and casex) have 3
gotchas that often result in design problems:

 - Not all possible branches need to be specified (incomplete decisions)
 - Redundant (duplicate) decision branches can be specified
 - Software simulation evaluates decisions in the order listed (priority
   decoding), but the decision might be able to be evaluated in any order
   in hardware (parallel decoding).

Verilog synthesis compilers attempt to overcome these Verilog gotchas,
using synthesis full_case and parallel_case pragmas.  These pragmas,
however, are fraught with danger, and often introduce worse gotchas than
those that they solve.  These gotchas are well documented in ESNUG 332,
"full_case & parallel_case, the Evil Twins of Verilog Synthesis" by
Cliff Cummings.

How to avoid these gotchas: System Verilog adds two decision modifiers,
unique and priority.  For example:

   unique case (sel)         priority if (a == 0) out = 0;
     2'b00: y = a;               else if (a < 5)  out = 1;
     2'b01: y = b;               else if (a < 10) out = 2;
     2'b10: y = c;               else             out = 3;
     2'b11: y = d;
   endcase

These modifiers eliminate all of the gotchas with incomplete and redundant
decision statements, and prevent the gotchas common to synthesis full_case
and parallel_case pragmas.  The benefits of the unique and priority decision
modifiers are described in two other SNUG papers, "System Verilog saves the
Day, 'unique' and 'priority' are the new Heroes", and "System Verilog's
priority & unique, A Solution to Verilog's full_case & parallel_case Evil
Twins!".  These benefits are not repeated here.

There is, however, a Synopsys DC synthesis compiler gotcha with the System
Verilog priority decision modifier.  The keyword "priority" would seem to
indicate that the order of a multi-branch decision statement will be
maintained by synthesis.  DC does not do this.  DC will still optimize
priority case decision ordering, the same as with a regular case decision
statement.  While gate-level optimization is a good thing, it is a gotcha
if the designer is expecting a priority case statement to automatically have
the identical priority decoding logic after synthesis.


6.15 Out-of-bounds assignments to enumerated types

     Gotcha: Enumerated types can have values other than those in their
             enumerated list.

Verilog is a loosely typed language. Any data type can be assigned to a
variable of a different type without an error or warning.  Unlike Verilog,
the System Verilog enumerated type is, in theory, a strongly typed variable.
Part of the definition of an enumerated type variable is the legal set of
values for that variable.  For example:

   typedef enum bit [2:0] {WAITE = 3'b001,
                           LOAD  = 3'b010,
                           STORE = 3'b100} states_t;

   states_t State, NextState;  // two enumerated variables

A surprising gotcha is that an enumerated type variable can have values that
are outside of the defined set of values.  Out-of-bounds values can occur in
two ways: uninitialized variables and statically cast values.  Each of these
is explained in more detail, below.

As with all static Verilog & System Verilog variables, enumerated variables
begin simulation with a default value.  For enumerated variables, this
default is the uninitialized value of its base data type.  In the preceding
example, the base data type of State is a 2-state bit type, which begins
simulation with an uninitialized value of zero.  This value is not in the
variable's enumerated list, and is, therefore, out-of-bounds.  GOTCHA!

How to avoid this gotcha: In actuality, this gotcha can be a positive one.
If the uninitialized enumerated variable value is out-of-bounds, it is a
clear indication that the design has not been properly reset.  This is even
more obvious if the base data type is a 4-state type, which has an
uninitialized value of X.

System Verilog requires that any procedural assignment to an enumerated
variable be in the enumerated list, or from another variable of the
same enumerated type.  The following examples illustrate legal and
illegal assignments to State:

   NextState = LOAD;         // legal assignment
   NextState = State;        // legal assignment
   NextState = 5;            // illegal (not in enumerated label list)
   NextState = 3'b001;       // illegal (not in enumerated label list)
   NextState = State + 1;    // illegal (not in enumerated list)

System Verilog allows a normally illegal assignment to be made to an
enumerated variable using casting.  For example:

   NextState = states_t'(State + 1);  // legal assignment, but a GOTCHA!

When a value is cast to an enumerated type, and assigned to an enumerated
variable, the value is forced into the variable, without any type checking.
In the example above, if State had the value of WAITE (3'b001), then
State + 1 would result in the value of 3'b010.  This can be forced into the
NextState variable using casting.  As it happens, this value matches the
enumerated label LOAD.  If, however, State had the value of LOAD, then
State + 1 would result in the value of 3'b011.  When this value is forced
into the enumerated variable NextState, it does not match any of the
enumerated labels.  The NextState variable now has an out-of-bounds value.
GOTCHA!

There are 2 ways to avoid this gotcha.  Instead of using the cast operator,
the System Verilog dynamic $cast function can be used.  Dynamic casting
performs run-time error checking, and will not assign an out-of-bounds value
to an enumerated variable.  In addition, System Verilog enumerated types
have several built-in methods which can manipulate the values of enumerated
variables, and, at the same time, ensure the variable never goes
out-of-bounds.  For example, the .next() method will increment an enumerated
variable to the next label in the enumerated list, rather than incrementing
by the value of 1.  If the enumerated variable is at the last label in the
enumerated list, .next() will wrap around to the first label in the list.
An example of using the .next() method is:

   NextState = State.next(1);   // increment by one to next label in list


6.16 Statements that hide design problems

     Gotcha: Some programming statements do not propagate design errors.

In 4-state simulation, a logic value of X can occur.  Logic X is not a real
hardware logic value.  Nor is it a "don't care", as it is in some data
books.  Logic X is the simulator's way of saying that simulation algorithms
cannot predict what actual HW would do with a given set of circumstances.
While no engineer likes to see X values in the simulation log files or
waveform displays, savvy engineers have come to know that X is their friend.
When an X value shows up, it is a clear indication of a design problem.

But there is a gotcha.  A number of Verilog programming statements can
swallow an X value, and generate a seemingly good value.  These statements
hide design problems, which can be disastrous.  Two of the most common X
hiding constructs are decisions statements and optimistic operators.  An
example of a decision statement that will hide design errors is:

   always_comb begin
     if ( sel ) y = a;   // 2-to-1 MUX
     else      y = b;
   end

In this example, should a design bug cause sel to have a logic X, the else
branch will be taken, and a valid value assigned to y.  The design bug has
been hidden.  GOTCHA!

How to avoid this gotcha: The simple example above could be re-coded to trap
an X value on sel.  However, this extra code must be hidden from Design
Compiler, as it is not really part of the hardware.  For example:

   always_comb begin
     if (sel)
       y = a;   // do true statements
     else
   //synopsys translate_off
     if (!if_condition)
   //synopsys translate_on
       y = b;   // do the not true statements
   //synopsys translate_off
     else
       $display("if condition tested either an X or Z");
   //synopsys translate_on
   end

A better way to avoid this gotcha is to use System Verilog assertions.
Assertions have several advantages.  Assertions are much more concise
than using programming statements to trap problems.  Assertions do not
need to be hidden from synthesis compilers.  Assertions are not likely
to affect the design code.  Assertions can easily be turned on and off
using large grain or fine grain controls.  Assertions can also provide
verification coverage information.  An assertion for the example above
can be written as:

   always_comb begin
     assert ($isunknown(sel)) else $error("sel = X");
     if ( sel) y = a;   // 2-to-1 MUX
     else      y = b;
  end

For more details on X hiding gotchas and using assertions to detect
design problems, see the SNUG papers "Being Assertive With Your X", and
"System Verilog Assertions are for Design Engineers, too".


6.17 Simulation versus synthesis mismatches

     Gotcha: Some coding styles can simulate correctly, but synthesize
             to hardware that is not functionally the same.

A number of programming statements will simulate one way, but have subtle
functional differences from the gate-level implementation created by the DC
synthesis compiler.  Several of the most common gotchas have been covered
under other sections in this paper.  Another excellent SNUG paper provides
additional insights on modeling gotchas that affect synthesis, "RTL Coding
Styles That Yield Simulation and Synthesis Mismatches".
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)