Skip to content

A Verilog Tutorial 1: Circuit Design

In this and the next few chapters, we will summarize SystemVerilog. We will not attempt to provide a detailed exposition. Instead, we will concentrate on just the bare minimum which will be relevant for this course. This means that we will only cover a tiny subset of the total SystemVerilog specification.

1. What is SystemVerilog?

Before starting a detailed explanation of SystemVerilog, it may be a good idea to get a bird’s eye view of of what it does, and how it is different from other high-level languages like C or Python..

Let us first remember why we use C when we program a computer: After all, we can write our program in machine language also. And, machine language is the only language that is understood by a CPU, hence even if we write everything in C it should be converted into machine code before being run on a CPU. So, why bother with C?

Actually, at this stage in our lives as computer engineers, we all know the answer: Writing, changing and maintaining even tiny machine language programs is extremely hard for us, humans. It is only for this reason that we opt to write our programs in C, and then use a C compiler to convert them into machine language, and lastly run the resulting machine language program to obtain the desired results..

In other words, C or any other high-level language only exists for our sake. By using a high-level language, we can describe what we want the computer to do without going into the machine-language details of the particular computer on which our program will run. The CPU in our computer, for its part, neither understands nor needs a high level language. All it needs is something written in machine code.

SystemVerilog serves a similar purpose. When we want to design some hardware, we need to produce a circuit diagram which describes it gate-by gate. Our first step is generally to take pen and paper and draw such a circuit diagram in terms of its basic elements, like gates, multiplexers, registers etc. But this is very similar to programming in machine language. Modern hardware is too complicated to be drawn on paper. What is needed is a high-level language which is powerful enough to describe how a hardware circuit behaves. We just want to use such a high level language to describe what we want our circuit to do, and how we want it it to behave under various circumstances. And then use a compiler to compile it and produce the gate-by-gate schematic. There are many such high level languages developed over time. They are collectively known as hardware definition languages (HDL’s). SystemVerilog is one such language.

After we describe our circuit in SystemVerilog, we compile it with a SystemVerilog compiler. As a C compiler translates our C program into machine language, so does a SystemVerilog compiler expands our SystemVerilog program into a circuit schematic with very low level of detail.

Of course, when we say “circuit schematic”, we don’t mean that SystemVerilog compiler generates a stack of papers with some schematics on it. It instead generates a text file indicating how many flip-flops, gates, multiplexers etc. must be used, and how they must be connected to each other to generate the desired hardware. Generally, open source SystemVerilog compilers like Odin-II use .blif file format for such a description. In closed source SystemVerilog compilers like Quartus this information is a commercial secret.

A serious mistake usually committed by students who learn SystemVerilog is the unfounded assumption that SystemVerilog programs is written to be “run”, just like other programming languages. SystemVerilog programs are not “run” after being compiled. Instead, the .blif file they generate is used to build a circuit. In other words, .blif file is the end product.

With .blif file in hand, one can follow two strategies:

  • Send the .blif file to a “chip foundry”, together with a few million dollars. There, they will convert the .blif file into a computer chip. The most famous foundries are TMSC in taiwan and Samsung in Korea. Only these two foundries have the secrets of 5mn technology, which must be used to build the largest and fastest chips like GPU’s. Other foundries produce slower and less critical chips.
  • There are cards, known as FPGA cards, which can be purchased for a few hundred dollars. These cards can be connected to your computer via a USB connection. Also you have to download the card’s driver software to your computer. The driver has an algorithm in it, known as the “place and route algorithm”, which translates your .blif file into a format understandable by the FPGA card. Then it uploads that format into the FPGA card via the USB connection, and the hardware circuit magically foms in the FPGA card. In particular, each FPGA card contains a chip which is known as the “FPGA chip”. The hardware is formed in that chip..

2. SystemVerilog Constants and Variables

2.1 Constants

In SystemVerilog, when we write down a constant, we have to specify how many bits it takes, and what is the base we use to represent it. A few examples are

  • 5’b10010 5 bit binary number 10010
  • 12’b101 12 bit binary number 000000000101.
  • -7’b101 twos complement negative of 7-bit binary number 101, ie,
  • 11’hf7a the rightmost 11 bits of the hexadecimal number f7a, ie, 77a.
  • -11h’27a twos complement negative

2.2. Variables

