Full adders

To add multi-bit numbers, we use the primary school process: first add the digits in the rightmost column, giving a sum and a carry. Then, add the digits in the second-rightmost column, plus the previous column's carry bit. There are three total inputs for each column: one from each number, plus the previous column's carry.

Our half adders can produce a carry, but they can't accept incoming carries from the previous column. Fortunately, we can combine two half adders to build a full adder that both accepts and produces a carry. The truth table is below, but it's twice as long as the previous one because there are three inputs instead of two. There's no need to read this in its entirety, but it can be handy to refer back to when working out specific cases.

x y carry_in carry_out sum
0 0 0 0 0
0 1 0 0 1
1 0 0 0 1
1 1 0 1 0
0 0 1 0 1
0 1 1 1 0
1 0 1 1 0
1 1 1 1 1

We could turn this table into a series of logic gates using one of the many automated methods available. Like generated code, those automatically generated gates are often needlessly complex, so there are additional methods to simplify them into compact expressions like those below. These methods are out of scope for this article, though. In truth, the full adder function below was written by translating a circuit diagram into code.

>>> def FULL(x, y, carry_in):
...     carry1, sum1 = HALF(x, y)
...     carry2, sum2 = HALF(carry_in, sum1)
...     carry_out = OR(carry1, carry2)
...     return (carry_out, sum2)

# Check
>>> for x in [0, 1]:
...     for y in [0, 1]:
...         for carry_in in [0, 1]:
...             print(FULL(x, y, carry_in))
(0, 0)
(0, 1)
(0, 1)
(1, 0)
(0, 1)
(1, 0)
(1, 0)
(1, 1)

The circuit diagram below shows the relationships between FULL's half adders more directly. Half adders are represented as a single unit, even though they each have a complex internal structure; abstraction is as important in hardware as it is in software. Like before, the intermediate values are labeled, this time using the names of the intermediate variables in the Python function.

If the Python function or this diagram are unclear, tracing the correspondences between those intermediate variables in the function and the diagram should help. Working through some entries in the truth table by setting the inputs and calculating the intermediate values will also help. It's a perennial involuntary passtime of first-year electrical engineering students.

We can now add one bit, including incoming and outgoing carry bits. This doesn't sound like a big achievement, but we're close to adding arbitrarily large binary numbers. The only remaining step is to combine these full adders to form the "columns" of the addition process, each one feeding into the next.

This is one section of The Programmer's Compendium's article on Digital Electronics, which contains more details and context.