I will not try and be as exhaustive or precise as Wikipedia, so if you want a detailed article go there, this will just concern the basics that interest me in this blog.
Verilog is a hardware description language (HDL) designed in an attempt to unify the hardware industry, to make them all use the same language, so that they can use similar tools, so software engineers can move from one company to another, so as to concentrate the efforts of all the community on making the best possible language.
SystemVerilog is an extension of Verilog, that adds many “higher level” capabilities to the programmers. Tools exist that convert SystemVerilog to Verilog sv2v.
One of the particularities of both these languages is that they are standardized by the IEEE:
In the case of more known languages such as python, or C the people who develop the interpreter/compilers… are those who define the language and it’s evolution. This is a major difference with SystemVerilog where the people who develop the tools need to abide by the rules of the IEEE. As we can see on this benchmark, tools do not all support the whole language and they do not all support the same parts. Nevertheless they nearly all support the Verilog subset (from a SystemVerilog perspective), and the basics of “synthesizable code”.
Synthesizable code
In SystemVerilog we find two different types of code:
- synthesizable code
- behavioral code (this name is not consensual but I did not want to call it “non-synthesizable code”)
Synthesizable code is code which can be synthesized… this means that it is code which can be transformed into a logic circuit of gates. It is code that can be “translated” into hardware.
Behavioural code is everything that is not in the afore category. Now you might be wondering why such a thing exists in a language that describes hardware. This type of code is essential to the testing part of the language. It is used to write “testbenches” that will see how each block of the code works, print values, raise errors, connect to other programs (in C)… I call this behavioural as it is much higher level, and is what you want to execute on your computer.
Therefore some tools choose to have very limited support of the non-synthesizable code as it is not the “purpose” of the language.
Tooling
As you have understood this language serves the purpose of describing hardware, but it enables to write code at a higher level than logic gates (AND/OR/NOT/…). So before being printed it needs to be synthesized, tools such as Yosys do this. It transforms a human readable code with nice variable names, structures, complicated types into wires and gates. This design can then be sent to other tools such as OpenROAD that will decide how to place the different gates/wires on a real silicone chip. The result of this being printable.
However to be able to test the code locally without being obliged to make a new design (which is long and costly and complicated), other tools exist to be able to simulate the designs, they are called… simulators ! Examples of these are ICARUS verilog and Verilator.
Simulation
One of the particularities of hardware code is that all of it happens at once, as it is an “electrical” circuit, modifications propagate nearly instantly across the whole design. This makes simulating hardware, and especially SystemVerilog hard, as our computers are sequential. To know how information propagates through the simulation the IEEE defined a very complicated “scheduling semantics” (an article delving into details about it). This gives us a glimpse of how complex simulators are under the hood. Moreover if we want the simulation to be efficient, some tools (such as Verilator) convert the entire design into C++ code.
Non determinism
Another major difference between hardware description languages is that they enable non deterministic code. This is different from “undefined behaviour” in C (which is a sort of violation of the language contract) this is really defined but unpredictable behaviour. For instance
- if two modules (the basic bloc of verilog code) assign a different value to a shared variable at the same time ?
- if I write a division and I end up dividing by zero ?
- if a variable has not been initialized, but is used ?
- if two variables depend on each other (a mofication of one entailing a modification of the other) ?
These are just a few examples of behaviours that can be written and that are legal in SV and whose output cannot be known for certain. For instance the concurrent assigments in real life might depend on the length of the wires between the registers storing the variables…
To capture some of these behaviours SystemVerilog logic can take 4 values 0 and 1, but also X and Z, which are respectively “unknown” (this can be a 0 or a 1), and “high impedance” (which represents an unconnected wire, neither 0 nor 1).
All of this makes verifiying SystemVerilog and it’s simulators all the more difficult.