SystemVerilog variables are defined with the keyword logic.  Simple variables  are binary, ie, they can only be set to 0 or 1 and no other value. When a variable is desired to represent more than one bits, it is declared as an array. Below are a few examples of declaration:

  • logic x                 //x is a one bit variable
  • logic [3:0] conn   //conn is a 4 bit variable
  • logic y                  // y is a one bit variable
  • logic [11:0] abc    //abc is a 12 bit variable

Array elements and subranges work in the usual way. If we continue from the above examples

  • conn[2]              refers to the second element of comm
  • abc [5:3]            refers to the subrange of bits 5,4,3 of abc.
  • abc                    refers to the 12-bit variable abc.

It is also possible to have two dimensional arrays. An example declarations are

logic [15:0] mem [0:512]    // 1KByte memory of 16-bit words.

The usage is as follows:

  • mem[65]            // 65th location in the variable mem. An 16-bit variable..
  • mem[5][65]        // 5th bit of 65th location in mem.
  • mem[5:3][65]     //subrange of bits 5, 4, 3 of 65th location in mem.

Higher dimensional arrays are also possible but in these lectures we will not need them..

Finally, we have to mention that there are three classes of variables: Input variables, output variables and local variables.  Their purpose will be explained in the next section. Just keep in mind that

  • When input or output variables are declared, they must be prefixed by the keywords “in” and “out”.
  • Local variables are declared locally

2.3. Concatenation Operator

The concatenation operator “{ , }” combines (concatenates) the bits of two or more data objects. The objects may be scalar (single bit) or vectored (muliple bit).

