Elements of Computing Systems CH 2 Boolean Arithmetic
Elements of Computing Systems CH 2 Boolean Arithmetic
2. Boolean Arithmetic1
Counting is the religion of this generation, its hope and salvation.
(Gertrude Stein, American writer, 1874-1946)
In this chapter we build the Boolean circuits that represent numbers and perform arithmetic
operations on them. Our starting point is the set of logic gates we built in the last chapter, and
our ending point is a fully functional Arithmetic Logical Unit. The ALU is the centerpiece chip
that executes all the arithmetic and logical operations performed by the computer.
1. Background
Binary Numbers: Unlike the decimal system, which is founded on base 10, the binary system is
founded on base 2. When we are given a certain binary pattern, say 10011, and we are told that
this pattern is supposed to represent an integer number, the decimal value of this number is
computed by convention as follows:
In general, let x = x n x n 1 ...x 0 be a string of digits. The value of x in base b, denoted ( x) b , is defined
as follows:
n
( xn xn1 ...x0 ) b = xi b i (2)
i =0
The reader can verify that in the case of (10011) two , rule (2) reduces to calculation (1).
The result of calculation (1) happens to be 19. Thus, when we press the keyboard keys labeled
1, 9 and ENTER while running, say, a spreadsheet program, what ends up in some register in
the computer is the binary code 10011. More precisely, if the computer happens to be a 32-bit
machine, say, what gets stored in the register is the bit pattern
00000000000000000000000000010011.
Binary addition: A pair of binary numbers can be added digit-by-digit from right to left,
according to the same elementary school method used in decimal addition. First, we add the two
right-most digits, also called the least significant bits of the two binary numbers. Next, we add
the resulting carry bit (which is either 0 or 1) to the sum of the next pair of bits up the
significance ladder. We continue the process until the two most significant bits are added. If the
last bit-wise addition generates a carry of 1, we can report overflow; otherwise, the addition
completes successfully.
1
From The Elements of Computing Systems, Nisan & Schocken, MIT Press, forthcoming in 2003, www.idc.ac.il/csd
Chapter 2: Boolean Arithmetic 2
0 0 0 1 (carry) 1 1 1 1
1 0 0 1 x 1 0 1 1
+ 0 1 0 1 y + 0 1 1 1
0 1 1 1 0 x+ y 1 0 0 1 0
no overflow overflow
We see that computer hardware for binary addition must be able to calculate the sum of three bits
(pair of bits plus carry bit) and pass the carry bit from the addition of one pair of bits to the
addition of the next significant pair of bits.
Signed binary numbers: A binary system with n digits can code 2 n different bit patterns. Let us
call this set of patterns the system's "code space". To represent signed numbers in binary code, a
natural solution is to split this space into two equal subsets. One subset of codes is assigned to
represent positive numbers, and the other negative numbers. The exact coding scheme should be
chosen in such a way that, ideally, the introduction of signed numbers would complicate the
hardware implementation as little as possible.
This challenge has led to the development of several coding schemes for representing signed
numbers in binary code. The method used today by almost all modern computers is called the 2s
complement method, also known as radix complement. In a binary system with n digits, the 2s
complement of the number x is defined as follows:
2 n x if x 0
x= (3)
0 otherwise
Positive Negative
Numbers Numbers
0 0000
1 0001 1111 -1
2 0010 1110 -2
3 0011 1101 -3
4 0100 1100 -4
5 0101 1011 -5
6 0110 1010 -6
7 0111 1001 -7
1000 -8
An inspection of Table 2 suggests that an n-bit binary system with 2s complement representation
has the following properties:
The system can code a total of 2 n signed numbers, of which the maximal and
minimal numbers are 2 n 1 1 and 2 n 1 , respectively;
The codes of all positive numbers begin with a "0";
The codes of all negative numbers begin with a "1";
To obtain the code of x from the code of x, leave all the trailing (least significant)
0s and the first least significant 1 intact, then flip all the remaining bits (convert 0s
to 1s and vice versa). An equivalent shortcut, which is easier to implement in
hardware, is to flip all the bits of x and add 1 to the result.
A particularly attractive feature of this representation is that addition of any two numbers in 2s
complement is exactly identical to addition of positive numbers. Consider, for example, the
addition operation (-2) + (-3): using 2s complement (in a 4-bit representation) we have to add, in
binary, (1110)two + (1101)two. Without paying any attention to which numbers (positive or
negative) these codes represent, bit-wise addition will yield 1011 (after throwing away the 5th
overflow bit). Indeed, this is the 2s complement representation of (-5).
In short, we see that we are able to perform addition of any two signed numbers without requiring
any special hardware beyond that needed for simple bit-wise addition. What about subtraction?
Recall that in the 2s complement method, the arithmetic negation of a signed number x, i.e.
computing x, is achieved by negating all the bits of x and adding 1 to the result. Thus
subtraction can be handled by x y = x + ( y ) . Once again, hardware complexity is kept to a
minimum.
The material implications of these theoretical results are significant. Basically, they imply that a
single chip, called Arithmetic Logical Unit, can be used to encapsulate all the basic arithmetic and
logical operators performed in hardware. We now turn to specify one such ALU, beginning with
the specification of an adder chip.
Chapter 2: Boolean Arithmetic 4
2. Specification
Adders
Half Adder: The first step on our way to adding binary numbers is to be able to add two bits.
This task requires the handling of four possible cases:
0+0 = 00
0+1 = 01
1+0 = 01
1+1 = 10
We will now present a chip, called half-adder, that implements this addition operation. The least
significant bit of the addition is called sum, and the most significant bit is called carry.
Inputs Outputs
a b carry sum
0 0 0 0 a s um
h alf
0 1 0 1
ad d er
1 0 0 1 b c arry
1 1 1 0
Full Adder: Now that we know how to add 2 bits, we present a full-adder chip, designed to add
3 bits. Like the half-adder case, the full-adder chip produces two outputs: the least significant bit
of the addition, and the carry bit.
a b c carry sum
0 0 0 0 0
0 0 1 0 1 a
0 1 0 0 1 s um
0 1 1 1 0 b fu ll
1 0 0 0 1 ad d er c arry
c
1 0 1 1 0
1 1 0 1 0
1 1 1 1 1
Adder: Memory and register chips represent integer numbers by n-bit patterns, n being 16, 32,
64, etc. depending on the computer platform. The chip whose job is to add such numbers is
called a multi-bit adder, or simply adder. We present a 16-bit adder, noting that our diagrams
and specifications scale up as-is to any n-bit system.
16
a 16
1 0 1 1 a 1 6 -b it
o ut
b + ad d e r
16
0 0 1 0 b
1 1 0 1 out
DIAGRAM 5: 16-bit adder. The example (top left) illustrates the addition
of two 4-bit numbers. n-bit addition for any n is more of the same.
Chapter 2: Boolean Arithmetic 6
Incrementer: It is convenient to have a special purpose chip dedicated to adding the constant 1
to a given number. Here is the API of a 16-bit incrementer:
zx nx zy ny f no
x
16 bits
ALU 16 bits
out
y
16 bits
zr ng
DIAGRAM 6: The ALU of the Hack platform: interface and API. The ALU operation (the
function computed on x and y) is determined by the six control bits. The ALU sets the output
bits zr and ng to 1 when the output out is zero or negative, respectively.
Note that each one of the six control bits instructs the ALU to carry out a certain operation.
Taken together, the combined effects of these operations cause the ALU to compute a variety of
useful functions. Since the ALU is controlled by six control bits, it can potentially compute
2 6 = 64 different functions. 18 of these functions are documented in Table 7.
Chapter 2: Boolean Arithmetic 8
these bits instruct how to these bits instruct how to this bit selects this bit inst. how resulting
pre-set the x input pre-set the y input betw. + / And to post-set out ALU output
zx nx zy ny f no out=
If f then
If zx then If nx then If zy then If ny then If no then
x=0 x=~x y=0 y=~y
out=x+y else
out=~out f(x,y)=
out=x And y
1 0 1 0 1 0 0
1 1 1 1 1 1 1
1 1 1 0 1 0 -1
0 0 1 1 0 0 x
1 1 0 0 0 0 y
0 0 1 1 0 1 ~x
1 1 0 0 0 1 ~y
0 0 1 1 1 1 -x
1 1 0 0 1 1 -y
0 1 1 1 1 1 x+1
1 1 0 1 1 1 y+1
0 0 1 1 1 0 x-1
1 1 0 0 1 0 y-1
0 0 0 0 1 0 x+y
0 1 0 0 1 1 x-y
0 0 0 1 1 1 y-x
0 0 0 0 0 0 x&y
0 1 0 1 0 1 x|y
TABLE 7: The ALU truth table. Taken together, the binary operations coded by the
first six columns (input control bits) in each row affect the overall function listed in
the right column of that row. (We use the symbols ~, &, and | to represent the
operators Not, And, and Or, respectively, performed bit-wise.). The complete ALU
truth table consists of 64 rows, of which only the 18 presented here are of interest.
We see that programming the ALU to compute a certain function f(x,y) is done by setting the six
control bits to the code of the desired function. From this point on, the internal ALU logic
specified in Diagram 6 should cause the ALU to output the value f(x,y) specified in Table 7. This
does not happen miraculously -- its the result of careful design.
For example, let us consider the 12th row of table 7, where the ALU is instructed to compute the
function x-1. The zx and nx bits are 0, so the x input is neither zeroed nor negated. The zy and
ny bits are 1, so the y input is first zeroed, and then negated bit-wise. Bit-wise negation of zero,
(000...00)two, gives (11111)two, which is the 2s complement code of -1. Thus the ALU inputs
end up being x and -1. Since the f-bit is 1, the selected operation is arithmetic addition, causing
Chapter 2: Boolean Arithmetic 9
the ALU to calculate x+(-1). Finally, since the no bit is 0, the output is not negated but rather left
as is. To conclude, the ALU ends up computing x-1, which was our goal.
Does the ALU logic described in Table 6 compute every one of the other 17 functions listed in
the right column of Table 7? To verify that this is indeed the case, the reader is advised to pick up
some other rows in the table and prove their respective ALU operation. We note in passing that
some of these computations, beginning with the function f(x,y)=1, are not trivial. We also note
that there are some other useful functions computed by the ALU but not listed in the table.
It may be instructive to describe the thought process that led to the design of this particular ALU.
First, we made a list of all the primitive operations that we wanted our computer to be able to
execute (right column in Table 7). Next, we used backward reasoning to figure out how x, y, and
out can be manipulated in binary fashion in order to carry out the desired operations. These
processing requirements, along with our objective to keep the ALU logic as simple as possible,
have led to the design decision to use six control bits, each associated with a certain binary
operation. The resulting ALU is simple and elegant.
3. Implementation
Our implementation guidelines are intentionally partial, since we want you to discover the actual
chip architectures yourself. As usual, each gate can be implemented in more than one way; the
simpler the implementation, the better.
Half Adder: An inspection of Diagram 3 reveals that the functions sum(a,b) and carry(a,b)
happen to be identical to the standard Xor(a,b) and And(a,b) functions. Thus, the implementation
of this adder is rather trivial, using previously built gates.
Full Adder: A Full-Adder chip can be implemented from two half-adder chips and one
additional simple gate. Other direct implementation options are also possible, without using half-
adder chips.
Adder: The addition of two signed numbers represented by the 2's complement method as two
n-bit busses can be done bit-wise, from right to left, in n steps. In step 0, the least significant pair
of bits is added, and the carry bit is fed into the addition of the next significant pair of bits. The
process continues until in step n-1 the most significant pair of bits is added. Note that each step
involves the addition of 3 bits. Hence, an n-bit adder can be implemented by creating an array of
n full-adder chips, and chaining them in such a way that the carry bit of each adder is fed into one
of the inputs of the next adder up the significance ladder.
ALU: Note that the ALU was carefully planned to effect all the desired ALU operations
logically, using simple Boolean operations. Therefore, the physical implementation of the ALU
is reduced to implementing these simple Boolean operations, following their pseudo-code
specifications. Your first step will likely be to create a logic circuit that manipulates a 16-bit
input according to nx and zx control bits (i.e. the circuit should conditionally zero and negate the
16-bit input). This logic can be used to manipulate the x and y inputs, as well as the out output.
Chapter 2: Boolean Arithmetic 10
Chips for addition and for bit-wise And-ing have already been built. Thus, what remains is to
build logic that chooses between them according to the f control bit. Finally, you will need some
logic that integrates all the other chips into the overall ALU.
4. Perspective
The construction of the multi-bit adder presented in this chapter was standard, although no
attention was paid to efficiency. In fact, our suggested adder implementation is rather inefficient,
due to the long delays incurred while the carry propagates from the least significant bit to the
most significant bit. This problem can be alleviated using logic circuits that effect so-called
"carry look-ahead" techniques. Since addition is one of the most prevalent operations in any
given computer architecture, such low-level improvements can result in dramatic and global
performance gains throughout the computer.
In any given computer, the overall functionality of the hardware/software platform is delivered
jointly by the ALU and the operating system that runs on top of it. Thus, when designing a new
computer system, the question of how much functionality the ALU should deliver is essentially a
cost/performance issue. The general rule is that hardware implementations of arithmetic and
logical operations are usually more costly, but achieve better performance. The design tradeoff
that we have chosen in this book is to specify an ALU hardware with a limited functionality and
then implement as many operations as possible in software. For example, our ALU features
neither multiplication and division operations, nor floating point arithmetic. Some of these
operations (as well as more mathematical functions) will be implemented at the operating system
level, as described in Chapter 11.
Detailed treatments of Boolean arithmetic and ALU design can be found in standard
undergraduate textbooks such as [Hennessy & Patterson, chapter 4].
5. Build It
Objective: Implement all the chips presented in this chapter, using previously built chips.
Tip: When your HDL programs invoke chips that you may have built in the previous project, it is
recommended to use instead the built-in versions of these chips. This will ensure correctness and
speed up the operation of the hardware simulator. There is a simple way to accomplish this
convention: make sure that your project directory includes only the files that belong to the present
project.
The remaining instructions for this project are identical to those from Chapter 1, except that every
occurrence of the text "project1" should be replaced with "project2".