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,
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
If you have ever wondered what, exactly, the difference between
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.
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
Descriptions of Data
There are two important built-in data types:
logiccan assume any of the four basic value states
bitcan assume only the true and false value states
logic is the default data type for everything in SystemVerilog. If
the data type of our example scalar declaration is omitted, the
is implicit. This also holds for port declarations.
There exists a set of numeric data types,
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
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
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
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,
wire. There is also a cohort of more specialized net kinds, such as
wand, and more. In addition to these, we can have user-defined
net kinds manifested using the
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
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
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
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
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
inout. Their usage is
self-evident. If omitted and not otherwise able to be derived, the default
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.
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
If the data type is omitted, it is
If the object kind is omitted:
inoutports, the object kind will be a net defined by the
- If the data type has also been omitted, the object kind will be a net
defined by the
- If the data type was declared, the object kind will be
- If the data type has also been omitted, the object kind will be a net defined by the
A quick summary of recommendations:
logicfor single driver circuits,
wirefor everything else
logicin synthesizable code. Avoid
varto be implicit, just
wireare 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
There exist various reasons to break these rules, but this will get you correct, compact, readable code in most situations.