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

— Joseph Campbell

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:

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:

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:

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.

Otherwise:

The default_nettype is wire initially.

Final Recommendations

A quick summary of recommendations:

There exist various reasons to break these rules, but this will get you correct, compact, readable code in most situations.