logic [7:0] a,c;
logic [12:0] b;
assign a={ 4'b1110, a[3:0]} //replace the leftmost 4 bits of a with 1110;
assign b = { b[12:8],a}; replace rightmost bits of b with a;
assign c = { c[3:0], c[7:4]}; //switch the nibbles of c

 

3. SystemVerilog Modules

A complete hardware circuit must have

  • Input ports
  • Output ports
  • Some logic to receive inputs and generate outputs

Each input port of the hardware circuit corresponds to an SystemVerilog variable in the program. Such SystemVerilog variables are called “input variables”.  Similarly for output ports.

A hardware is described by a “module” in SystemVerilog, which has the following format:

module module_name(input and output variables)
   declaration of input and output variables;
   declaration of local variables;
   statements to compute output variables from input variables
endmodule

Modules in SystemVerilog are analogs of functions/procedures in other programming languages. As can be seen from the above format, they contain

  1. Keyword “module”, followed by module name and a list of module’s input/output variables
  2. Input and output variable declarations
  3. Local variable declarations
  4. Statements that describe the internal logic of the circuitry, which is composed of four categories: Combinatorial always blocks, sequential always blocks, assign statements and calls to other modules.
  5. Keyword “endmodule”.

Below, we will investigate each of these elements.

Note that a module can call other modules. A SystemVerilog project is nothing but a hierarchical collection of interlinked modules, with the top module representing the final hardware design.

4. Variable Declarations in a module

As mentioned before, SystemVerilog modules contain three different kind of variables:

  1. Input variables (Representing input ports)
  2. Output variables (Representing output ports)
  3. Local variables (used in internal oprtations)

Local variables are declared in the normal way:

  • logic x
  • logic [3:0] yy

Input/output variable declarations are preceeded the keywords “input” and “output”:

  • output logic bxx, ay
  • input logic [5:0] c1, c2

SystemVerilog modules use input and output variables to communicate with the outside world, ie, to receive/send data, and use local variables as intermediates for the local computation. Let us give an example:

module foo(x,y,count,a,b);    //module foo has 5 ports: x, y, a, b, count
input logic[4:0] x, count;    //x and count are 5 bit input ports.
input logic y;                //y is a 1-bit input port.
output logic [2:0] a;         //a is a 3-bit output port
output logic [1:0] b;         //b is a 2-bit output port

logic [3:0] aa, loc1;          //aa and loc1 are 4 bit local variables
logic [1:0] cc;                //cc is a 2-bit local variable.

... statements ...

endmodule

 

5. Combinatorial Circuit Design in SystemVerilog

We have learned that digital circuits can be roughly classified into two groups: Combinatorial and sequential. Let us first remember in what ways these two differ.

5.1. Basic idea of combinatorial circuits

We have analyzed the combinatorial circuits in the previous chapters. But, in order to make this chapter self-contained, we will give a short review here from the perspective of SystemVerilog.

Consider a combinatorial circuit with three 1-bit inputs (a, b, c) and two 1 bit outputs (x, y):

As soon as any of the inputs a, b, c change, the outputs x, y will also change.. In all combinatorial circuits, there is a very small propagation delay between inputs and outputs. How small? Much smaller than the clock period. Other than that, we can say that outputs follow inputs instantaneously. Actually, a combinatorial circuit is equivalent to a truth table and it is completely independent of the clock.

A combinatorial circuit can be defined in two different ways in SystemVerilog:

  • Assign statements
  • Combinatorial always blocks

We will start with assign statements, which is the simpler of the two.

5.2. Assign Statements

Let us design a SystemVerilog module which will compute the sum and difference oft wo 16-bit numbers in parallel.

module addandsub(x,y,sum,diff);
input [2:0] x, y;
output [2:0] sum, diff;

assign sum = x | y | diff;
assign diff = x & y;
endmodule

When compiled, this code will generate a 16-bit adder circuit and an 16-bit subtractor circuits in parallel.  assign statements are self explanatory. The resulting circuit is given below.

One very important thing to keep in mind when we read the above code segment is that this code produces a circuit. Only the connectivity information is important (ie, x, y and diff are connected to sum and x and y are connected to diff). Therefore, the order of the assign statements are not important. Do not forget that his code does not rune one line after another like in C or Java, but generates a circuit.

Assign statements are very simple, and they are not suitable to represent complicated combinatorial circuits. For such circuits, there is the combinatorial always block.

5.3. Combinatorial Always Blocks

Combinatorial always blocks are the standart way to design complicated combinatorial circuits. Let us list their salient properties:

  • Combinatorial always blocks start with the keyword always_comb
  • The keyword always_comb is followed by a begin…end block.
  • The code within the begin…end block describes function of the circuit.
  • That code consists of three different kinds of statements: blocking assignment statements, if statements and switch statements..
  • Order is important. If a variable is assigned in a combinatorial always block, it cannot be used in the RHS of any expression above that assignment.

Combinatorial always statements are the only place in SystemVerilog where order is important. In all the other places you can mix the order of your statements to your hearts’ desire… The following example is correct:

always_comb
begin         
   x = a+2;
   y = x+3;    \\ correct, x's definition preceeds its use.
end

The following example is wrong:
always_comb
begin  
   y = x+3;       
   x = a+2;  \\ wrong, x is used before it is defined. 
end

Order of combinatorial always blocks themselves are not important. The following example is correct, even though  u and v are used before defined.
always_comb
begin  
   y = u+3;       
   x = v+2;  
end

always_comb begin 
   u = 3; 
   v = 2; 
end

How do combinatorial always blocks work? Consider the following example code:
always_comb
begin  
   b = a+3;       
   c = b+2;  
   d = a+c;
   e = b+d;
end

If a=2, then b=5; c=7, d=9, e=14. The value of a is defined somewhere out of the above always_comb block.

Now assume that the value of a is changed to 10. Then the values of b, c, d and e will instantaneously change. They will become b=13, c=15, d=25 and e=38.

Let us write down some example modules whose body consist of combinatorial always blocks:

Example 1:

Assume that we have the following module:

module foo1(a,b,c,y);
input a, b, c;         //three one-bit variables as inputs
output y;          //a single one-bit variable as output.
logic x;                 //a one-bit local variable

always_comb
begin
   x = (a | b) & c;   //blocking assignment
   y = x & c;         //blocking assignment
end
endmodule

will generate the following circuit:

5.4. Blocking assignment statements.

Note that the notation used for all the operations are the same with C (ie, | denote OR, & denote AND etc)

In the generated circuit, if any variable on the RHS (ie, a, b, c, x) changes, the LHS’s (ie, the variables x and y) will also change instantaneously.

Let us emphasize again that you shouldn’t perceive this code as you perceive a C or Python code. A SystemVerilog code is not written to run “line by line” as them. Instead, a SystemVerilog code is written to generate a circuit. Therefore, only the connectivity information encoded in the program is eventually important, ie, a, b and c connect to x, and x and c connect to y. But, like C or Python, the ordering of the lines within a combinatorial always block is important. The combinatorial always block in the above program can NOT be written as

always_comb
begin
   y = x & c;
   x = (a | b) & c;  //wrong
end

The ordering of always blocks is also unimportant. . If we divide this always block into two always blocks, the order will become irrelevant again and the same code can be written as

always_comb
   y = x & c;

always_comb
   x = (a | b) & c;

Note that we do not need “begin” and “end” keywords when an always block contains only a single assignment.

Example 2:

The module

module foo2(in1, in2, add, msb);
output logic [3:0] add;
input logic [3:0] in1, in2;
output logic msb;
logic [3:0] sub            //local variable

always_comb
begin
   add = in1 + in2;
   sub = in1 - 4'h7;
   msb = add[3] & sub[3];
end
endmodule

will generate the following circuit:

Even this diagram is high level. We have used a 4-bit adder block and a 4-bit subtractor block in order not to clutter the figure. The circuit generated by SystemVerilog will replace these blocks with circuits formed from elementary logic gates. Except, in some cases, the FPGA this circuit will be loaded on contains ready-made adders. In such cases, SystemVerilog will utilize these.

The most important advantage of the combinatorial always blocks in comparison to the assign statement is that it can uilize if and switch constructs.

5.5. If statement in combinatorial always blocks

“If” statements in SystemVerilog are used to construct multiplexer-like circuits. Consider the following module

module foo1(a,b,res,operation);
input logic [3:0] a, b;
output logic [3:0] res;
input logic operation;

always_comb
if (operation==1b'1)      \\as this if..else construct constitute a single statement, we
   res=a+b;              \\don't need begin..end keywords in the always block.
else
   res=a-b;
endmodule

This generates the following circuit

Multiplexer networks can also be constructed via if-elseif-else statements. For example, the following code

module foo1(a,b,c,res,operation);
input logic [3:0] a, b;
output logic [3:0] res;
input logic [1:0] operation;

always_comb
if (operation==2h'0)     \\as this if..else construct constitute a single statement, we
   res=a+b;             \\dont need begin..end keywords in the always block.
else if (operation==2h'1)
   res=a-b;
else                    \\operation=2h'2 or operation=2'h3
   res=b;
endmodule

generates this circuit

Each if, elseif and else construct generates a multiplexer in the multiplexer chain.

If statements can be nested, and much more complicated circuits can be formed. For example

module foo3(sel, sel_2, a, b, f, g)
output f, g;
input sel, sel_2, a, b;
always_comb
  if (sel == 1)
    begin
      f = a;
      if (sel_2 == 1)
        g = ~a;
      else
        g = ~b;
    end
  else
    begin
      f = b;
      if (sel_2 == 1)
        g = a & b;
      else
        g = a | b;
    end
endmodule

 

This will generate the circuit in the following figure:

Note that there is a multiplexer chain for each of the two variables (f and g) assigned.

5.6. Case statement in combinatorial always blocks

Case statements, like if statements, is used to construct multiplexer circuits. For example, the code

module foo1(a,b,res,operation)
input logic [3:0] a, b;
output logic [3:0] res;
input logic operation;

always_comb
case (operation)
   1b'1 : res=a+b;
   1'b0 : res=a-b;
endcase

endmodule

This will generate the same circuit as in figure .. Consider the more complicated code

module foo1(a,b,res,op)
input logic [3:0] a, b;
output logic [3:0] res;
input logic [1:0] op;

always_comb
case (op)
   2'h0 : res=a+b;
   2h'1 : res=a-b;
default : res=a;          // handles all the remaining cases, ie, when op=2h'2 or op=2'h3

endmodule

This code  will generate a different circuit

Generally, if-elseif-else structures tend to use a multiplexer chain with many small multiplexers. This uses a small number of gates to synthesize, but signal propagation through such a network is slow. On the other hand, case statement use one big multiplexer.. This approach uses a lot more gates, but signal propagation is fast.

if-elseif-and statements and case statements can be used in a nested way. For example:

always_comb
case (sel)
1'b1: begin
      f = a;
      if (sel_2 == 1)
        g = ~a;
      else
        g = ~b;
      end
1'b0  begin
      f = b;
      if (sel_2 == 1)
        g = a & b;
      else
        g = a | b;
      end
endcase

5.7. Incomplete assignment problem

When we create a combinatorial circuit, we are basically creating a truth table. In a truth table (or, in a combinatorial circuit which realizes it), there should be no memory. For each possible input, there should always be a well-defined output. Recall that this is not so in sequential circuits. They may generate different outputs for the same input, depending on their memory state.

When describing combinational logic in always_comb blocks, you have to make sure that all your variables are assigned to a well-defined value in all paths in your code. Otherwise a latch will be inferred. It’s easy to miss something like this in traditional always blocks, so the always_comb block was introduced in SystemVerilog to explicitly check for this.

Assume that we want to realize a combinatorial circuit with the following truth table:

6. Sequential Design

Basic design of a sequential circuit is given below:

Note that in sequential circuits, in addition to the usual inputs and outputs, we have a clock input, which will play an immensely important role. The core memory element of the sequential circuit is flip-flops, hence its outputs will only be updated at the clock edges (we assume that . At all other times, the outputs just remember the state (which is a function of initial state and all previous inputs) and do not change.

In most of the practical cases, we will also have a load input, as we will use registers instead of flip-flops as memory elements. Normally, we dont want to change the state at every clock edge. Using a register instead of SR flip-flops directly will enable us to change the state only when load=1.

There is only one way to define a sequential circuit in SystemVerilog:  A sequential always block.

6.1 Sequential Always Blocks and non-blocking assignments

A sequential always block is formed from non-blocking assignments, <=. If and case statements are also used. The order of non-blocking assignments are unimportant.

the difference between always_com and always_ff @(posedge clk)

Example 1

Let us consider the following code segment:

always_ff @(posedge clk)
begin
   y <= x & c;           //non-blocking assignment
   x <= (a | b) & c;  //order of non-blocking assignments 
end                   //is unimportant. x is used befpre defined

The corresponding circuit is:

Note that y and x are only updated at the clock edges, not instantaneously when c, a and b changes. Except at clock edges, a, b and c will not have any effect on x and y.

The prevalent verilog code style is to wrtite combinatorial and sequential parts of a verilog code as separate always_comb and always_seq blocks. So the above code can be modified as

always_ff @(posedge clk)
begin
   y <= y_next;
   x <= x_next;
end

always_comb
begin
  x_next = (a | b) & c;
  y_next = x & c;
end

Example 2

If and case statements are also available on Verilog. Consider the following

In this code, three cases are covered, namely,

  • ld=1, op=1
  • ld=0, op=1
  • ld=1, op=0

What will happen when ld=0, op=0? In this case, the value in y will continue to be remembered. So, unlike the always_comb blocks, in always_seq blocks you dont need to cover all the cases in if or case statements, ie, there is no incomplete assignmet problem. If an uncovered case is encountered, the assigned variable will simply continue to remember whatever is inside it.

verilog code:

always_ff @(posedge clk)
   if ( ld == 1'b1 )
      y <= x;
   else if (op == 1'b1)
      y <= y & x;
end

The corresponding circuit is

Let us separate the sequential and combinatorial parts of our verilog code:

always_ff @(posedge clk)
   y <= y_next;

always_comb
begin
   if ( ld == 1'b1 )
      y_next = x;
   else if (op == 1'b1)
      y_next = y & x;
   else
      y_next = y  //memory state. And the obligatory last "else"
end

Example 3 Let us construct a shift register:
module shift_register(x,y)
input dir, x;
output [11:0] y;

always_ff @(posedge clk)
   if (dir) 
      y <= {y[10:0],x};
   else
      y <= {x, y[11:1]};
endmodule

 

 

7. Module Hierarchy

A SystemVerilog program is a large module (circuit), which contains other modules (subcircuits) within it. Each module is formed from always blocks, assign statements, and other, smaller modules.

Below is a two-bit equality tester module:

Module eq1(i0,i1,eq)
input logic i0, i1;
output logic eq;

logic p0, p1;
assign eq = p0 | p1;
assign p0 = ~i0 & ~i1;
assign p1 = i0 & i1;
endmodule

To construct a two-bit equality tester, we may invoke 1-bit equality tester module two times.

module eq2(
            input logic [1:0] a,b;
            output logic aeqb;
           )
logic e0, e1;
eq1 eq_bit0( .i0(a[0]), .i1(b[0]), .eq(e0) )
eq1 eq_bit1( .i0(a[1]), .i1(b[1]), .eq(e1) )
assign aeqb = e0&e1;
endmodule

Nested modules are allowed in SystemVerilog.

 

Berkeley Logic Interchange Format (.blif)

.blif is a file format which is used to describe digital circuits in a textual form. A digital circuit can be considered as a graph, with combinatorial or sequential elements its the nodes.

A complete spec of blif files is given in