Return of The SystemVerilog Gotchas - Bresticker
Return of The SystemVerilog Gotchas - Bresticker
Shalom Bresticker
Intel Corporation
ABSTRACT
All programming languages have gotchas: easy-to-make errors or misunderstandings that look
OK, but don't work, don't behave as expected, or differ between tools. The canonical example
from C is writing "if (a=b)" instead of "if (a==b)". Knowing about SystemVerilog's gotchas helps
prevent making these mistakes, and eases detecting and debugging them when they do occur.
Sutherland and Mills published a book of gotchas in 2007, but there are many more. This paper
presents a selection of additional gotchas that have bitten the author and his colleagues over the
years, some of them very nasty. The gotchas are divided into (1) gotchas that exist in Verilog (2)
gotchas that are new in SystemVerilog (3) gotchas connected to how the RTL code is
synthesized.
This is a revision of a paper of the same name that was presented at Boston SNUG 2008.
This work does not in any way constitute an Intel endorsement of a product or supplier.
Table of Contents
Plenty, it turns out. That‘s bad for users and application engineers, but it‘s good for authors of
papers.
What is a gotcha? A gotcha is code that looks right, feels right, and smells right ... but isn‘t. (See
[21] for a fuller definition.) If you‘re lucky, it won‘t pass compilation, so that you‘ll know
immediately that something is wrong. If you‘re less lucky, it will do something obviously wrong
in simulation, like getting stuck in reset. If you‘re really unlucky, it will pass compilation and
simulate, but with a subtle error that you‘ll be hard pressed to detect. And in the worst case, it
will cause a bug in silicon.
Two papers in recent SNUGs have described Verilog and SystemVerilog gotchas, by Stuart
Sutherland and Don Mills (and Chris Spear on the second one). Stuart and Don followed this up
with a book that took the two papers and revised them, as well as adding additional gotchas.
This paper follows in their footsteps. The list of gotchas is not over yet, and even after this paper,
we still have more. Gotchas come in various degrees of severity, but we will try to describe some
of the nastier ones, which we or our colleagues have actually experienced. However, we will also
show a few mild ones as well.
The gotchas come from all versions of Verilog and SystemVerilog, so that both a person who is
still using only Verilog-1995 and one who is writing in SystemVerilog can learn from this paper.
In addition to gotchas in the language, we will also mention a few gotchas which derive purely
from how various constructs are synthesized.
This is a revised version of a paper of the same name that was presented at Boston SNUG 2008.
Minor changes were made in various places, sections 2.11, 3.9-3.12, and 4.6 are new, and
reference [22] was added.
LRM - Language Reference Manual. Refers to the language standard documents. See the
References section at the end of the paper for the exact references.
V95 – Verilog-1995, IEEE Std 1364-1995, Reference [4].
V2K1 – Verilog-2001, IEEE Std 1364-2001, Reference [5].
V2K5 – Verilog-2005, IEEE Std 1364-2005, Reference [6].
SV – the SystemVerilog language, in a generic sense.
SV-2005 - SystemVerilog-2005, IEEE Std 1800-2005, Reference [7].
SV-2009 - SystemVerilog-2009. The next revision of IEEE Std 1800, in progress,
planned to be approved in 2009.
WG – Working Group. Refers to the IEEE P1800 SystemVerilog standards committee.
Mantis[35] is the issues database of the IEEE 1800 SV standards committee. Mantis is the name
of an insect, i.e., a "bug", but the database contains clarification and enhancement requests as
well as reports of errata in the standard. This paper references some Mantis items. Read access to
the database is public. To get to an issue, you go to http://www.eda.org/svdb, and log in with
Username and Password both ―guest‖. Then in the upper right hand corner, you enter the Issue #
in the box that says ―Issue #‖ and press on ―Jump‖. That‘s it. (As shorthand, we often say simply
"Mantis N" instead of "Mantis issue number N". I use "Manti" as a shorthand plural form
instead of "Mantis issues".)
But seriously, this is actually an important question, because sometimes people really ask why
there have to be gotchas. So here are a few serious answers:
ALL programming languages have gotchas. It is inherent in their nature. It does NOT mean that
SV is a bad language, but it does mean that you need to be careful. There are some languages that
have fewer gotchas. Generally, they are more verbose and less flexible and users don‘t like them
(e.g., ADA). Such languages can be good for writing code for life-critical applications, like
spacecraft.
It is also important to understand that most of the members of the standards committee work
either as volunteers on their own time, or at best their employer allows them a limited amount of
time to work on it. There are very few people, if any, whose main assignment from their
employer is to work on the SV standard.
Sometimes you hear comments that basically accuse the standards committee of making silly
decisions. Sorry, I disagree. They sometimes make errors or bad decisions, like all humans, but
the committee contains some of the top industry experts. Usually there was a good reason why
the standard was defined in a certain way. In retrospect, it can sometimes turn out that it was not
Ambiguities can arise because the contributors to the standards committee are engineers, not
technical writers. Sometimes, they write not in the best way. They may forget that readers of the
LRM are not as expert in the language as they are. Sometimes they thought that what they wrote
was clear or that a particular point was obvious, and maybe it is to most readers, and yet there
may still be a significant minority of readers for whom it is not clear or obvious. Another
problem is that the full description of all the information related to a particular construct is
sometimes distributed over several places, or simply difficult to find.
Additionally, the textual descriptions in the LRM are often informal. This is actually good, as it
makes the LRM much more understandable than many others I have seen, but has the price
sometimes of less preciseness. There is sometimes a tradeoff between clarity and preciseness.
Finally, sometimes the committee knew there was an issue, but did not have time to handle it.
With limited resources (manpower, time), priorities are often determined by importance and
complexity.
You think it should compile/elaborate cleanly, but it doesn‘t. Either it fails completely or it
gives an unexpected warning.
It may compile, but behave differently than you expected. It may not work at all, such as
producing X outputs, getting stuck in a loop, or not finishing reset.
One of the most dangerous types of gotchas is where it behaves differently, but not blatantly.
Then it is hard to spot that there is even a problem.
Another difficult type of gotcha is where it fails intermittently. Then you also have to try to
understand under what conditions it fails.
A gotcha may express itself by working on some tools or tool versions and failing on others.
Another way to classify gotchas is whether they are written "accidentally" or "deliberately". By
this, I mean the following:
An "accidentally" written gotcha is one where you write something different than what you
really intended to write, due to a typographical error or due to forgetfulness, for example. If
someone would point out to you what you wrote, you would say, "Oops!". This turns from a
simple error into a gotcha if it is difficult to spot the mistake or if instead of completely
failing, the code works but wrongly.
A "deliberately" written gotcha is one where you wrote what you intended to write, because
that is how you believe and understand it should be written, and you are unaware that your
understanding is incorrect or that there is some problem with it. If someone would point out
to you what you wrote, you would say, "So what? What is the problem?".
One of the classic gotcha forms is where there are two similar syntax forms that are both legal,
but behave differently. You may get confused between them, or simply accidentally write the
wrong one. Gotchas 2.3 and 2.7 below are of this type.
A major source of gotchas is ambiguities in the LRM. There are cases that are unspecified. In
some places, there is unclear or misleading language. Unspecified cases can occur because no
one thought of them or because someone did think of them but thought that the LRM does
specify the behavior in that case, and only deeper thought shows that it is not specified well
enough. Gotcha 2.10 below is an example of this.
Sometimes an ambiguity is deliberate because the committee was unable to reach a consensus on
the desired behavior and just leaves the issue open. Gotcha 3.3 below is one that is still
unresolved for this reason.
Other times, though, users or developers misunderstand the LRM even though the LRM does
explain it clearly. Someone may have a blind spot in reading that section of the LRM.
Occasionally, people make an incorrect deduction because some statement that appears in one
place is not repeated somewhere else, and then they (incorrectly) conclude that the statement
does not apply in the second place.
Among the worst problems is where tools implement an ambiguity differently. Then your code
becomes non-portable. You may not even be aware of the problem and discover it at the worst
possible time. Gotcha 3.4 below is a good example of this.
Another source of gotchas is errors in the LRM. It is not so bad when the error is obvious. But
when it is not so obvious, one tool may implement the LRM as written while another implements
the ―correct‖ behavior. Similarly, one user may expect the tool to implement the LRM as written,
whereas another may expect the tool to implement the ―correct‖ behavior. A mismatch between
the user and the tool, moving the code from one tool to another, even one user reusing the code
of another, may all result in unexpected behavior.
Another problem is differences between versions of the language. You can get confused. The
differences are almost not documented in the LRM itself. So what worked till now might not
work or might not work the same way in the future, although the standards committee tries to
minimize that. The biggest problem is usually new keywords, but occasionally there are other
things, such as generate syntax and semantics that changed from V2K1 to V2K5.
You especially have to be careful when writing code according to a newer version of the LRM,
then moving to a tool that uses an older one. In fact, sometimes different tools from the same
vendor implement different versions of the standard. And two versions of a tool may implement
different versions of the LRM. Or a tool may implement some features according to one version
(To be fair, the tool vendors have a tough job. Each version of the standard adds a bunch of new
features and corrects errors in the previous versions. Vendors are always playing catch-up.)
There are recognized gotchas that the committee would love to remove but is unable to do so,
because that would break back-compatibility. Legacy code would stop working.
A lot of SystemVerilog is based on older languages such as C and Vera. In some cases, SV
implements features in the same way as the older language and sometimes differently. Where it is
the same as the older language, it inherits gotchas from that language. Where it is different, there
is a new gotcha: people expect it to be like the older language, but it is different. Some days, you
just can't win!
Built-in default behavior in the language can also be a gotcha source. What happens when you do
not specify something explicitly in the code? If you are unaware of or forget the default behavior,
you may get unexpected results. An example is how module port attributes, such as direction,
kind (net or variable), and packed and unpacked dimensions, are inherited from previous ports in
the port list. (This particular one is clarified in the next revision of the LRM, see Mantis 1465.)
There are other sources of gotchas, but this paper is supposed to be about the gotchas themselves,
not where they come from, so we'll stop here.
It can help us prevent them from occurring. Awareness causes more careful coding. You can
formulate coding guidelines that reduce the risk of making these mistakes. Context-sensitive
editors (e.g., with auto-indent) can aid us.
Knowing about gotchas helps us to detect them after they have already been written. We can look
for them in code reviews. We can have Lint tools check for them.
When trying to debug a problem, they give us ideas of possible causes to look for, at least sub-
consciously. We can try to match the symptoms with possible causes. Experience also gives you
an idea of the frequency with which gotchas occur. Just hearing the symptom often enables you
to make a good guess of the problem.
Since some gotchas come from ambiguities and errors in the LRMs and from the fact that the
2005 standard is comprised of a combination of the Verilog and SystemVerilog LRMs, it is
helpful to look at an up-to-date draft of the new merged SystemVerilog LRM (if your company
has access to it). Besides eliminating most of the confusion that resulted from having two
Finally, consider participating in the IEEE P1800 SV standards committee. This brings you
together with the top SV experts in the industry and brings to your attention issues that you
would never otherwise hear about, often before you encounter them yourself. It also gives you a
forum before which you can bring up issues for discussion. Of course, it is not for beginners, but
you won‘t be a beginner any more after reading this paper!
At SNUG Israel 2007, Qualcomm engineers presented gotchas related to the Vera constraint
solver [22]. Since that part of SystemVerilog was based on Vera, those gotchas are largely
relevant to SystemVerilog as well. The same engineers are presenting more Vera gotchas at
SNUG Israel 2009.
Earlier, Don Mills and Cliff Cummings compiled some gotchas related to synthesis-simulation
mismatches ([14]) and there were a number of papers that touched on various specific types of
gotchas, particularly related to X-propagation (See [13] and [15]-[19], for example).
Two excellent recent books devoted to teaching the design and verification parts of
SystemVerilog ([9] and [10]) made a point of noting gotchas related to the new SystemVerilog
features discussed.
[11] is a very nice book written in Question and Answer fashion that also discusses a number of
gotchas. Although the book title mentions only Verilog, it also discusses some SystemVerilog.
Finally, the Doulos SV Reference Guide ([12]) is organized according to alphabetical order of
language construct names. Many of the sections include, in addition to a description of the
construct, "Tips" and "Gotchas!" related to the construct. Very nice.
However, there is a case where that can get you into trouble. Suppose you have two always @*
procedures containing for-loops and you use the same loop index variable for both, like this:
integer k;
reg [31:0] out1[0:7], out2[0:15], in1, in2;
always @*
for (k = 0; k < 8; k = k + 1)
out1[k] = in1 + k ;
always @*
for (k = 0; k < 16; k = k + 1)
out2[k] = in2 * k ;
Why not? In Verilog-1995, we used the same loop index with multiple loops all the time for RTL
without problems. (Gotcha 66 in [3] does show a potential problem, but that rarely occurred in
synthesizable RTL.)
Suddenly, in V2K1, we find that some simulators get stuck. All we did was change the sensitivity
list to @*. Darn those language developers! Can‘t they leave well enough alone??
What happens here is that according to the LRM of V2K1, the first always @* turns into
―always @(in1 or k)‖ and the second turns into ―always @(in2 or k)‖. Note that k
appears in both lists.
Suppose that in1 changes. This triggers the first always @* procedure. It executes, including
the loop, and k changes a number of times. k changes, you said? That triggers the second
procedure, even though in2 has not changed. The second procedure executes unnecessarily,
wasting simulator time, but that is not the worst of it. When the second procedure executes, it
also executes its for-loop, also changing k. This retriggers the first procedure. And so they go on,
back and forth, with the simulator stuck in an infinite loop. GOTCHA!
Why did this not happen in Verilog-1995? Because then we did not put the loop index into the
manual sensitivity list. But in always @*, it is put in automatically. (This problem does not
occur with all simulators. Apparently some of them don‘t put the loop index into the sensitivity
This only occurs when both loops are in always @* procedures. If only one loop is, and the
other is in a sequential always @(posedge clk) procedure or a regular always procedure
with a manual sensitivity list and the loop index does not appear in the list, then this will not
happen.
Workarounds:
How can we fix this? One way is to avoid using always @* with loops, but we would like to
avoid writing the sensitivity list manually. Within Verilog-2001, we have two workarounds. We
have to use different variables for each of the loops. We can declare two different variables
globally, like this:
always @*
for (k1 = 0; k1 < 8; k1 = k1 + 1)
out1[k1] = in1 + k1 ;
always @*
for (k2 = 0; k2 < 16; k2 = k2 + 1)
out2[k2] = in2 * k2 ;
Another way is to declare the loop variables locally within the always @* procedures, like
this:
always @* begin:loop1
integer k;
for (k = 0; k < 8; k = k + 1)
out1[k] = in1 + k ;
end
always @* begin:loop2
integer k;
for (k = 0; k < 16; k = k + 1)
out2[k] = in2 * k ;
end
If we can use SystemVerilog, then we can change the always @* to always_comb, which
will not put in the sensitivity list any variable that is written to within the procedure (which has
This also makes each loop variable separate. This is the best solution, as using the same variable
for the different loops is what caused the problem in the first place.
always @*
`ifdef FULL
out = in ;
`else
out = 0;
This did not work on some tools when FULL was not defined. It reduces to an always
procedure with a null sensitivity list, and was never executed. There was nothing to trigger its
execution. GOTCHA!
Changing the always @* to always_comb would solve the problem, but we were not using
SV. In the real case, the code inside the `ifdef FULL was actually a case statement of
substantial size, so the code rewrite was more work. But the big problem was simply to
understand why it did not work.
A variation of this is where the right-hand side contains a variable, but the variable never
changes. This can occur, for example, if the variable is initialized in the variable declaration, like
this:
logic a = 0 ;
This is called a variable declaration assignment. Such initializations are executed before the
always procedure is activated. Thus, although the variable‘s value changes at the time of
initialization, no change in the value occurs after activation of the always procedure.
This can also occur if the variable is initialized in an initial procedure at time 0. Since no
order is defined between initial and always procedures, the initial procedure may be
executed first. Or it can occur if there is some force to the variable value before the always
procedure starts working.
The logical AND and OR operators treat their operands as either TRUE or FALSE, non-zero and
zero, respectively. They return a 1-bit result, either 1‘b1 or 1‘b0 (or 1‘bx if the result is
unknown). Thus, 2’b10 && 2’b01 is TRUE because both operands are non-zero and returns
1‗b1. This is like writing (2’b10 != 0) && (2’b01 != 0).
On the other hand, the bit-wise operators treat each bit of the operands individually, size-extend
the shorter operand to the length of the longer operand, and return an N-bit result, where N is the
size of the longer operand. Thus, 2’b10 & 2’b01 is 2‘b00.
For 1-bit operands, the logical and bit-wise operators behave the same. However, a myth has
developed that says that you need to use logical operators for control signals and bit-wise
operators for data-path signals. No! Verilog simulators have no understanding of ―control‖ and
―data-path‖. Both forms of operators simply work according to their definitions in the LRM,
which are identical for one-bit operands.
The gotcha which can occur is using logical operators instead of bit-wise operators where the
operands are vectors. This mistake is very easy to make. It is simply to type the & or | character
twice instead of once. And the code is perfectly legal. It's just wrong.
Fortunately, it is uncommon to write code with logical operations on vectors, like this:
It is much more common and also much more readable to write it with explicit comparisons to
zero, like this:
Thus, we can configure our Lint tool (and we should all be using a Lint tool) to flag logical
ANDs and ORs with vector operands.
As a coding guideline, I strongly recommend to use only bit-wise AND and ORs in synthesizable
RTL, and not use logical ANDs and ORs at all. This has several reasons. One is that it avoids this
gotcha. Another is that anything that can be written with the logical operators can be written with
the bit-wise operators as well (by comparison to zero), whereas the reverse is not true. Writing
There are nevertheless two other differences between logical and bit-wise operators that fairness
requires me to mention. One is that some code coverage tools check logical operators but not bit-
wise operators. But these can usually be configured to cover the bit-wise operators as well.
In practice, however, many tools implement short-circuiting as in C, on logical operators, but not
on bit-wise operators. In practice, short-circuiting did not make any difference in real
synthesizable RTL descriptions. However, in behavioral or test-bench code, you might want the
short-circuiting behavior. The upcoming SV-2009 LRM will codify this behavior (Mantis 997).
What happens if N is zero? The 1995 LRM does not say. Most tools replace the entire replication
by 1‘b0. So {b, {0{a, b}}} becomes {b, 1’b0}.
A true zero replication would actually be very useful. What do you want/expect to happen if the
replication constant is zero? You want the replication to disappear entirely! The most common
place where this occurs is where you use replication to add N leading zeros to an expression that
is P-N bits wide in order to get a total of P bits. For example, if N is a parameter in the range 0 to
31,
This works fine for N=1 up to 31. What happens if N is 0? Now you have
But what happens if the replication is not of leading zeros, but in the middle or end of your
concatenation? Suppose it was like this:
The Verilog-2001 LRM attempted to deal with this gotcha by declaring zero replications illegal,
but it did not help much. Now many tools will issue a warning on a zero replication, but
engineers are infamous for ignoring warnings. Furthermore, since the vast majority of cases are
the harmless leading zero cases, you can‘t find the one warning which points out a real problem.
GOTCHA again! And even if you do find the real problems, the warning does not help you to
actually solve them.
The Verilog-2005 LRM attempts to deal with this in a better way. As we said, when the
replication constant is zero, you want the replication to disappear entirely That is exactly what
the 2005 LRM says should happen. In order for this to not cause the problem of having an
expression of zero bits, the LRM also says that a zero replication can only appear as part of a
concatenation where at least one element of the concatenation has a positive length. So {b,
{0{a, b}}} is legal, but {{0{a, b}}} is not. Unfortunately, most tools are still not
implementing this part of the 2005 LRM and are still doing the same erroneous behavior of the
past. GOTCHA yet again! They probably do this for reasons of backwards compatibility.
What can you do today if the current zero replication behavior will cause an error? Typically,
intermediate variables, shift operations, and part-selects can be used to make sure that the code is
correct for all values of the replication constant. For example, in our last case,
As a guideline, where a replication is used with a parameterized replication constant, you should
verify that the code works correctly for the maximum and minimum values of the replication
constant.
Like zero replications, the Verilog-2001 LRM also makes these illegal. But unlike zero
replications, these remain illegal in the 2005 standard, and should cause compilation errors.
However, many tools treat these in the same way as zero replications, turn them into 1‘b0, and
just give a warning. So you both only get a warning, which many ignore, and it also ends up
doing something strange. GOTCHA!
Sometimes the warning just says that the replication constant is ―zero or negative‖, and then you
have to examine it to see whether it is zero or negative.
In the following example, the parameter DEV_NUM determines how many times to instantiate
module mm, each time with a different value of parameter S. The code allows as many as three
instantiations, but it has been determined that only 2 are to be used in this version of the design,
so only S0 and S1 are needed. S2 has been set to 0 since it will not be used. mm will not be
instantiated using S2. Anytime S2 is to be used, it will be given a positive value.
module top;
parameter DEV_NUM = 2;
parameter S0 = 2, S1 = 1, S2 = 0;
wire sig[31:0];
generate
for (genvar ii=0; ii <DEV_NUM; ii++) begin :loop
mm mm #(.S((ii == 0) ? S0 :
(ii == 1) ? S1 : S2 )
(.sig((ii == 0) ? sig[S0-1:0] :
(ii == 1) ? sig[S1-1:0] : sig[S2-1:0]);
end
endgenerate
endmodule
But, you say, ii never gets to be 2! It does not matter. The code generated for ii = 0 will be
(.sig((0 == 0) ? sig[2-1:0] :
(0 == 1) ? sig[1-1:0] : sig[0-1:0]);
(.sig((1 == 0) ? sig[2-1:0] :
(1 == 1) ? sig[1-1:0] : sig[0-1:0]);
It is true that in simulation, short-circuiting will occur, but in the elaboration stage of the
compilation it sees sig[-1:0].
In this simple example, of course, we can just set S2 to a positive number, but in the real case
this came from, S2 needed to be 0.
(.sig(sig[((ii == 0) ? S0 :
(ii == 1) ? S1 : S2)-1:0]);
In this way, the index needed is computed as part of a constant expression that is computed at the
elaboration stage. It never tries to actually reference sig[-1:0].
There is an additional advantage to this recoding. In the original code, there is a conditional
selection between sig[S2-1:0], sig[S1-1:0], and sig[S0-1:0]. Each has a different
length. Even if S0 did have a legal value, the compiler would do size-extension of each to the
length of the longest of them. This might not cause a functional problem (though it might in
some cases), but at the very least, you get port size mismatch warnings from the tools.
Sometimes the recoding is considerably more difficult than in this simple example.
A regular part-select is of the form [c1:c2], where c1 and c2 are constant expressions. The
placement of such a part-select is therefore constant. V2K1 introduced indexed part-selects to
allowed part-selects of constant width, but with variable positions. An indexed part-select has the
form [n +: c] or [n -: c], where n can be a constant or a variable, and c is a constant
Suppose you forget whether the indexed part-select is written +: or :+? It took me a few years
to get it straight in my mind. What happens if your fingers are dyslexic and type them in the
wrong order?
Example:
logic [255:0] cucu;
...
if (cucu[95:+32] == 0) ... // instead of cucu[95+:32]
In this example, the user wants to check the value of the 32 bits 127 to 95 from the vector cucu,
but by reversing the symbols of the indexed part-select operator, he instead got the 64 bits from
95 to 32. GOTCHA!
If your coding guidelines are strict on bit-size matching and you use a Lint tool, then you have a
good chance of detecting this error. For example, if you wrote
then the Lint tool could flag that the left-hand side of the comparison is 64-bits wide, whereas the
right-hand side is only 32 bits wide, and would indicate that something is wrong. It could still
take you time to figure out the problem, because the code looks correct, but at least you have a
hint of the problem.
It is possible to have a situation where even the size would remain the same, such as [15+:8]
vs. [15:+8], both of which are 8-bits wide, but that is rarer.
then the shorter expression of expr1 and expr2 is extended to the size of the wider one. But
suppose we have something like this:
integer file;
file = $fopen({"filename", dat1 ? ".dat1" : ".dat"}) ;
In this contrived example, we concatenate a file extension .dat1 or .dat to the given
filename, where a variable called dat1 tells us the type of the file. If the variable dat1 is true,
there is no problem, we open a file named ―filename.dat1‖, but if dat1 is false, then we try
to open a file called ―filename .dat‖, with a space before ―.dat‖, which is extended to the
size of ―.dat1‖ before being concatenated to ―filename‖. GOTCHA!
Actually, the shorter string literal is not extended with a space character, which is x20 ASCII, but
rather with zeroes (zero-extension), which are null characters. However, when used as a string,
this often becomes a space.
Note that if we had assigned the concatenation to a variable of string type, this would not
occur.
string temp;
temp = {"filename", dat1 ? ".dat1" : ".dat"} ;
file = $fopen(temp) ;
The shorter string literal would still be zero-extended. However, upon assignment to string
variables, null-characters are ignored, so ―.dat‖ would still be appended directly to
―filename‖.
... `CUCU * 12
You want to get (a+4) * 12. Since macros are pure text substitution, what you really get is a+4 *
12, which is a + (4*12), which is not what you wanted. This can happen when a macro defines an
arithmetic or boolean expression that is then substituted into code next to an operator of higher
precedence. In this example, the macro contains the addition operator, but is substituted into text
next to a multiplication operator, which is of higher precedence.
But this is not enough. The book Code Complete, section 5.9, shows that if you have a macro
with arguments, then each use of the arguments also has to be enclosed in parentheses. For
example:
... `PR(a+3,2)
This will still give the wrong result, a+3*2 instead of (a+3)*2. Instead the macro has to be
written like this:
`define HI Hello
initial $display("`HI, world");
The LRM has not been sufficiently clear. In practice, some tools have interpreted the LRM in one
way and some have interpreted it the other way. This causes the problem that people have written
code assuming that a text macro call inside a string literal is expanded, because that is what their
tool does, and then the code no longer works when moving to a different tool. GOTCHA!
One of the justifications given for expanding macros within a string literal has been that this is a
very useful feature. The opposing opinion has been that this is inconsistent with the Verilog
concept that a string literal is a single lexical token. Furthermore, SystemVerilog has added the
ability to construct string literals (within macro text) using the `" token.
The IEEE SV WG has resolved this issue in Mantis 1119 in accordance with the opinion that no
expansion of any sort occurs within string literals. This means in addition that if a macro text
definition contains a string literal, and that literal contains a character sequence that matches the
name of a formal argument to the macro, it also is not recognized there as a call to the argument
and not expanded. Thus, in this example,
no expansion of macro calls or macro arguments occurs within the string literals and the result is:
`HI, world
`HI, world
Hello, x
module top(
a, b,
/* c */, d,
e, f) ;
The answer is that between ports b and d, there are two consecutive commas:
The third port is called a "null port" or an "empty port". It has no name, size, or direction, but
exists all the same. And it is legal syntax. It may even compile without any warnings.
Until you try to instantiate and connect it. Then you start getting warnings about unconnected
ports or if you connect the ports by position, you may get a type match error between the port
type and the connection type. GOTCHA!
This mistake is especially likely to occur when you list the ports one per line with a comma at the
end and then delete or comment out the last one, like this:
module top(
a,
b,
// c
When you get "unconnected port" warnings, and you can see that the number of connections is
correct, it is a good bet that this is the problem.
This error is also less likely to pass undetected by the tools when declaring ports in "ANSI-style,"
where the port direction and size as well as the port name appears in the module header. The
following will probably not compile, for example:
module top(
input a,
output b,
// c
);
defines two different types and you cannot simply assign a variable of one type to a variable of
the other, even though the type contents are identical. You must use an explicit type cast.
GOTCHA!
Furthermore, if the type declaration of AB_t is found in module m, and m is instantiated twice, as
m1 and m2, then the two types m1.AB_t and m2.AB_t are considered different types and again
cannot be assigned from one to the other without an explicit cast.
An anonymous type declaration also defines its own type. An anonymous type declaration is
where the type definition appears as part of the variable declaration, and not as a separate
typedef. For example:
AB4 and AB5 are defined with the same anonymous type declaration, and so they are
assignment-compatible, but AB6 has a separate anonymous type definition and thus is not
assignment-compatible with AB4 and AB5 without a cast, even though the type definitions are
identical.
As stated in the LRM, these restrictions apply to enums, unpacked structures and unions, and
classes. So they do not apply, for example, to packed structs or to arrays, packed or unpacked.
So a function can return an unpacked struct, for example, but you won‘t want to define the struct
as an anonymous type in the function header, like this:
because then you will not be able to assign the function return value to another variable in the
calling scope, as they will be considered to have different types:
This made for confusion between these and concatenations, which had essentially identical
syntax. It made life more difficult for tools also.
In IEEE 1800-2005, the syntax of structure and array literals and expressions (now called
"assignment patterns") was changed to require an apostrophe before the curly brackets, like this:
bit unpackedbits [1:0] = '{1,1};
This initializes the queue array q to three elements, where q[0]=3, q[1]=2, and q[2]=7.
We also have examples like the following, which describes insertion of a new element to a
queue:
q = {q[0:pos-1], e, q[pos,$]}; // insert 'e' at position pos
This creates the dynamic array p with contents "a", "b", "c", "hello", "d", and "e".
This is obviously not a regular concatenation, whose elements can only be sized scalars or
vectors, but not array slices (e.g., d[1:3]) or unsized integers (e.g., 3).
These examples show a sort of concatenation where an element that is an unpacked array, such as
d[1:3], is expanded into its individual members, as though you had written d[1], d[2],
d[3].
There was a long discussion in the IEEE SV committee as to whether this syntax and these
examples should be legal, or whether they should be considered errors in the LRM and queue
concatenations should use apostrophes, like other assignment patterns. Tool support is not
consistent. So do you use apostrophes or not? Only your compiler knows for sure. GOTCHA!
In the end, it has been decided to allow all of these examples and define an ―unpacked array
concatenation‖ that will have the shown behavior. The exact description is beyond the scope of
this paper, but can be found in Mantis 1702, and is part of the draft for the next version of IEEE
1800, in 2009.
For example,
expands to
bar_suffix
What happens if the left-hand side of this ―glue operator‖ is a back-slashed character string (i.e.,
an escaped identifier)? For example, suppose you have the following:
You want to instantiate the mod module with an instance name that includes the name of the
output signal. However, the output signal can be a bit-select, containing square bracket
characters. In order for the name to include such characters, the created identifier has to be an
escaped identifier. So you prefix the left-hand side of the glue operator with a backslash.
The question is whether a double back-tic is recognized as a glue operator in this case. Normally
in an escaped identifier, all the characters after the backslash, until the concluding white space,
are considered part of the identifier name and lose any other special meanings they might
normally have. So a plus character does not mean addition, a quotation mark does not start a
string literal, and a square bracket does not indicate a bit-select.
The LRM does not indicate what happens in this case. VCS does treat the double back-tic as a
glue operator in this case, which is fortunate, because that is what you want. However, many
other tools do not do so. GOTCHA!
This issue came up to discussion in the IEEE SV committee as Mantis 1537. No consensus has
been reached. However, there is a lot of sentiment that treating the double back-tic here as a glue
operator is inconsistent and not backwards compatible with Verilog, but nevertheless a desirable
capability. There were several proposals as to alternate ways to do this, but no proposal has been
approved, and thus this will not be clarified any more in SV-2009. This means that tool behavior
in this case will continue to be inconsistent.
Normally, quotation marks (" ") begin and end strings literals. Everything between the
quotation marks is considered part of the literal. That means that you could not construct a string
literal containing a call to a macro (See Section 2.10, above). Nor could a string literal appearing
SV added a `" character sequence for macros that is transformed into a quotation mark in the
macro expansion, but still allow macro arguments and macro calls to be recognized correctly.
SV also added a `\`" to generate a \" character sequence, and the double back-tic (``)
sequence to act as a ―glue operator‖, as described in the preceding section in this paper.
The LRM says this: ―In SystemVerilog, the macro text can also include `", `\`", and ``.‖
The question is, can these special character sequences be used outside macro text as well? The
LRM does not say explicitly whether they can or cannot. It only describes them in the context of
macro text, but some people apparently thought either that the LRM was only saying that they
can also be used in macros or decided to extend the standard to allow them elsewhere as well.
While many tools implement the more restrictive interpretation of the LRM, which does seem its
plain meaning, the VCS implementation allows them elsewhere as well. And where a tool has an
extra capability, by golly, some people are going to use it. Many of those people are unaware that
other tools do not implement this extension, and their code no longer compiles when they try it
on another tool. GOTCHA!
One aspect of this is when an actual macro argument is substituted for the formal argument into
the macro text. Is the white space around the macro argument considered part of the argument or
not? For example, you can call a macro A with an argument, like this:
`A( arg )
Is the argument considered to be ―arg‖ and the spaces around it have no significance, or is the
argument considered to be ― arg ―?
In Verilog, it is very difficult, maybe impossible, to find a case in which this makes a difference.
That is why this issue was not important in the past. But in SystemVerilog, where you can
construct a string literal from macro arguments, it now becomes important.
For example,
Does this display "AA0BB" or "AA 0 BB" or "AA0 BB", etc. Again, it turns out that not all
tools do the same thing. GOTCHA!
Most tools do drop the white space around the arguments both before and after the argument, but
there are exceptions.
3.6 String equality and compare functions have opposite return values
SV introduced a string data type and a number of operations that can be performed on it. In
SV-2005, Table 4-2 describes the string equality operation: str1 == str2 works like the
regular == equality operator and returns 1 if the strings are equal and 0 if they are not.
The LRM also defines the compare() string comparison method. Intuitively, you would expect
this to work the same as the other comparison operator, ==. The LRM does not say otherwise. It
just says, ―str.compare(s) compares str and s, as in the ANSI C strcmp function,‖
without specifying the behavior of strcmp. You might not remember or be aware of the
definition of strcmp, or you might simply be unaware that compare() is defined in terms of
strcmp.
However, the return values of strcmp are the opposite of those of the equality operator. So
compare() returns 0, not 1, if the strings are equal and 1 if the strings are different.
GOTCHA!
Thus, where you would write ―if (str1 == str2)‖, you must write ―if
(!str1.compare(str2)). See also Solvnet article 022593
(https://solvnet.synopsys.com/retrieve/022593.html) on this subject.
A related Verilog gotcha is when comparing two vectors. Many people remember that the bit-
wise XOR operator ^ can be used to compare two vectors. People often gets its polarity mixed
up, and forget that vector1 ^ vector2 is 1 if they are different and 0 if they are the same.
One problem in the definition of how they work in SV-2005 is that glitches can occur. That is,
variables can go through intermediate state combinations when they are changing that cause
unique and priority violations to occur. These cause false warnings to be displayed, even
though no violation occurs after the signal values have stabilized.
always_comb
ff1_d = ff1 && 1 ;
In this example, the unique case asserts that ff1_d and ff1 are the same. When the
always_ff procedure executes, it assigns a value to ff1, which is then propagated to ff1_d.
All the simulators tested issued a violation message of the unique case between the
assignments to ff1 and ff1_d. Now you have to distinguish between the true violations and
the false violations. GOTCHA!
Intel‘s CPU projects suffered from this problem, and we filed this with the IEEE SV WG as
Mantis 2008. A solution was devised and it will be part of the SV-2009 revision of the LRM. A
high-level description of the solution is this: if an always_comb procedure containing a
unique or priority if-else or case statement is triggered more than once during the
same time step, then only violations detected during the last execution of the procedure during
the time step will be reported, and violations detected during previous executions of the
procedure during the same time step will be flushed.
This will work for zero-time glitches in the values of the variables. It will not help for glitches
whose length is more than a single time step, but it is assumed that at RTL level, most of the
glitches will be zero-time.
This problem was filed with the IEEE SV WG as Mantis 2005. A solution was devised which
has the same basic idea as the solution for unique and priority, described in the previous
section, and this will also be part of the SV-2009 LRM. However, there is a significant difference
between the two solutions.
For unique and priority, the committee found that no one was interested in leaving the
glitchy behavior available, and so their behavior was redefined so as to eliminate the glitches. No
change in the code by the user is required in order to get the new behavior.
For immediate assertions, the committee decided not to change the existing behavior, in order to
avoid problems of backward compatibility. Remember that while unique and priority are
limited to very specific contexts, immediate assertions can be used much more generally, and it
was feared that changing the existing behavior might cause problems.
So a variation of immediate assertions was defined, called deferred assertions. Whereas a regular
immediate assertion is specified by writing assert, a deferred assertion will be specified by
writing assert #0. Verilog users are used to the meaning of #0 as a sort of "delta delay", so
this syntax can be easily understood.
SystemVerilog does not let you do this. To use an assignment in an expression, you have to add
an extra pair of parentheses: "if ((a=b))".
The LRM relates to this as preventing the C gotcha. So does Gotcha 44 in [3]. In fact, the authors
in [3] note that this unavoidably creates a new gotcha, where someone will try to write as in C, a
language he is familiar with, without the extra parentheses, and then not understand why it fails
compilation.
The fact is, however, that SystemVerilog's requirement of extra parentheses only reduces the risk
of hitting this gotcha, but does not prevent it. If you write an assignment instead of a comparison
and enclose the assignment in parentheses, the gotcha will still occur. The code will be legal and
wrong, and the simulator compiler will be silent about it.
always_comb
b = (a=1) ? 1 : 0 ;
We had such a case, in a more complex statement, and no tool even issued a warning.
GOTCHA!
To create a separate bin for each value, square brackets, [], follow the bin name. To create a fixed
number of bins for a set of values, you specify the number of bins inside the square brackets. For
example:
The 12 possible values are distributed as follows: <1,2,3>, <4,5,6>, <7,8,9>, <10,14,15>.
That works fine where the number of values can be distributed evenly among the bins. What
happens if the number of values is not divisible by the number of bins?
"If a fixed number of bins is specified and that number is smaller than the specified
number of values, then the possible bin values are uniformly distributed among the
specified bins. The first ‗n‘ specified values are assigned to the first bin, the next ‗n‘
specified values are assigned to the next bin, etc. ... If the number of values is not
divisible by the number of bins, then the last bin will include the remaining items. For
example:
The 13 possible values are distributed as follows: <1,2,3>, <4,5,6>, <7,8,9>, <10,1,4,7>."
A proposal was submitted to the SV Working Group in Mantis 2055 to fix this and make the
distribution truly uniform, but it was rejected on the grounds that it is not backwards-compatible.
The thought behind the existing behavior is that it is easy and fast to implement. However, it was
shown that the new proposal would not be complex, either.
const logic a = 0;
const variables can be either static or automatic. Here we will just talk about static const
variables. Automatic const variables are a little different.
A const variable is assigned a value in its declaration and cannot be assigned a value in a
procedural assignment.
Many people see this and think that if it is a constant, then it can be used in place of a constant,
like a parameter, for example in a range specification, like this:
const integer a = 3;
logic [a:0] b;
And then they don't understand why they get a compiler error. GOTCHA!
The catch here is that a const variable is still a variable, and can only be used where a variable
can. Its value is assigned after compilation and elaboration, at the beginning of run-time.
Anywhere the language requires that an elaboration-time constant be used, such as in the
specification of the range of a data object's dimension, a const variable cannot be used. For
example, anywhere the language syntax specifies a "constant_expression", only elaboration-time
constants may be used.
The confusion arises because the standard uses the term "constant" with more than one meaning.
In fact, most uses of the term "constant" in the standard mean elaboration-time constants.
The discussion in this section is limited to static variables. Automatic variables are a little
different.
In both Verilog and SystemVerilog, there is no guaranteed order of execution between initial
and always procedures. This is a frequent source of problems, as pointed out in Gotcha 56 in
[3].
However, you don't need always procedures to have execution order problems with initial
procedures and initializations.
Naturally, SV does not guarantee order of execution between initial procedures. So in the
following:
reg a, b;
initial a = 0;
initial b = a;
there is no guarantee that the first initial procedure will execute before the second and
therefore there is no guarantee as to the value that will be assigned to b.
reg b;
reg a = 0;
initial b = a;
SystemVerilog has defined that VDAs are executed before initial and always procedures,
so now this code is guaranteed to initialize a before b.
Many people understand this. However, fewer people understand that the same is true for const
variable declarations. Many people think of const variables as being like parameters and expect
that the following will work:
const logic a = 0;
logic b = a;
However, const variables are still variables. The only difference is that a const variable must
be assigned a value in its declaration and may not be assigned a new value afterwards.
Enter SV‘s new always_comb and always_latch procedures. Now I can indicate explicitly
the intent of my always procedures as combinational or latches.
But always_comb does not guarantee that the code you write will actually model
combinational logic. If the code does not assign a value to the output under all input conditions,
it will still model latched logic, not combinational logic. The always_comb keyword indicates
your intent, but does not guarantee that your intent is actually implemented.
Well then, my simulator will at least tell me that I made a mistake, right? Don't count on it.
The 1800-2005 LRM says in Section 11.2, ―Software tools can perform additional checks to
warn if the behavior within an always_comb procedure does not represent combinational
logic, such as if latched behavior can be inferred.‖ The key word is ―can‖. That is, tools may
perform such additional checks, but they are not required to.
And in fact, most simulators do not do so (today). You can write an always_comb procedure
that will model and synthesize a latch, but it will pass simulator compilation silently. Simulators
Synthesis tools and Lint tools will, however, now be able to distinguish between intentional and
unintentional latches. However, you still cannot rely on simulators to give you any information
about this.
The SV-2009 LRM draft changes that ―can‖ to ―should‖, in order to encourage tool developers
to implement such checks. Some simulator developers opposed that change, saying that for non-
synthesis tools, it is not well-defined exactly what check they should perform. However, the
proponents of the change said that mostly what was wanted is a check that the outputs of the
always_comb procedure are assigned a value under all input conditions. The new wording still
leaves these checks non-mandatory, so no one will be able to claim that a tool is non-compliant
because it missed some corner case. This change was made in Mantis 1828.
This also makes some sense to us, because we know that we write latches in RTL in a way that is
very similar to combinational always procedures. In Verilog-1995, for both latches and
combinational logic, we write always with a sensitivity list that includes all the signals that are
read within the procedure. In Verilog-2001, we use always @* for both. In SystemVerilog,
there are separate keywords, always_comb and always_latch, but these only indicate
intent. They behave identically.
But how does it happen that we write code that looks like it should create a flip-flop and we still
get a latch out of it?
Here the signal out is assigned a value when the asynchronous reset is active and is never
overwritten by data loaded at the clock edge. This is equivalent to the following:
This example is not so bad. You can look at the original code and see that there is only a reset
condition and that no data is loaded otherwise. However, other cases are less obvious, such as
this:
Here out is a 32-bit register with three fields, bits 31-18, 16-11, and 11-0. Bit 17 is not used.
This leaves bit 17 with an asynchronous reset and no data load. Synthesis and Lint tools will tell
you that bit 17 is a latch, even though you think you wrote a register of 32 flip-flops. GOTCHA!
This is actually harmless, but can be quite surprising and confusing if you are unaware of this
behavior. The day before I wrote this, it actually pointed out an error in real code. A user reported
that he has this code (slightly edited):
Thus, the comparison result is always false, and that part of the code is therefore ignored, as if it
did not exist. That leaves us with only a reset assignment and no data assignment to a_en and
b_en, and therefore they became latches. GOTCHA again!
Since this gotcha is really harmless, you just need to be aware of this behavior, and then you will
know why you get latch reports.
always_comb
case (sel[1:0])
2’b10: out = a;
2’b11: out = b;
endcase
This case statement assigns a value to out only for two of the four input combinations. In
order to prevent a latch from being formed, we need to assign a value to out for the other two
input combinations as well. We can add additional lines to the case statement for each of the
additional combinations, but for large case statements with a large number of possible
combinations, that can be very unwieldy.
Another method is to add a default clause to the case statement, like this:
always_comb
case (sel[1:0])
2’b10: out = a;
2’b11: out = b;
default: out = 1'b0 ;
endcase
always_comb
case (sel[1:0]) // synopsys full_case
2’b10: out = a ;
2’b11: out = b ;
The full_case pragma acts like adding a ―default: out = 'x;‖ clause to the case
statement, and specifies that the output value for all unspecified case expression values is
"don‘t care", and the synthesis tool is free to assign any value it wishes in order to optimize the
implementation of the logic.
In SV, the priority and unique keywords can be used instead of the full_case pragma.
(priority is like a "full_case" pragma whereas unique is like a "full_case
parallel_case" pragma combination.)
All these methods will work for this code and prevent latch logic from being synthesized instead
of combinational logic. As a result, many coding guideline documents specify one of these
methods as standard methodology to insure that latches will not be generated.
always_comb
unique case (sel[1:0]) // synopsys full_case
2’b00: out = 1’b0;
2’b01: out = 1’b0;
2’b10: if (cond) out = a;
2’b11: out = b;
default: out = 1’b0;
endcase
Here we have used all the methods together: the case statement is complete, with a default
clause, with a full_case pragma, and even unique pre-pended to it. None of this will help.
A latch will still be formed. Why? Because there is still an input condition for which out is not
assigned a value: if sel[1:0] is 2’b10 and cond is 0. The code will go to the 2’b10 branch
of the case, but the condition of the if statement is false, and the else branch is null. No
value is assigned to out in this case. GOTCHA!
All the methods mentioned have the effect of covering all values of the case expression and
thus that the list of case items is complete. However, they do not guarantee that within the
case items, all the outputs are assigned values for all possible input combinations.
always_comb
case (sel[1:0])
2’b10: begin out1 = a; out2 = 1’b1; end
2’b11: begin out2 = b; end
default: begin out1 = 1’b0; out2 = 1’b1; end
endcase
However, there is one way to handle this that will guarantee that latches will not be formed. We
call this a ―default pre-assignment‖. That is to assign default values to all the outputs of the
combinational logic before the if-else and case statements, as in this example:
always_comb
begin
out1 = 1’b0;
out2 = 1’b1;
case (sel[1:0])
2’b10: begin out1 = a; out2 = 1’b1; end
2’b11: out2 = b;
endcase
end
This does guarantee that out1 and out2 will be assigned values within the always_comb
procedure for every input combination, if not by the case statement, then by the pre-assignment.
Until SV, this worked fine. But now, SV‘s unique keyword has introduced a new gotcha into
the use of default pre-assignments. One uses unique to assert that the case items are mutually
exclusive, i.e., that only one case item can match the case expression simultaneously. The
advantage of unique over the parallel_case pragma is that unique causes the simulator
to issue a violation message if the unique-ness of the case statement is violated.
But unique also includes the effects of a full_case pragma. In particular, if the case
statement does not include an explicit default clause, unique effectively adds an implicit
"default: out = 'x;" case item to the case statement, but only for synthesis tools, not
for simulators. See [18] for a fuller discussion of this.
The result is that if the case expression has a value which does not match any of the case
items, the simulator will use the default pre-assignment value, whereas the synthesis tool will use
the implicit default don't care assignment and choose whatever value it likes. Weirdly, the
unique construct, which had a goal of preventing synthesis-simulation mismatches, can itself
cause a mismatch.
For this and other reasons, SV-2009 will include a new unique0 construct, which will be like
parallel_case without full_case, checking unique-ness of the case items, but
allowing for none of them to match as well. Think of unique0 as meaning zero-one-hot
whereas unique means one-hot. The details can be found in Mantis 2131.
Suppose a particular combinational always procedure has several output variables. As in usual
in combinational logic, you have written the procedure with blocking assignments, whereas you
use nonblocking assignments for latches and flip-flops.
All of the output variables but one are modeled correctly. An assignment to one of them was
omitted in some case, as in this example from earlier:
always_comb
case (sel[1:0])
2’b10: begin out1 = a; out2 = 1’b1; end
2’b11: begin out2 = b; end
default: begin out1 = 1’b0; out2 = 1’b1; end
endcase
out1 synthesizes to a latch instead of to combinational logic. You are as yet unaware of this.
You get messages from your synthesis or Lint tool that you have used blocking assignments
instead of nonblocking assignments for out2 in a sequential procedure, and you ask yourself,
―What sequential procedure??‖
It does not occur to you that one of the output variables has turned into a latch. And even if it did
occur to you, you would say to yourself, ―Maybe the procedure is sequential for some variable,
but I can see that this one is combinational, that it is assigned a value in all situations.‖ You
might even curse buggy tools.
What has happened is that once the tool has determined that one of the variables in the procedure
models a latch, the entire procedure is considered sequential, and therefore the tool expects
nonblocking assignments on all of the variables. GOTCHA!
Messages of this sort are a sign that one of the output variables in the procedure has turned into a
latch. The messages about blocking assignments used in a sequential procedure don‘t tell you
which one, though. For that, you have to look for the messages about inferred latches.
That is, we have a loop of N iterations. On each iteration, we compare the value of the loop index
to some variable, and if their values are equal, then we do an assignment to a word whose index
is equal to the value of that variable. When the loop is unrolled, we get N conditional
assignments to a variable-indexed word of the array. Of course, all N assignments are to the same
word, the word whose index is var, and only one can be executed.
All the tools we saw turned this into very large logic with very high run-time and memory
consumption. But really we know that for each iteration, the assignment will be executed only if
the loop index value i is equal to the value of var. So instead of writing array[var], we can
write array[i].
Remember that when loops are synthesized, the loop variable turns into a constant value for each
iteration. Then we get N conditional assignments to the array, but each to a different, constant-
indexed word. This simple, almost trivial change makes a tremendous difference in the results.
The synthesis engine is not sophisticated enough to make this optimization.
For example, we had an always_ff procedure that contained the following code excerpt in the
middle of its body:
Changing the subscript for vf_g to i reduced the cell count by over 60K !
You might ask why the loop is needed at all, why not just write
begin
vm_mb[vf_g][2] <= d_del[7] ;
vm_mb[vf_g][1:0] <= d_del[1:0];
end
The answer is that this was part of a much larger code segment. It made sense in context to use
the loop. This is often true.
You can test the effect yourself with this simple test case:
We also tried the intermediate cases in a synthesis tool, i.e., using the variable j just on the left-
hand side or just on the right-hand side. We found that using j on either side significantly
increases the area, but using it on the left-hand side explodes the run-time as well.
Suppose that we want to describe in this code not a single register, but an entire array of registers.
We might write a loop that iterates over each of the words of the register, like this:
If you are unlucky, you will only get a message from your synthesis tool telling you that this is
not synthesizable. If your tool is more friendly, then it might tell you that the asynchronous reset
needs to be at the top of the always procedure. GOTCHA!
The code has to describe the reset of the entire array first, and only then the data load. This
requires using two loops instead of one.
In SystemVerilog, this gotcha is less likely to be encountered because one does not usually need
a loop to reset the array. One can usually reset the entire array in a single statement, like this:
Where possible, prepare coding guidelines to prevent gotchas. Check for them using Lint tools.
In code reviews, look for those gotchas that your Lint tool does not check.
Remember gotchas when you come to debug problems. Consider whether the symptoms remind
you of any gotchas you know about.
When you meet a new gotcha, spread the word among your colleagues, and think about finding a
way to prevent it or to detect it earlier.
If you have an idea how to improve the language so as to remove or at least reduce a gotcha
(preferably in a backwards-compatible way), tell the IEEE SV WG.
Thanks to Stuart Sutherland and Don Mills for encouragement to follow in their footsteps.
Thanks to Cliff Cummings for pushing me to submit a paper to SNUG and for a special
friendship.
7.0 References
[1] Stuart Sutherland and Don Mills, ―Standard Gotchas: Subtleties in the Verilog and
SystemVerilog Standards That Every Engineer Should Know,‖ SNUG Boston 2006
[2] Stuart Sutherland, Don Mills, and Chris Spear, ―Gotcha Again: More Subtleties in the
Verilog and SystemVerilog Standards That Every Engineer Should Know,‖ SNUG San Jose
2007
[3] Stuart Sutherland and Don Mills, Verilog and SystemVerilog Gotchas: 101 Common
Coding Errors and How to Avoid Them, Springer, 2008, ISBN 978-0-387-71714-2
[4] IEEE Std 1364-1995, IEEE Standard Hardware Description Language Based on the
Verilog® Hardware Description Language, IEEE, 1995, ISBN 1-55937-727-5
[5] IEEE Std 1364-2001, IEEE Standard Verilog® Hardware Description Language, IEEE,
2001, ISBN 0-7381-2827-9
[6] IEEE Std 1364-2005, IEEE Standard for Verilog® Hardware Description Language, IEEE,
2005, ISBN 0-7381-4851-2
[7] IEEE Std 1800-2005, IEEE Standard for SystemVerilog—Unified Hardware Design,
Specification, and Verification Language, IEEE, 2005, ISBN 0-7381-4811-3
[9] Stuart Sutherland, Simon Davidmann, and Peter Flake, SystemVerilog for Design, 2ed,
Springer, 2006, ISBN 978-0-387-33399-1
[10] Chris Spear, SystemVerilog for Verification: A Guide to Learning the Testbench Language
Features, 2ed, Springer, 2008, ISBN 978-0-387-76529-7
[13] David Black and Lewis Sternberg, ―Avoiding Verilog Nightmares During Verification,‖
SNUG San Jose 2000
[14] Don Mills and Clifford E. Cummings, ―RTL Coding Styles That Yield Simulation and
Synthesis Mismatches,‖ SNUG Europe 2001
[15] Duane Galbi and Lok Kee Ting, ―RTL X's - A Treasure Trove of Trouble,‖ SNUG Boston
2002
[16] Mike Turpin, ―The Dangers of Living with an X (bugs hidden in your Verilog)‖, SNUG
Boston 2003
[17] Cliff Cummings, ―Verilog Nonblocking Assignments With Delays, Myths and Mysteries,‖
miniSUNG Ottawa 2003
[18] Cliff Cummings, ―SystemVerilog's priority & unique - A Solution to Verilog's "full_case" &
"parallel_case" Evil Twins!,‖ SNUG Israel 2005
[19] Moe Kinney, Denise Powell, and Ray Yock, ―The Living Hell of Identifiers and
define_name_rules,‖ SNUG Boston 2005
[20] Colin Paul Gloster, ―ACCU Book Review: The Design and Evolution of C++,‖
http://accu.org/index.php/book_reviews?url=view.xqy?review=14179489152797087495
[21] http://www.hyperdictionary.com/computing/gotcha
[22] Yossi Ginzburg, Eyal Skulsky, Ziv Baum, and Tzahi Sabo, "Are you satisfied with your
constraints? Eight ways to misuse Vera solver," SNUG Israel 2007