Verilog and System Verilog Differences
Verilog and System Verilog Differences
Stuart Sutherland
Sutherland HDL, Inc.
[email protected]
Don Mills
Microchip Technology
[email protected]
ABSTRACT
A definition of “gotcha” is: “A misfeature of....a programming language...that tends to breed bugs
or mistakes because it is both enticingly easy to invoke and completely unexpected and/or
unreasonable in its outcome. A classic gotcha in C is the fact that ‘if (a=b) {code;}’ is
syntactically valid and sometimes even correct. It puts the value of b into a and then executes
code if a is non-zero. What the programmer probably meant was ‘if (a==b) {code;}’, which
executes code if a and b are equal.” (from http://www.hyperdictionary.com/computing/gotcha)
The Verilog and SystemVerilog standards define hundreds of subtle rules on how software tools
should interpret design and testbench code. These subtle rules are documented in the IEEE
Verilog and SystemVerilog Language Reference Manuals...all 1,500 plus pages! The goal of this
paper is to reveal many of the mysteries of Verilog and SystemVerilog, and help engineers
understand many of the important underlying rules of the Verilog and SystemVerilog languages.
Dozens of gotchas in the standards are explained, along with tips on how to avoid these gotchas.
Table of Contents
1.0 Introduction ............................................................................................................................3
2.0 Declaration gotchas ................................................................................................................4
2.1 Case sensitivity ............................................................................................................ 4
2.2 Implicit net declarations ............................................................................................... 5
2.3 Escaped identifiers in hierarchy paths ......................................................................... 6
2.4 Verification of dynamic data ....................................................................................... 7
2.5 Variables declared in unnamed blocks ........................................................................ 8
2.6 Hierarchical references to declarations imported from packages ................................ 8
2.7 Variables with no hierarchy path are not dumped to VCD files .................................. 9
2.8 Shared variables in modules ........................................................................................ 9
1.0 Introduction
A programming “gotcha” is a language feature, which, if misused, causes unexpected—and, in
hardware design, potentially disastrous—behavior. The classic example in the C language is
having an assignment within a conditional expression, such as:
if (a=b) /* GOTCHA! assigns b to a, then if a is non-zero sets match */
match = 1;
else
match = 0;
Most likely, what the programmer intended to code is if (a==b) instead of if (a=b). The results
are very different!
Why do programming languages allow engineers to make these stupid mistakes? In the case of
the Verilog and SystemVerilog languages, the primary reasons are:
• A syntax may be desirable in some contexts, but a gotcha if used incorrectly.
• An underlying philosophy of Verilog and SystemVerilog is that the language should allow
proving both what will work correctly in hardware, and what will not work in hardware.
• Verilog and SystemVerilog provide some freedom in how the language is implemented. This is
necessary because tools such as simulation, synthesis and formal analysis work differently.
• Verilog and SystemVerilog simulation event scheduling allows tools to optimize order of
concurrent events differently, which can lead to race conditions in a poorly written model.
• Some tools are not 100% standards compliant. This is not a gotcha in the standards, but it is
still a source of gotchas when simulation or synthesis results are not what was expected.
The C example above, if (a=b), is an example of a syntax that is a gotcha in one context, but is
useful in a different context. The following example is similar, in that it contains an assignment
operation within a true/false conditional test. In this example, however, there is no gotcha. The
while loop repeatedly assigns the value of b to a and does some processing on a. The loop
continues to run until the value of a is 0;
while (a=b) { /* assign b to a; exit loop when a is 0 */
...
}
Does this same C gotcha exist in Verilog and SystemVerilog? If you don’t know the answer, then
you need to read this paper! (You will find the answer in Section 6.1.)
One gotcha in this example is the declaration of n1 (“en-one”) but the usage of nl (“en-ell”) in the
g2 primitive instance. Another gotcha is an extra identifier, c, in the second g3 primitive instance.
These typos are not syntax errors. Instead, they infer implicit nets in the design, causing
functional errors that must be detected and debugged. GOTCHA!
This example comes from a debugging lab in Sutherland HDL’s Verilog training course (but
without the GOTCHA comments). It is surprising how difficult the two typos in the netlist can be
to find. Imagine how much more difficult a simple typo would be to find in a one-million gate
netlist with several layers of design hierarchy. Why does Verilog allow this gotcha? Because, like
many gotchas, the ability to have implicit data types automatically inferred can be useful, when
not abused. One of the benefits of implicit data types is that in a large, multi-million gate design
that has thousands of interconnecting wires, it is not necessary to explicitly declare every wire.
How to avoid this gotcha: There are two ways to avoid this implicit data type gotcha. Verilog
provides a ‘default_nettype none compiler directive. When this directive is set, implicit data
types are disabled, which will make any undeclared signal name a syntax error. A limitation of
this directive is that the benefits of implicit data types are also lost. Another limitation is that
compiler directives are not bound by design blocks, or even by source code files. If the
‘default_nettype none directive is turned on in one file, it can affect the compilation of other
files, which is yet another GOTCHA!. To avoid this gotcha, the directive ‘default_nettype
wire should be added at the end of each file where implicit nets have been turned off.
SystemVerilog provides two convenient short cuts for connecting nets to module instances,
.<name> and .*. These shortcuts remove the repetition in Verilog named port connections. By
reducing the number of times a signal name must be typed, the possibility of typographical errors
is also reduced. The .<name> and .* shortcuts also require that all nets be explicitly declared. The
shortcuts will not infer an implicit data type due to a typo. Another advantage of these
SystemVerilog shortcuts is that they are local to the module in which they are used. The shortcuts
do not affect other design blocks, the way compiler directives can.
module adder (input wire a, b, ci,
output wire sum, co);
...
endmodule
module top;
wire a, b, ci;
wire s1, s2, s3, c1, c2, c3;
adder i1 (.a(a), .b(b), .ci(ci), .sum(s1), .co(c1) ); // Verilog style
adder i2 (.a, .b, .ci, .sum(s2), .co(c2) ); // SystemVerilog .name style
adder i3 (.*, .sum(s3), .co(c3) ); // SystemVerilog .* style
endmodule
\d-flop \d-0 (.q(q[0]), .\q~ (), .\d[0] (d[0]), .clk(clk), .\rst- (rstN));
escaped brackets (part of name) non-escaped brackets (bit-select)
initial begin
$display(“d = %b”, \d-0.\d[0] ); // GOTCHA! missing white space
$display(“d = %b”, \d-0 .\d[0] ); // OK: white space in path
end // required; does not split
// path into two names
endmodule
How to avoid this gotcha: One could simply not used escaped names in a design, especially an
escaped name with square brackets in the name, as in the example above. Unfortunately, life is not
that simple. Not all identifiers are user-defined. Software tools, such as the DC synthesis
compiler, create tool-generated identifier names in the Verilog or SystemVerilog code. And, as
ugly as these tool-generated identifiers looks to users, these tools often put square brackets in
escaped identifiers. The gotcha of having to reference escaped identifiers using hierarchy paths is
one that cannot be completely avoided. Engineers need to know that having a space in a hierarchy
path involving escaped identifiers is not illegal. It may make the code harder to read, but it is the
way Verilog works.
How to avoid this gotcha: The correct way to reference package items is using the scope
resolution operator ( :: ) instead of hierarchy paths. For example:
$display (“the value of RESET is %b”, chip_types::RESET);
2.7 Variables with no hierarchy path are not dumped to VCD files
Gotcha: Changes on dynamic variables and variables in unnamed scopes are not saved in VCD
files.
Another gotcha with dynamically and locally defined storage is that the IEEE Verilog and
SystemVerilog standards explicitly state that this type of storage is not dumped out to a Value
Change Dump (VCD) file. VCD files are used as an input to waveform displays and other design
analysis tools, in order to analyze what activity has, or has not, occurred during simulation. As a
standard, VCD files are portable, and can be used with many different third party and in-house
tools. However, changes on dynamically allocated and local data are not dumped to VCD files,
which means waveform displays and design analysis tools that read in VCD files do not see all the
activity that took place in simulation.
How to avoid this gotcha: There is no work around for this VCD limitation. It should be noted,
though, that proprietary dump files that are part of most waveform display tools might not have
this limitation.
How to avoid this gotcha: The answer on how to avoid this gotcha depends on whether the code is
for a synthesizable RTL model or for verification. For RTL models, the preferred way to avoid
this gotcha is to use SystemVerilog’s always_comb, always_ff, always_comb, and continuous
assign to a variable. These processes make it a syntax error if a variable is written to by multiple
processes. If the code is for verification or an abstract bus functional model, the way to avoid this
gotcha is to use process synchronization (flags, event triggers, semaphores or mailboxes) so that
concurrent processes are not writing to the same variable at the same time.
For the example above, there is another way to avoid this shared variable gotcha. SystemVerilog
allows local variables to be declared as part of a for loop definition. This solution has its own
gotcha, however, as was discussed in Section 2.5.
How to avoid this gotcha: This gotcha can usually be avoided by wildcard importing the package.
In this way, both the enumerated type definition and the enumerated labels are imported.
module chip (...);
import chip_types::*; // wildcard import of package declarations
However, wildcard imports have a gotcha if multiple packages are used in a design block, as
discussed in Section 2.12, which follows.
How to avoid this gotcha: The gotcha with wildcard package imports occurs when there are some
identifiers common to more than one package. In this case, at most only one of the packages with
duplicate identifiers can be wildcard imported. Any references to a duplicate identifier in another
package must be explicitly referenced, using its package name each place the identifier is
referenced. For example:
import bus1_types::*; // wildcard import of a package
logic [bus2_types::SIZE-1:0] a; // explicit reference to different package
In the example above, the always_ff flip-flop in module chip is supposed to reset on a negative
edge of rst_n. The testbench sets rst_n to zero at the beginning of simulation, and holds it low
for 3 nanoseconds. However, in the testbench, rst_n is a 2-state type, which begins simulation
with a value of zero. Setting rst_n to zero does not change its value, and therefore does not cause
a negative edge on rst_n. Since the testbench does not cause a negative edge on rst_n, the
always_ff sensitivity list for the flip-flop in module chip does not trigger, and the flip-flop does
not reset asynchronously. If rst_n were held low at least one clock cycle, the flip-flop would
reset synchronously when clock occurred. In this example, though, the test stimulus does not hold
rst_n low a full clock cycle, and therefore the reset is completely missed. GOTCHA!
How to avoid this gotcha: This gotcha can be avoided in a number of ways. One way is to
initialize the 2-state reset signal to the non reset value with a blocking assignment, and then to the
reset value with a nonblocking assignment: This will trigger the always_ff blocks waiting for a
negative edge of reset. Additionally, the nonblocking assignment will ensure that all the
always_ff blocks are active before the transition to zero occurs.
initial begin
rst_n = 1; // initialize to inactive value
rst_n <= 0; // set reset to active value using nonblocking assign
#3ns rst_n = 1;
...
A second way to avoid this gotcha is to ensure that the reset is held longer then a clock period,
thus using the clock to trigger the always_ff block. This is not a recommended approach, due to
reset acting like a synchronous reset, rather than an asynchronous reset, which means that the
RTL simulation does not match the gate-level simulation or the design intent.
A third way to fix this gotcha is to use 4-state types instead of 2-state types for active-low signals.
4-state variable types will begin simulation with a value of X. Assigning a 4-state type a value of
zero, even at simulation time zero, will cause an X to zero transition, which is a negative edge.
Note: VCS version 2006.06 does not fully adhere to the IEEE SystemVerilog standard for 2-state
types. The standard says that 2-state types should begin simulation with a value of zero. In VCS,
2-state variable types automatically transition to zero sometime during simulation time zero. This
will cause a negative edge transition at simulation time zero, which might, depending on event
ordering, trigger the design procedural blocks that are looking for a negative edge transition. This
tool-specific, non-standard behavior can hide this 2-state reset gotcha. Engineers should not rely
on this tool-specific behavior as a solution to the gotcha. It is dependent on event ordering, and, at
some future time, VCS could implement the 2-state behavior as it is specified in the standard.
In simulation, this example will lock up in the WAITE state. Applying reset, whether 2-state or 4-
state, will not get the state machine out of this lock up. This is because State and NextState are
2-state enumerated variables. 2-state types begin simulation with a value of zero, which is the
value of WAITE in the enumerated list. When the always_ff state sequencer is reset, it will assign
State the value of WAITE, which is the same value as the current value of State, and thus does
not cause a transition on State. Since State does not change, the always @(State)
combinational procedural block does not trigger. Since the combinational block is not entered,
NextState is not updated to a new value, and remains its initial value of WAITE. On a positive
edge of clock, State is assigned the value of NextState, but, since the two variables have the
same value of WAITE, State does not change, and, once again, the always @(State)
combinational block is not triggered and NextState is not updated. The simulation is stuck in the
start-up state no matter how many clock cycles are run, and no matter how many times the state
machine is reset. GOTCHA!
How to avoid this gotcha: The best way to avoid this gotcha is to use the SystemVerilog
always_comb for the combinational block in this code. Unlike the Verilog always procedural
block, an always_comb procedural block will automatically execute once at time zero, even if the
sensitivity list was not triggered. When the always_comb block executes, NextState will be
assigned the correct value of LOAD. Then, after reset is removed, the state machine will function
correctly, and not be locked in a WAITE state.
A second method to avoid this gotcha is to declare the State and NextState enumerated
variables as 4-state types, as follows:
enum logic [1:0] {WAITE, LOAD, STORE} State, NextState; // 4-state
By doing this, State and NextState will begin simulation with the value of X. When State is
assigned WAITE during reset, the always @(State) will trigger, setting NextState to LOAD.
A third way to fix this 2-state lock-up gotcha is to explicitly assign values to the WAITE, LOAD and
READY that are different than the uninitialized value of the enumerated variables. For example:
In this example, State and NextState are 2-state types, which begin simulation with an
uninitialized value of zero. This value does not match any of the values in the enumerated list.
When reset is applied, State will be assigned WAITE. The change on State will trigger the
always @(State) combinational block, which will update NextState to LOAD, preventing the
lock up gotcha.
The initial value of data_reg is zero. This is also the value to which data_reg is reset. This
means that, if for some reason the design fails to generate a reset, it will not be obvious by looking
at the value of data_reg that there was a failure in the design logic.
Another way in which 2-state logic can hide design errors is when an operation returns a logic X,
as illustrated below:
module comparator (output bit eq, // 2-state output
input bit a, b); // 2-state inputs
assign eq = (a == b);
endmodule
In the example above, the gotcha is the 2-state inputs. What will happen if there is a design error,
and either the a or b input is left unconnected? With 4-state values, the unconnected input would
float at high-impedance, and the (a == b) operation will return a logic X—an obvious design
failure. With 2-state inputs, however, there is no high-impedance to represent a floating input. The
design error will result in zero on the input, and an output of one or zero. The design failure has
been hidden, and did not propagate to an obvious incorrect result. GOTCHA!
What if the inputs and outputs in the preceding example were 4-state, but the output was
connected to another design block, perhaps an IP model written by a third party provider, that was
modeled using 2-state types? In this case, the comparator module would output a logic X, due the
unconnected input design failure, but that X would be converted to a zero as it propagates into the
2-state model, once again hiding the design problem. GOTCHA!
How to avoid this gotcha: The best way to avoid this gotcha is to use 4-state types in all design
blocks. 4-state variables begin simulation with a value of X, making it very obvious if reset did
In this example, the address bus is wider than is required to access all addresses of mem_array. If
a 4-state array is accessed using an address that does not exist, a logic X is returned. But, when a
2-state array is accessed using an address that does not exist, a value of zero is returned. Since a
value of zero could be a valid value, the out-of-bounds read error has been hidden. GOTCHA!
The example above is an obvious design error, but is also one that could easily be inadvertently
coded. The same error is less obvious when the defaults of the memory size and address bus
parameters are correct, but an error is made when redefining the parameter values for an instance
of the RAM. GOTCHA, again!
How to avoid this gotcha: One way to avoid this gotcha is to use 4-state types for arrays.
However, a 4-state array requires twice the amount of simulation storage as a 2-state array. It can
be advantageous to use 2-state arrays to model large memories. Another way to avoid this gotcha
is to use SystemVerilog assertions to verify that the redefined values of parameters cannot result
in an out-of-bounds access.
Where:
• <size> is optional. If given, it specifies the total number of bits represented by the literal
value. If not given, the default size is 32 bits (the Verilog standard says “at least” 32 bits, but
all implementations the authors are aware of use exactly 32 bits).
• s is optional. If given, it specifies that the literal integer should be treated as a 2’s
complemented signed value. If not given, the default is unsigned.
• <base> is required, and specifies whether the value is in binary, octal, decimal, or hex.
• <value> is required, and specifies the literal integer value.
How to avoid this gotcha: The difference between adding 1 and 1’b1 is that the literal integer 1 is
a signed value, whereas the literal integer 1’b1 is an unsigned value. This difference affects how
the ADD operation is performed. Signed arithmetic is discussed in more detail in Section 5.3.
This gotcha fits nicely with the well known joke that only engineers laugh at: “There are 10 types
of people in the world, those that know binary, and those that don’t”. The example above may
look reasonable, and it is syntactically correct. But, since the default base of a simple integer is
decimal, the values “10” and “11” are ten and eleven, respectively. The size of the values is not
specified, and so defaults to 32-bits wide. The 2-bit select signal will be zero-extended to 32-
bits wide, and then compared to zero, one, ten and eleven. The only values of select that will
ever match are a and b, which extend to 32-bit 00...00 and 00...01, respectively. The extended
value of select will never match the decimal “10” and “11”. Therefore, the c and d inputs of the
multiplexer will never be selected. Since this is not a syntax error, the problem shows up in
simulation as a functional failure which can be difficult to detect and debug. GOTCHA.
How to avoid this gotcha: An easy way to avoid this gotcha—or more correctly, to detect this
gotcha—is to use the SystemVerilog unique modifier to the case statement, as in:
unique case (select) // intent is for a 4-to-1 MUX behavior
The unique modifier reports an error if two or more case select items are true at the same time, or
if no case select items are true. The example above becomes a simulation error, rather than a
functional bug in the code.
Code coverage utilities and/or SystemVerilog functional coverage can also be used to detect that
some branches of the case statement are not being executed. Also, some EDA tools, such as
LEDA and DC, will warn about a mismatch in the size of select and the literal integer values to
which it is compared.
How to avoid this gotcha: The way to avoid this gotcha is to be sure that the bit size specified is at
A surprising gotcha can occur when the bit-size is larger than the value, and the value is signed.
The expansion rules above do not sign-extend a signed value. Even if the number is specified to
be signed, and the most significant bit is a 1, the value will still be extended by 0’s. For example:
logic [11:0] a, b, c;
initial begin
a = 12’h3c; // unsigned value 00111100 expands to 000000111100
b = 12’sh3c; // signed value 00111100 expands to 000000111100
c = 12’so74; // signed value 111100 expands to 000000111100
if (a == b) ...; // evaluates as true
if (a == c) ...; // evaluates as true
end
In this example, a hex 3c is an 8-bit value, which does not set its most-significant bit. When the
value is expanded to the 12-bit size, the expansion zero-extends, regardless of whether the value
is signed or unsigned. This is as expected.
The octal value 73 is the same bit pattern as a hex 3c, but is a 6-bit value with its most-significant
bit set. But, the expansion to the 12 bit size still zero-extends, rather than sign-extends. If sign
extension was expected, then GOTCHA!.
How to avoid this gotcha: The subtlety in the preceding example is that the sign bit is not the
most-significant bit of the value. It is the most-significant bit of the specified size. Thus, to
specify a negative value in the examples above, the value must explicitly set bit 12 of the literal
integer.
12'h805 // expands to 100000000101, which is 2053 decimal
12'sh805 // expands to 100000000101, which is -2043 decimal
12'shFFB // expands to 111111111011, which is -5 decimal
-12'sh5 // expands to 111111111011, which is -5 decimal
Unspecified size. If the size of a literal integer is not specified, then the bit size of the literal
number defaults to 32 bits. This default size can be a gotcha when the <value> is not 32 bits, as
discussed in the preceding paragraphs.
The fill rules in Verilog-1995 make it difficult to write models that scale to new vector sizes.
Redefinable parameters can be used to scale vector widths, but the Verilog source code must still
be modified to alter the literal value widths used in assignment statements. (And yes, believe it or
not, there are still some companies primarily using Verilog-1995.) The example above applies
equally to assigning Xs to a vector.
How to avoid this gotcha: One solution to this gotcha (but not the best) came with Verilog-2001,
which changed the rule for assignment expansion of unsized Z and X literal integers. The unsized
value of Z or X will automatically expand to fill the full width of the vector on the left-hand side
of the assignment.
Verilog-2001:
parameter WIDTH = 64;
reg [WIDTH-1:0] data;
data = ’bz; // fills with 64’hzzzzzzzzzzzzzzzz
The Verilog-2001 enhancement allows writing code that scales when vector sizes are redefined.
But, this solution is also a gotcha, because it is not backward compatible with Verilog-1995. If
In order to assign a vector of any size with all bits set to one, designers must learn clever coding
tricks involving various Verilog operators. These tricks are not discussed in this paper, because
there is a better way to avoid this gotcha...
How to avoid this gotcha: SystemVerilog comes to the rescue to fix this gotcha by defining a
consistent syntax for filling any size of variable with all ones, all zeros, all Xs or all Zs. This is
done by just assigning ’<value>.
SystemVerilog:
parameter WIDTH = 64;
reg [WIDTH-1:0] data;
data = ’1; // fills with 64’hffffffffffffffff
data = ’0; // fills with 64’h0000000000000000
data = ’z; // fills with 64’hzzzzzzzzzzzzzzzz
data = ’x; // fills with 64’hxxxxxxxxxxxxxxxx
How to avoid this gotcha: While Verilog does provide a solution for passing real numbers through
ports, the solution is not well known, and is awkward to use. SystemVerilog provides a much
more elegant way to avoid this gotcha. In SystemVerilog, real types can be passed directly
through ports SystemVerilog also allows both the sending and receiving side of ports to be real
variables. The example above can be coded much more simply in SystemVerilog.
module top;
real r; // variable types can be used in netlist
real_out ro (.r);
real_in ri (.r);
endmodule
module real_out(output real r); // output is real variable type
...
endmodule
module real_in(input real r); // input is real variable type
...
endmodule
The example above shows how confusing unconnected or partially connected ports can be. The
value of data has mysteriously changed, gaining an extra four bits. The value of address
changed from 255 to 15 (decimal), and byte_en is high-impedance instead of a valid logic value.
Most simulators generate warnings when code like this is elaborated, but such warnings are not
required by the Verilog standard, and engineers are notorious for ignoring these warnings.
How to avoid this gotcha: SystemVerilog provides a great solution to this gotcha: implicit port
connections using either .<name> or .* module instantiations. When using the .<name> or the .*
module instantiation syntax, the driver and receiver port signals are required to be the same size.
If, for some reason, a driver/receiver signal pair size mismatch is desired, the port must be
explicitly connected. This makes it very obvious in the code that the mismatch was intended.
module top;
wire [7:0] data;
wire [7:0] address;
block1 b1 (.data, // implicit port connections
.address);
endmodule: top
In this example, the size of the wire called data has been corrected to be 8-bits wide, which is the
same as the size of the data port in block1. The .<name> shortcut will infer that the wire called
data is connected to the port called data. However, there is still a typo in the declaration of the
wire called address. Instead of a port connection mismatch (a gotcha), an elaboration error will
Even though signal a is an input to module backdrive, it can still be assigned a value within
backdrive. This has the effect of a having two drivers wired together. This is legal because, in
Verilog, ports are treated as continuous assignments. The code above is equivalent to:
module top;
wire w1;
wire a, b;
assign a = w1; // same as connecting ‘w1’ to input port ‘a’
assign a = b; // a second continuous assignment to ‘a’
endmodule
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:
Why were a and b left-extended with zeros? That question is answered in Section 5.2, which
discusses zero-extension and sign-extension in Verilog.
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: After expansion, the operation is:
a: 010101 (6-bits) a: 00010101 (8-bits)
&b: & 1 (1-bit result of &b) &b: & 00000001 (8-bits)
c: ???????? (8-bits) c: 00000101 (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 SystemVerilog operators are
evaluated, and therefore expecting a different result. The only way to avoid the gotcha is proper
education on Verilog and SystemVerilog. Table 1, below, should help. This table lists the Verilog
and SystemVerilog operators, and whether they are self-determined or context-determined.
Operand
Operator Extension Notes
Determined By
Operand
Operator Extension Notes
Determined By
Arithmetic
context
+ - * / %
Unary Reduction
self Result is a self-determined, unsigned, 1-bit value.
~ & ~& | ~| ^ ~^ ^~
Bitwise
context
~ & | ^ ~^ ^~
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.
{} {{}}
1 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.
2
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.
A gotcha can occur is when an engineer forgets—or doesn’t understand—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
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 (which are 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)
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. This change is 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 SystemVerilog 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.
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. The 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.
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 SystemVerilog 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]);
The preceding example is not a good design example. It does, however, illustrate the gotcha with
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 SystemVerilog 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.
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
How to avoid this gotcha: 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 that modify the 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.
SystemVerilog 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 SystemVerilog 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
How to avoid this gotcha: This gotcha can be avoided by not using operators which make
multiple reads and writes to a variable within the same statement. The DC synthesis compiler
does not permit these types of operations, because of the indeterminate results.
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
Ironically, in an effort to prevent the common C gotcha of if (a=b), the SystemVerilog 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 SystemVerilog’s syntax is different than C’s. GOTCHA!
How to avoid this gotcha: The SystemVerilog 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 SystemVerilog use a different syntax to make an assignment within an
expression.
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!
How to avoid this 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.
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 SystemVerilog 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];
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 SystemVerilog’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];
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];
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.
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 could be written as:
always @(posedge address[15]) // trigger to change on MSB of vector
...
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 usage 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.
How to avoid this gotcha: When modeling combinational logic, the best way to avoid this gotcha
is to use the SystemVerilog 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 Section 6.3). When
modeling sequential logic, engineers need to be careful to avoid using operations within a
sensitivity list.
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 the procedural block, then 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 lead to 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
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.
How to avoid this 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.
SystemVerilog cross coverage can be useful in verifying that all variables that are assigned values
on a clock are also reset.
The gotcha is that 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.
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 correctly coded as follows:
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.
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
A second solution to the gotcha is the SystemVerilog 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
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,
“SystemVerilog Saves the Day—the Evil Twins are Defeated! ‘unique’ and ‘priority’ are the new
Heroes”[6], and “SystemVerilog’s priority & unique—A Solution to Verilog’s ‘full_case’ &
‘parallel_case’ Evil Twins!”[7]. These benefits are not repeated in this paper.
There is, however, a Synopsys DC synthesis compiler gotcha with the SystemVerilog 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.
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.
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!
How to avoid this gotcha: There are two ways to avoid this gotcha. Instead of using the cast
operator, the SystemVerilog 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, SystemVerilog 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
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 synthesis compilers, 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 SystemVerilog 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, refer to the
SNUG paper “Being Assertive With Your X”[8], and “SystemVerilog Assertions are for Design
Engineers, too”[9].
How to avoid this gotcha: This gotcha can be avoided by proper training and an understanding of
how SystemVerilog virtual methods work, coupled with adopting a proper object-oriented
verification usage methodology, such as the Synopsys VMM.
In this simple example, the two always blocks model simple behavioral handshaking using event
data type to signal the completion of one block and enabling the other. The initial block is used
to start the handshaking sequence.
The gotcha lies in the fact that, at simulation time zero, each of the procedural blocks must be
activated. If the initial block activates and executes before the always @(get_data) block
activates, then the sequence will never start.
How to avoid this gotcha: In Verilog, the only way to solve this issue is to delay the trigger in the
initial block from occurring until all the procedure blocks have been activated. This is done by
preceding the statement with a delay, as shown in the code below.
initial #0 -> get_data; // start handshaking at time 0, but after all
// procedural blocks have been activated
Using the #0 delay will hold off triggering the get_data event until all the procedure blocks have
been activated. This ensures that the always @(get_data) block will sense the start of a
handshake sequence at time zero.
But, using #0 is another gotcha! The Verilog #0 construct is an easily abused construct, and does
not truly ensure the delayed statement will execute after all other statements in a given time step.
Many Verilog trainers have recommended that #0 should never be used. There are alternatives
based on the nonblocking assignments that have more reliable and predictable event ordering. Not
using #0 is a good guideline, except for event data types. In Verilog, there is no way to defer the
event triggering to the nonblocking event queue.
SystemVerilog comes to the rescue with two solutions that will remove the event trigger race
condition, and remove the need to use a #0.
How to avoid this gotcha, solution 1: SystemVerilog defines a nonblocking event trigger, ->>,
that will schedule the event to trigger in the nonblocking queue. For the example in this section,
this eliminates the race condition at time zero, and eliminates the need for a #0 delay. Triggering
the get_data event in the nonblocking queue allows for the always procedure blocks to become
active before the event is triggered.
How to avoid this gotcha, solution 2: SystemVerilog provides a second approach that will provide
a solution to many more situations than the simple example shown in this section. This second
solution uses a trigger persistence property that will make the trigger visible through the entire
time step, and not just in the instantaneous moment that the event was triggered.
module event_example2 ( ... );
event get_data, send_data; // handshaking flags
always begin
wait(get_data.triggered) // wait for a get_data event
... // do code to get data
... // when done, trigger send_data
-> send_data; // sync with send_data process
end
The wait (get_data.triggered) returns true in the time step in which get_data is triggered. It
does not matter if the trigger event occurs before or after the wait statement is activated. So, in
the above example, if the initial block activates and executes before the first always block, the
trigger persistence will still be visible when the first always block becomes active and executes
the wait (get_data.triggered) statement.
A second gotcha occurs when a process has to wait for keys. Keys can be retrieved from the
bucket by using the get() method. The get() method is blocking. If the number of keys
requested is not available, the process suspends execution until that number of keys is available.
The get() method has a subtle, non-intuitive gotcha. If the number of keys requested is not
available, then the request is put into a FIFO queue and will wait until the number of keys
becomes available. If more than one process requests keys that are not available, the requests are
queued in the order received. When keys become available, the requests in the queue are serviced
in the order in which the requests were received, First In, First Out. The gotcha is that, when
get() is called (a new request), an attempt will be immediately made to retrieve the requested
keys, without first putting the request into the FIFO queue. Thus, a new request for keys can be
serviced, even if other requests are waiting in the semaphore request queue. The following
example demonstrates this gotcha.
module sema4_example ( ... );
semaphore queue_test = new; // create a semaphore bucket
initial begin: Block1 // at simulation time zero...
queue_test.put(5); // bucket has 5 keys added to it
queue_test.get(3); // bucket has 2 keys left
queue_test.get(4); // get(4) cannot be serviced because the
// bucket only has 2 keys; therefore the
// request is put in the FIFO queue
$display(“Block1 completed at time %0d”, $time);
end: Block1
initial begin: Block2 #10 // at simulation time 10...
queue_test.get(2); // GOTCHA! Even though the get(4) came
// first, and is waiting in the FIFO
// queue, get(2) will be serviced first
queue_test.get(1); // this request will be put on the fifo
// queue because the bucket is empty;
// it will not be serviced until the
// get(4) is serviced
$display(“Block2 completed at time %0d”, $time);
end: Block2
initial begin: Block3 #20 // at simulation time 20...
queue_test.put(3); // nothing is run from the fifo queue
// since get(4)is first in the queue
When a get() method is called and there are enough keys in the bucket to fill the request, it will
be retrieve the requested keys immediately, even if there are previous get() requests waiting in
the FIFO queue for keys. In the example above, the Block1 process begins execution at
simulation time 0. It executes until get(4) is called. At that time, there are only 2 keys available.
Since the request could not be filled, it is put on the queue. The execution of Block1 is then
suspended until 4 keys are retrieved.
Next, a separate process, Block2 requests 2 keys at simulation time 10. The get(2) executes and
retrieves the 2 remaining keys from the bucket immediately, even though there is the get(4) in
the queue waiting to be serviced. The process then executes a get(1). The request cannot be
serviced because the bucket is now empty, and therefore is put on the queue.
At simulation time 30, the Block3 process puts three keys back in the semaphore bucket. The
get(4) request sitting in the FIFO queue still cannot be serviced, because there are not enough
keys available. There is also a get(1) request in the queue, but is not serviced because that
request was received after the get(4) request. Once placed on the queue, the get() requests are
serviced in the order which they were received. The get(4) must be serviced first, then the
get(1).
How to avoid this gotcha: This gotcha of having a get() request serviced immediately, even
when other get() requests are waiting in the FIFO queue, can be avoided, if the get() requests
are restricted to getting just one key at a time. If a process needs more then one key, then it would
need to call get(1) multiple times. When the process is done, it could return multiple keys with a
single put(). It is not necessary to call put(1) multiple times.
The third approach to avoiding a mailbox run-time error gotcha is to use typed mailboxes. These
mailboxes have a fixed storage type. The tool compiler or elaborater will give a compilation or
elaboration error if the code attempts to place any messages with incompatible data types into the
mailbox. The get() method can be safely used, because it is known before hand what data type
will be in the mailbox.
The next example illustrates declaring a typed mailbox.
typedef struct {int a, b} data_packet_t;
mailbox #(data_packet_t) mbox2 = new; // typed mailbox
With this typed mailbox example, only messages of data type data_packet_t can be put into
mbox2. If an argument to the put() method is any other type, a compilation or elaboration error
will occur.
These covergroup bins will count the number of times each state of a state machine was entered,
as well as the number of times certain state transition sequences occurred.
SystemVerilog also provides built-in methods for reporting coverage. It seems intuitive for
coverage reports to list coverage by the individual bins within a covergroup. GOTCHA!
When the SystemVerilog get_inst_coverage() method is called to compute coverage for an
instance of a covergroup, the coverage value returned is based on all the coverpoints and
crosspoints of the instance of that covergroup.
When the SystemVerilog get_coverage() method is called, the computed coverage is based on
data from all the instances of the given covergroup.
The gotcha with coverage reporting is that coverage is based on crosspoints or coverpoints. There
are no built in methods to report details of individual bins of a crosspoint. If the coverage is not
100%, the designer has no way to tell which bins are empty.
How to avoid this gotcha: If the coverage details for each bin are needed, then each covergroup
should have just one coverpoint, and that coverpoint should have just one bin. Then, when the
coverage is reported for that cover group, it represents the coverage for the coverpoint bin.
A gotcha with $unit is that these shared definitions and declarations can be scattered throughout
multiple source code files, and can be at the beginning or end of a file. At best, this is an
unstructured, spaghetti-code modeling style, that can lead to design and verification code that is
difficult to debug, difficult to maintain, and nearly impossible to reuse. Worse, is that $unit
definitions and declarations scattered across multiple files can result in name resolution conflicts.
Say, for example, that a design has a $unit definition of an enumerated type containing the label
RESET. By itself, the design may compile just fine. But, then, let’s say an IP model is added to the
design that also contains a $unit definition of an enumerated type containing a label called
RESET. The IP model also compiles just fine by itself, but, when the design files, with their $unit
declarations are compiled along with the IP model file, with its $unit declarations, there is a
name conflict. There are now two definitions in the same name space trying to reserve the label
RESET. GOTCHA!
How to avoid this gotcha: Use packages for shared declarations, instead of $unit. Packages
serves as containers for shared definitions and declarations, preventing inadvertent spaghetti
code. Packages also have their own name space, which will not collide with definitions in other
packages. There can still be name collision problems if two packages are wildcard imported into
the same name space. This can be prevented by using explicit package imports and/or explicit
package references, instead of wildcard imports (see Section 2.6 for examples of wildcard and
explicit imports).
8.0 References
[1] “IEEE P1364-2005 standard for the Verilog Hardware Description Language”, IEEE, Pascataway,
New Jersey, 2001. ISBN 0-7381-4851-2.
[2] “IEEE 1800-2005 standard for the SystemVerilog Hardware Description and Verification Language”,
IEEE, Pascataway, New Jersey, 2001. ISBN 0- 7381-4811-3.
[3] “SystemVerilog for Design: A Guide to Using SystemVerilog for Hardware Design and Modeling”, by
Stuart Sutherland, Simon Davidmann and Peter Flake. Published by Springer, Boston, MA, 2004,
ISBN: 0-4020-7530-8.
[4] “Signed Arithmetic in Verilog 2001—Opportunities and Hazards”, by Dr. Greg Tumbush.
Published in the proceedings of DVCon, 2005.
[5] “‘full_case parallel_case’, the Evil Twins of Verilog Synthesis”, by Clifford Cummings. Published in
the proceedings of SNUG Boston, 1999.
[6] “SystemVerilog Saves the Day—the Evil Twins are Defeated! “unique” and “priority” are the
new Heroes”, by Stuart Sutherland. Published in the proceedings of SNUG San Jose, 2005.
[7] “SystemVerilog’s priority & unique - A Solution to Verilog’s ‘full_case’ & ‘parallel_case’ Evil
Twins!”, by Clifford Cummings. Published in the proceedings of SNUG Israel, 2005.
[8] “Being Assertive With Your X”, by Don Mills. Published in the proceedings of SNUG San
Jose, 2004.
[9] “SystemVerilog Assertions are for Design Engineers, Too”, by Don Mills and Stuart Suther-
land. Published in the proceedings of SNUG San Jose, 2006.
[10]“RTL Coding Styles That Yield Simulation and Synthesis Mismatches”, by Don Mills and Clif-
ford Cummings. Published in the proceedings of SNUG San Jose, 1999, and SNUG Europe,
2001.