The Universality of NAND

Here's the OR function again:

>>> def OR(x, y):
...     return NOT(AND(NOT(x),
...                    NOT(y)))

To make NAND's universality a bit more tangible, we'll translate the OR function into NAND calls. We expand x OR y piece by piece:

Expression                    Notes
----------                    -----
x OR y
!(!x AND !y)                  By De Morgan's Laws.
!x NAND !y                    We had a NOT-AND, which is exactly what NAND is!
(x NAND x) NAND (y NAND y)    !x can be rewritten as x NAND x.

Here it is written in Python:

>>> def OR(x, y):
...     return NAND(NAND(x, x),
...                 NAND(y, y))

# Check
>>> [OR(0, 0), OR(0, 1), OR(1, 0), OR(1, 1)]
[0, 1, 1, 1]

Using Python is a fine way to understand logical operations. However, unfortunately for programmers, hardware isn't built in Python. Still, it's all ones and zeros, so digital electronics often map closely to our understanding of Boolean expressions in programming.

Here are four digital logic diagrams as an electrical engineer would draw them on paper. First, a buffer, which outputs whatever value is sent to it, and thus has no implications for us in this article. It's included only because it shows that a little circle on the right side of a gate indicates negation: the NOT symbol is a buffer symbol with the little circle added. Next comes AND, then NAND, which is drawn as an AND plus the negation circle. Each symbol includes labels with programmer-friendly boolean expressions for the outputs.

Things get more interesting when we combine gates. Here's a diagram for our Python OR function above. It's a direct translation of the Python expression NAND(NAND(x, x), NAND(y, y)). Each NAND gate has two inputs and one output, corresponding to the NAND function's two arguments and one return value. Some intermediate values in the diagram are also called out for clarity.

(In English: the input x is sent to both inputs of NAND1, outputting !x. The input y is sent to both inputs of NAND2, outputting !y. Those two outputs are NANDed together, giving !(!x AND !y), which is x OR y.)

Translating logical operators like OR into NAND gates is strange at first, but there are reasons to do it in the real world. For circuits built out of individual logic gate chips, it's easier and cheaper to buy many of one type of chip than to buy smaller numbers of different chips. In many situations, NAND is also electrically simpler than other gates, requiring fewer transistors. Finally, complex digital logic circuits usually go through an automated simplification step anyway, so that step may as well target one type of universal gate.

To make this a bit more concrete, take a look at this hobbyist computer built out of 352 NAND gates. It has one minor complication: many of those gates have more than two inputs. But that's an extension of the two-input NAND gates we've already seen, just as x AND y AND z is an extension of the more basic x AND y. For example, the "133" row in the NAND gate usage table on that page refers to the Texas Instruments SN74ALS133 chip, which is a 13-input NAND gate.

As another real-world example, the Apollo Guidance Computer was built from 2,800 three-input NOR gates, which are universal in the same way that NAND is. (A useful exercise: write a naive NOR (NOT-OR) function. Use it to build AND, OR, and NOT. The process will be similar to our NAND process, with slight differences.)

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