Lecture 17: Boolean Algebra and Logic (by Trek Palmer) ===================================== Boolean algebra is an algebraic system, whose elements are true and false and whose operations are AND and OR. If you recall, from our previous encounters with these operations: x AND y y| true | false x OR y y | true | false --+------+------- ----+------+------- true | true | false true | true | true x ------+------+------- x ------+------+------- false | false| false false | true | false Although, in boolean algebra '.' or '^' is used for AND and '+' or 'v' for OR. The way that many logic functions are described is with truth tables, and two logic functions are equivalent if their truth tables are the same. In addition to XOR, there's another useful function, NOT (often written as '~'). NOT just flips the value of it's input. Consider the following logic function: (~x ^ y) v (x ^ ~y) x t | f ---+---+--- t| f | t y ---+---+--- f| t | f Look familiar? It should, it's the way you construct XOR out of ANDs and ORs. In the end, there are six basic logic functions that are useful for constructing logic circuits: AND = x ^ y OR = x v y XOR = (~x ^ y) v (x ^ ~y) NOT = ~x NAND = ~(x ^ y) NOR = ~(x v y) It is, in fact, possible to synthesize all the ALU functions using these six functions. In fact its possible to do it all with NAND and NOT. Some logic review ----------------- X ^ t = X X ^ f = f X v t = t X v f = X ~(X v Y) = ~X ^ ~Y \ De Morgan's Law ~(X ^ Y) = ~X v ~Y / X XOR t = ~X X XOR f = ~X Logic with switches -------------------- But first, we'll look at how you actually build these things. Imagine you have a circuit with a power supply and a light bulb. Note that it really isn't important to understand electronics to understand this diagram. Just understand that the light bulb will only light up if there is a direct connection between the power supply and the bulb: | | | ||||--------------| Logic goes here |-----------Bulb--------|| | | | So, if we make the analogy that lit light bulb is true, and unlit light bulb is false, we can insert stuff between the power and the light to perform logic functions. If we have a set of switches, x and y, and the state of the switches reflects the state of x and y, then: x +--- false +----+ true | \+--------------+ power--------+ | | y +--- false +--------bulb +----+ true | \+--------------+ This represents OR, because if only one of the switches is in the 'true' position will the bulb light up. x +---false power----------+ true y +----false \+----------------+ true \+------------------------bulb This represents AND because only if x and y are true will the bulb light up. The reason for all the switches is because a transistor is a kind of solid-state switching device. Actually, its a kind of amplifier, using a weak signal to control a larger signal. So, using transistors it is possible to synthesize AND and OR gates. Actually, due to physical reasons NAND gates are smaller than any of the others, so most logic on modern processors is composed of NANDS. Actually using these things --------------------------- Now, we'll see how we actually use these gates to accomplish a task, in this case, adding two bits. First we note that logic functions take in multiple inputs and output a single output. Because addition of two bits can generate two bits of output, we need to seperate the output into two bits and do a truth table for each. A | B | A+B ----+-----+----- 0 | 0 | 00 0 | 1 | 01 1 | 0 | 01 1 | 1 | 10 low-bit truth table: A | B | lo(A+B) ----+-----+-------- 0 | 0 | 0 0 | 1 | 1 1 | 0 | 1 1 | 1 | 0 carry-biit truth table: A | B | hi(A+B) ----+-----+-------- 0 | 0 | 0 0 | 1 | 0 1 | 0 | 0 1 | 1 | 1 The low bit looks like XOR and the high bit looks like AND 2-bit adder = (A ^ B) (A XOR B) You can, in fact simplify this further by using the formulation of XOR in terms of AND and OR, 2-bit adder = (A ^ B) ((A v B) ^ ~(A ^ B)) This device is known as a half-adder (so-called because it can't add a carry bit coming in from a lower order bit). The circuit that can handle carry bits is known as a full-adder and is a touch more complicated. full-adder: carry A B | | | +-+-----+-+ | | half-add| | +-+-----+-+ | c| sum| | | +--+----+-+ | | half-add| | +-+-----+-+ | c| sum| | | | +----+ | | OR | | \___/ | | | carry sum ripple-carry ------------ To create the simplest kind of adder, the ripple carry adder, we just chain full adders together. So a 32-bit ripple-carry adder would look like: X31 Y31 X30 Y30 C from | | | | cond. codes X0 Y0 +--------+ +----------+ -------+ | | | c| | | | c| | | | | | | | +--+--+ | +---+--+ | | +---+---+ | FA | | | FA | | . . . | | FA | +-----+ | +-+--+-+ | | ++--+---+ | | | c| | | | c| | c sum +-----+ sum +--- +----+ sum Now, there's a problem, namely that in order for the n-th bit adder to compute its result, it needs the carry in from the (n-1)th bit adder. So, what this means is that a ripple-carry adder for a 32-bit number will take 32 steps to fully compute the value. This is bad.