3. Gate-level Modeling
A Hardware Description Language (HDL) allows us to model the structure and behavior of hardware modules. Different levels of abstraction exist for such models. Computer-Aided Design (CAD) tools which understand an HDL perform the mapping of abstract module descriptions 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. SystemVerilog supports hardware modeling and simulation. The following tools will allow us to compile and test our designs:
Simulation: Icarus Verilog
Waveform visualization: GTKWave
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 lab sessions, use the provided installations. If you’d like to explore further and use a custom installation, we can talk in the open lab sessions.
SystemVerilog may be used at different levels of abstractions. More abstract models are typically easier to comprehend but further away from the actual hardware. In this exercise we’ll make our start in SystemVerilog by designing gate-level modules. Gate-level models represent a low-level abstraction and are also called structural-level models.
Note
Engineers typically design their circuits at higher levels. As we’ll see in Section 4.1 these higher-level formulations are then automatically lowered to the gate level. For the time being, SystemVerilog’s gate-level primitives are a good first choice since they allow us to transfer our schematics directly into the HDL.
Primitive |
#Inputs |
#Outputs |
Description |
---|---|---|---|
and |
2 or more |
1 |
AND gate |
nand |
2 or more |
1 |
NAND gate |
or |
2 or more |
1 |
OR gate |
nor |
2 or more |
1 |
NOR gate |
xor |
2 or more |
1 |
OR gate |
xnor |
2 or more |
1 |
NOR gate |
buf |
1 |
1 or more |
Buffer gate |
not |
1 |
1 or more |
Inverter gate |
Table 3.1 shows a selection of the available primitives. The syntax of the gate-level primitives is given as:
gate_type instance_name( outputs, inputs );
For example, assume that we have two input signals i_a
and i_b
and would like to use them as inputs for a 2-input AND gate.
If the output of the gate is assigned to o_c
and the gate named m_my_gate
, we’d use the and
primitive in the following way:
and m_my_gate( o_c, i_a, i_b );
3.1. My First Module
Our first module using gate-level primitives implements the following function:
where \(A\) and \(B\) are one-bit inputs. \(C\) represents the one-bit output. Table 3.1.1 gives the truth table of this simple function.
\(A\) |
\(B\) |
\(C\) |
---|---|---|
0 |
0 |
1 |
0 |
1 |
1 |
1 |
0 |
0 |
1 |
1 |
1 |
Tasks
Implement Eq. (3.1.1) in the module
my_module
through gate-level primitives. Use the filenamemy_module.sv
.Test your implementation in the testbench
my_module_tb
. Use the filenamemy_module_tb.sv
. Verify that your module produces the correct outputs for all inputs (see Table 3.1.1).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.
3.2. Full Adder
This section designs a 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}\):
\(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 3.2.1 represents the truth table of the full adder. In your tests and waveform visualization cover all possible inputs and outputs. Additionally, follow the order of the table, i.e., test/visualize the first row before the second one.
1/**
2 * Gate-level implementation of a 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( input logic i_a,
11 i_b,
12 i_carry_in,
13 output logic o_s,
14 o_carry_out );
15 // TODO: implement full adder through gate-level primitives
16endmodule
1/**
2 * Testbench of the gate-level full adder.
3 */
4module full_adder_tb;
5 logic l_a, l_b, l_s;
6 logic l_carry_in, l_carry_out;
7
8 full_adder m_dut( .i_a( l_a ),
9 .i_b( l_b ),
10 .i_carry_in( l_carry_in ),
11 .o_s( l_s ),
12 .o_carry_out( l_carry_out ) );
13
14 initial begin
15 $dumpfile("dump_full_adder.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 3.2.1 for your implementation of the full adder in the module full_adder
using the filename full_adder.sv
.
Use the template in Listing 3.2.2 for your implementation of the testbench full_adder_tb
in the file full_adder_tb.sv
.
Tasks
Implement the module
full_adder
. Exclusively use gate-level primitives in your design.Test your implementation in the testbench
full_adder_tb
. Your testbench should check the outputs for all possible inputs (see Table 3.2.1) throughassert()
-statements.Visualize the waveform generated by your module. Only show
i_a
,i_b
,i_carry_in
,o_s
ando_carry_out
of thefull_adder
module in that order.
3.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 combination 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 3.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 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. (3.2.1) to our input:
Combined, Equations (3.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 connect our full adders: The carry ripples through the obtained multi-bit 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 to the small subset given in Table 3.3.1.
Note
Table 3.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.
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 i_b,
12 input logic i_carry_in,
13 output logic [3:0] o_s,
14 output logic o_carry_out );
15 // TODO: implement 4-bit ripple-carry adder here
16endmodule
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 m_dut( .i_a( l_a ),
9 .i_b( l_b ),
10 .i_carry_in( l_carry_in ),
11 .o_s( l_s ),
12 .o_carry_out( 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 tests
26
27 $finish;
28 end
29endmodule
Tasks
Draw the schematic of the four-bit ripple-carry adder as a sequence of full adders.
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?
Complete Table 3.3.1!
Implement the module
ripple_carry_adder_4
by completing the module in Listing 3.3.1.Test your implementation in the testbench
ripple_carry_adder_4_tb
by completing the module in Listing 3.3.2. Your testbench should check the outputs for all inputs in Table 3.3.1 throughassert()
-statements.Visualize the waveform generated by your module for Table 3.3.1’s inputs. Only show the four-bit inputs
i_a
,i_b
, the carry ini_carry_in
, the four-bit outputo_s
and the carry outo_carry_out
of theripple_carry_adder_4
module in that order.
3.4. Carry-Lookahead Adder
As discussed in the lectures, the ripple-carry adder is inefficient for larger inputs. Here, the propagation delay is bound by the critical path from the carry in to the last full adder producing the final carry out. Accordingly, the propagation delay grows linearly with the number of involved full adders (or input bits).
The carry-lookahead adder is one option for a better design at the cost of additional required gates for the lookahead logic. This exercise implements a 64-bit carry-lookahead adder using 4-bit carry-lookahead blocks. The respective schematics are given in Fig. 3.4.1, Fig. 3.4.2, Fig. 3.4.3 and Fig. 3.4.4.
Tasks
Implement the module
cla_pre_4
shown in Fig. 3.4.1. Use testbenchcla_pre_4_tb
for your tests.Implement the module
cla_logic_4
shown in Fig. 3.4.2. Use the testbenchcla_logic_4_tb
for your tests.Implement 4-bit carry-lookahead block shown in Fig. 3.4.3 using the module name
cla_block_4
. Use the two modulescla_pre_4
andcla_logic_4
in your design. Use the testbenchcla_block_tb_4
for your tests.Implement a 64-bit carry-lookahead adder by chaining sixteen 4-bit carry lookahead blocks as shown in Fig. 3.4.4. Use the testbench
cla_adder_tb_64
for your tests.
3.5. Delays
As discussed in class, the circuit shown in Fig. 3.5.1 realizes the Boolean equation \(D = \bar{C} A + CB\). This circuit was also our running example when discussing timing analyses in class. In this exercise we’ll use SystemVerilog’s support for delays to study the delayed circuit’s behavior in simulation. We may add delays to SystemVerilog’s gate-level primitives by using the following slightly extended syntax:
gate_type delay instance_name( outputs, inputs );
The specified delays are assumed during simulation but ignored when lowering SystemVerilog modules to actual hardware. For example, if we’d like to add a delay of three time units to an AND gate, we could do the following:
and #3 m_my_gate( o_c, i_a, i_b );
Additionally to providing our desired delays we should inform the compiler of the time unit and scale of our modules. We use 1ns for both by adding the following line to the beginning of the simulated modules:
timeunit 1ns; timeprecision 1ns;
We model the combinational logic using inertial delays when adding delays as described above. Simulated inertial delays are described in the document Correct Methods For Adding Delays To Verilog Behavioral Models, especially in chapters 2.0 and 5.0.
Assume a delay of 3ns for all gates in this exercise.
Tasks
Implement the circuit shown in Fig. 3.5.1 in SystemVerilog using gate-level primitives. Use the file name
module_with_delays.sv
for your implementation. Add delays to your circuit.Illustrate the behavior of your circuit by implementing a testbench in the file
module_with_delays_tb.sv
. Show that we are able to simulate a glitch when changing the inputs from \((A, B, C) = (1, 1, 1)\) to \((A, B, C) = (1, 1, 0)\). Visualize the obtained waveforms.