Introduction to SystemVerilog

What is SystemVerilog?

SystemVerilog is a hardware description language.

What is a hardware description language?

A hardware description language is a way to describe a set of logic gates and their connections with each other and input/output devices in a textual format. While HDLs can look similar to programming languages at a first glance, they are semantically extremely different from programming languages. A programming language defines a sequence of steps for a computer to execute while an HDL defines hardware components and how they are connected with each other.

SystemVerilog is one of the two major HDLs used in industry, the other major HDL being VHDL. SystemVerilog is an updated version of an older language called Verilog and it adds a lot of new features and fixes a few problems.

Types of SystemVerilog

There are three major types of SystemVerilog:

  1. Structural SystemVerilog
  2. Dataflow SystemVerilog
  3. Behaviorial SystemVerilog

Structural SystemVerilog

In this type, we instantiate modules and connect them together. Everything in SystemVerilog is inside a module; modules are the basic building blocks in SystemVerilog.

module module_a(
input a,
input b,
input c,
output out
);

logic d;
and iand1 (d, a, b);
xor ixor1 (out, d, c);

endmodule

This creates the following circuit, using the and and xor builtins provided by SystemVerilog. The iand1 and ixor1 are names given to instantiated modules and can be anything, similar to variable names. Note how an additional internal signal is created using the logic statement.

A diagram showing logic gates connected in the pattern D = A and B ; out = D xor C.

We can instantiate modules to connect them together. There are two ways to connect signals together: by position and by name. I have shown both in the example below. It is generally preferred to connect by name when instantiating non-builtin modules as it makes the code clearer and reduces the chance of errors especially when instantiating modules with a lot of signals.

module module_b(
input a,
input b,
input c,
output beta
);

logic alpha;
module_a iA1 (a, b, c, alpha);
module_a iA2 (.a(alpha), .b(b), .c(c), .out(beta));

endmodule


module module_a(
input a,
input b,
input c,
output out
);

logic d;
and iand1 (d, a, b);
xor ixor1 (out, d, c);

endmodule

The diagram for the circuit produced by this code:

A diagram showing module_b consisting of two instantiations of module_a.

Dataflow SystemVerilog

Dataflow SystemVerilog uses assign statements and is usually a simpler way to encode basic logic gates. The same module_a circuit from the structural example can now be written like this:

module module_a(
input a,
input b,
input c,
output out
);

assign out = (a & b) ^ c;

endmodule

Note how we don’t need the intermediate signal d in this implementation of module_a.

Behavioral SystemVerilog

Behavioral SystemVerilog is somewhat similar to software and has somewhat similar semantics but ultimately can be synthesized to hardware.

Our module_a example from above in behavioral SystemVerilog would be:

module module_a(
input a,
input b,
input c,
output logic out
);

always_comb begin
out = (a & b) ^ c;
end

endmodule

Behavioral SystemVerilog can only be written inside of always blocks. There are different types of always blocks depending on what kind of hardware you want to synthesize. For purely combinational logic, there is always_comb while for sequential logic that creates flip-flops and registers, we have always_ff.

Another way to create the same logic using if statements would be the following code. This uses the fact that the xor operator can be thought of as conditional negation, with 1 xor X being the same as not X and 0 xor X being just X.

module module_a(
input a,
input b,
input c,
output logic out
);

always_comb begin
if (a & b) begin
out = ~c;
end else begin
out = c;
end
end

endmodule

Moving on to sequential logic, here we have an example of creating a D flip-flop using behavioral SystemVerilog:

module flip_flop(
input clk,
input d,
output logic q
);

always_ff @(posedge clk) begin
q <= d;
end

endmodule

Let’s decompose this example. The ff in always_ff stands for flip-flop. The @(posedge clk) tells the compiler that to create a rising-edge flip flop. The q <= d; is a non-blocking assignment as opposed to doing q = d; which would be a blocking assignment. The <= operator is basically the flip-flop operator and is required to instantiate flip-flops.

Vectors and Vectored Instantiation

Vectors in SystemVerilog are a powerful feature that lets you create buses composed of multiple signals joined together. The same module_a logic but with 8-bit wide buses would be the following code.

module module_a_wide(
input [7:0] a,
input [7:0] b,
input [7:0] c,
output [7:0] out
);

assign out = (a & b) ^ c;

endmodule

The [7:0] part means that the the vector goes from 0 to 7, making a total of 8 bits.

We can also instantiate multiple modules at once and this is called vectored instantiation. If we modified our module_b example from above to use vectored instantiation and have 16-bit wide buses, it would look something like this:

module module_b(
input [15:0] a,
input [15:0] b,
input [15:0] c,
output [15:0] beta
);

logic [15:0] alpha;
module_a iA [1:0] (.a({a,alpha}), .b({2{b}}), .c({2{c}}), .out({alpha,beta}));

endmodule


module module_a(
input [15:0] a,
input [15:0] b,
input [15:0] c,
output [15:0] out
);

logic [15:0] d;
and iand1 [15:0] (d, a, b);
xor ixor1 [15:0] (out, d, c);

endmodule

This example shows two major operations that can be used on vectors: concatenation and repetition. Concatenation is used to join two or more vectors together to form a bigger vector and its syntax is {a,b,c}. Repetition is used to repeat a vector multiple times and its syntax is {n{x}}.

Synthesis vs Simulation

There are two main ways SystemVerilog can be “ran”: synthesis and simulation.

In synthesis, the SystemVerilog is converted into lower level constructs that are used for programming FPGAs or manufacturing ASICs. For FPGAs, these lower level constructs would be logic block configurations and the interconnect between them. For ASICs, this would be transistors and their interconnections.

In simulation, you use software to run a model of the hardware. The software code written for this that provides inputs to the hardware model is called a testbench while the software that runs the model of hardware and connects it to the testbench is the simulator.

The SystemVerilog used for writing testbenches is very different from the SystemVerilog used to describe hardware and basically acts as software. There also exist ways to write testbenches in Python and C.

Writing testbenches is an essential part of digital hardware design and unlike the realm of software, writing testbenches is generally considered more important than writing the implementation itself. This is because synthesis to physical hardware is an expensive process, which means that we care a lot about the correctness of our implementation.

The reason the difference between simulation and synthesis is important to mention is because the synthesis behaviour of the physical hardware and the behaviour in simulation can often have discrepancies which can lead to situations where something works in simulation but fails in synthesis.

Further resources

Knowledge of basic digital circuit design is essential to properly utilize SystemVerilog and create synthesizable logic. For this, I recommend watching UW-Madison’s ECE 352 videos. For an in-depth tutorial on SystemVerilog that goes into much more detail than this article, I would recommend Greg Stitt’s SystemVerilog tutorial. I also recommend the HDLBits exercises for practice.