5. SystemVerilog

Up to this point, we specified our hardware modules through elementary logic gates. Given an abstract description of a function, we had to manually lower it to logic gates. This process is cumbersome, error-prone and time-consuming, especially when aiming for an optimized circuit where different realizations have to be considered.

A Hardware Description Language (HDL) allows us to “simply” specify the logical function of a module. Computer-Aided Design (CAD) tools which understand such an HDL then perform the mapping of our abstract module description to logic gates. This process process is called synthesis. Further, we are able to test our HDL-based hardware designs through simulation. Here, we apply inputs to our hardware modules and check the correctness of the outputs.

In this class we harness SystemVerilog as our HDL. The following tools will allow us to test our designs:

Note

We rely on open source or at least free software if possible. This means that you may install and run the tools on your own machines if desired. However, for the regular sessions, use the provided installations. If you’d like to explore further and use a custom installation, we can talk in the Open Lab / Q&A.

5.1. Simple Example Module

Our first module implements the following function:

(5.1.1)C = F(A,B) = \bar{A} + B,

where A and B are one-bit inputs and C the one-bit output. Table 5.1.1 is the truth table of this simple example function.

Table 5.1.1 Truth table of the simple example function in Eq. (5.1.1).

A

B

C

0

0

1

1

0

0

0

1

1

1

1

1

Tasks

  1. Implement Eq. (5.1.1) in the module simple_example_module using the filename simple_example_module.sv.

  2. Test your implementation in the testbench simple_example_module_tb. Use the filename simple_example_module_tb.sv. Verify that your module produces the correct outputs for all inputs (see Table 5.1.1).

  3. Visualize the waveforms generated by your module.

Hint

Use the arguments -Wall -Winfloop -g2012 when invoking iverilog. Details on using GTKWave with Icarus Verilog are available from the Icarus Verilog Wiki. You may increase the GTKWave’s font size, e.g., by using the arguments -A --rcvar 'fontname_signals Monospace 12' --rcvar 'fontname_waves Monospace 12' when invoking the GTKWave-binary.

5.2. One-bit Full Adder

This chapter designs a one-bit full adder in SystemVerilog. The full adder consists of the three one-bit inputs A, B and C_\text{in} and has the two one-bit outputs S and C_\text{out}:

(5.2.1)&S = F(A,B,C_\text{in}) = A \oplus B \oplus C_\text{in}, \\
  &C_\text{out} = G(A,B,C_\text{in}) = AB + AC_\text{in} + BC_\text{in}.

Table 5.2.1 Truth table of the full adder.

A

B

C_\text{in}

S

C_\text{out}

0

0

0

0

0

1

0

0

1

0

0

1

0

1

0

0

0

1

1

0

1

1

0

0

1

1

0

1

0

1

0

1

1

0

1

1

1

1

1

1

Table 5.2.1 is the truth table of the full adder. In your tests and waveform visualization cover all possible inputs and output. Additionally, follow the order of the table, e.g., test/visualize the first row before the second one.

Listing 5.2.1 Template for the module full_adder_1.
 1/**
 2 * 1-bit full adder.
 3 *
 4 * @param i_a input A.
 5 * @param i_b input B.
 6 * @param i_carry_in carry in.
 7 * @param o_s sum.
 8 * @param o_carry_out carry out.
 9 **/
