The Realm of Confusion:
Object Kinds in SystemVerilog
Or, a short treatise on the subject of nets and variables
Computers are like Old Testament gods; lots of rules and no mercy
SystemVerilog is a strange language. Students of computer science frequently struggle when they first encounter it, because if you squint and turn your head just so it looks like a programming language. However, the parts of the language commonly known as “the synthesizable subset” have very little to do with programming.
SystemVerilog does exactly what it says on the tin, it’s a hardware description language. It describes a physical object, a heathen rock which we have imbued with great and terrible power.
In keeping with its nature as not-a-programming-language, SystemVerilog has a
somewhat strange relationship with the concept of types. In this post we’ll
briefly explore the difference between the two major groups of data objects,
nets
and variables
, and how they (don’t) relate to SystemVerilog “data
types”. Along the way we will touch on all the parts of a basic scalar
declaration, noting how parts are implicitly declared when omitted from said
declaration.
If you have ever wondered what, exactly, the difference between var
,
logic
, and reg
is, or what parts of a port list are actually required,
this post is for you.
Definitions and Notations
A scalar is a data object which describes a single bit of information. We’re using the term bit here rather loosely, as the student of computer science will assert that a bit is an object that assumes one of two possible states. But we are not computer scientists, we are masons, carving trans-resistive elements into the devil’s stones.
Our bits are not restricted to merely on or off, most of our bits will have at least four states. These are:
- 0: a logic zero or a false condition
- 1: a logic one or a true condition
- x: an unknown logic value
- z: a high-impedance state
An array is a data object which encompasses one or more bits of information. Arrays come in two flavors, packed arrays, which are also known as vectors, and unpacked arrays. When the unqualified term array is used, we’re usually talking about unpacked arrays.
Arrays and vectors are a subject all their own, for the remainder of this post we’ll deal with scalars. We bring them up only to mention that everything we discuss with regards to scalars will also apply to arrays and vectors.
What’s in a declaration?
A basic declaration of a scalar contains three elements, in order they are: the object kind, the data type, and the identifier.
Working backwards:
-
identifier is the name by which we will refer to our freshly declared scalar
-
data type defines the range of values our scalar can assume
-
object kind describes the behavior of our scalar, chiefly the mechanisms by which it can be assigned and hold values
So for example, in the following scalar declaration:
var logic omega;
var
is the object kind, logic
is the data type, and omega
is the
identifier.
Descriptions of Data
There are two important built-in data types:
-
logic
can assume any of the four basic value states -
bit
can assume only the true and false value states
Notably, logic
is the default data type for everything in SystemVerilog. If
the data type of our example scalar declaration is omitted, the logic
keyword
is implicit. This also holds for port declarations.
There exists a set of numeric data types, byte
, shortint
, int
,
and longint
. These can represent two-state real numbers of lengths suggested
by their names. They are mostly of use in non-synthesizable simulation, and
will not be further considered here.
The usage of bit
is similarly discouraged, its two-state nature is simply not
enough state for us in most cases. However, it does see some use in
synthesizable code.
There are also 4-state numeric types, the 32-bit integer
and the 64-bit
time
. If we need such types in synthesizable code, we should be using
vectors. These too will not be further considered.
Finally there are user-defined data types. These include structures, enumerations, and typedefs which we have manifested into existence. They too may appear in the data type position of a declaration.
At this point it bears mentioning there is a keyword called reg
, which is
almost-but-not-quite a data type. Like Twinnings’ relation to tea or the
Electoral College’s relation to democracy, it leaves something to be
desired. If it were a data type reg
would be analogous to logic
, a
four-state scalar or vector type. This will be explored more later.
Conceptions of Kinds
There are two major groupings of object kinds, nets and variables.
In truth, “variables” should not be plural. There is only one variable kind,
var
. Data objects of kind var
can be written to by one or more procedural
statements, or alternatively can be written by one continuous assignment or
one port.
Outside the context of port declarations, all data objects are variables by
default. This means we can omit the var
keyword from our scalar declarations,
it is implicit.
All other object kinds are nets. The most prolific of these is the old,
reliable, wire
. There is also a cohort of more specialized net kinds, such as
tri
, wor
, wand
, and more. In addition to these, we can have user-defined
net kinds manifested using the nettype
keyword.
An exploration of even just the built-in net kinds is beyond the scope of this post. However, they all follow the same rules with regards to assignment: a net can be written by one or more continuous assignments and/or module ports. A net cannot be procedurally assigned to.
A final caveat, nets only work with four-state data types. For example, you
cannot combine wire
with bit
.
Earlier we said that our bits will encode at least four states. Net objects can encode far more information. In order to resolve the value of a net driven by more than one continuous assignment, nets carry additional inform about the strength of their value. Strength can be one of seven possible levels, and this allows for a trivial sort of mixed-signal simulation that is useful in the digital design space.
Notably, this can be used to model multi-driver buses where non-selected components are in a high-impedance state, or simulate the presence of pull-up/pull-down resistors.
From this we can derive a simple rule for the use of nets and variables. If the
circuit being modeled is multi-driver use a wire
, for everything else use
a var
.
And sometimes reg
Consider the following scalar declarations:
var reg alpha; // Four-state variable
reg beta; // Equivalent to above, var is implicit
wire reg gamma; // Invalid ???
We see here why reg
is only sorta a data type. It cannot be combined with
net kinds. reg
must be a variable.
The reason is staring us in the face. wire reg
is a phrase that puts a shiver
down one’s spine. Even barbarous idolators, worshippers of the silicon throne
such as we, are appalled by the combination.
reg
is a keyword brought over from the original Verilog standard, which had
no concept of data types or object kinds. In SystemVerilog, wire
and its kin
were made into object kinds, while reg
was nominally classified as a data
type.
The rules of the language would allow us to combine these, but the authors of the LRM could not bring themselves to sanction this unholy marriage. Such a union could only be made in error or as the result of an elaborate torture, no person would ever do so of their own free will. Thus it is forbidden as a mercy to wayword and anguished souls.
It is recommended to avoid the reg
keyword. It is a misleading half-type, let
it be relinquished to the sands of legacy compatibility and spoken of no more.
But what of the ports?
So far we have discussed declarations in isolation, but port declarations have some special rules associated with them.
Firstly, port declarations have an additional characteristic, direction,
which precedes all other characteristics in a declaration. The three basic
directions a port may be are input
, output
, and inout
. Their usage is
self-evident. If omitted and not otherwise able to be derived, the default
direction is inout
.
Next we must differentiate between the two styles of port declarations, ANSI and non-ANSI.
In a non-ANSI port list, the declarations of port characteristics are seperated from the port list itself.
If the direction, object kind, and data type are all omitted from the first element of the port list, the port list is non-ANSI. All other constructions are ANSI port lists.
Non-ANSI port lists have a number of curious properties of questionable utility. As with the various specialized net kinds and the particular details of vectors and arrays, we will not explore them further here. Non-ANSI port lists are not recommended as they are both verbose and error-prone.
ANSI port lists declare port characteristics inside the port list. The rules for determining implicit defaults for ANSI port lists are involved, what follows is a best effort attempt to condense them.
- If all characteristics of a port are omitted, then they are inherited from the previous port declaration.
Otherwise:
-
If the direction is omitted, it is inherited from the previous port declaration. If this is not possible (first port in the list), the direction is
inout
-
If the data type is omitted, it is
logic
-
If the object kind is omitted:
- For
input
andinout
ports, the object kind will be a net defined by thedefault_nettype
compiler directive - For
output
ports:- If the data type has also been omitted, the object kind will be a net
defined by the
default_nettype
compiler directive - If the data type was declared, the object kind will be
var
- If the data type has also been omitted, the object kind will be a net
defined by the
- For
The default_nettype
is wire
initially.
Final Recommendations
A quick summary of recommendations:
-
Use
logic
for single driver circuits,wire
for everything else -
Stick to
logic
in synthesizable code. Avoidreg
-
Allow
var
to be implicit, justlogic
andwire
are enough for almost all declarations -
Use ANSI-style ports
-
Always declare port direction for each port
-
Allow everything else in a port declaration to be implicit except if you need an output variable, then use
output logic
There exist various reasons to break these rules, but this will get you correct, compact, readable code in most situations.