Circuits and truth tables

- 0.1 Base 2 arithmetic
- 0.2 Gates and truth tables
- 0.3 Hardware description language
- 0.4 Knowledge travels along the wires of a circuit

In this chapter, we review basic notions about gates and learn the relationship between circuits and assignment-based computer programs. This sets the stage for analyzing modern programs.

A computer does arithmetic by sensing the voltage levels at the bits and computing new voltage levels, which are deposited into another word. For example, the addition of two words is done by sensing the bits of the words, right to left, and computing new bits in the answer. Consider the addition of 21 to 30, which we write like this:

The computation steps for each bit-addition are wired together with electronic sensors, called0 0 0 1 0 1 0 1 + 0 0 0 1 1 1 1 0 ------------------ 1 1 0 (with a ``carry'' of 1) 0 (because carry 1 + 0 + 1 is 0 with carry of 1) 1 (because carry 1 + 1 + 1 is 1 with carry of 1) 1 (because carry 1 + 0 + 0 is 1) 0 0 So, 0 0 1 1 0 0 1 1 (51) is deposited into a new register/word

===================================================In the above drawings, the input wires are labelled with the namesAND: OR: NOT:===================================================

AND: P Q | --------------- 1 1 | 1 1 0 | 0 0 1 | 0 0 0 | 0

AND: P Q | OR: P Q | NOT: P | ------------- ------------ ----------- t t | t t t | t t | f t f | f t f | t f | t f t | f f t | t f f | f f f | f

Each gate defines a one-bit arithmetic operation. (Later, we will see how to make a multi-bit operation, e.g., an 8-bit adder, as was illustrated in the example.)

It is standard to write each gate in a linear notation,
that is, instead of drawing
,
we write ` P ^ Q` instead. (The tradition of writing
linear notations to represent two-dimensional structures
goes back centuries in physics and math.)
The notations are

AND is ^ OR is v NOT is ~

We can also compose the gates to define new operations. For example, this circuit,

written

P Q | ~(P ^ Q) --------------- t t | f t f | t f t | t f f | t

P Q | ~ (P ^ Q) ------------------------- t t | f t t t t f | t t f f f t | t f f t f f | t f f f

We can make circuits that take three or more inputs, e.g.,
` (~(P ^ Q)) v R`
computes

Here, the column underneath OR defines the output. We see this circuit emits false only whenP Q R | (~ (P ^ Q) ) v R ------------------------------------------ t t t | f t t t t t t t f | f t t t f f t f t | t t f f t t t f f | t t f f t f f f t | t f f t t t f t f | t f f t t f f t t | f f t f t t f f f | t f f f t f

The examples show that circuit theory is an arithmetic built from true, false, and AND, OR, NOT gates. Indeed, hardware description languages for circuit building are little more than arithmetic expressions written to look like assignment commands. When we write

we mean that the inputs to an AND gate are wires namedA = P ^ Q

A = P ^ Q OUT = ~ A

===================================================(We will use Python notation for our programming examples.)def MyGate(P, Q): A = P ^ Q OUT = ~ A return OUT===================================================

Now, every time we must manufacture a ` MyGate` for a chip,
we use the name,

This is how computer engineers lay out modern-day circuits --- they program them in a hardware layout language.# say that the inputs to this circuit are X, Y, and Z, and the output is OUT: M = MyGate(X, Y) N = MyGate(M, Z) OUT = ~N

The point is:
*a circuit is a set of equations, where the variable names are
the names of the wires.*
Later we will see how the variable names can also be the names
of storage cells.

(You can skip this part if you wish.) In this section, we use hardware layout language (equations) to program an adder circuit, which is found inside every computer processor.

To do this, we must implement these two tables,
which define the two computations for one-bit addition.
The first table defines how to add the bits from two registers ` P` and

For example,ADDONE: C P Q | CARRY: C P Q | -------------------- ------------------- t t t | t t t t | t t t f | f t t f | t t f t | f t f t | t t f f | t t f f | f f f t | t f f t | f f t f | t f t f | f f t t | f f t t | t f f f | f f f f | f

Here is a coding of ` CARRY`:

You are left the exercise of writing the function that computesdef CARRY(C, P, Q) : A = P ^ Q B = P ^ R C = Q ^ R OUT = (A v B) v C return OUT

Given these two functions, we can program a 4-bit adder like this:
Let register ` P` be an array (list) of 4

Say that we have designed (coded) ` ADDONE` and

===================================================We can try it:def ADD4(P, Q, R): """ADD4 reads the t-f values in arrays/registers P and Q and deposits its answer into a register, R, that holds the sum of P and Q.""" R[3] = ADDONE(False, P[3], Q[3]) # no carry when we start C3 = CARRY(False, P[3], Q[3]) R[2] = ADDONE(C3, P[2], Q[2]) C2 = CARRY(C3, P[2], Q[2]) R[1] = ADDONE(C2, P[1], Q[1]) C1 = CARRY(C2, P[1], Q[1]) R[0] = ADDONE(C1, P[0], Q[0]) # and we lose (forget) the carry bit===================================================

RegP = [0,0,1,0] RegQ = [0,0,1,1] RegR = [0,0,0,0] ADD4(RegP, RegQ, RegR)

You can read the coding of ` ADD4` as a computer program, but it is also
a sequence of instructions for wiring a circuit that connects to
three registers (that is, three arrays of four cells each)
and sends voltage levels along the connection points to alter the
values in the registers' cells.

A true hardware description language would let us use loop code to define the adder's wiring. Here is a coding for an 8-bit adder that uses a loop:

===================================================A hardware fabrication machine converts code like the above into microcode that is burned into a chip's ROM (read-only memory for its controller) or it might even generate a wiring layout on a chip template itself. (In the latter case, the loop is unfolded intosize = 8 # how many bits are held by a register def ADD(P, Q, R): """ADD reads the t-f values in registers P and Q and deposits the their sum into register R.""" num = size - 1 carry = False while num >= 0 : R[num] = ADDONE(carry, P[num], Q[num]) carry = CARRY(carry, P[num], Q[num]) num = num - 1===================================================

Here is a circuit:

and here is its coding in equations:

R = P ^ Q S = R v Q T = ~ S

Let's redraw the circuit vertically and lay it side by side with the assignment equations:

===================================================Each wire in the circuit is named. These are just===================================================

The modern stored-program computer, developed by John von Neumann in the 1950s, used storage registers to hold the values of the variable names and used a processor to compute the values of the equations. In this way, a processor-plus-registers can simulate a circuit, and

Now, at each of the *program points*, marked by stars in the above diagram,
what information travels in the wires?
We might use the circuit with some inputs to see ``what happens.''
Say that we supply ` t` for

===================================================The diagram shows the values on the wires labelled===================================================

Just as interesting is that we can analyze the program/circuit
*before* it is completely tested. For example, say that
the circuit will be inserted into a board where its ` P` wire will
always receive a

===================================================In the diagram, we see that===================================================

First, we do know that ` R = P ^ Q`. But

The above reasoning is a *deduction* --- we deduced
from the facts ` P = t` and

The other deductions in the example are calculated with similar uses of substitution, simplification, and cases analysis.

The point of the previous example is that we can deduce (predict) properties of the circuit in advance of using it. These deductions complement testing.

Next, say that we don't know anything about ` P` and

stating the obvious! But by a careful examination of the truth tables, we can also state thatT = ~((P ^ Q) v Q)