2

I've implemented an 8bit adder in python as follows:

from gates import AND, OR, XOR
from utils import number_to_binary, binary_to_number

def EightBitAdder(s1='110101', s2='00010', carry_in=0):
    # Limit to 8 bits
    s1 = s1[-8:].zfill(8)
    s2 = s2[-8:].zfill(8)
    s_out = ''
    carry_out = None
    for i in range(8):
        bit_1 = int(s1[8-1-i])
        bit_2 = int(s2[8-1-i])
        carry_in = carry_out if (carry_out is not None) else carry_in
        value_out = XOR(carry_in, XOR(bit_1, bit_2))
        carry_out = OR(AND(bit_1, bit_2), AND(bit_1, carry_in), AND(bit_2, carry_in))
        s_out = str(int(value_out)) + s_out
    print ("  %s (%s) \n+ %s (%s) \n= %s (%s)  -- Carry %s" % (s1, binary_to_number(s1), s2, binary_to_number(s2), s_out, binary_to_number(s_out), int(carry_in)))
    return (s_out, int(carry_out))

The striking thing for me is the "gates" will evaluate lazily, so it won't return a 1/0 unless I call int(), and it seems like there are a tremendous amount of gates in an 8-bit adder. For example:

enter image description here

Am I making a mistake somewhere (or redundancy) somewhere in the carry/value out evaluation, or does a basic 8bit ripple adder really have this many gates in it??

carl.hiass
  • 1,526
  • 1
  • 6
  • 26

2 Answers2

1

If implemented directly, a full adder does have that many gates in it. Have you considered using composite gates, such as 8-bit primitives or using a half adder? I don't have direct experience, but I don't think full adders are implemented directly with primitives in practice, instead they probably use these intermediate parts.

The second chapter of nand2tetris covers the half adder approach, which if you were to apply to your code allows you to make a slight simplification:

        carry_in = carry_out if (carry_out is not None) else carry_in
        value_out = XOR(carry_in, XOR(bit_1, bit_2))
        carry_out = OR(AND(bit_1, bit_2), AND(bit_1, carry_in), AND(bit_2, carry_in))

can be instead written as:

        carry_in = carry_out if (carry_out is not None) else carry_in
        half_sum = XOR(bit_1, bit_2)
        half_carry = AND(bit_1, bit_2)
        full_sum = XOR(carry_in, half_sum)
        full_carry = AND(half_sum, carry_in)
        value_out = full_sum
        carry_out = OR(half_carry, full_carry)

This drops the number of gates per iteration from 6 to 5, so it should reduce your output by 1/6th. I'd still recommend putting that in a separate gate though, as a half adder is independently useful.

kevmo314
  • 4,223
  • 4
  • 32
  • 43
1

In a real adder, the gates are connected into a graph, where the output of a gate may be used as the input to several others.

You are writing the output as an expression, where the output of a gate can only be used in one place.

This is accomplished by copying the whole expression for each output into all the places it is used. You do this in each iteration -- carry_in is used once to produce the value and 3 times to produce the next carry.

The size of the carry expression is multiplied by 3 in every iteration leading to an exponential explosion in the number of operators you use.

You should probably be generating your output in a different form that can preserve the gate graph, like static single assignment: https://en.wikipedia.org/wiki/Static_single_assignment_form

Matt Timmermans
  • 53,709
  • 3
  • 46
  • 87