10module full_adder_1( input  logic i_a,
11                     input  logic i_b,
12                     input  logic i_carry_in,
13                     output logic o_s,
14                     output logic o_carry_out );
15  // TODO: implement full adder
16endmodule
Listing 5.2.2 Template for the testbench full_adder_1_tb.
 1/**
 2 * Testbench of the 1-bit full adder.
 3 */
 4module full_adder_1_tb;
 5  logic l_a, l_b, l_s;
 6  logic l_carry_in, l_carry_out;
 7
 8  full_adder_1 dut( l_a,
 9                    l_b,
10                    l_carry_in,
11                    l_s,
12                    l_carry_out );
13
14  initial begin
15    $dumpfile("dump_full_adder_1.vcd");
16    $dumpvars;
17
18    l_a = 1'b0;
19    l_b = 1'b0;
20    l_carry_in = 1'b0;
21    #10;
22    assert( l_s === 1'b0 );
23    assert( l_carry_out === 1'b0 );
24
25    l_a = 1'b1;
26    l_b = 1'b0;
27    l_carry_in = 1'b0;
28    #10;
29    assert( l_s === 1'b1 );
30    assert( l_carry_out === 1'b0 );
31
32    // TODO: add additional tests
33
34    $finish;
35  end
36endmodule

Use the template in Listing 5.2.1 for your implementation of the full adder in the module full_adder_1 using the filename full_adder_1.sv. Use the template in Listing 5.2.2 for your implementation of the testbench full_adder_1_tb in the file full_adder_1_tb.sv.

Tasks

  1. Implement the module full_adder_1.

  2. Test your implementation in the testbench full_adder_1_tb. Your testbench should check the outputs for all possible inputs (see Table 5.2.1) through assert()-statements.

  3. Visualize the waveform generated by your module. Only show i_a, i_b, i_carry_in, o_s and o_carry_out of the full_adder_1-module in that order.

5.3. Four-bit Ripple-Carry Adder

This task covers basic modularity in SystemVerilog. In detail, we use simple(r) SystemVerilog modules as building blocks for more complex ones. Nothing else is done in processor design. This means, that one would combine a “simple” module, e.g., an Arithmetic and Logic Unit (ALU), with other modules such that the combined logics executes desired instructions. We’ll cover details on ALUs and processors later in the lab. For now, we’ll stick to our adders.

Specifically, instead of adding three one-bit inputs as done in Section 5.2, we’ll add two four-bit inputs A_{[3:0]} and B_{[3:0]}, and the carry in C_\text{in}. We’ll use the so-called ripple-carry adder to perform this task. A ripple-carry adder chains a series of 1-bit full adders together and is one possible implementation of a carry propagate adder.

We obtain a respective formula for the ripple-carry adder by repeatedly applying the two functions F and G of Eq. (5.2.1) to our input:

(5.3.1)S_0          = F(A_0,B_0,C_\text{in}), \\
 C_0          = G(A_0,B_0,C_\text{in}), \\
 S_1          = F(A_1,B_1,C_0),         \\
 C_1          = G(A_1,B_1,C_0),         \\
 S_2          = F(A_2,B_2,C_1),         \\
 C_2          = G(A_2,B_2,C_1),         \\
 S_3          = F(A_3,B_3,C_2),         \\
 C_\text{out} = G(A_3,B_3,C_2).         \\

Combined the equations (5.3.1) compute the four-bit output S_{[3:0]} and the one-bit carry out C_\text{out} from the inputs. C_{[2:0]} are intermediate carries through which we harness to connect our one-bit full adders: The carry ripples through the obtained multi-bit adder.

Table 5.3.1 Truth table of the four-bit ripple carry adder.

A_{[3:0]}

B_{[3:0]}

C_\text{in}

S_{[3:0]}

C_\text{out}

0000

0000

0

0000

0

1010

0001

0

1011

0

1100

0000

1

0101

1010

1

0111

1100

0

1111

1111

1

Once again we have to to think about reasonable unit tests for our testbench. Now, we have to deal with an excessive amount of potential inputs due to the increased size of the inputs. Thus, we limit ourselves for the mandatory tests to the small subset given in Table 5.3.1.

Note

Table 5.3.1 only gives the required tests for a successful completion of this task. Feel free to try additional inputs and implement further test if you see a need for this.

Listing 5.3.1 Template for the module ripple_carry_adder_4.
 1/**
 2 * 4-bit ripple-carry adder.
 3 *
 4 * @param i_a input A.
 5 * @param i_b input B.
 6 * @param i_carry_in carry in.
 7 * @param o_s sum.
 8 * @param o_carry_out carry out.
 9 **/
10module ripple_carry_adder_4( input  logic [3:0] i_a,
11                             input  logic [3:0] i_b,
12                             input  logic       i_carry_in,
13                             output logic [3:0] o_s,
14                             output logic       o_carry_out );
15  // TODO: implement ripple-carry adder here
16endmodule
Listing 5.3.2 Template for the testbench ripple_carry_adder_4_tb.
 1/**
 2 * Testbench of the 4-bit ripple-carry adder.
 3 */
 4module ripple_carry_adder_4_tb;
 5  logic [3:0] l_a, l_b, l_s;
 6  logic l_carry_in, l_carry_out;
 7
 8  ripple_carry_adder_4 dut( l_a,
 9                            l_b,
10                            l_carry_in,
11                            l_s,
12                            l_carry_out );
13
14  initial begin
15    $dumpfile("dump_ripple_carry_adder_4.vcd");
16    $dumpvars;
17
18    l_a = 4'b0000;
19    l_b = 4'b0000;
20    l_carry_in = 1'b0;
21    #10;
22    assert( l_s === 4'b0000 );
23    assert( l_carry_out === 1'b0 );
24
25    // TODO: add additional unit tests
26
27    $finish;
28  end
29endmodule

Tasks

  1. Draw the schematic of the four-bit ripple-carry adder as a sequence of one-bit full adders.

  2. Derive the total number of possible inputs for the four-bit ripple-carry adder! How would this change if we were to implement a 32-bit ripple-carry adder?

  3. Complete Table 5.3.1!

  4. Implement the module ripple_carry_adder_4 by completing the module in Listing 5.3.1.

  5. Test your implementation in the testbench ripple_carry_adder_4_tb by completing the module in Listing 5.3.2. Your testbench should check the outputs for all inputs in Table 5.3.1 through assert()-statements.

  6. Visualize the waveform generated by your module for Table 5.3.1’s inputs. Only show the four-bit inputs i_a, i_b, the carry in i_carry_in, the four-bit output o_s and the carry out o_carry_out of the ripple_carry_adder_4 module in that order.