Design Arithmetic Circuits

Project 10: Comparator, Adder, Multiplier, and ALU

Introduction

In this project, we will design arithmetic circuits using an FPGA. We will build a 4-bit magnitude comparator, a ripple-carry adder, and a multiplier circuit. You can challenge yourself by integrating all of those circuits together with some multiplexers to build an arithmetic logic unit (ALU).

Before you begin, you should:
  • Have the Xilinx® ISE WebPACK™ installed.
  • Have your FPGA board set up.
  • Be able to describe digital circuits using logic operators.
  • Be able to write test bench and simulate circuit using ISim.
After you're done, you should:
  • Understand how magnitude comparators, ripple-carry adders, and multipliers work.
  • Be able to describe magnitude comparators, ripple-carry adders, and multipliers structurally.

Comparator

A magnitude comparator is a device that receives two N-bit inputs and asserts one of three possible outputs depending on whether one input is greater than, less than, or equal to the other (simpler comparators, called equality comparators, provide a single output that is asserted whenever the two inputs are equal). The truth table of a bit-sliced magnitude comparator and the block diagram of a magnitude comparator are shown in Figs. 1 and 2 below.

Figure 1. Truth table for a bit-sliced magnitude comparator.
Figure 2. Block diagram of an 8-bit magnitude comparator using a bit-sliced magnitude comparator.

Step 1: Design a 4-bit Comparator

  1. Create a Verilog module for a bit-sliced magnitude comparator according to the truth table presented in Fig. 1 above.
    						module cmp_bitslice(
    						    input A,
    						    input B,
    						    input LT_I,
    						    input EQ_I,
    						    input GT_I,
    						    output LT_O,
    						    output EQ_O,
    						    output GT_O
    						    );
    						
    						assign GT_O = ( A & ~B ) | GT_I;
    						assign EQ_O = EQ_I & (( A & B ) | (~A & ~B));
    						assign LT_O = ( B & ~A ) | LT_I;
    						
    						endmodule
    					
  2. Connect the bit-sliced magnitude comparator according to the block diagram shown in Fig. 2 above.
    						module cmp(
    						    input [3:0] A,
    						    input [3:0] B,
    						    output LT_O,
    						    output EQ_O,
    						    output GT_O
    						    );
    						
    						wire [3:0] GT_int;
    						wire [3:0] EQ_int;
    						wire [3:0] LT_int;
    						
    						cmp_bitslice slice_0 (
    						    .A(A[0]), 
    						    .B(B[0]), 
    						    .LT_I(1'b0), 
    						    .EQ_I(1'b1), 
    						    .GT_I(1'b0), 
    						    .LT_O(LT_int[0]), 
    						    .EQ_O(EQ_int[0]), 
    						    .GT_O(GT_int[0])
    						    );
    						
    						cmp_bitslice slice_1 (
    						    .A(A[1]), 
    						    .B(B[1]), 
    						    .LT_I(LT_int[0]), 
    						    .EQ_I(EQ_int[0]), 
    						    .GT_I(GT_int[0]), 
    						    .LT_O(LT_int[1]), 
    						    .EQ_O(EQ_int[1]), 
    						    .GT_O(GT_int[1])
    						    );
    						 
    						cmp_bitslice slice_2 (
    						    .A(A[2]), 
    						    .B(B[2]), 
    						    .LT_I(LT_int[1]), 
    						    .EQ_I(EQ_int[1]), 
    						    .GT_I(GT_int[1]), 
    						    .LT_O(LT_int[2]), 
    						    .EQ_O(EQ_int[2]), 
    						    .GT_O(GT_int[2])
    						    );
    						 
    						cmp_bitslice slice_3 (
    						    .A(A[3]), 
    						    .B(B[3]), 
    						    .LT_I(LT_int[2]), 
    						    .EQ_I(EQ_int[2]), 
    						    .GT_I(GT_int[2]), 
    						    .LT_O(LT_int[3]), 
    						    .EQ_O(EQ_int[3]), 
    						    .GT_O(GT_int[3])
    						    );
    						 
    						assign LT_O = LT_int[3];
    						assign EQ_O = EQ_int[3];
    						assign GT_O = GT_int[3];
    						
    						endmodule
    					
  3. Create a test bench on your own to test the comparator. Please make sure that all possible cases are covered.
  4. To implement a comparator, it is more convenient to use behavioral descriptions in Verilog. The following codes can implement the same circuit as the structural implementation above:
    						module cmp(
    						    input [3:0] A,
    						    input [3:0] B,
    						    output LT_O,
    						    output EQ_O,
    						    output GT_O
    						    );
    						 
    						assign LT_O = (A < B) ? 1'b1 : 1'b0;
    						assign EQ_O = (A == B) ? 1'b1 : 1'b0;
    						assign GT_O = (A > B) ? 1'b1 : 1'b0;
    						
    						endmodule
    					
  5. Apply the same test bench on the second comparator and check the result.

Adders

Adder circuits add two N-bit operands to produce an N-bit result and a carry out signal (the carry out is a '1' only when the addition result requires more than N-bits). The logic graph in Fig. 3 below shows the eight different cases that may be encountered when adding two binary numbers. The highlighted bit pairs and the associated carries show that a bit-slice adder circuit must process three inputs (the two addend bits and a carry-in from the previous stage) and produce two outputs (the sum bit and a carry out bit). The circuit block that implements the bit-sliced adder is called a “full adder” (FA).

Figure 3. Truth table for a bit-slice adder (full adder).

Note the carry-in of the RCA LSB is connected directly to ground, because (by definition) the carry-in to the LSB of any adder must be logic '0'. It is possible to capitalize on this observation, and create a smaller bit-slice circuit for use in the LSB position that does not have a carry-in input. Called a half adder (HA), this reduced-function adder circuit is often used in the LSB position. The ripple carry adder block diagram is displayed in Fig. 4 below.

Figure 4. Ripple-carry adder block diagram.

Step 2: Design a 4-bit Binary Adder

  1. Create a Verilog module for a full adder.
    						module FA(
    						    input A,
    						    input B,
    						    input Cin,
    						    output S,
    						    output Cout
    						    );
    							
    						assign S = A ^ B ^ Cin;
    						assign Cout = (A & B) | ((A ^ B) & Cin);
    						
    						endmodule
    					
  2. Create another Verilog module for a half adder.
    						module HA(
    						    input A,
    						    input B,
    						    output S,
    						    output Cout
    						    );
    						
    						assign S = A ^ B;
    						assign Cout = A & B;
    						
    						endmodule
    					
  3. Create a Verilog module called “adder” to wrap three FAs with one HA to form a 4-bit adder:
    						module adder(
    						    input [3:0] A,
    						    input [3:0] B,
    						    output [3:0] S,
    						    output Cout
    						    );
    						
    						wire [3:0] Carry;
    						
    						HA add_0 (
    						    .A(A[0]), 
    						    .B(B[0]), 
    						    .S(S[0]), 
    						    .Cout(Carry[0])
    						    );
    						
    						FA add_1 (
    						    .A(A[1]), 
    						    .B(B[1]), 
    						    .Cin(Carry[0]), 
    						    .S(S[1]), 
    						    .Cout(Carry[1])
    						    );
    						
    						FA add_2 (
    						    .A(A[2]), 
    						    .B(B[2]), 
    						    .Cin(Carry[1]), 
    						    .S(S[2]), 
    						    .Cout(Carry[2])
    						    );
    						
    						FA add_3 (
    						    .A(A[3]), 
    						    .B(B[3]), 
    						    .Cin(Carry[2]), 
    						    .S(S[3]), 
    						    .Cout(Carry[3])
    						    );
    						
    						assign Cout = Carry[3];
    						
    						endmodule
    					
  4. Create a test bench that covers all of the possible inputs to the adder and verify whether the result is correct. After the verification, you can tie SW3-0 to A and SW7-4 to B as two operands in UCF file, and tie LED4-0 to result of adder, you can test out your adder on your FPGA board.
  5. Here, we provide you with another way of implementing adder in Verilog behaviorally. In the codes below, we use operator “+” to indicate an adder. It is very important to remember that when you put down a “+” in Verilog HDL, the tools will instantiate an adder for you, instead of just a operation as you might be familiar with in software programming language.
    						module adder(
    						    input [3:0] A,
    						    input [3:0] B,
    						    output [3:0] S,
    						    output Cout
    						    );
    						
    						wire [4:0] Result;
    						assign Result = A + B;
    						
    						assign S = Result[3:0];
    						assign Cout = Result[4];
    						
    						endmodule
    					

Multipliers

Hardware multipliers, based directly on adder architectures, have become indispensable in modern computers. Multiplier circuits are modeled after the “shift and add” algorithm, as shown below. In this algorithm, one partial product is created for each bit in the multiplier—the first partial product is created by the LSB of the multiplier, the second partial product is created by the second bit in the multiplier, and so forth. The partial product bits need to be fed to an array of full adders (and half adders where appropriate), with the adders shifted to the left as indicated by the multiplication example. The final partial products are added with a CLA circuit. Note that some full-adder circuits bring signal values into the carry-in inputs (instead of carry's from the neighboring stage). This is a valid use of the full-adder circuit; the full adder simply adds any three bits applied to its inputs. The circuit for a partial product and the block diagram of the multiplier is shown in Fig. 5 below.

Figure 5. Hardware multiplier.

Step 3: Design a 4-bit Multiplier

Up to this point, you are expected to be able to describe circuits structurally. Based on the block diagram shown above in Fig. 5, you can use assignment statements to implement the circuit for partial products, and use the FA, HA, and the adder you implemented in previous steps to add those partial products together. Before deploying your circuit on your board, write a test bench to verify that your circuit is correct. Use SW3-0 as A and SW7-4 as B (A and B are multiplicants), and show the result of multiplication on 8-bit LEDs.

Unlike the adder and subtractor, multipliers do not have an operator support in Verilog, mostly due to the fact that there are various ways to implement a multiplier which trade off power, hardware resource, and speed. So implementing a multiplier structurally is the only solution.

Test Your Knowledge!

Now that you've completed this project, try these modifications:

  1. Implement a 4-bit borrow ripple subtractor using bit-sliced design methodology and describe it structurally in Verilog.
  2. Modify the adder you implemented in step 2 into an add_subtract circuit. Add an input “subtract”. When “subtract” is '1' the output of add_subtract equals $A - B$. When “subtract” is “1”, the output of add_subtract equals $A+B$. Inputs and outputs of the add_subtract circuit are represented in 2's complement. Instead of cout, an output “ERR” is needed as overflow/underflow indicator, i.e., ERR is “1” when overflow/underflow occurs.
  3. Can you combine add_subtract with other logics to implement an ALU according to the following opcode table? (Inputs and Output of the ALU are 4-bit binary numbers in 2's complement).
    OpCode Description Output $F$
    000 Addition $A + B$
    001 Increment $A + 1$
    010 Subtract $A - B$
    011 Bit- wise XOR $A \oplus B$
    100 Bit- wise OR $A | B$
    101 Bit-wise AND $A \& B$

  • Other product and company names mentioned herein are trademarks or trade names of their respective companies. © 2014 Digilent Inc. All rights reserved.